Me gustaría agregar algo más que está insinuado por otras respuestas, pero no creo que se haya mencionado explícitamente:
@puck dice "Todavía no hay garantía de que el primer argumento mencionado en el nombre de la función sea realmente el primer parámetro".
@cbojar dice "Usar tipos en lugar de argumentos ambiguos"
El problema es que los lenguajes de programación no entienden los nombres: solo se tratan como símbolos atómicos opacos. Por lo tanto, al igual que con los comentarios de código, no hay necesariamente ninguna correlación entre el nombre de una función y cómo funciona realmente.
Compare assertExpectedEqualsActual(foo, bar)
con algunas alternativas (de esta página y de otras partes), como:
# Putting the arguments in a labelled structure
assertEquals({expected: foo, actual: bar})
# Using a keyword arguments language feature
assertEquals(expected=foo, actual=bar)
# Giving the arguments different types, forcing us to wrap them
assertEquals(Expected(foo), Actual(bar))
# Breaking the symmetry and attaching the code to one of the arguments
bar.Should().Be(foo)
Todos estos tienen más estructura que el nombre detallado, lo que le da al lenguaje algo no opaco para mirar. La definición y el uso de la función también dependen de esta estructura, por lo que no puede estar fuera de sincronización con lo que está haciendo la implementación (como puede hacerlo un nombre o comentario).
Cuando encuentro o veo un problema como este, antes de gritarle frustrado a mi computadora, primero me tomo un momento para preguntar si es 'justo' culpar a la máquina. En otras palabras, ¿se le dio a la máquina suficiente información para distinguir lo que quería de lo que pedí?
Una llamada como assertEqual(expected, actual)
tiene tanto sentido como assertEqual(actual, expected)
, por lo que es fácil para nosotros mezclarlos y que la máquina avance y haga lo incorrecto. Si lo assertExpectedEqualsActual
usáramos, podría hacernos menos propensos a cometer un error, pero no da más información a la máquina (no puede entender inglés y la elección del nombre no debería afectar la semántica).
Lo que hace que los enfoques "estructurados" sean más preferibles, como argumentos de palabras clave, campos etiquetados, tipos distintos, etc., es que la información adicional también es legible por máquina , por lo que podemos hacer que la máquina detecte usos incorrectos y nos ayude a hacer las cosas bien. El assertEqual
caso no es tan malo, ya que el único problema serían los mensajes inexactos. Podría ser un ejemplo más siniestro String replace(String old, String new, String content)
, que es fácil de confundir y String replace(String content, String old, String new)
que tiene un significado muy diferente. Un remedio simple sería tomar un par [old, new]
, lo que haría que los errores desencadenaran un error de inmediato (incluso sin tipos).
Tenga en cuenta que incluso con los tipos, es posible que no estemos "diciéndole a la máquina lo que queremos". Por ejemplo, el antipatrón llamado "programación tipada en cadena" trata todos los datos como cadenas, lo que facilita mezclar argumentos (como este caso), olvidar realizar algún paso (p. Ej., Escapar), romper accidentalmente invariantes (p. Ej. haciendo JSON no analizable), etc.
Esto también está relacionado con la "ceguera booleana", donde calculamos un montón de booleanos (o números, etc.) en una parte del código, pero al intentar usarlos en otra no está claro qué representan realmente, si los tenemos mezclados, etc. Compare esto con, por ejemplo, enumeraciones distintas que tienen nombres descriptivos (por ejemplo, en LOGGING_DISABLED
lugar de false
) y que provocan un mensaje de error si los mezclamos.