Vorrei aggiungere qualcos'altro suggerito da altre risposte, ma non penso sia stato menzionato esplicitamente:
@puck dice "Non c'è ancora alcuna garanzia che il primo argomento menzionato nel nome della funzione sia davvero il primo parametro."
@cbojar dice "Usa tipi invece di argomenti ambigui"
Il problema è che i linguaggi di programmazione non comprendono i nomi: vengono trattati come simboli atomici e opachi. Quindi, come con i commenti al codice, non c'è necessariamente alcuna correlazione tra ciò che viene chiamata una funzione e come opera effettivamente.
Confronta assertExpectedEqualsActual(foo, bar)
con alcune alternative (da questa pagina e altrove), come:
# 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)
Tutti hanno più struttura del nome dettagliato, che dà al linguaggio qualcosa di non opaco da guardare. Anche la definizione e l'uso della funzione dipende da questa struttura, quindi non può uscire fuori sincrono con ciò che sta facendo l'implementazione (come può fare un nome o un commento).
Quando incontro o prevedo un problema come questo, prima di urlare al mio computer per la frustrazione, prima mi prendo un momento per chiedere se è "giusto" dare la colpa alla macchina. In altre parole, la macchina ha fornito informazioni sufficienti per distinguere ciò che volevo da ciò che ho chiesto?
Una chiamata come assertEqual(expected, actual)
ha lo stesso senso di assertEqual(actual, expected)
, quindi è facile per noi farli confondere e perché la macchina possa andare avanti e fare la cosa sbagliata. Se invece usassimo assertExpectedEqualsActual
, potrebbe rendere noi meno probabilità di commettere un errore, ma non dà più informazioni alla macchina (non può capire l'inglese, e la scelta del nome non dovrebbe influenzare semantica).
Ciò che rende gli approcci "strutturati" più preferibili, come gli argomenti delle parole chiave, i campi etichettati, i tipi distinti, ecc. è che le informazioni extra sono anche leggibili dalla macchina , così possiamo avere la macchina in posizione sbagliata usi e aiutaci a fare le cose bene. Il caso assertEqual
non è male, poiché l'unico problema sarebbero i messaggi imprecisi. Un esempio più sinistro potrebbe essere String replace(String old, String new, String content)
, che è facile da confondere con String replace(String content, String old, String new)
che ha un significato molto diverso. Un rimedio semplice sarebbe prendere una coppia [old, new]
, il che farebbe sì che gli errori inneschino immediatamente un errore (anche senza tipi).
Si noti che anche con i tipi, potremmo trovarci a non "dire alla macchina ciò che vogliamo". Ad esempio l'anti-pattern chiamato "programmazione a stringhe tipizzate" tratta tutti i dati come stringhe, il che rende facile confondere gli argomenti (come in questo caso), dimenticare di eseguire alcuni passaggi (es. Escape), per rompere involontariamente invarianti (es. rendendo JSON non analizzabile, ecc.
Questo è anche legato alla "cecità booleana", in cui calcoliamo un gruppo di booleani (o numeri, ecc.) in una parte del codice, ma quando proviamo ad usarli in un'altra non è chiaro cosa siano in realtà rappresentando, se li abbiamo mescolati, ecc. Confronta questo ad es enumerazioni distinte che hanno nomi descrittivi (ad esempio LOGGING_DISABLED
anziché false
) e che causano un messaggio di errore se vengono confusi.