¿Comentario correcto para colocar argumentos de función booleana que son "falsos"?

19

De algunos proyectos de código abierto, reuní el siguiente estilo de codificación

void someFunction(bool forget);

void ourFunction() {
  someFunction(false /* forget */);
}    

Siempre tengo dudas sobre lo que falsesignifica aquí. ¿Significa "olvidar", o "olvidar" se refiere a su parámetro correspondiente (como en el caso anterior), y "falso" tiene la intención de negarlo?

¿Qué estilo se usa con mayor frecuencia y cuál es la mejor manera (o algunas de las mejores) para evitar la ambigüedad?

Johannes Schaub - litb
fuente
38
use enumeraciones (incluso si solo hay 2 opciones) en lugar de bools
Esailija
21
Algunos idiomas admiten argumentos con nombre. En esos idiomas, podría usarsomeFunction(forget: true);
Brian
3
Me siento obligado a proporcionar los argumentos de Martin Fowler sobre los argumentos de la bandera (también habla sobre los acomodadores booleanos). En general, trate de evitarlos.
FGreg
3
Para aclarar lo obvio, los comentarios pueden mentir. Por lo tanto, siempre es mejor hacer que el código auto-documentado, porque algunos cambiarán truea falseno actualizar el comentario. Si no puede cambiar la API, entonces la mejor manera de comentar esto essomeFunction( false /* true=forget, false=remember */)
Mark Lakata el
1
@Bakuriu: en realidad, probablemente todavía tenga la API pública con dos métodos separados ( sortAscendingy sortDescending/ o similares). Ahora, por dentro , ambos pueden llamar al mismo método privado, que podría tener este tipo de parámetro. En realidad, si el lenguaje lo soportara, probablemente lo que pasaría sería una función lambda que contuviera la dirección de clasificación ...
Clockwork-Muse

Respuestas:

33

En el código de muestra que publicó, parece que forgetes un argumento de bandera. (No puedo estar seguro porque la función es puramente hipotética).

Los argumentos de la bandera son un olor a código. Indican que una función hace más de una cosa, y una buena función debe hacer solo una cosa.

Para evitar el argumento de la bandera, divida la función en dos funciones que expliquen la diferencia en el nombre de la función.

Argumento de la bandera

serveIceCream(bool lowFat)

Sin argumento de bandera

serveTraditionalIceCream()
serveLowFatIceCream()

editar: Idealmente, no es necesario que mantengas una función con el parámetro flag en absoluto. Hay casos similares a los que Fowler llama una implementación enredada donde la separación completa de las funciones crea código duplicado. Sin embargo, cuanto mayor es la complejidad ciclomática de la función parametrizada, más fuerte es el argumento para deshacerse de ella.


Esto es solo una corazonada, pero un parámetro llamado forgetsuena como la envidia de la característica. ¿Por qué una persona que llama le dice a otro objeto que olvide algo? Puede haber un problema de diseño más grande.

Aaron Kurtzhals
fuente
44
+17, casco puesto. ¿Cómo son los cuerpos serveTraditionalIceCreamy serveLowFatIceCreamcómo se ven? Tengo una enumeración con 14 tipos de helados.
JohnMark13
13
Para los métodos públicos, esta convención es buena, pero como JohnMark alude, la otra mitad de SRP es "un buen método debería ser el único método que hace lo que hace". Veo que hay variantes N + 1 de este método; N público sin parámetros, todos los cuales llaman a un método privado con el parámetro que está tratando de evitar exponer. En algún momento, simplemente te rindes y expones el maldito parámetro. Los olores de código no necesariamente significan que el código debe ser refactorizado; son solo cosas que merecen una segunda mirada en una revisión de código.
KeithS
@ Aaron Por "envidia de la característica", ¿qué querías decir?
Geek
27

En palabras simples:

  • false es un literal
  • estás pasando un literal false
  • estás diciendo someFunctionque no olvides
  • estás diciendo someFunctionque el parámetro olvidar esfalse
  • estás diciendo someFunctionque recuerdes

En mi opinión, sería mejor si la función fuera así:

void someFunction(bool remember);

lo puedes llamar

