¿Deberían los captadores de Java 8 devolver el tipo opcional?

Optional tipo Optional introducido en Java 8 es algo nuevo para muchos desarrolladores.

¿Es un método getter que devuelve el tipo Optional en lugar del clásico Foo una buena práctica? Suponga que el valor puede ser null .

Por supuesto, las personas harán lo que quieran. Pero teníamos una intención clara al agregar esta función, y no era para ser un tipo de Tal vez de uso general o scala.Option , tanto como a muchas personas les hubiera gustado que lo hiciéramos. Nuestra intención era proporcionar un mecanismo limitado para los tipos de devolución del método de la biblioteca, donde era necesario que hubiera una forma clara de representar “sin resultado”, y el uso de null para eso era abrumadoramente propenso a causar errores.

Por ejemplo, probablemente nunca deberías usarlo para algo que arroje una matriz de resultados, o una lista de resultados; en su lugar, devuelve una matriz o lista vacía. Casi nunca debería usarlo como un campo de algo o un parámetro de método.

Creo que usarlo rutinariamente como un valor de retorno para los compradores definitivamente sería un uso excesivo.

No hay nada de malo en Opcional que deba evitarse, simplemente no es lo que muchas personas desearían que fuera, y en consecuencia, estábamos bastante preocupados por el riesgo de un celoso uso excesivo.

(Anuncio de servicio público: NUNCA llame a Optional.get menos que pueda probar que nunca será nulo; en su lugar, use uno de los métodos seguros como orElse o ifPresent . En retrospectiva, deberíamos haber llamado get getOrElseThrowNoSuchElementException get getOrElseThrowNoSuchElementException or getOrElseThrowNoSuchElementException o algo que lo hiciera mucho más claro que este era un método altamente peligroso que en primer lugar socavó todo el propósito de Optional . Lección aprendida. (ACTUALIZACIÓN: Java 10 tiene Optional.orElseThrow() , que es semánticamente equivalente a get() , pero cuyo nombre es más apropiado. )

Después de investigar un poco, me he encontrado con varias cosas que podrían sugerir cuando sea apropiado. La más autorizada es la siguiente cita de un artículo de Oracle:

“Es importante tener en cuenta que la intención de la clase Opcional no es reemplazar todas las referencias nulas . En cambio, su propósito es ayudar a diseñar API más comprensibles de modo que con solo leer la firma de un método, pueda saber si puede esperar un valor opcional. Esto te obliga a desenvolver activamente un Opcional para lidiar con la ausencia de un valor “. – ¿ Cansado de las excepciones del puntero nulo? Considere usar Java SE 8’s Opcional!

También encontré este extracto de Java 8 Opcional: Cómo usarlo

“Opcional no está destinado a ser utilizado en estos contextos, ya que no nos comprará nada:

  • en la capa de modelo de dominio (no serializable)
  • en DTO (mismo motivo)
  • en parámetros de entrada de métodos
  • en parámetros de constructor “

Lo cual también parece plantear algunos puntos válidos.

No pude encontrar ninguna connotación negativa o bandera roja para sugerir que se debe evitar Optional . Creo que la idea general es que, si es útil o mejora la usabilidad de su API, úsela.

En general, diría que es una buena idea usar el tipo opcional para los valores de retorno que pueden ser anulables. Sin embargo, en el caso de los frameworks, asumo que reemplazar getters clásicos con tipos opcionales causará muchos problemas cuando se trabaja con frameworks (p. Ej., Hibernate) que se basan en convenciones de encoding para getters y setters.

El motivo por el que se agregó la Optional a Java es porque esto:

 return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods()) .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName()) .filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses)) .filter(m -> Objects.equals(m.getReturnType(), returnType)) .findFirst() .getOrThrow(() -> new InternalError(...)); 

es mejor que esto:

 Method matching = Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods()) .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName()) .filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses)) .filter(m -> Objects.equals(m.getReturnType(), returnType)) .getFirst(); if (matching == null) throw new InternalError("Enclosing method not found"); return matching; 

Mi punto es que Opcional fue escrito para soportar progtwigción funcional , que se agregó a Java al mismo tiempo. (El ejemplo es cortesía de un blog de Brian Goetz. Un mejor ejemplo podría usar el método orElse() , ya que este código emitirá una excepción de todos modos, pero se obtiene la imagen).

