Retorno booleano de set.add () en if conditional?

15

El operador add de la clase set devuelve un valor booleano que es verdadero si el elemento (que se agregará) ya no estaba allí, y falso de lo contrario. Esta escribiendo

if (set.add(entry)) {
    //do some more stuff
}

considerado un buen estilo en términos de escribir código limpio? Me pregunto ya que haces dos cosas a la vez. 1) agregar el elemento y 2) verificar si el elemento existió.

Andreas Braun
fuente
66
Estás hablando del estándar java.util.Set, que devuelve verdadero addcuando el elemento ya no estaba allí, ¿verdad?
user2357112 es compatible con Monica el
1
Por lo general, consideraría la prueba opuesta:if (!set.add(entry)) {// entry already present, possibly a case you want to handle}
njzk2
¿Hay algo malo en hacer dos cosas a la vez?
user207421
@ user2357112 sí, eso es correcto.
Andreas Braun

Respuestas:

19

Sí lo es.

El punto habitual de que una operación devuelva un valor booleano es que puede usarlo para tomar decisiones, es decir, dentro de una ifconstrucción. La única otra forma en que podría darse cuenta de este potencial sería almacenar el valor de retorno en una variable y luego reutilizarlo inmediatamente en un if, lo cual es simplemente tonto y ciertamente no es preferible de ninguna manera a la escritura if(operation()) { ... }. Así que solo adelante y haz eso, no te juzgaremos (por eso).

Kilian Foth
fuente
Gracias por tu respuesta. Como @Niels vanReijmersdal señala en su respuesta, esto rompe el comando y la separación de consultas, ¿no? ¿No crees que eso importa?
Andreas Braun
44
@AndreasBraun personalmente, creo que la separación de consultas de comandos es tonta. A menudo es lógico que un método realice una acción y devuelva un valor relacionado con esa acción. Hacer cumplir una separación artificial entre los dos conduce a un código innecesariamente detallado.
1
@ dan1111: de hecho. Además, la "separación de comandos y consultas" lleva a "verificar y luego actuar" y anti patrones similares, que pueden romperse en contextos de subprocesos múltiples. Es bastante extraño anunciar tal separación, cuando el resto del mundo está tratando de construir operaciones fusionadas atómicas para soluciones SMP robustas y eficientes al mismo tiempo ...
Holger
2
@ Jörg W Mittag: ¿página 759 de qué? Dado que una operación atómica es intrínsecamente inmune contra el antipatrón "comprobar luego actuar", no parece beneficioso dividirlo solo para leer un "capítulo completo" de lo que sea que se descubra, cómo hacer que el hilo de construcción sea seguro de nuevo. Como no mencionó ningún argumento real, no tengo "objeciones específicas a esos argumentos".
Holger
1
@ Jörg W Mittag: Bueno, estoy hablando de la respuesta en esta página, desalentando el uso Set.addcomo operación única sin nombrar una alternativa. Si la operación prevista es agregar un solo elemento y averiguar si se ha agregado, no hay necesidad de una alternativa más poderosa que aún complica la operación sin beneficio. Espero que sepa que Set.addes un método JRE integrado que puede usarse sin estudiar primero un libro de> 700 páginas.
Holger
12

Diría que no es tan limpio como sea posible, porque obliga al mantenedor a saber o buscar lo que significa el valor de retorno. ¿Significa que el valor ya existía, no existía, se insertó con éxito? Si no lo usa mucho, no lo sabrá, e incluso si lo hace, es mucha más carga mental.

Preferiría lo siguiente:

boolean added = set.add(entry);

if (added) {
    //do some more stuff
}

Sí, un poco más detallado, pero el compilador debería generar prácticamente el mismo código de bytes, e incluso las personas que no han usado conjuntos de Java en años pueden seguir la lógica sin buscar nada.

Karl Bielefeldt
fuente
2
adddevuelve verdadero si el elemento ya no estaba allí, no si lo estaba. (La redacción de la pregunta es engañosa)
User2357112 apoya a Monica el
99
Buscar un método es menos complicado que tratar de comprender la intención del desarrollador original de una variable redundante declarada fuera del alcance donde se usa. En todos los IDE de Java modernos, un desarrollador puede pasar el puntero del mouse sobre un método y acceder instantáneamente a su documentación. Los buenos desarrolladores hacen esto constantemente cuando trabajan con cualquier API desconocida.
Kevin Krumwiede
99
Yo segundo @ Holger. Si no lo sabe Set.add, no tiene experiencia y debería buscar estos métodos para aprenderlos y adquirir más experiencia.
Vuelva a instalar Mónica
77
notAlreadyPresentNo es la mejor redacción. Yo usaría added, y espero que el lector sepa por qué no se agregaría un valor a un Conjunto.
njzk2
33
@JustinTime: Usar comentarios para describir lo que hace el código se considera una mala práctica. Su código debe ser autodescriptivo. En una revisión de código, marcaría su ejemplo como inaceptable.
BlueRaja - Danny Pflughoeft
8

Si verdadero significa éxito, entonces es bueno, código claro.

Existe una convención generalizada de que una función o método devuelve verdadero (o algo que se evalúa como verdadero) en caso de éxito. Mientras su código siga eso, creo que poner el método en condicional está bien.

Un código como este está innecesariamente desordenado en mi opinión:

boolean frobulate_succeeded = thing.frobulate();

if (frobulate_succeeded) {
    ...
}

Se siente como si te estuvieras repitiendo.

Sin embargo, la pregunta es ambigua sobre el significado del valor de retorno. Usted dice "un valor booleano que indica si el elemento agregado ya existía", lo que podría implicar que verdadero significa que el elemento existió (y no se produjo la suma). Si ese es el caso, idealmente cambiaría el comportamiento de retorno del método para que sea más convencional. Si eso no es posible, agregaría una variable intermedia adicional que le permite etiquetar claramente el resultado de retorno en su código (como lo sugieren otros).


fuente
¿Qué significa el éxito en el contexto de agregar un elemento a un conjunto? No lanzar una excepción significa éxito; verdadero versus falso significa algo más específico en este contexto.
Vuelva a instalar Mónica
@ Solomonoff'sSecret En este caso, una excepción significa que el conjunto se negó a agregar el elemento, falsesignifica que el elemento no se agregó porque ya estaba contenido y truesignifica que el elemento ya no estaba contenido. Como add()agrega el elemento al conjunto si el conjunto no lo contenía, truesignifica que el elemento se agregó correctamente al conjunto.
Justin Time - Restablece a Mónica el
@JustinTime Está agregando sus propios juicios de valor a los dos casos. Otra interpretación es que el éxito es que el elemento está en el conjunto cuando el método regresa. Si el elemento ya estaba en el conjunto, addtiene éxito sin tener que hacer nada. Si el elemento no estaba ya en el conjunto, addtiene éxito al agregar el elemento al conjunto. La interpretación correcta es arbitraria. La definición menos arbitraria de éxito es la del lenguaje: el método tiene éxito si vuelve normalmente.
Vuelva a instalar Mónica
1
La definición menos arbitraria de éxito es que el método realizó con éxito la tarea que se propuso realizar. Si tengo un método firstLessThanSecond(int l, int r), y devuelve truesi l > ro falsesi l <= r, entonces ese método no tiene éxito, aunque regrese normalmente.
Justin Time - Restablece a Mónica el
1
@JustinTime addes una abreviatura severa del contrato. La documentación comienza con "Agrega el elemento especificado a este conjunto si aún no está presente", que se satisface si el elemento ya estaba o no en el conjunto. Usted es libre de interpretar el valor de retorno como lo desee, pero en última instancia es solo su interpretación. Y en su segundo ejemplo, el método evalúa si una condición es verdadera. Si la condición es falsa, el método ciertamente no ha fallado, ya que ha evaluado con éxito el condicional.
Vuelva a instalar a Mónica el
2

Yo diría que es muy parecido a C. La mayoría de las veces preferiría tener una variable con un nombre descriptivo para un resultado de mutación, y que no ocurra mutación en una ifcondición.

Un compilador eliminará esta variable si se reutiliza de inmediato. Un humano tendrá más fácil leer la fuente; Para mí, es más importante.

Si alguien tiene que extender la condición agregando una cláusula and/ ora la condición if, puede terminar no llamando .add()en ciertos casos debido a la evaluación de cortocircuito. A menos que se anticipe específicamente un cortocircuito, esto puede terminar como un error.

9000
fuente
Si escribo, if (set.contains(entry)){set.add(entry); //do more stuff}¿eso también lo elimina el compilador?
Andreas Braun el
1
@AndreasBraun: No lo creo; el compilador no tiene idea sobre el enlace semántico entre containsy add. Además, hace doble trabajo al buscar entryen el set; Esto puede jugar un papel para conjuntos muy grandes y bucles muy ligeros.
9000
1
Entonces, supongo que preferiría no escribir, if (set.contains(entry)){set.add(entry); //do more stuff}sino ir con, por ejemplo, la respuesta de Karl Bielefeldt
Andreas Braun
@AndreasBraun: exactamente (elevó esa respuesta).
9000
1
Un buen punto. Las mutaciones en ifs son complicadas, ya que un futuro desarrollador puede tener otras condiciones con cortocircuitos.
njzk2
0

Su código parece romper la separación de consulta de comando . Esto se discute en el libro Clean Code y en el video Function Structure . Entonces, desde una perspectiva de Código limpio, creo que no se considera un buen estilo.

“El código limpio es simple y directo. El código limpio se lee como una prosa bien escrita. El código limpio nunca oscurece la intención del diseñador, sino que está lleno de abstracciones nítidas y líneas de control directas.

- Grady Booch, autor de Análisis y diseño orientado a objetos con aplicaciones "

Para mí, la intención de su código no está clara. ¿Se ejecuta ambos si la entrada se agrega con éxito o también cuando ya existe? ¿Qué add()devuelve? ¿el objeto? El código de error?

Niels van Reijmersdal
fuente
2
Bueno, sí, eso es lo que yo también pensé. Pero también creo que @Kilian Foth tiene un punto. Si quisiera separar la consulta y el comando, tendría que escribir, lo if (set.contains(entry)){set.add(entry); //do more stuff}que parece un poco tonto. ¿Cuál es su opinión sobre esto?
Andreas Braun el
No sé el dominio y el contexto de este código para sugerir un buen nombre, pero lo envolvería en una función con una intención clara. Por ejemplo: doesEntryExistOrCreateEntry (entrada), esto podría contener su lógica contiene () + add () y devolver un valor booleano. Pregunta similar aquí: softwareengineering.stackexchange.com/questions/149708/… que analiza esta práctica fuera del código limpio.
Niels van Reijmersdal
66
Creo que la regla de separación de consulta de comando es tonta, particularmente cuando se aplica a un caso como este, donde un método realiza una acción y devuelve el estado de éxito de la acción. Pero tampoco explicas cuál es la regla ni aportas mucho para justificarla. Votado por estas razones.
1
"¿Qué devuelve add ()?" Espero que cualquier desarrollador de Java que revise el código conozca la CollectionAPI.
njzk2
33
De acuerdo con @ dan1111. Es especialmente tonto porque es el tipo de cosas que hacen un terreno fértil para las condiciones de carrera.
Paul Draper