update - java 9




¿Por qué no debería usarse Java 8's Opcional en argumentos (13)

He leído en muchos sitios web. Opcional debe usarse solo como tipo de retorno y no en argumentos de método. Estoy luchando por encontrar una razón lógica por la cual. 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 de esta manera (solución 1):

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

Muchas páginas web especifican Opcional no deben usarse como argumentos de método. Con esto en mente, podría usar la siguiente firma de método y agregar un comentario claro de Javadoc 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 que sea 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 trato de escribir el código de la clase que invoca esta pieza lógica para cada enfoque. Primero recupero los dos parámetros de entrada de otro objeto que devuelve Optional sy luego, invoco calculateSomething . Por lo tanto, si se usa 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 usa 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 lo siguiente (pero es significativamente 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 los Optional como argumentos de método (ver solución 1)? Me parece la solución más fácil de leer 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 de Optional pretendían que solo se usara como un tipo de retorno, pero no puedo encontrar ninguna razón lógica para no usarlo en este escenario).


Aceptar Opcional como parámetros provoca un ajuste innecesario a nivel de la persona que llama.

Por ejemplo en el caso de:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

Suponga que tiene dos cadenas no nulas (es decir, devueltas por algún otro método):

String p1 = "p1"; 
String p2 = "p2";

Estás obligado a envolverlos en Opcional incluso si sabes que no están vacíos.

Esto empeora aún más cuando tienes que componer con otras estructuras "asignables", es decir. Eithers :

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it's a 
  string >));

árbitro:

los métodos no deberían esperar la Opción como parámetros, esto casi siempre es un olor a código que indica una fuga de flujo de control de la persona que llama a la persona que llama, debe ser responsabilidad de la persona que llama verificar el contenido de una Opción

árbitro. https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749


Al principio, también preferí pasar los Opcionales como parámetro, pero si cambia de una perspectiva de Diseñador de API a una perspectiva de Usuario de API, verá las desventajas.

Para su ejemplo, donde cada parámetro es opcional, sugeriría cambiar el método de cálculo en una clase propia como la siguiente:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();

Casi no hay buenas razones para no usar Optional como parámetros. Los argumentos en contra de esto se basan en argumentos de la autoridad (ver Brian Goetz; su argumento es que no podemos aplicar opciones no nulas) o que los argumentos Optional pueden ser nulos (esencialmente el mismo argumento). Por supuesto, cualquier referencia en Java puede ser nula, necesitamos alentar las reglas que impone el compilador, no la memoria de los programadores (lo cual es problemático y no escala).

Los lenguajes de programación funcional fomentan los parámetros Optional . Una de las mejores formas de usar esto es tener múltiples parámetros opcionales y usar liftM2 para usar una función asumiendo que los parámetros no están vacíos y devolviendo un opcional (ver 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 opcional.

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



Creo que la resonancia del ser es que primero debe verificar si Opcional es nulo en sí mismo y luego tratar de evaluar el valor que envuelve. Demasiadas validaciones innecesarias.


El patrón con Optional es para evitar volver null . Todavía es perfectamente posible pasar null a un método.

Si bien estos aún no son realmente oficiales, puede usar anotaciones de estilo JSR-308 para indicar si acepta o no valores null en la función. Tenga en cuenta que tendría que tener las herramientas adecuadas para identificarlo realmente, y proporcionaría más una comprobación estática que una política de tiempo de ejecución exigible, pero ayudaría.

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}

Este consejo es una variante de la regla general "sea lo más inespecífico posible con respecto a las entradas y lo más específico posible con respecto a las salidas".

Por lo general, si tiene un método que toma un valor simple no nulo, puede asignarlo a Optional , por lo que la versión simple es estrictamente más inespecífica con respecto a las entradas. Sin embargo, hay un montón de posibles razones por las que desearía requerir un argumento Optional :

  • desea que su función se use junto con otra API que devuelve un Optional
  • Su función debe devolver algo diferente a un vacío Optional si el valor dado está vacío
  • Piensas que Optional es tan increíble que cualquiera que use tu API debería estar obligado a conocerlo ;-)

Esto me parece un poco tonto, pero la única razón por la que puedo pensar es que los argumentos de objeto en los parámetros del método ya son opcionales de alguna manera, pueden ser nulos. Por lo tanto, obligar a alguien a tomar un objeto existente y envolverlo en una opción no tiene sentido.

Dicho esto, encadenar métodos que toman / devuelven opciones es algo razonable de hacer, por ejemplo, tal vez mónada.


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

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

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

A primera vista, puede parecer una decisión de diseño correcta. 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 solo distraen al lector. Tenga en cuenta que solo 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 el enfoque bien probado y diseñar tal caso utilizando la sobrecarga del método. En el ejemplo de la clase SystemMessage, declarar dos constructores separados es superior al uso de 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);

Los opcionales no están diseñados para este propósito, como lo explicó Brian Goetz .

Siempre puede usar @Nullable para denotar que un argumento de método puede ser nulo. El uso de un opcional no le permite realmente escribir la lógica de su método de manera más clara.


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; dejando eso al método, está bastante bien.
  2. (-) El uso de parámetros opcionales que causan lógica condicional dentro de los métodos es literalmente contraproducente.
  3. (-) La necesidad de empaquetar un argumento en un Opcional, es subóptima para el compilador y realiza un ajuste innecesario.
  4. (-) En comparación con los parámetros anulables Opcional es más costoso.

En general: Opcional unifica dos estados, que tienen que ser desentrañados. Por lo tanto, es más adecuado para el resultado que la entrada, para la complejidad del flujo de datos.


Otra razón para tener cuidado al pasar un parámetro Optional como es que un método debe hacer una cosa ... Si pasa un parámetro Optional , podría favorecer hacer más de una cosa, podría ser similar a pasar un parámetro booleano.

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }

Un enfoque más, lo que puedes hacer es

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

Una vez que haya creado una función (proveedor en este caso) podrá pasarla como cualquier otra variable y podrá llamarla utilizando

calculatedValueSupplier.apply();

La idea aquí es si tiene un valor opcional o no será un detalle interno de su función y no estará en el parámetro. Pensar funciones cuando se piensa en opcional como parámetro es en realidad una técnica muy útil que he encontrado.

En cuanto a su pregunta si realmente debería hacerlo o no, se basa en su preferencia, pero como otros dijeron, hace que su API sea fea, por decir lo menos.







optional