void ourFunction() {
  someFunction(true);
} 

o mantenga el nombre anterior pero cambie la función de contenedor a

void ourFunctionWithRemember() {
  someFunction(false);
} 

EDITAR:

Como dijo @Vorac, siempre esfuérzate por usar palabras positivas. La doble negación es confusa.

Tulains Córdova
fuente
15
+1 para la idea de esforzarse siempre por usar palabras positivas. La doble negación es confusa.
Vorac
1
De acuerdo, gran idea. Decirle al sistema que no haga algo es confuso. Si acepta un parámetro booleano, expréselo positivamente.
Brandon
En la mayoría de los casos, creo que también desearía ser más específico que remember, a menos que el nombre de la función haga que el significado sea remember muy obvio. rememberToCleanUp* or *persisto algo.
itsbruce
14

El parámetro puede estar bien nombrado; Es difícil saberlo sin saber el nombre de la función. Asumo que el comentario fue escrito por el autor original de la función, y fue un recordatorio de lo que pasa falseen someFunctionlos medios, pero para cualquiera que venga a lo largo después, que es un poco confuso a primera vista.

El uso de un nombre de variable positivo (sugerido en Code Complete ) puede ser el cambio más simple que haría que este fragmento sea más fácil de leer, p. Ej.

void someFunction(boolean remember);

luego se ourFunctionconvierte en:

void ourFunction() {
    someFunction(true /* remember */);
}

Sin embargo, el uso de una enumeración hace que la llamada de función sea aún más fácil de entender, a expensas de algún código de soporte:

public enum RememberFoo {
    REMEMBER,
    FORGET
}

...

void someFunction(RememberFoo remember);

...

void ourFunction() {
    someFunction(RememberFoo.REMEMBER);
}

Si no puede cambiar la firma someFunctionpor alguna razón, el uso de una variable temporal también hace que el código sea más fácil de leer, algo así como simplificar los condicionales al introducir una variable sin otra razón que hacer que el código sea más fácil de analizar por humanos .

void someFunction(boolean remember);

...

void ourFunction() {
    boolean remember = false;
    someFunction(remember);
}
Mike Partridge
fuente
1
Establecer rememberen verdadero significa olvidar (en su ejemplo de someFunction(true /* forget */);)?
Brian
2
El enumes, con mucho, la mejor solución. El hecho de que un tipo pueda representarse como un bool—es decir, son isomórficos— no significa que deba representarse como tal. El mismo argumento se aplica a stringe incluso int.
Jon Purdy
10

Cambie el nombre de la variable para que tenga sentido un valor bool.

Esto es un millón de veces mejor que agregar un comentario para explicar los argumentos de una función porque el nombre es ambiguo.

Enderland
fuente
3
Eso no responde la pregunta. Todas las cosas son obvias cuando el método se define y se llama en el mismo archivo con 4 líneas de diferencia. Pero, ¿qué pasa si todo lo que ves en este momento es la persona que llama? ¿Qué pasa si tiene múltiples booleanos? A veces, un simple comentario en línea es muy útil.
Brandon
@ Brandon Eso no es un argumento en contra de llamar al boolean doNotPersist (o, mejor, Persistir). Llamarlo "olvidar" sin decir qué olvidar es francamente inútil. Ah, y un método que toma varios booleanos como una opción apesta al cielo.
itsbruce
5

Cree un booleano local con un nombre más descriptivo y asígnele el valor. De esa manera el significado será más claro.

void ourFunction() {
    bool takeAction = false;  /* false means to forget */
    someFunction( takeAction );
}    

Si no puede cambiar el nombre de la variable, entonces el comentario debería ser un poco más expresivo:

void ourFunction() {
    /* false means that the method should forget what was requested */
    someFunction( false );
}    

fuente
1
Definitivamente un buen consejo, pero no creo que aborde el problema que el /* forget */comentario está ahí para abordar, y es que sin la declaración de la función frente a usted, puede ser difícil recordar lo que se está configurando false. (Es por eso que creo que el consejo de @ Esailija para agregar una enumeración es mejor, y por qué me encantan los idiomas que permiten parámetros con nombre.)
Gort the Robot
@StevenBurnap - ¡gracias! Tienes razón en que mi vieja respuesta no fue lo suficientemente clara al abordar la pregunta del OP. Lo he editado para que quede más claro.
3