Pero ahora, las personas usan Opcional por una razón muy diferente. Lo están usando para abordar un defecto en el diseño del lenguaje. El error es este: no hay forma de especificar cuál de los parámetros de una API y los valores de retorno pueden ser nulos. Se puede mencionar en los javadocs, pero la mayoría de los desarrolladores ni siquiera escriben javadocs para su código, y no muchos verán los javadocs mientras escriben. Esto conduce a una gran cantidad de código que siempre verifica valores nulos antes de usarlos, aunque a menudo no pueden ser nulos porque ya fueron validados nueve o diez veces en la stack de llamadas.

Creo que hubo una sed real para resolver este error, porque muchas personas que vieron la nueva clase opcional asumieron que su propósito era agregar claridad a las API. ¿Cuál es la razón por la cual la gente hace preguntas como “¿deberían los getters devolver opciones?” No, probablemente no deberían, a menos que espere que el getter se use en la progtwigción funcional, lo cual es muy poco probable. De hecho, si observa dónde se usa Opcional en la API de Java, se trata principalmente de las clases Stream, que son el núcleo de la progtwigción funcional. (No he revisado a fondo, pero las clases Stream pueden ser el único lugar donde se usan).

Si planea utilizar un getter en un código funcional, podría ser una buena idea tener un getter estándar y un segundo que devuelva Optional.

Ah, y si necesitas que tu clase sea serializable, no debes usar Opcional.

Los opcionales son una muy mala solución para el error API porque a) son muy detallados, yb) nunca tuvieron la intención de resolver ese problema en primer lugar.

Una solución mucho mejor a la falla API es el Nullness Checker . Este es un procesador de anotaciones que le permite especificar qué parámetros y valores devueltos pueden ser nulos anotándolos con @Nullable. De esta forma, el comstackdor puede escanear el código y descubrir si un valor que realmente puede ser nulo se está pasando a un valor donde no está permitido null. De forma predeterminada, asume que no se permite que nada sea nulo a menos que esté anotado. De esta manera, no tiene que preocuparse por los valores nulos. Pasar un valor nulo a un parámetro dará como resultado un error de comstackción. Probar un objeto para null que no puede ser nulo produce una advertencia del comstackdor. El efecto de esto es cambiar NullPointerException de un error de tiempo de ejecución a un error en tiempo de comstackción.

Esto cambia todo.

En cuanto a sus captadores, no use Opcional. Y trate de diseñar sus clases para que ninguno de los miembros sea nulo. Y tal vez intente agregar el Nullness Checker a su proyecto y declarar sus getters y parámetros del setter @Nullable si lo necesitan. Solo he hecho esto con nuevos proyectos. Probablemente produce muchas advertencias en proyectos existentes escritos con muchas pruebas superfluas para nulo, por lo que puede ser difícil realizar modificaciones. Pero también atrapará muchos errores. Lo amo. Mi código es mucho más limpio y más confiable debido a eso.

(También hay un nuevo lenguaje que aborda esto. Kotlin, que comstack el código de bytes de Java, le permite especificar si un objeto puede ser nulo cuando lo declara. Es un enfoque más limpio).

Si está utilizando serializadores modernos y otros marcos que entienden Optional entonces he encontrado que estas pautas funcionan bien al escribir beans de Entity y capas de dominio:

  1. Si la capa de serialización (generalmente un DB) permite un valor null para una celda en la columna BAR en la tabla FOO , entonces el Foo.getBar() puede devolver Optional indicando al desarrollador que este valor puede razonablemente esperarse que sea nulo y que debería manejar esto. Si el DB garantiza que el valor no será nulo, el captador no debería envolverlo en un Optional .
  2. Foo.bar debe ser private y no ser Optional . Realmente no hay ninguna razón para que sea Optional si es private .
  3. El setter Foo.setBar(String bar) debería tomar el tipo de bar y no Optional . Si está bien utilizar un argumento null indíquelo en el comentario de JavaDoc. Si no está bien usar null IllegalArgumentException o alguna lógica comercial apropiada es, en mi humilde opinión, más apropiada.
  4. Los constructores no necesitan argumentos Optional (por razones similares al punto 3). En general, solo incluyo argumentos en el constructor que deben ser no nulos en la base de datos de serialización.

Para hacer que lo anterior sea más eficiente, quizás desee editar sus plantillas IDE para generar captadores y plantillas correspondientes para toString() , equals(Obj o) etc. o use campos directamente para ellos (la mayoría de los generadores IDE ya se ocupan de los nulos).