Kontext
Vor kurzem habe ich mich dafür interessiert, besser formatierten Code zu produzieren. Und damit meine ich, "Regeln zu befolgen, die von genug Leuten gebilligt wurden, um sie als eine gute Praxis zu betrachten" (da es natürlich niemals einen einzigen "besten" Weg zum Code geben wird).
Heutzutage codiere ich hauptsächlich in Ruby, daher habe ich angefangen, einen Linter (Rubocop) zu verwenden, um mir einige Informationen über die "Qualität" meines Codes zu liefern (diese "Qualität" wird vom Community-orientierten Projekt Ruby-Style-Guide definiert ).
Beachten Sie, dass ich „Qualität“ , wie in „Qualität der Formatierung“, nicht so sehr um die Effizienz des Codes verwenden, auch wenn in einigen Fällen der Codeeffizienz tatsächlich wird durch folgende Faktoren beeinträchtigt , wie der Code geschrieben wird.
Jedenfalls wurde mir bei all dem einiges klar (oder zumindest fiel mir ein):
- Einige Sprachen (vor allem Python, Ruby und andere) ermöglichen es, großartige Code-Einzeiler zu erstellen
- Das Befolgen einiger Richtlinien für Ihren Code kann ihn erheblich kürzer und dennoch sehr klar machen
- Wenn Sie diese Richtlinien jedoch zu streng befolgen, ist der Code möglicherweise weniger klar und einfach zu lesen
- Der Code kann einige Richtlinien fast perfekt einhalten und dennoch von schlechter Qualität sein
- Die Lesbarkeit des Codes ist größtenteils subjektiv (wie in "Was ich als klar empfinde, ist für einen Mitentwickler möglicherweise völlig undurchsichtig").
Das sind nur Beobachtungen, natürlich keine absoluten Regeln. Sie werden auch bemerken, dass die Lesbarkeit von Code und das Befolgen von Richtlinien an dieser Stelle möglicherweise nicht miteinander zusammenhängen. In diesem Fall sind die Richtlinien jedoch eine Möglichkeit, die Anzahl der Möglichkeiten zum Neuschreiben eines Codeblocks einzugrenzen.
Nun einige Beispiele, um das alles klarer zu machen.
Beispiele
Nehmen wir einen einfachen Anwendungsfall: Wir haben eine Anwendung mit einem " User
" Modell. Ein Benutzer hat optional firstname
und surname
und eine obligatorische email
Adresse.
Ich möchte eine Methode " name
" schreiben, die dann name ( firstname + surname
) des Benutzers zurückgibt, wenn mindestens sein firstname
oder surname
vorhanden ist, oder email
als Fallback - Wert, wenn nicht.
Ich möchte auch, dass diese Methode einen " use_email
" als Parameter (Boolean) verwendet und die Benutzer - E - Mail als Fallback - Wert verwendet. Dieser " use_email
" Parameter sollte standardmäßig (falls nicht übergeben) " true
" lauten.
Der einfachste Weg, dies in Ruby zu schreiben, wäre:
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
Dieser Code ist der einfachste und verständlichste Weg, dies zu tun. Auch für jemanden, der Ruby nicht "spricht".
Nun wollen wir versuchen, das zu verkürzen:
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
Dies ist kürzer, immer noch leicht zu verstehen, wenn nicht sogar einfacher (Guard-Klausel ist natürlicher zu lesen als ein bedingter Block). Die Guard-Klausel macht es auch konformer mit den Richtlinien, die ich verwende, also win-win hier. Wir reduzieren auch die Einrückungsstufe.
Verwenden wir jetzt etwas Ruby-Magie, um es noch kürzer zu machen:
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
Noch kürzer und den Richtlinien perfekt folgend ... aber viel weniger klar, da das Fehlen einer return-Anweisung es für diejenigen, die mit dieser Praxis nicht vertraut sind, etwas verwirrend macht.
Hier können wir die Frage stellen: Lohnt es sich wirklich? Sollten wir "Nein" sagen, machen Sie es lesbar und fügen Sie return
"" hinzu (da wir wissen, dass dies die Richtlinien nicht einhält). Oder sollten wir sagen "Es ist in Ordnung, es ist die Ruby Art, die verdammte Sprache zu lernen!"?
Wenn wir Option B wählen, warum nicht noch kürzer machen:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
Hier ist es, der Einzeiler! Natürlich ist es kürzer ... hier nutzen wir die Tatsache, dass Ruby den einen oder anderen Wert zurückgibt, je nachdem, welcher definiert ist (da E-Mail unter den gleichen Bedingungen wie zuvor definiert wird).
Wir können es auch schreiben:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
Es ist kurz, nicht so schwer zu lesen (ich meine, wir haben alle gesehen, wie ein hässlicher Einzeiler aussehen kann), guter Ruby, es entspricht der Richtlinie, die ich verwende ... Aber dennoch, verglichen mit der ersten Art zu schreiben es ist viel weniger leicht zu lesen und zu verstehen. Wir können auch argumentieren, dass diese Zeile zu lang ist (mehr als 80 Zeichen).
Frage
Einige Codebeispiele können zeigen, dass die Wahl zwischen einem Code "voller Größe" und vielen seiner reduzierten Versionen (bis auf den berühmten Einzeiler) schwierig sein kann, da Einzeiler, wie wir sehen können, nicht so beängstigend sein können, aber Trotzdem wird nichts den "Full-Size" -Code in Bezug auf die Lesbarkeit übertreffen ...
Hier ist also die eigentliche Frage: Wo aufhören? Wann ist kurz, kurz genug? Wie kann man wissen, wann der Code "zu kurz" und weniger lesbar wird (wenn man bedenkt, dass er ziemlich subjektiv ist)? Und noch mehr: Wie kann ich immer dementsprechend programmieren und vermeiden, Einzeiler mit "normalen" Codestücken zu mischen, wenn ich Lust dazu habe?
TL; DR
Die Hauptfrage hier ist: Wenn es darum geht, zwischen einem "langen, aber klaren, lesbaren und verständlichen Codeabschnitt" und einem "leistungsfähigen, kürzeren, aber schwerer zu lesenden / zu verstehenden Einzeiler" zu wählen, müssen Sie wissen, dass diese beiden die obersten und die obersten sind Der untere Teil einer Skala und nicht die beiden einzigen Optionen: Wie kann man definieren, wo die Grenze zwischen "klar genug" und "nicht so klar, wie es sein sollte" liegt?
Die Hauptfrage ist nicht die klassische "Einzeiler vs. Lesbarkeit: Welcher ist besser?" aber "Wie finde ich das Gleichgewicht zwischen diesen beiden?"
Bearbeiten 1
Kommentare in den Codebeispielen sollen "ignoriert" werden, sie sollen klarstellen, was passiert, sind jedoch bei der Bewertung der Lesbarkeit des Codes nicht zu berücksichtigen.
return
hinzugefügten Schlüsselwort bevorzugen . Diese sieben Zeichen verleihen meinen Augen einiges an Klarheit.
[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... schreiben, weil es false.blank?
true zurückgibt und der ternäre Operator Ihnen ein paar Zeichen erspart ... ¯ \ _ (ツ) _ / ¯
return
Schlüsselwort hinzufügen ?! Es werden keinerlei Informationen bereitgestellt . Es ist reine Unordnung.