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.
quelle
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 esfalse.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.Antworten:
Egal welchen Code Sie schreiben, lesbar ist am besten. Short ist zweitbester. Und lesbar bedeutet in der Regel kurz genug, damit Sie den Code, die gut benannten Bezeichner und die gebräuchlichen Redewendungen der Sprache, in der der Code geschrieben ist, verstehen können.
Wenn dies sprachunabhängig wäre, wäre dies definitiv meinungsbasiert, aber im Rahmen der Ruby-Sprache denke ich, dass wir darauf antworten können.
Erstens besteht ein Merkmal und eine idiomatische Art, Ruby zu schreiben, darin, das
return
Schlüsselwort wegzulassen, wenn ein Wert zurückgegeben wird, es sei denn, Sie kehren frühzeitig von einer Methode zurück.Ein weiteres Merkmal und eine weitere Redewendung sind nachfolgende
if
Anweisungen, um die Lesbarkeit des Codes zu verbessern. Eine der treibenden Ideen in Ruby ist das Schreiben von Code, der als natürliche Sprache gelesen wird. Dazu gehen wir zu _why's Poignant Guide to Ruby, Kapitel 3 .Aus diesem Grund ist Codebeispiel 3 für Ruby am typischsten:
Wenn wir nun den Code lesen, heißt es:
Das kommt dem eigentlichen Ruby-Code ziemlich nahe.
Es sind nur 2 Zeilen des eigentlichen Codes, also ist es ziemlich knapp und es entspricht den Redewendungen der Sprache.
quelle
use_email
vor die anderen Bedingungen zu stellen, da es sich eher um eine Variable als um einen Funktionsaufruf handelt. Aber die String-Interpolation überflutet trotzdem den Unterschied.do send an email if A, B, C but no D
, ist es selbstverständlich, 2 if / else -Blöcke einzugeben, wenn die Codierung wahrscheinlich einfacher istif not D, send an email
. Seien Sie beim Lesen der natürlichen Sprache vorsichtig und wandeln Sie sie in Code um, da Sie so eine neue Version der "Neverending Story" schreiben können . Mit Klassen, Methoden und Variablen. Immerhin keine große Sache.if !D
besser ist, ist das in Ordnung, daD
es einen aussagekräftigen Namen hat. Und wenn sich der!
Operator zwischen den anderen Codes verirrt,NotD
wäre es angemessen , einen Bezeichner aufzurufen.Ich glaube nicht, dass Sie eine bessere Antwort erhalten, als "nach bestem Wissen und Gewissen". Kurz gesagt, Sie sollten eher nach Klarheit als nach Kürze streben . Oft ist der kürzeste Code auch der klarste. Wenn Sie sich jedoch nur auf die Erzielung von Kürze konzentrieren, kann die Klarheit darunter leiden. Dies ist eindeutig in den letzten beiden Beispielen der Fall, deren Verständnis mehr Aufwand erfordert als die der vorherigen drei Beispiele.
Ein wichtiger Gesichtspunkt ist das Publikum des Codes. Die Lesbarkeit hängt natürlich vollständig von der lesenden Person ab. Kennen die Leute, von denen Sie erwarten, dass sie den Code lesen (außer sich selbst), die Redewendungen der Ruby-Sprache? Nun, diese Frage können nicht zufällige Leute im Internet beantworten, dies ist nur Ihre eigene Entscheidung.
quelle
Ein Teil des Problems ist hier "Was ist Lesbarkeit". Für mich schaue ich mir dein erstes Codebeispiel an:
Und ich finde es schwer zu lesen, da es voller "lauter" Kommentare ist, die nur den Code wiederholen. Zieh sie aus:
und es ist jetzt so viel besser lesbar. Wenn ich es dann lese, denke ich "hmm, ich frage mich, ob Ruby den ternären Operator unterstützt? In C # kann ich es schreiben als:
Ist so etwas in Ruby möglich? Ich arbeite mich durch Ihren Beitrag und sehe, dass es Folgendes gibt:
Alles gute Zeug. Aber das ist für mich nicht lesbar; einfach weil ich scrollen muss um die ganze zeile zu sehen. Also lassen Sie uns das beheben:
Jetzt bin ich glücklich. Ich bin mir nicht ganz sicher, wie die Syntax funktioniert, aber ich kann verstehen, was der Code tut.
Aber das bin nur ich. Andere Leute haben sehr unterschiedliche Vorstellungen darüber, was ein gut lesbares Stück Code ausmacht. Sie müssen also Ihre Zielgruppe kennen, wenn Sie Code schreiben. Wenn Sie ein absoluter Anfänger sind, sollten Sie es einfach halten und möglicherweise wie Ihr erstes Beispiel schreiben. Wenn Sie unter professionellen Entwicklern mit langjähriger Ruby-Erfahrung arbeiten, schreiben Sie Code, der die Sprache nutzt, und halten Sie ihn kurz. Wenn es irgendwo dazwischen liegt, dann ziele auf irgendwo dazwischen.
Eines würde ich allerdings sagen: Vorsicht vor "cleverem Code", wie in Ihrem letzten Beispiel. Fragen Sie sich,
[firstname, surname].all?(&:blank?)
ob Sie sich nicht schlau fühlen, weil es Ihre Fähigkeiten unter Beweis stellt, auch wenn es jetzt etwas schwieriger zu lesen ist? Ich würde sagen, dass dieses Beispiel wahrscheinlich in diese Kategorie passt. Wenn Sie jedoch fünf Werte vergleichen würden, wäre das ein guter Code. Auch hier gibt es keine absolute Grenze. Sei dir nur bewusst, dass du zu schlau bist.Fazit: Um lesbar zu sein, müssen Sie Ihre Zielgruppe kennen und Ihren Code entsprechend ausrichten und prägnanten, aber klaren Code schreiben. Schreiben Sie niemals "cleveren" Code. Halte es kurz, aber nicht zu kurz.
quelle
return "#{firstname} #{surname}".strip
Dies ist wahrscheinlich eine Frage, bei der es schwierig ist, keine meinungsbasierte Antwort zu geben, aber hier sind meine zwei Cent.
Wenn Sie feststellen, dass die Kürzung des Codes die Lesbarkeit nicht beeinträchtigt oder sogar verbessert, entscheiden Sie sich dafür. Wenn der Code weniger lesbar ist, müssen Sie prüfen, ob es einen guten Grund gibt, ihn so zu belassen. Es nur zu tun, weil es kürzer oder cool ist oder weil man es kann, sind Beispiele für schlechte Gründe. Sie müssen auch überlegen, ob eine Kürzung des Codes für andere Personen, mit denen Sie zusammenarbeiten, weniger verständlich ist.
Was wäre also ein guter Grund? Eigentlich ist es ein Urteil, aber ein Beispiel könnte so etwas wie eine Leistungsoptimierung sein (nach Leistungstests natürlich nicht im Voraus). Etwas, das Ihnen Vorteile bringt, für die Sie bereit sind, mit verminderter Lesbarkeit zu bezahlen. In diesem Fall können Sie den Nachteil abmildern, indem Sie einen hilfreichen Kommentar abgeben (der erklärt, was der Code bewirkt und warum er ein bisschen kryptisch gemacht werden musste). Noch besser ist, Sie können diesen Code in eine separate Funktion mit einem aussagekräftigen Namen extrahieren, sodass nur eine Zeile an der Aufrufstelle erklärt, was (über den Namen der Funktion) vor sich geht, ohne auf Details einzugehen (die Leute haben jedoch unterschiedliche Namen) Meinungen dazu, so ist dies ein weiteres Urteil, das Sie machen müssen).
quelle
Die Antwort ist etwas subjektiv, aber Sie müssen sich mit aller Ehrlichkeit fragen, ob Sie diesen Code verstehen könnten, wenn Sie in ein oder zwei Monaten darauf zurückkommen.
Jede Änderung sollte die Fähigkeit der durchschnittlichen Person verbessern, den Code zu verstehen. Um den Code verständlich zu machen, sollten Sie die folgenden Richtlinien beachten:
Es gibt jedoch Zeiten, in denen erweiterter Code zum besseren Verständnis der Vorgänge beiträgt. Ein Beispiel dafür stammt aus C # und LINQ. LINQ ist ein großartiges Tool und kann in einigen Situationen die Lesbarkeit verbessern, aber ich bin auch auf eine Reihe von Situationen gestoßen, in denen es viel verwirrender war. Ich habe im Peer Review einige Rückmeldungen erhalten, die vorschlugen, den Ausdruck in eine Schleife mit geeigneten if-Anweisungen umzuwandeln, damit andere ihn besser beibehalten können. Als ich nachkam, hatten sie recht. Technisch gesehen ist LINQ für C # idiomatischer, aber es gibt Fälle, in denen die Verständlichkeit beeinträchtigt wird und eine ausführlichere Lösung dies verbessert.
Ich sage alles, um das zu sagen:
Denken Sie daran, dass Sie oder jemand wie Sie diesen Code später pflegen müssen. Wenn Sie das nächste Mal darauf stoßen, kann dies Monate dauern. Tun Sie sich selbst einen Gefallen und versuchen Sie nicht, die Anzahl der Zeilen zu reduzieren, um Ihren Code zu verstehen.
quelle
Lesbarkeit ist eine Eigenschaft, die Sie haben möchten, wenn Sie mehrere Einzeiler haben, nicht. Anstelle von "Einzeiler vs. Lesbarkeit" sollte die Frage also lauten:
Wann verbessern Einzeiler die Lesbarkeit und wann schaden sie ihr?
Ich glaube, Einzeiler sind gut lesbar, wenn sie diese beiden Bedingungen erfüllen:
Nehmen wir zum Beispiel an, es
name
war kein guter Name für Ihre Methode. Das Kombinieren des Vor- und Nachnamens oder das Verwenden der E-Mail anstelle des Namens war keine Selbstverständlichkeit. Anstelle desname
Besten, was Sie sich einfallen lassen konnten, stellte sich heraus, dass es lang und umständlich war:Solch ein langer Name zeigt an, dass dies sehr spezifisch ist - wenn es allgemeiner wäre, hätten Sie einen allgemeineren Namen finden können. Das Einschließen in eine Methode hilft also weder bei der Lesbarkeit (zu lang) noch bei der Trockenheit (zu spezifisch, um anderswo verwendet zu werden). Lassen Sie den Code einfach dort.
Trotzdem - warum ein Einzeiler? Sie sind normalerweise weniger lesbar als mehrzeiliger Code. Hier sollten wir meine zweite Bedingung überprüfen - den Fluss des umgebenden Codes. Was ist, wenn Sie so etwas haben:
Der mehrzeilige Code selbst ist lesbar - aber wenn Sie versuchen, den umgebenden Code zu lesen (die verschiedenen Felder zu drucken), unterbricht dieses mehrzeilige Konstrukt den Fluss. Das ist leichter zu lesen:
Ihr Fluss wird nicht unterbrochen, und Sie können sich bei Bedarf auf den spezifischen Ausdruck konzentrieren.
Ist das dein Fall? Definitiv nicht!
Die erste Bedingung ist weniger relevant - Sie haben sie bereits als allgemein genug erachtet, um eine Methode zu verdienen, und einen Namen für diese Methode gefunden, der viel besser lesbar ist als die Implementierung. Offensichtlich würden Sie es nicht wieder in eine Funktion extrahieren.
Was die zweite Bedingung betrifft - unterbricht sie den Fluss des umgebenden Codes? Nein! Der umgebende Code ist eine Methodendeklaration. Das Auswählen von
name
ist der einzige Zweck. Die Logik, den Namen zu wählen, unterbricht nicht den Fluss des umgebenden Codes - es ist der eigentliche Zweck des umgebenden Codes!Fazit: Machen Sie nicht den gesamten Funktionskörper zu einem Einzeiler
Einzeiler sind gut, wenn Sie etwas Komplexes tun möchten, ohne den Fluss zu unterbrechen. Eine Funktionsdeklaration unterbricht bereits den Ablauf (damit er nicht unterbrochen wird, wenn Sie diese Funktion aufrufen), sodass die Lesbarkeit nicht dadurch verbessert wird, dass der gesamte Funktionskörper einzeilig ist.
Hinweis
Ich beziehe mich auf "ausgewachsene" Funktionen und Methoden - nicht auf Inline-Funktionen oder Lambda-Ausdrücke, die normalerweise Teil des umgebenden Codes sind und in dessen Ablauf passen müssen.
quelle