Contesto
Di recente mi sono interessato a produrre codice meglio formattato. E per meglio intendo "seguire le regole approvate da un numero sufficiente di persone per considerarlo una buona pratica" (dal momento che non ci sarà mai un modo unico "migliore" per codificare, ovviamente).
In questi giorni, scrivo principalmente codice in Ruby, quindi ho iniziato a usare una linter (Rubocop) per fornirmi alcune informazioni sulla "qualità" del mio codice (questa "qualità" è definita dalla guida alla ruby del progetto guidata dalla comunità ).
Si noti che userò la "qualità" come nella "qualità della formulazione", non tanto sull'efficienza del codice, anche se in alcuni casi, l'efficienza del codice è effettivamente influenzata dalla sua scrittura.
Ad ogni modo, facendo tutto ciò, ho realizzato (o almeno ricordato) alcune cose:
- Alcuni linguaggi (in particolare Python, Ruby e simili) consentono di creare grandi linee di codice
- Seguire alcune linee guida per il codice può renderlo significativamente più breve e tuttavia ancora molto chiaro
- Tuttavia, seguire queste linee guida troppo rigorosamente può rendere il codice meno chiaro / facile da leggere
- Il codice può rispettare alcune linee guida quasi perfettamente ed essere di scarsa qualità
- La leggibilità del codice è per lo più soggettiva (come in "quello che trovo chiaro potrebbe essere completamente oscuro per un collega sviluppatore")
Quelle sono solo osservazioni, non regole assolute ovviamente. Noterai anche che la leggibilità del codice e le seguenti linee guida potrebbero sembrare non correlate a questo punto, ma qui le linee guida sono un modo per restringere il numero di modi per riscrivere un pezzo di codice.
Ora, alcuni esempi, per rendere tutto più chiaro.
Esempi
Prendiamo un semplice caso d'uso: abbiamo un'applicazione con un " User
" modello. Un utente ha un indirizzo opzionale firstname
e surname
obbligatorio email
.
Voglio scrivere un metodo " name
" che restituirà quindi il nome ( firstname + surname
) dell'utente se almeno il suo firstname
o surname
è presente, oppure il suo email
valore di fallback in caso contrario.
Voglio anche che questo metodo prenda un " use_email
" come parametro (booleano), che consenta di utilizzare l'e-mail dell'utente come valore di fallback. Questo use_email
parametro " " dovrebbe essere predefinito (se non passato) come " true
".
Il modo più semplice per scrivere che, in Ruby, sarebbe:
def name(use_email = true)
# If firstname and surname are both blank (empty string or undefined)
# and we can use the email...
if (firstname.blank? && surname.blank?) && use_email
# ... then, return the email
return email
else
# ... else, concatenate the firstname and surname...
name = "#{firstname} #{surname}"
# ... and return the result striped from leading and trailing spaces
return name.strip
end
end
Questo codice è il modo più semplice e facile da capire per farlo. Anche per qualcuno che non "parla" di Ruby.
Ora proviamo a renderlo più breve:
def name(use_email = true)
# 'if' condition is used as a guard clause instead of a conditional block
return email if (firstname.blank? && surname.blank?) && use_email
# Use of 'return' makes 'else' useless anyway
name = "#{firstname} #{surname}"
return name.strip
end
Questo è più breve, ancora facile da capire, se non più semplice (la clausola di guardia è più naturale da leggere rispetto a un blocco condizionale). La clausola di Guard rende anche più conforme alle linee guida che sto usando, quindi win-win qui. Riduciamo anche il livello di rientro.
Ora usiamo un po 'di magia Ruby per renderla ancora più breve:
def name(use_email = true)
return email if (firstname.blank? && surname.blank?) && use_email
# Ruby can return the last called value, making 'return' useless
# and we can apply strip directly to our string, no need to store it
"#{firstname} #{surname}".strip
end
Ancora più breve e seguendo le linee guida perfettamente ... ma molto meno chiaro poiché la mancanza di dichiarazione di ritorno lo rende un po 'confuso per coloro che non hanno familiarità con questa pratica.
È qui che possiamo iniziare a porre la domanda: ne vale davvero la pena? Dovremmo dire "no, renderlo leggibile e aggiungere ' return
'" (sapendo che questo non rispetterà le linee guida). O dovremmo dire "Va bene, è il modo di Ruby, impara la dannata lingua!"?
Se prendiamo l'opzione B, perché non renderla ancora più breve:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
Eccolo qui, il one-liner! Naturalmente è più breve ... qui sfruttiamo il fatto che Ruby restituirà un valore o l'altro a seconda di quale è definito (poiché l'e-mail sarà definita nelle stesse condizioni di prima).
Possiamo anche scriverlo:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
È breve, non così difficile da leggere (voglio dire, tutti abbiamo visto come può essere un brutto one-liner), buon Ruby, è conforme alle linee guida che uso ... Ma comunque, rispetto al primo modo di scrivere è molto meno facile da leggere e capire. Possiamo anche sostenere che questa riga è troppo lunga (più di 80 caratteri).
Domanda
Alcuni esempi di codice possono mostrare che scegliere tra un codice "full-size" e molte delle sue versioni ridotte (fino al famoso one-liner) può essere difficile dal momento che, come possiamo vedere, i one-liner non possono essere così spaventosi ma tuttavia, nulla batterà il codice "full-size" in termini di leggibilità ...
Quindi ecco la vera domanda: dove fermarsi? Quando è breve, abbastanza corto? Come sapere quando il codice diventa "troppo corto" e meno leggibile (tenendo presente che è abbastanza soggettivo)? E ancora di più: come codificare sempre di conseguenza ed evitare di mescolare una riga con blocchi di codice "full-size" quando ne ho voglia?
TL; DR
La domanda principale qui è: quando si tratta di scegliere tra un "pezzo di codice lungo ma chiaro, leggibile e comprensibile" e un "one-liner potente, più breve ma più difficile da leggere / comprendere", sapendo che quei due sono il massimo e il fondo di una scala e non le uniche due opzioni: come definire dove si trova la frontiera tra "abbastanza chiaro" e "non così chiaro come dovrebbe essere"?
La domanda principale non è la classica "One-liner vs readability: quale è meglio?" ma "Come trovare l'equilibrio tra quei due?"
Modifica 1
I commenti negli esempi di codice devono essere "ignorati", sono qui per chiarire cosa sta succedendo, ma non devono essere presi in considerazione quando si valuta la leggibilità del codice.
return
parola chiave aggiunta . Quei sette personaggi aggiungono un po 'di chiarezza ai miei occhi.
[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... perché false.blank?
ritorna vero e l'operatore ternario ti salva alcuni caratteri ... ¯ \ _ (ツ) _ / ¯
return
aggiungere la parola chiave ?! Non fornisce alcuna informazione . È puro disordine.