Perché la codifica dei nomi degli argomenti nei nomi di funzione non è più comune? [chiuso]

47

In Pulisci codice l'autore fornisce un esempio di

assertExpectedEqualsActual(expected, actual)

vs

assertEquals(expected, actual)

con il primo ha affermato di essere più chiaro perché elimina la necessità di ricordare dove vanno gli argomenti e il potenziale abuso che ne deriva. Tuttavia, non ho mai visto un esempio del precedente schema di denominazione in alcun codice e vedo quest'ultimo sempre. Perché i programmatori non adottano il primo se è, come afferma l'autore, più chiaro di quest'ultimo?

    
posta EternalStudent 30.06.2018 - 14:39
fonte

8 risposte

64

Perché è più da digitare e più da leggere

Il motivo più semplice è che alle persone piace digitare meno e codificare tali informazioni significa più digitazione. Durante la lettura, ogni volta devo leggere il tutto anche se ho familiarità con quale dovrebbe essere l'ordine degli argomenti. Anche se non ha familiarità con l'ordine degli argomenti ...

Molti sviluppatori utilizzano IDE

Gli IDE spesso forniscono un meccanismo per visualizzare la documentazione per un determinato metodo passando con il mouse o tramite una scorciatoia da tastiera. Per questo motivo, i nomi dei parametri sono sempre a portata di mano.

La codifica degli argomenti introduce la duplicazione e l'accoppiamento

I nomi dei parametri dovrebbero già documentare ciò che sono. Scrivendo i nomi nel nome del metodo, stiamo duplicando anche queste informazioni nella firma del metodo. Creiamo anche un accoppiamento tra il nome del metodo e i parametri. Supponiamo che expected e actual confondano i nostri utenti. Passare da assertEquals(expected, actual) a assertEquals(planned, real) non richiede la modifica del codice client utilizzando la funzione. Passare da assertExpectedEqualsActual(expected, actual) a assertPlannedEqualsReal(planned, real) significa una violazione dell'API. O non cambiamo il nome del metodo, che diventa rapidamente confuso.

Usa tipi invece di argomenti ambigui

Il vero problema è che abbiamo argomenti ambigui che sono facilmente commutabili perché sono dello stesso tipo. Possiamo invece usare il nostro sistema di tipi e il nostro compilatore per far rispettare l'ordine corretto:

class Expected<T> {
    private T value;
    Expected(T value) { this.value = value; }
    static Expected<T> is(T value) { return new Expected<T>(value); }
}

class Actual<T> {
    private T value;
    Actual(T value) { this.value = value; }
    static Actual<T> is(T value) { return new Actual<T>(value); }
}

static assertEquals(Expected<T> expected, Actual<T> actual) { /* ... */ }

// How it is used
assertEquals(Expected.is(10), Actual.is(x));

Questo può quindi essere applicato a livello di compilatore e garantisce che non è possibile ottenerli all'indietro. Avvicinandosi da una diversa angolazione, questo è essenzialmente ciò che fa la libreria Hamcrest per i test.

    
risposta data 30.06.2018 - 16:34
fonte
20

Chiedete un dibattito di vecchia data in programmazione. Quanta verbosità è buona? Come risposta generale, gli sviluppatori hanno scoperto che la prolissità extra che nomina gli argomenti non vale la pena.

La verbosità non significa sempre più chiarezza. Considerare

copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)

vs

copy(output, source)

Entrambi contengono lo stesso bug, ma in realtà abbiamo reso più facile trovare quell'errore? Come regola generale, la cosa più facile da eseguire per eseguire il debug è quando tutto è al massimo teso, tranne le poche cose che hanno il bug, e quelle sono abbastanza dettagliate da dirti cosa è andato storto.

C'è una lunga storia di aggiunta di verbosità. Ad esempio, c'è la " notazione ungherese " generalmente impopolare che ci ha dato nomi meravigliosi come lpszName . Ciò è generalmente caduto nel dimenticatoio nella popolazione generale dei programmatori. Tuttavia, l'aggiunta di caratteri ai nomi delle variabili membro (come mName o m_Name o name_ ) continua ad avere popolarità in alcune cerchie. Altri lo hanno completamente abbandonato. Mi capita di lavorare su un codebase di simulazione fisica i cui documenti in stile di codifica richiedono che qualsiasi funzione che restituisca un vettore debba specificare il frame del vettore nella chiamata di funzione ( getPositionECEF ).

Potresti essere interessato ad alcune delle lingue rese famose da Apple. Objective-C include i nomi degli argomenti come parte della firma della funzione (La funzione [atm withdrawFundsFrom: account usingPin: userProvidedPin] è scritta nella documentazione come withdrawFundsFrom:usingPin: . Questo è il nome della funzione). Swift ha preso una serie di decisioni simili, richiedendo di inserire i nomi degli argomenti nelle chiamate di funzione ( greet(person: "Bob", day: "Tuesday") ).

    
risposta data 01.07.2018 - 03:21
fonte
8

L'autore di "Clean Code" indica un problema legittimo, ma la sua soluzione suggerita è piuttosto inelegante. Di solito ci sono modi migliori per migliorare i nomi dei metodi poco chiari.