Hay un buen artículo que menciona esta situación exacta ya que se refiere a las API Qt-Style. Allí, se llama The Boolean Parameter Trap y vale la pena leerlo.

La esencia de esto es:

  1. Sobrecargar la función para que no se necesite el bool es mejor
  2. Usar una enumeración (como sugiere Esailija) es mejor
Steven Evers
fuente
2

Este es un comentario extraño.

Desde la perspectiva del compilador, someFunction(false /* forget */);es en realidad someFunction(false);(el comentario se elimina). Entonces, todo lo que hace esa línea es llamar someFunctioncon el primer (y único) argumento establecido en false.

/* forget */es solo el nombre del parámetro. Probablemente no sea más que un recordatorio rápido (y sucio), que realmente no necesita estar allí. Simplemente use un nombre de parámetro menos ambiguo, y no necesitará el comentario en absoluto.

usuario2590712
fuente
1

Uno de los consejos del código Clean es minimizar el número de comentarios innecesarios 1 (porque tienden a pudrirse) y nombrar las funciones y los métodos correctamente.

Después de eso, simplemente eliminaría el comentario. Después de todo, los IDE modernos (como eclipse) muestran un cuadro con código cuando coloca el mouse sobre la función. Ver el código debería aclarar la ambigüedad.


1 Comentando algún código complejo está bien.

BЈовић
fuente
por cierto ¿Quién dijo algo como esto: "el peor problema del programador es cómo nombrar variable y compensar por una"?
Bћовић
44
Probablemente esté buscando martinfowler.com/bliki/TwoHardThings.html como la fuente de la misma. Escuché ajustar "Solo hay dos cosas difíciles en Ciencias de la Computación: invalidación de caché, nombrar cosas y desaparecer por un error".
1

Para aclarar lo obvio, los comentarios pueden mentir. Por lo tanto, siempre es mejor hacer que el código de auto-documentación sin tener que recurrir a los comentarios de explicar, porque una persona (tal vez) cambiará truea falseno actualizar el comentario.

Si no puede cambiar la API, recurro a 2 opciones

  • Cambie el comentario para que siempre sea cierto, independientemente del código. Si solo llama a esto una vez, esta es una buena solución, ya que mantiene la documentación local.
     algunaFunción (falso / * verdadero = olvidar, falso = recordar * /); `
  • Use #defines, especialmente si lo llama más de una vez.
     #define FORGET true
     #define RECUERDA falso
     algunaFunción (RECUERDA);
Mark Lakata
fuente
1

me gusta la respuesta acerca de hacer que el comentario sea siempre verdadero , pero si bien es bueno, creo que pierde el problema fundamental con este código: se llama con un literal.

Debe evitar el uso de literales al llamar a métodos. Variables locales, parámetros opcionales, parámetros con nombre, enumeraciones: la mejor manera de evitarlos dependerá del idioma y de lo que esté disponible, pero trate de evitarlos. Los literales tienen valores, pero no tienen significado.

jmoreno
fuente
-1

En C #, utilicé parámetros con nombre para aclarar esto

someFunction(forget: false);

O enum:

enum Memory { Remember, Forget };

someFunction(Memory.Forget);

O sobrecargas:

someFunctionForget();

O polimorfismo »:

var foo = new Elephantine();

foo.someFunction();
Jay Bazuzi
fuente
-2

La denominación siempre debe resolver la ambigüedad de los booleanos. Siempre nombro algo booleano como 'isThis' o 'shouldDoThat', por ejemplo:

void printTree(Tree tree, bool shouldPrintPretty){ ... }

y así. Pero cuando hace referencia al código de otra persona, es mejor dejar un comentario al pasar valores.

dchhetri
fuente
¿Cómo responde esto a la pregunta que se hace?
mosquito
@gnat Estaba respondiendo a su pregunta sobre la resolución de la ambigüedad con parámetros booleanos. Quizás leí mal su pregunta.
dchhetri