¿Por qué no debería usarse el opcional de Java 8 en los argumentos?

Resuelto Neil Stevens asked hace 9 años • 26 respuestas

He leído en muchos sitios web que Opcional debe usarse solo como tipo de retorno y no en los argumentos del método. Estoy luchando por encontrar una razón lógica. Por ejemplo, tengo una lógica que tiene 2 parámetros opcionales. Por lo tanto, creo que tendría sentido escribir la firma de mi método así (solución 1):

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2) {
    // my logic
}

Muchas páginas web especifican que Opcional no debe usarse como argumentos de método. Con esto en mente, podría usar la siguiente firma de método y agregar un comentario Javadoc claro para especificar que los argumentos pueden ser nulos, con la esperanza de que los futuros mantenedores lean el Javadoc y, por lo tanto, siempre realicen comprobaciones nulas antes de usar los argumentos (solución 2). :

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

Alternativamente, podría reemplazar mi método con cuatro métodos públicos para proporcionar una interfaz más agradable y hacer más obvio que p1 y p2 son opcionales (solución 3):

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

Ahora intento escribir el código de la clase que invoca esta lógica para cada enfoque. Primero recupero los dos parámetros de entrada de otro objeto que devuelve Optionals y luego invoco calculateSomething. Por lo tanto, si se utiliza la solución 1, el código de llamada se vería así:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

Si se utiliza la solución 2, el código de llamada se vería así:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

Si se aplica la solución 3, podría usar el código anterior o podría usar el siguiente (pero es mucho más código):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}

Entonces mi pregunta es: ¿Por qué se considera una mala práctica usar Optionals como argumentos del método (ver solución 1)? Me parece la solución más legible y hace que sea más obvio que los parámetros podrían estar vacíos/nulos para futuros mantenedores. (Soy consciente de que los diseñadores Optionalpretendían que solo se usara como tipo de devolución, pero no encuentro ninguna razón lógica para no usarlo en este escenario).

Neil Stevens avatar Aug 10 '15 21:08 Neil Stevens
Aceptado

Oh, esos estilos de codificación deben tomarse con un poco de sal.

  1. (+) Pasar un resultado Opcional a otro método, sin ningún análisis semántico; dejar eso al método, está bastante bien.
  2. (-) El uso de parámetros opcionales que provocan lógica condicional dentro de los métodos es literalmente contraproducente.
  3. (-) La necesidad de empaquetar un argumento en un Opcional no es óptima para el compilador y realiza un ajuste innecesario.
  4. (-) En comparación con los parámetros que aceptan valores NULL, Opcional es más costoso.
  5. (-) El riesgo de que alguien pase el Opcional como nulo en los parámetros reales.

En general: Opcional unifica dos estados, que hay que desentrañar. Por lo tanto, es más adecuado para resultados que para entradas, debido a la complejidad del flujo de datos.

Joop Eggen avatar Aug 10 '2015 15:08 Joop Eggen

La mejor publicación que he visto sobre el tema fue escrita por Daniel Olszewski :

Aunque podría resultar tentador considerar Opcional para parámetros de método no obligatorios, dicha solución palidece en comparación con otras posibles alternativas. Para ilustrar el problema, examine la siguiente declaración del constructor:

public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}

A primera vista puede parecer una decisión de diseño acertada. Después de todo, marcamos explícitamente el parámetro adjunto como opcional. Sin embargo, en cuanto a llamar al constructor, el código del cliente puede volverse un poco torpe.

SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));

En lugar de proporcionar claridad, los métodos de fábrica de la clase Opcional sólo distraen al lector. Tenga en cuenta que sólo hay un parámetro opcional, pero imagine tener dos o tres. El tío Bob definitivamente no estaría orgulloso de ese código 😉

Cuando un método puede aceptar parámetros opcionales, es preferible adoptar un enfoque bien probado y diseñar dicho caso utilizando la sobrecarga de métodos. En el ejemplo de la clase SystemMessage, declarar dos constructores separados es superior a usar Opcional.

public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}

Ese cambio hace que el código del cliente sea mucho más simple y fácil de leer.

SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);
Gili avatar Aug 17 '2016 20:08 Gili

Casi no hay buenas razones para no utilizarlos Optionalcomo parámetros. Los argumentos en contra de esto se basan en argumentos de autoridad (ver Brian Goetz; su argumento es que no podemos imponer opciones no nulas) o que los Optionalargumentos pueden ser nulos (esencialmente el mismo argumento). Por supuesto, cualquier referencia en Java puede ser nula, debemos fomentar que el compilador aplique las reglas, no la memoria del programador (lo cual es problemático y no escala).

Los lenguajes de programación funcionales fomentan Optionallos parámetros. Una de las mejores formas de usar esto es tener múltiples parámetros opcionales y usar liftM2una función asumiendo que los parámetros no están vacíos y devolver un opcional (consulte http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/ data/Option.html#liftM2-fj.F- ). Desafortunadamente, Java 8 ha implementado una biblioteca muy limitada que admite opciones.

Como programadores de Java, solo deberíamos usar null para interactuar con bibliotecas heredadas.

Mark Perry avatar Aug 10 '2015 16:08 Mark Perry