Ha ragione che assertEquals (dalle librerie di test dell'unità stile xUnit) non chiarisce quale argomento è l'atteso e quale è l'effettivo. Anche questo mi ha morso! Molte librerie di test di unità hanno rilevato il problema e hanno introdotto sintassi alternative, come:

actual.Should().Be(expected);

O simili. Che è sicuramente molto più chiaro di assertEquals ma anche molto meglio di assertExpectedEqualsActual . Ed è anche molto più componibile.

    
risposta data 30.06.2018 - 17:52
fonte
5

Stai cercando di orientare la tua strada tra Scilla e Cariddi alla chiarezza, cercando di evitare inutili verbosità (anche conosciute come vagabondaggi senza scopo) e di eccessiva brevità (anche nota come cripticità).

Quindi, dobbiamo esaminare l'interfaccia che vuoi valutare, un modo per fare affermazioni di debug che due oggetti sono uguali.

  1. C'è qualche altra funzione che potrebbe prendere in considerazione per nome e ragione?
    No, quindi il nome stesso è abbastanza chiaro.
  2. I tipi di qualsiasi significato?
    No, quindi ignoriamoli. L'hai già fatto? Buona.
  3. È simmetrico nei suoi argomenti?
    Quasi, per errore il messaggio mette ogni rappresentazione degli argomenti nel proprio punto.

Quindi, vediamo se questa piccola differenza ha un qualche significato e non è coperta da convenzioni forti esistenti.

Il pubblico previsto è scomodo se gli argomenti vengono scambiati involontariamente?
No, anche gli sviluppatori ottengono una traccia dello stack e devono comunque controllare il codice sorgente per correggere il bug.
Anche senza una traccia di stack completa, la posizione delle asserzioni risolve la domanda. E se anche questo manca e non è ovvio dal messaggio che è, al massimo raddoppia le possibilità.

L'ordine degli argomenti segue la convenzione?
Sembra essere il caso. Anche se sembra al meglio una convenzione debole.

Quindi, la differenza sembra abbastanza insignificante, e l'ordine degli argomenti è coperto da una convenzione abbastanza strong che qualsiasi tentativo di metterlo in codice nel nome-funzione ha un'utilità negativa.

    
risposta data 01.07.2018 - 16:34
fonte
3

Spesso non aggiunge alcuna chiarezza logica.

Confronta "Aggiungi" a "AddFirstArgumentToSecondArgument".

Se hai bisogno di un sovraccarico che, diciamo, aggiunge tre valori. Cosa avrebbe più senso?

Un altro "Aggiungi" con tre argomenti?

o

"AddFirstAndSecondAndThirdArgument"?

Il nome del metodo dovrebbe esprimere il suo significato logico. Dovrebbe dire cosa fa. Dicendo, a livello micro, quali passi ci vuole non rende più facile per il lettore. I nomi degli argomenti forniranno ulteriori dettagli se necessario. Se hai ancora bisogno di ulteriori dettagli, il codice sarà proprio lì per te.

    
risposta data 30.06.2018 - 15:50
fonte
2

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.

    
risposta data 02.07.2018 - 17:07
fonte
1

because it removes the need to remember where the arguments go

Lo fa davvero? Non c'è ancora alcuna garanzia che il primo argomento menzionato nel nome della funzione sia davvero il primo parametro. Quindi, meglio guardarlo (o lasciare che il tuo IDE lo faccia) e stare con nomi ragionevoli che basarsi ciecamente su un nome abbastanza sciocco.

Se leggi il codice dovresti vedere facilmente cosa succede quando i parametri sono nominati come dovrebbero. copy(source, destination) è molto più facile da capire rispetto a qualcosa come copyFromTheFirstLocationToTheSecondLocation(placeA, placeB) .

Why don't coders adopt the former if it is, as the author asserts, clearer than the latter?

Perché ci sono diversi punti di vista su stili diversi e puoi trovare x autori di altri articoli che affermano il contrario. Diventerai pazzo cercando di seguire tutto ciò che qualcuno scrive da qualche parte; -)

    
risposta data 01.07.2018 - 15:16
fonte
0

Sono d'accordo che codificare i nomi dei parametri in nomi di funzioni rende la scrittura e l'utilizzo di funzioni più intuitive.

copyFromSourceToDestination( // "...ahh yes, the source directory goes first"

È facile dimenticare l'ordine degli argomenti nelle funzioni e nei comandi della shell e molti programmatori si affidano alle funzionalità IDE o ai riferimenti alle funzioni per questo motivo. Avere gli argomenti descritti nel nome sarebbe una soluzione eloquente a questa dipendenza.

Tuttavia, una volta scritta, la descrizione degli argomenti diventa ridondante al programmatore successivo che deve leggere l'istruzione, poiché nella maggior parte dei casi verranno utilizzate variabili denominate.

copy(sourceDir, destinationDir); // "...makes sense"

La chiarezza di ciò vincerà la maggior parte dei programmatori e personalmente trovo che sia più facile da leggere.

EDIT: come sottolineato da @Blrfl, i parametri di codifica non sono poi così "intuitivi" dal momento che è necessario ricordare il nome della funzione in primo luogo. Ciò richiede la ricerca di riferimenti alle funzioni o l'aiuto di un IDE che probabilmente fornirà comunque informazioni sull'ordinamento dei parametri.

    
risposta data 30.06.2018 - 16:42
fonte

Leggi altre domande sui tag