Vorrei aggiungere qualcos'altro che è accennato da altre risposte, ma non credo 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 capiscono i nomi: sono solo trattati come simboli atomici opachi. Quindi, come con i commenti sul codice, non esiste necessariamente alcuna correlazione tra il nome di una funzione e il modo in cui funziona 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 questi hanno più struttura del nome dettagliato, che dà al linguaggio qualcosa di non opaco da guardare. La definizione e l'utilizzo della funzione dipendono anche da questa struttura, quindi non può essere fuori sincrono con ciò che l'implementazione sta facendo (come può fare un nome o un commento).
Quando incontro o prevedo un problema come questo, prima di gridare frustrato al mio computer, per prima cosa mi chiedo se sia "giusto" dare la colpa alla macchina. In altre parole, alla macchina sono state fornite informazioni sufficienti per distinguere ciò che volevo da ciò che chiedevo?
Una chiamata come assertEqual(expected, actual)
ha più senso assertEqual(actual, expected)
, quindi è facile per noi confonderli e per la macchina andare avanti e fare la cosa sbagliata. Se assertExpectedEqualsActual
invece lo usassimo, potrebbe renderci meno propensi a fare un errore, ma non fornisce più informazioni alla macchina (non capisce l'inglese e la scelta del nome non dovrebbe influire sulla semantica).
Ciò che rende più preferibili gli approcci "strutturati", come argomenti di parole chiave, campi etichettati, tipi distinti, ecc. È che le informazioni extra sono anche leggibili automaticamente , in modo che possiamo avere la macchina individuare usi errati e aiutarci a fare le cose nel modo giusto. Il assertEqual
caso non è poi così grave, poiché l'unico problema sarebbero i messaggi imprecisi. Un esempio più sinistro potrebbe essere String replace(String old, String new, String content)
, che è facile confondere con il String replace(String content, String old, String new)
quale ha un significato molto diverso. Un semplice rimedio sarebbe quello di prenderne una coppia [old, new]
, il che renderebbe immediatamente gli errori un errore (anche senza tipi).
Nota che anche con i tipi, potremmo trovarci a non "dire alla macchina ciò che vogliamo". Ad esempio l'anti-pattern chiamato "programmazione tipicamente stringa" tratta tutti i dati come stringhe, il che rende facile confondere gli argomenti (come in questo caso), dimenticare di fare qualche passo (es. Fuga), per rompere accidentalmente invarianti (es. rendendo JSON non analizzabile), ecc.
Ciò è anche correlato alla "cecità booleana", in cui calcoliamo un gruppo di booleani (o numeri, ecc.) In una parte del codice, ma quando si cerca di usarli in un'altra non è chiaro cosa rappresentino effettivamente, se li abbiamo mescolati, ecc. Confronta questo con ad esempio enumerazioni distinte che hanno nomi descrittivi (ad esempio LOGGING_DISABLED
piuttosto che false
) e che causano un messaggio di errore se le mescoliamo .