¿Los captadores de Java 8 deberían devolver un tipo opcional?
Optional
El tipo introducido en Java 8 es algo nuevo para muchos desarrolladores.
¿Es una buena práctica un método getter que devuelva Optional<Foo>
el tipo en lugar del clásico ? Foo
Supongamos que el valor puede ser null
.
Por supuesto, la gente hará lo que quiera. Pero teníamos una intención clara al agregar esta característica, y no era ser un tipo Quizás de propósito general, por mucho que a muchas personas les hubiera gustado que lo hiciéramos. Nuestra intención era proporcionar un mecanismo limitado para los tipos de devolución de métodos de biblioteca donde era necesario que hubiera una forma clara de representar "sin resultado", y null
era abrumadoramente probable que su uso causara errores.
Por ejemplo, probablemente nunca deberías usarlo para algo que devuelva una serie de resultados o una lista de resultados; en su lugar, devuelve una matriz o lista vacía. Casi nunca deberías usarlo como campo de algo o parámetro de método.
Creo que usarlo habitualmente como valor de retorno para los captadores definitivamente sería un uso excesivo.
No hay nada malo en Opcional que deba evitarse, simplemente no es lo que mucha gente desearía que fuera y, en consecuencia, estábamos bastante preocupados por el riesgo de un uso excesivo y celoso.
(Anuncio de servicio público: NUNCA llame Optional.get
a menos que pueda demostrar que nunca será nulo; en su lugar, utilice uno de los métodos seguros como orElse
o ifPresent
. En retrospectiva, deberíamos haber llamado get
algo como getOrElseThrowNoSuchElementException
o algo que dejara mucho más claro que se trataba de un método altamente peligroso eso socavó todo el propósito de Optional
en primer lugar. 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 por mi cuenta, encontré una serie de cosas que podrían sugerir cuándo esto es 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 cada referencia nula . En cambio, su propósito es ayudar a diseñar API más comprensibles para que con solo leer la firma de un método, puedas saber si "Puede esperar un valor opcional. Esto le obliga a desenvolver activamente un opcional para hacer frente a la ausencia de un valor". - ¿ Estás cansado de las excepciones de puntero nulo? ¡Considere usar Java SE 8 opcional!
También encontré este extracto de Java 8 Opcional: Cómo usarlo
"Opcional no debe usarse en estos contextos, ya que no nos comprará nada:
- en la capa del modelo de dominio (no serializable)
- en DTO (mismo motivo)
- en los parámetros de entrada de los métodos
- en los parámetros del constructor"
Lo que también parece plantear algunos puntos válidos.
No pude encontrar ninguna connotación negativa ni señales de alerta que sugirieran que esto Optional
debería evitarse. Creo que la idea general es, si es útil o mejora la usabilidad de su API, úsela.
La razón Optional
por la que se agregó a Java es porque esto:
return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
.stream()
.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 más limpio que esto:
Method matching =
Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
.stream()
.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 la programació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 orElse()
método, ya que este código generará una excepción de todos modos, pero ya se hace una idea).
Pero ahora, la gente usa Opcional por una razón muy diferente. Lo están usando para abordar una falla en el diseño del lenguaje. El error es este: no hay forma de especificar cuáles de los parámetros y valores de retorno de una API pueden ser nulos. Puede que se mencione en los javadocs, pero la mayoría de los desarrolladores ni siquiera escriben javadocs para su código, y no muchos comprobarán los javadocs mientras escriben. Entonces, esto lleva a una gran cantidad de código que siempre verifica los valores nulos antes de usarlos, aunque a menudo no es posible que sean nulos porque ya fueron validados repetidamente nueve o diez veces en la pila de llamadas.
Creo que había una verdadera sed de resolver esta falla, porque muchas personas que vieron la nueva clase Opcional asumieron que su propósito era agregar claridad a las API. Es por eso que la gente hace preguntas como "¿los compradores deberían devolver los opcionales?" No, probablemente no deberían hacerlo, a menos que espere que el captador se utilice en programación funcional, lo cual es muy poco probable. De hecho, si observa dónde se usa Opcional en la API de Java, es principalmente en las clases Stream, que son el núcleo de la programación funcional. (No lo he comprobado muy a fondo, pero las clases Stream podrían ser el único lugar donde se utilizan).
Si planea utilizar un captador en un fragmento de código funcional, podría ser una buena idea tener un captador estándar y un segundo que devuelva Opcional.
Ah, y si necesita que su clase sea serializable, no debe usar Opcional en absoluto.
Los opcionales son una muy mala solución a la falla de la API porque a) son muy detallados y b) nunca tuvieron la intención de resolver ese problema en primer lugar.
Una solución mucho mejor para la falla de la API es Nullness Checker . Este es un procesador de anotaciones que le permite especificar qué parámetros y valores de retorno pueden ser nulos anotándolos con @Nullable. De esta manera, el compilador puede escanear el código y determinar si un valor que en realidad puede ser nulo se pasa a un valor donde no se permite nulo. De forma predeterminada, asume que no se permite que nada sea nulo a menos que esté anotado así. De esta manera, no tienes que preocuparte por los valores nulos. Pasar un valor nulo a un parámetro provocará un error del compilador. Probar un objeto nulo que no puede ser nulo genera una advertencia del compilador. El efecto de esto es cambiar NullPointerException de un error de tiempo de ejecución a un error de tiempo de compilación.
Esto lo cambia todo.
En cuanto a sus captadores, no utilice Opcional. Y trate de diseñar sus clases para que ninguno de los miembros pueda ser nulo. Y tal vez intente agregar el Comprobador de nulidad a su proyecto y declarar sus parámetros getters y setters @Nullable si lo necesitan. Sólo he hecho esto con proyectos nuevos. Probablemente produzca muchas advertencias en proyectos existentes escritos con muchas pruebas superfluas para nulos, por lo que podría ser difícil adaptarlo. Pero también detectará muchos errores. Me encanta. Mi código es mucho más limpio y confiable gracias a eso.
(También hay un nuevo lenguaje que aborda esto. Kotlin, que compila en código de bytes de Java, le permite especificar si un objeto puede ser nulo cuando lo declara. Es un enfoque más limpio).
Anexo a la publicación original (versión 2)
Después de pensarlo mucho, llegué a la conclusión de que es aceptable devolver Opcional con una condición: que el valor recuperado pueda ser en realidad nulo. He visto una gran cantidad de código en el que la gente habitualmente devuelve Opcional de captadores que no pueden devolver nulo. Veo esto como una muy mala práctica de codificación que solo agrega complejidad al código, lo que aumenta la probabilidad de errores. Pero cuando el valor devuelto pueda ser nulo, continúe y envuélvalo dentro de un Opcional.
Tenga en cuenta que los métodos que están diseñados para programación funcional y que requieren una referencia de función se escribirán (y deberían) escribirse en dos formas, una de las cuales utiliza Opcional. Por ejemplo, Optional.map()
y Optional.flatMap()
ambos toman referencias de funciones. El primero toma una referencia a un captador ordinario y el segundo toma uno que devuelve Opcional. Así que no le estás haciendo un favor a nadie al devolver un Opcional donde el valor no puede ser nulo.
Habiendo dicho todo esto, sigo viendo que el enfoque utilizado por Nullness Checker es la mejor manera de lidiar con los valores nulos, ya que convierten las NullPointerExceptions de errores de tiempo de ejecución en errores de tiempo de compilación.