it-swarm-eu.dev

Wie rechtfertigen Sie das Schreiben von mehr Code, indem Sie die Praktiken für sauberen Code befolgen?

Moderatorennotiz
Auf diese Frage wurden bereits siebzehn Antworten gepostet. Bevor Sie eine neue Antwort veröffentlichen, lesen Sie bitte die vorhandenen Antworten und stellen Sie sicher, dass Ihr Standpunkt noch nicht ausreichend abgedeckt ist.

Ich habe einige der in Robert Martins "Clean Code" -Buch empfohlenen Praktiken befolgt, insbesondere diejenigen, die für die Art der Software gelten, mit der ich arbeite, und diejenigen, die für mich sinnvoll sind (ich folge ihr nicht als Dogma). .

Ein Nebeneffekt, den ich jedoch bemerkt habe, ist, dass der "saubere" Code, den ich schreibe, mehr Code ist, als wenn ich einige Praktiken nicht befolgt hätte. Die spezifischen Praktiken, die dazu führen, sind:

  • Einkapselung von Bedingungen

Also statt

if(contact.email != null && contact.emails.contains('@')

Ich könnte eine kleine Methode wie diese schreiben

private Boolean isEmailValid(String email){...}
  • Ersetzen eines Inline-Kommentars durch eine andere private Methode, sodass der Methodenname sich selbst beschreibt, anstatt einen Inline-Kommentar darüber zu haben
  • Eine Klasse sollte nur einen Grund haben, sich zu ändern

Und ein paar andere. Der Punkt ist, dass eine Methode mit 30 Zeilen aufgrund der winzigen Methoden, die Kommentare ersetzen und Bedingungen usw. kapseln, eine Klasse wird. Wenn Sie feststellen, dass Sie so viele Methoden haben, ist dies "sinnvoll" Fassen Sie alle Funktionen in einer Klasse zusammen, wenn es eigentlich eine Methode gewesen sein sollte.

Mir ist bewusst, dass jede extreme Praxis schädlich sein kann.

Die konkrete Frage, nach der ich eine Antwort suche, lautet:

Ist dies ein akzeptables Nebenprodukt beim Schreiben von sauberem Code? Wenn ja, mit welchen Argumenten kann ich die Tatsache rechtfertigen, dass mehr LOC geschrieben wurden?

Die Organisation ist nicht speziell über mehr LOC besorgt, aber mehr LOC kann zu sehr großen Klassen führen (die wiederum aus Gründen der Lesbarkeit durch eine lange Methode ohne eine Reihe von einmal verwendeten Hilfsfunktionen ersetzt werden könnten).

Wenn Sie eine Klasse sehen, die groß genug ist, entsteht der Eindruck, dass die Klasse beschäftigt genug ist und ihre Verantwortung abgeschlossen wurde. Sie könnten daher am Ende mehr Klassen erstellen, um andere Funktionen zu erhalten. Das Ergebnis sind dann viele Klassen, die alle mit Hilfe vieler kleiner Hilfsmethoden "eine Sache" tun.

Dies ist das spezifische Anliegen ... diese Klassen könnten eine einzelne Klasse sein, die immer noch "eine Sache" erreicht, ohne die Hilfe vieler kleiner Methoden. Es könnte eine einzelne Klasse mit vielleicht 3 oder 4 Methoden und einigen Kommentaren sein.

108
CommonCoreTawan

... wir sind ein sehr kleines Team, das eine relativ große und nicht dokumentierte Codebasis (die wir geerbt haben) unterstützt. Einige Entwickler/Manager sehen Wert darin, weniger Code zu schreiben, um Dinge zu erledigen, damit wir weniger Code warten müssen

Diese Leute haben etwas richtig identifiziert: Sie möchten, dass der Code einfacher zu warten ist. Wenn sie jedoch einen Fehler gemacht haben, wird davon ausgegangen, dass die Wartung umso einfacher ist, je weniger Code vorhanden ist.

Damit Code einfach zu warten ist, muss er leicht zu ändern sein. Der bei weitem einfachste Weg, um einfach zu ändernden Code zu erhalten, besteht darin, einen vollständigen Satz automatisierter Tests durchzuführen, die fehlschlagen, wenn Ihre Änderung fehlerhaft ist. Tests sind Code, daher wird das Schreiben dieser Tests Ihre Codebasis anschwellen lassen. Und das ist gut so.

Zweitens muss Ihr Code sowohl leicht zu lesen als auch leicht zu verstehen sein, um herauszufinden, was geändert werden muss. Es ist sehr unwahrscheinlich, dass sehr knapper Code, dessen Größe verkleinert wird, um den Zeilenzähler niedrig zu halten, leicht zu lesen ist. Es ist offensichtlich ein Kompromiss zu schließen, da das Lesen von längerem Code länger dauert. Aber wenn es schneller zu verstehen ist, dann lohnt es sich. Wenn es diesen Vorteil nicht bietet, ist diese Ausführlichkeit kein Vorteil mehr. Aber wenn längerer Code die Lesbarkeit verbessert, ist dies wiederum eine gute Sache.

129
David Arno

Ja, es ist ein akzeptables Nebenprodukt, und die Rechtfertigung ist, dass es jetzt so strukturiert ist, dass Sie den größten Teil des Codes nicht die meiste Zeit lesen müssen. Anstatt jedes Mal, wenn Sie eine Änderung vornehmen, eine 30-Zeilen-Funktion zu lesen, lesen Sie eine 5-Zeilen-Funktion, um den Gesamtfluss zu erhalten, und möglicherweise einige der Hilfsfunktionen, wenn Ihre Änderung diesen Bereich berührt. Wenn Ihre neue "zusätzliche" Klasse EmailValidator heißt und Sie wissen, dass Ihr Problem nicht mit der E-Mail-Validierung zusammenhängt, können Sie das Lesen insgesamt überspringen.

Es ist auch einfacher, kleinere Teile wiederzuverwenden, wodurch sich die Zeilenanzahl für Ihr Gesamtprogramm verringert. Ein EmailValidator kann überall verwendet werden. Einige Codezeilen, die eine E-Mail-Validierung durchführen, jedoch zusammen mit dem Datenbankzugriffscode ausgeführt werden, können nicht wiederverwendet werden.

Und dann überlegen Sie, was zu tun ist, wenn die E-Mail-Validierungsregeln jemals geändert werden müssen - was Sie lieber möchten: einen bekannten Ort; oder viele Orte, möglicherweise fehlen ein paar?

154
Karl Bielefeldt

Bill Gates wurde bekanntermaßen zugeschrieben: "Das Messen des Programmierfortschritts anhand von Codezeilen ist wie das Messen des Fortschritts beim Flugzeugbau nach Gewicht."

Ich stimme diesem Gefühl demütig zu. Dies bedeutet nicht, dass ein Programm mehr oder weniger Codezeilen anstreben sollte, sondern dass dies letztendlich nicht zählt, um ein funktionierendes und funktionierendes Programm zu erstellen. Es ist hilfreich, sich daran zu erinnern, dass der Grund für das Hinzufügen zusätzlicher Codezeilen letztendlich darin besteht, dass es theoretisch auf diese Weise besser lesbar ist.

Es kann zu Meinungsverschiedenheiten darüber kommen, ob eine bestimmte Änderung mehr oder weniger lesbar ist, aber ich glaube nicht, dass Sie falsch wären, eine Änderung an Ihrem Programm vorzunehmen weil Sie denken, dass Sie dies tun, indem Sie dies tun es besser lesbar. Zum Beispiel könnte das Erstellen eines isEmailValid als überflüssig und unnötig angesehen werden, insbesondere wenn es von der Klasse, die es definiert, genau einmal aufgerufen wird. Ich würde jedoch viel lieber ein isEmailValid in einer Bedingung sehen als eine Folge von AND-Bedingungen, wobei ich bestimmen muss, was jede einzelne Bedingung prüft und warum sie geprüft wird.

Wenn Sie eine isEmailValid -Methode erstellen, die Nebenwirkungen hat oder andere Dinge als die E-Mail überprüft, geraten Sie in Schwierigkeiten, weil dies schlimmer ist, als einfach alles aufzuschreiben. Es ist schlimmer, weil es irreführend ist und ich möglicherweise einen Fehler verpasse.

Obwohl Sie dies in diesem Fall eindeutig nicht tun, möchte ich Sie ermutigen, so fortzufahren, wie Sie es tun. Sie sollten sich immer fragen, ob es durch die Änderung einfacher zu lesen ist, und wenn dies Ihr Fall ist, dann tun Sie es!

34
Neil

einige Entwickler/Manager sehen Wert darin, weniger Code zu schreiben, um die Dinge zu erledigen, damit wir weniger Code warten müssen

Hier geht es darum, das eigentliche Ziel aus den Augen zu verlieren.

Was zählt, ist Verringerung der Entwicklungsstunden. Dies wird in Zeit (oder gleichwertigem Aufwand) gemessen, nicht in Codezeilen.
Dies ist wie zu sagen, dass Autohersteller ihre Autos mit weniger Schrauben bauen sollten, da das Einsetzen jeder Schraube ungleich Null dauert. Während dies pedantisch korrekt ist, wird der Marktwert eines Autos nicht durch definiert Wie viele Schrauben hat es oder nicht. Vor allem muss ein Auto leistungsfähig, sicher und wartungsfreundlich sein.

Der Rest der Antwort sind Beispiele dafür, wie sauberer Code zu Zeitgewinnen führen kann.


Protokollierung

Nehmen Sie eine Anwendung (A) ohne Protokollierung. Erstellen Sie nun Anwendung B, die dieselbe Anwendung A ist, jedoch mit Protokollierung. B hat immer mehr Codezeilen, und daher müssen Sie mehr Code schreiben.

Aber es wird viel Zeit darauf verwendet, Probleme und Fehler zu untersuchen und herauszufinden, was schief gelaufen ist.

Bei Anwendung A müssen Entwickler den Code nicht mehr lesen und müssen das Problem kontinuierlich reproduzieren und den Code durchgehen, um die Ursache des Problems zu finden. Dies bedeutet, dass der Entwickler vom Beginn der Ausführung bis zum Ende in jeder verwendeten Schicht testen und jede verwendete Logik beachten muss.
Vielleicht hat er das Glück, es sofort zu finden, aber vielleicht wird die Antwort an der letzten Stelle sein, an die er denkt.

Bei Anwendung B beobachtet ein Entwickler unter der Annahme einer perfekten Protokollierung die Protokolle, kann die fehlerhafte Komponente sofort identifizieren und weiß nun, wo er suchen muss.

Dies kann eine Frage von Minuten, Stunden oder Tagen sein. abhängig von der Größe und Komplexität der Codebasis.


Regressionen

Nehmen Sie die Anwendung A, die überhaupt nicht trocken ist.
Nehmen Sie die Anwendung B, die TROCKEN ist, aber aufgrund der zusätzlichen Abstraktionen mehr Zeilen benötigt.

Es wird eine Änderungsanforderung eingereicht, die eine Änderung der Logik erfordert.

Für Anwendung B ändert der Entwickler die (eindeutige, gemeinsam genutzte) Logik gemäß der Änderungsanforderung.

Für Anwendung A muss der Entwickler alle Instanzen dieser Logik ändern, in denen er sich daran erinnert, dass sie verwendet wurde.

  • Wenn er sich an alle Instanzen erinnert, muss er dieselbe Änderung noch mehrmals implementieren.
  • Wenn er es nicht schafft, sich an alle Instanzen zu erinnern, haben Sie es jetzt mit einer inkonsistenten Codebasis zu tun, die sich selbst widerspricht. Wenn der Entwickler einen selten verwendeten Code vergessen hat, wird dieser Fehler für die Endbenutzer möglicherweise erst in der Zukunft sichtbar. Werden die Endbenutzer zu diesem Zeitpunkt die Ursache des Problems ermitteln? Selbst wenn dies der Fall ist, kann sich der Entwickler möglicherweise nicht daran erinnern, was die Änderung mit sich gebracht hat, und muss herausfinden, wie diese vergessene Logik geändert werden kann. Vielleicht arbeitet der Entwickler bis dahin noch nicht einmal im Unternehmen, und dann muss jetzt jemand anderes alles von Grund auf neu herausfinden.

Dies kann zu einer enormen Zeitverschwendung führen. Nicht nur in der Entwicklung, sondern auch bei der Jagd und dem Auffinden des Fehlers. Die Anwendung kann sich unregelmäßig verhalten, was Entwickler nicht leicht nachvollziehen können. Und das wird zu langen Debugging-Sitzungen führen.


Austauschbarkeit der Entwickler

Entwickler Eine erstellte Anwendung A. Der Code ist weder sauber noch lesbar, funktioniert jedoch wie ein Zauber und wurde in der Produktion ausgeführt. Es überrascht nicht, dass es auch keine Dokumentation gibt.

Entwickler A ist wegen Feiertagen einen Monat lang abwesend. Eine Notfalländerungsanforderung wird eingereicht. Es kann keine weiteren drei Wochen warten, bis Dev A zurückkommt.

Entwickler B muss diese Änderung ausführen. Er muss jetzt die gesamte Codebasis lesen, verstehen, wie alles funktioniert, warum es funktioniert und was es zu erreichen versucht. Das dauert ewig, aber sagen wir, er kann es in drei Wochen schaffen.

Gleichzeitig hat Anwendung B (die von Entwickler B erstellt wurde) einen Notfall. Dev B ist besetzt, aber Dev C ist verfügbar, obwohl er die Codebasis nicht kennt. Was machen wir?

  • Wenn wir B weiterhin an A arbeiten lassen und C an B arbeiten lassen, haben wir zwei Entwickler, die nicht wissen, was sie tun, und die Arbeit wird suboptimal ausgeführt.
  • Wenn wir B von A wegziehen und ihn B machen lassen und jetzt C auf A setzen, wird möglicherweise die gesamte Arbeit von Entwickler B (oder ein erheblicher Teil davon) verworfen. Dies ist möglicherweise eine Verschwendung von Tagen/Wochen.

Dev A kommt aus seinem Urlaub zurück und stellt fest, dass B den Code nicht verstanden und ihn daher schlecht implementiert hat. Es ist nicht Bs Schuld, weil er alle verfügbaren Ressourcen verwendet hat und der Quellcode einfach nicht ausreichend lesbar war. Muss A nun Zeit damit verbringen, die Lesbarkeit des Codes zu verbessern?


All diese und viele weitere Probleme enden Zeitverschwendung. Ja, kurzfristig erfordert sauberer Code jetzt mehr Aufwand , aber am Ende zahlt er sich aus in der Zukunft , wenn unvermeidliche Fehler/Änderungen behoben werden müssen.

Das Management muss verstehen, dass eine kurze Aufgabe Ihnen in Zukunft mehrere lange Aufgaben erspart. Wenn Sie nicht planen, ist ein Fehlschlagen geplant.

Wenn ja, mit welchen Argumenten kann ich die Tatsache rechtfertigen, dass mehr LOC geschrieben wurden?

Meine Erklärung lautet, das Management zu fragen, was es bevorzugen würde: eine Anwendung mit einer 100KLOC-Codebasis, die in drei Monaten entwickelt werden kann, oder eine 50KLOC-Codebasis, die in sechs Monaten entwickelt werden kann.

Sie werden offensichtlich die kürzere Entwicklungszeit auswählen, da sich das Management nicht um KLOC kümmert. Manager, die sich auf KLOC konzentrieren, verwalten Mikromanagement, während sie nicht über das informiert sind, was sie verwalten möchten.

23
Flater

Ich denke, Sie sollten sehr vorsichtig sein, wenn Sie "Clean Code" -Praktiken anwenden, falls diese zu einer höheren Gesamtkomplexität führen. Vorzeitiges Refactoring ist die Wurzel vieler schlechter Dinge.

Das Extrahieren einer Bedingung zu einer Funktion führt zu einfacherem Code an dem Punkt, aus dem die Bedingung extrahiert wurde, führt jedoch zu mehr insgesamt Komplexität, da Sie jetzt eine Funktion haben, von der aus sichtbar ist Weitere Punkte im Programm. Sie fügen allen anderen Funktionen, bei denen diese neue Funktion jetzt sichtbar ist, eine leichte Komplexitätsbelastung hinzu.

Ich sage nicht, dass Sie sollten nicht die Bedingung extrahieren, nur dass Sie sorgfältig überlegen sollten, wenn Sie müssen.

  • Wenn Sie die E-Mail-Validierungslogik speziell testen möchten. Dann müssen Sie diese Logik in eine separate Funktion extrahieren - wahrscheinlich sogar in eine Klasse.
  • Wenn dieselbe Logik an mehreren Stellen im Code verwendet wird, müssen Sie sie offensichtlich in eine einzelne Funktion extrahieren. Wiederhole dich nicht!
  • Wenn die Logik offensichtlich eine separate Verantwortung ist, z. Die E-Mail-Validierung erfolgt in der Mitte eines Sortieralgorithmus. Die E-Mail-Validierung ändert sich unabhängig vom Sortieralgorithmus, daher sollten sie in separaten Klassen sein.

In all dem ist der Grund dafür, dass die Extraktion darüber hinaus nur "sauberer Code" ist. Darüber hinaus würden Sie wahrscheinlich nicht einmal im Zweifel sein, ob es das Richtige war.

Ich würde sagen, wenn Sie Zweifel haben, wählen Sie immer den einfachsten und einfachsten Code.

23
JacquesB

Ich möchte darauf hinweisen, dass daran nichts von Natur aus falsch ist:

if(contact.email != null && contact.email.contains('@')

Zumindest unter der Annahme, dass es dieses Mal verwendet wird.

Ich könnte sehr leicht Probleme damit haben:

private Boolean isEmailValid(String email){
   return email != null && email.contains('@');
}

Ein paar Dinge, auf die ich achten würde:

  1. Warum ist es privat? Es sieht aus wie ein potenziell nützlicher Stummel. Ist es nützlich genug, eine private Methode zu sein und keine Chance zu haben, dass sie weiter verbreitet wird?
  2. Ich würde die Methode IsValidEmail nicht persönlich nennen, möglicherweise ContainsAtSign oder LooksVaguelyLikeEmailAddress weil es fast keine wirkliche Validierung gibt, was vielleicht gut ist, vielleicht nicht das, was erwartet wird.
  3. Wird es mehr als einmal verwendet?

Wenn es einmal verwendet wird, einfach zu analysieren ist und weniger als eine Zeile benötigt, würde ich die Entscheidung als zweites erraten. Es ist wahrscheinlich nichts, was ich nennen würde, wenn es kein besonderes Problem eines Teams wäre.

Andererseits habe ich Methoden gesehen, die so etwas tun:

if (contact.email != null && contact.email.contains('@')) { ... }
else if (contact.email != null && contact.email.contains('@') && contact.email.contains("@mydomain.com")) { //headquarters email }
else if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") ) { //internal contract teams }

Dieses Beispiel ist offensichtlich nicht trocken.

Oder auch nur diese letzte Aussage kann ein anderes Beispiel geben:

if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") )

Das Ziel sollte sein, den Code lesbarer zu machen:

if (LooksSortaLikeAnEmail(contact.Email)) { ... }
else if (LooksLikeFromHeadquarters(contact.Email)) { ... }
else if (LooksLikeInternalEmail(contact.Email)) { ... }

Ein anderes Szenario:

Möglicherweise haben Sie eine Methode wie:

public void SaveContact(Contact contact){
   if (contact.email != null && contact.email.contains('@'))
   {
       contacts.Add(contact);
       contacts.Save();
   }
}

Wenn dies zu Ihrer Geschäftslogik passt und nicht wiederverwendet wird, gibt es hier kein Problem.

Aber wenn jemand fragt "Warum wird '@' gespeichert, weil das nicht richtig ist!" und Sie beschließen, eine tatsächliche Validierung hinzuzufügen und diese dann zu extrahieren!

Sie werden es nicht bereuen, wenn Sie auch das zweite E-Mail-Konto des Präsidenten abrechnen müssen Pr3 $ sid3nt @ h0m3! @ Mydomain.com und sich entscheiden, einfach alles zu tun und zu versuchen, RFC 2822 zu unterstützen.

Zur Lesbarkeit:

// If there is an email property and it contains an @ sign then process
if (contact.email != null && contact.email.contains('@'))

Wenn Ihr Code so klar ist, brauchen Sie hier keine Kommentare. Tatsächlich brauchen Sie keine Kommentare, um zu sagen, was der Code die meiste Zeit tut, sondern warum es tut:

// The UI passes '@' by default, the DBA's made this column non-nullable but 
// marketing is currently more concerned with other fields and '@' default is OK
if (contact.email != null && contact.email.contains('@'))

Ob die Kommentare über einer if-Anweisung oder innerhalb einer winzigen Methode für mich pedantisch sind. Ich könnte sogar das Gegenteil von nützlich mit guten Kommentaren in einer anderen Methode argumentieren, weil Sie jetzt zu einer anderen Methode navigieren müssten, um zu sehen, wie und warum es macht, was es macht.

Zusammenfassend: Messen Sie diese Dinge nicht; Konzentrieren Sie sich auf die Prinzipien, nach denen der Text erstellt wurde (DRY, SOLID, KISS).

// A valid class that does nothing
public class Nothing 
{

}
9
AthomSfere

Clean Code ist ein ausgezeichnetes Buch und es lohnt sich zu lesen, aber es ist nicht die letzte Instanz in solchen Angelegenheiten.

Das Aufteilen von Code in logische Funktionen ist normalerweise eine gute Idee, aber nur wenige Programmierer tun dies in dem Maße, wie Martin es tut. Irgendwann werden die Renditen geringer, wenn alles in Funktionen umgewandelt wird, und es kann schwierig werden, zu folgen, wenn der gesamte Code winzig ist Stücke.

Eine Option, wenn es sich nicht lohnt, eine ganz neue Funktion zu erstellen, besteht darin, einfach eine Zwischenvariable zu verwenden:

boolean isEmailValid = (contact.email != null && contact.emails.contains('@');

if (isEmailValid) {
...

Dies hilft dabei, den Code einfach zu befolgen, ohne viel in der Datei herumspringen zu müssen.

Ein weiteres Problem ist, dass Clean Code jetzt als Buch ziemlich alt wird. Ein Großteil der Softwareentwicklung hat sich in Richtung funktionaler Programmierung bewegt, während Martin alles daran setzt, den Dingen Status zu verleihen und Objekte zu erstellen. Ich vermute, er hätte ein ganz anderes Buch geschrieben, wenn er es heute geschrieben hätte.

6
Rich Smith

In Anbetracht der Tatsache, dass die Bedingung "ist E-Mail gültig", die Sie derzeit haben, die sehr ungültige E-Mail-Adresse "@ ", Ich denke, Sie haben allen Grund, eine EmailValidator-Klasse zu abstrahieren. Noch besser ist es, eine gute, gut getestete Bibliothek zu verwenden, um E-Mail-Adressen zu validieren.

Codezeilen als Metrik sind bedeutungslos. Die wichtigen Fragen in der Softwareentwicklung sind nicht:

  • Hast du zu viel Code?
  • Hast du zu wenig Code?

Die wichtigen Fragen sind:

  • Ist die Anwendung insgesamt richtig gestaltet?
  • Ist der Code korrekt implementiert?
  • Ist der Code wartbar?
  • Ist der Code testbar?
  • Ist der Code ausreichend getestet?

Ich habe nie an LoC gedacht, als ich Code für einen anderen Zweck als Code Golf geschrieben habe. Ich habe mich gefragt: "Könnte ich das prägnanter schreiben?", Aber aus Gründen der Lesbarkeit, Wartbarkeit und Effizienz, nicht nur der Länge.

Sicher, vielleicht könnte ich eine lange Kette von Booleschen Operationen anstelle einer Utility-Methode verwenden, aber sollte ich?

Ihre Frage lässt mich tatsächlich an einige lange Ketten von Booleschen Werten zurückdenken, die ich geschrieben habe, und mir wird klar, dass ich wahrscheinlich stattdessen eine oder mehrere Dienstprogrammmethoden hätte schreiben sollen.

5
Clement Cherlin

Auf einer Ebene haben sie Recht - weniger Code ist besser. Eine andere Antwort zitierte Gate, ich bevorzuge:

"Wenn beim Debuggen Softwarefehler behoben werden, müssen sie beim Programmieren eingefügt werden." - Edsger Dijkstra

„Beim Debuggen fügen Anfänger Korrekturcode ein. Experten entfernen fehlerhaften Code. “ - Richard Pattis

Die billigsten, schnellsten und zuverlässigsten Komponenten sind diejenigen, die es nicht gibt. - Gordon Bell

Kurz gesagt, je weniger Code Sie haben, desto weniger kann schief gehen. Wenn etwas nicht notwendig ist, schneiden Sie es.
Wenn es zu komplizierten Code gibt, vereinfachen Sie ihn, bis nur noch die eigentlichen Funktionselemente übrig sind.

Wichtig ist hierbei, dass sich alle auf die Funktionalität beziehen und nur das dafür erforderliche Minimum haben. Es sagt nichts über wie aus, was ausgedrückt wird.

Was Sie tun, indem Sie versuchen, sauberen Code zu haben, ist nicht gegen das oben Gesagte. Sie fügen Ihrem LOC hinzu, fügen jedoch keine nicht verwendeten Funktionen hinzu.

Das Endziel ist lesbarer Code, aber keine überflüssigen Extras. Die beiden Prinzipien sollten nicht gegeneinander handeln.

Eine Metapher würde ein Auto bauen. Der funktionale Teil des Codes ist das Fahrgestell, der Motor, die Räder ... was das Auto zum Laufen bringt. Wie Sie das auflösen, ähnelt eher der Federung, der Servolenkung usw. und erleichtert die Handhabung. Sie möchten, dass Ihre Mechaniker so einfach wie möglich sind, während sie ihre Arbeit ausführen, um die Wahrscheinlichkeit zu minimieren, dass etwas schief geht, aber das hindert Sie nicht daran, schöne Sitze zu haben.

3
Baldrickk

Die vorhandenen Antworten enthalten viel Weisheit, aber ich möchte noch einen weiteren Faktor hinzufügen: die Sprache.

Einige Sprachen benötigen mehr Code als andere, um den gleichen Effekt zu erzielen. Während Java (was meiner Meinung nach die Sprache in der Frage ist) sehr bekannt und im Allgemeinen sehr solide und klar und unkompliziert ist, sind einige modernere Sprachen viel prägnanter und ausdrucksvoller.

Zum Beispiel könnte es in Java] leicht 50 Zeilen dauern, um eine neue Klasse mit drei Eigenschaften zu schreiben, jede mit einem Getter und einem Setter und einem oder mehreren Konstruktoren - während Sie genau dasselbe in erreichen können eine einzelne Zeile von Kotlin * oder Scala. (Noch mehr Einsparung, wenn Sie auch geeignete Methoden equals(), hashCode() und toString() wünschen.)

Das Ergebnis ist, dass in Java die zusätzliche Arbeit bedeutet, dass Sie ein allgemeines Objekt, das nicht wirklich passt, eher wiederverwenden, Eigenschaften in vorhandene Objekte pressen oder eine Reihe von "nackten" Eigenschaften einzeln weitergeben. In einer prägnanten, ausdrucksstarken Sprache schreiben Sie mit größerer Wahrscheinlichkeit besseren Code.

(Dies unterstreicht den Unterschied zwischen der 'Oberflächen'-Komplexität des Codes und der Komplexität der Ideen/Modelle/Verarbeitung, die er implementiert. Codezeilen sind kein schlechtes Maß für die erste, haben aber viel weniger mit der zweiten zu tun .)

Die „Kosten“, um die Dinge richtig zu machen, hängen also von der Sprache ab. Vielleicht ist ein Zeichen einer guten Sprache eines, das nicht dazu bringt, dass Sie wählen, ob Sie Dinge gut oder einfach machen wollen!

(* Dies ist nicht wirklich der richtige Ort für einen Stecker, aber Kotlin ist meiner Meinung nach einen Blick wert.)

2
gidds

Es wurde festgestellt, dass die Verringerung des LOC korreliert mit reduzierten Defekten ist, sonst nichts. Unter der Annahme, dass Sie jedes Mal, wenn Sie den LOC reduzieren, die Wahrscheinlichkeit von Fehlern verringert haben, geraten Sie im Wesentlichen in die Falle, zu glauben, dass Korrelation gleichbedeutend mit Kausalität ist. Der reduzierte LOC ist ein Ergebnis guter Entwicklungspraktiken und nicht das, was Code gut macht.

Nach meiner Erfahrung sind Leute, die ein Problem mit weniger Code (auf Makroebene) lösen können, tendenziell geschickter als diejenigen, die mehr Code schreiben, um dasselbe zu tun. Was diese erfahrenen Entwickler tun, um die Codezeilen zu reduzieren, ist die Verwendung/Erstellung von Abstraktionen und wiederverwendbaren Lösungen zur Lösung häufiger Probleme. Sie verbringen keine Zeit damit, Codezeilen zu zählen und sich zu quälen, ob sie hier oder da eine Zeile schneiden können. Oft ist der Code, den sie schreiben, ausführlicher als nötig, sie schreiben nur weniger davon.

Lassen Sie mich Ihnen ein Beispiel geben. Ich musste mich mit Logik in Zeiträumen befassen und wie sie sich überlappen, ob sie benachbart sind und welche Lücken zwischen ihnen bestehen. Als ich anfing, an diesen Problemen zu arbeiten, hatte ich überall Codeblöcke, die die Berechnungen durchführten. Schließlich habe ich Klassen erstellt, um die Zeiträume und Operationen darzustellen, die Überlappungen, Ergänzungen usw. berechnet haben. Dadurch wurden sofort große Codeschwaden entfernt und in einige Methodenaufrufe umgewandelt. Aber diese Klassen selbst waren überhaupt nicht knapp geschrieben.

Einfach ausgedrückt: Wenn Sie versuchen, den LOC zu reduzieren, indem Sie versuchen, hier oder da eine Codezeile knapper zu schneiden, machen Sie es falsch. Es ist wie der Versuch, Gewicht zu verlieren, indem Sie die Menge an Gemüse reduzieren, die Sie essen. Schreiben Sie Code, der leicht zu verstehen, zu warten, zu debuggen und den LOC durch Wiederverwendung und Abstraktion zu reduzieren ist.

1
JimmyJames

Sie haben einen gültigen Kompromiss identifiziert

Hier gibt es also tatsächlich einen Kompromiss, und er ist der Abstraktion inhärent als Ganzes. Immer wenn jemand versucht, [~ # ~] n [~ # ~] Codezeilen in seine eigene Funktion zu ziehen, um sie zu benennen und zu isolieren, erleichtert er gleichzeitig das Lesen der aufrufenden Site ( indem Sie sich auf einen Namen beziehen und nicht auf alle blutigen Details, die diesem Namen zugrunde liegen) und komplexer (Sie haben jetzt eine Bedeutung, die in zwei verschiedenen Teilen der Codebasis verwickelt ist). "Leicht" ist das Gegenteil von "schwer", aber es ist kein Synonym für "einfach", was das Gegenteil von "komplex" ist. Die beiden sind keine Gegensätze, und die Abstraktion erhöht immer die Komplexität, um die eine oder andere Form der Leichtigkeit einzufügen.

Wir können die zusätzliche Komplexität direkt erkennen, wenn eine Änderung der Geschäftsanforderungen dazu führt, dass die Abstraktion zu lecken beginnt. Vielleicht wäre eine neue Logik in der Mitte des vorabstrahierten Codes am natürlichsten verlaufen, zum Beispiel wenn der abstrahierte Code einen Baum durchquert und Sie wirklich gerne Informationen sammeln (und vielleicht darauf reagieren) möchten, während Sie es sind den Baum durchqueren. Wenn Sie diesen Code abstrahiert haben, gibt es möglicherweise andere Aufrufseiten, und das Hinzufügen der erforderlichen Logik in die Mitte der Methode kann diese anderen Aufrufstellen beschädigen. Sehen Sie, wann immer wir eine Codezeile ändern, müssen wir nur den unmittelbaren Kontext dieser Codezeile betrachten; Wenn wir eine Methode ändern, müssen wir unseren gesamten Quellcode mit Cmd-F nach etwas durchsuchen, das aufgrund einer Änderung des Vertrags dieser Methode möglicherweise beschädigt wird, oder wir hoffen, dass Tests den Fehler für uns erkennen.

Der Greedy-Algorithmus kann in diesen Fällen fehlschlagen

Die Komplexität hat den Code auch in gewissem Sinne weniger lesbar gemacht, anstatt mehr. In einem früheren Job habe ich mich mit einer HTTP-API befasst, die sehr sorgfältig und präzise in mehrere Ebenen strukturiert war. Jeder Endpunkt wird von einem Controller angegeben, der die Form der eingehenden Nachricht überprüft und sie dann an einen Manager der "Business-Logic-Layer" weitergibt Dies stellte dann eine Anfrage an eine "Datenschicht", die für mehrere Abfragen an eine "Datenzugriffsobjekt" -Schicht verantwortlich war, die für die Erstellung mehrerer SQL-Delegaten verantwortlich war, die Ihre Frage tatsächlich beantworteten. Das erste, was ich dazu sagen kann, war, dass etwa 90% des Codes das Boilerplate zum Kopieren und Einfügen waren, mit anderen Worten, es waren No-Ops. In vielen Fällen war das Lesen einer bestimmten Codepassage sehr "einfach", da "oh, dieser Manager leitet die Anforderung nur an dieses Datenzugriffsobjekt weiter". Aber die Tatsache, dass Sie zwischen all diesen verschiedenen Ebenen springen und eine Reihe von Boilerplates durchlesen mussten, um herauszufinden, ob etwas für diesen speziellen Fall angepasst wurde, bedeutete, dass Sie eine Menge von machten Kontextwechsel und Suchen von Dateien und der Versuch, Informationen zu verfolgen, die Sie niemals hätten verfolgen sollen. "Dies wird auf dieser Ebene als X bezeichnet. Auf dieser anderen Ebene wird es als X 'bezeichnet. In dieser anderen Ebene wird es als X' 'bezeichnet." . "

Ich denke, als ich aufgehört habe, war diese einfache CRUD-API in der Phase, in der, wenn Sie sie mit 30 Zeilen pro Seite druckten, 10 bis 20 Lehrbücher mit fünfhundert Seiten in einem Regal aufgenommen würden: Es war eine ganze Enzyklopädie von Wiederholungen Code. In Bezug auf die wesentliche Komplexität bin ich mir nicht sicher, ob sich dort auch nur die Hälfte eines Lehrbuchs mit wesentlicher Komplexität befand. Wir hatten nur vielleicht 5-6 Datenbankdiagramme, um damit umzugehen. Eine geringfügige Änderung daran vorzunehmen war ein Mammut-Unterfangen, zu lernen, dass es ein Mammut-Unterfangen war, und das Hinzufügen neuer Funktionen musste so schmerzhaft sein, dass wir tatsächlich Vorlagen für Boilerplate-Vorlagen hatten, mit denen wir neue Funktionen hinzufügen würden.

Ich habe aus erster Hand gesehen, wie das Lesen jedes Teils sehr lesbar und offensichtlich das Ganze sehr unlesbar und nicht offensichtlich machen kann. Dies bedeutet, dass der Greedy-Algorithmus fehlschlagen kann. Sie kennen den Greedy-Algorithmus, ja? "Ich werde jeden Schritt vor Ort tun, um die Situation am meisten zu verbessern, und dann werde ich darauf vertrauen, dass ich mich in einer global verbesserten Situation befinde." Es ist oft ein schöner erster Versuch, kann aber auch in komplexen Zusammenhängen fehlen. Zum Beispiel könnten Sie in der Fertigung versuchen, die Effizienz jedes einzelnen Schritts in einem komplexen Herstellungsprozess zu steigern - größere Chargen ausführen, Leute auf dem Boden anschreien, die anscheinend nichts tun, um ihre Hände mit etwas anderem zu beschäftigen - und Dies kann häufig die globale Effizienz des Systems zerstören.

Best Practice: Verwenden Sie DRY und Längen, um den Anruf zu tätigen

(Hinweis: Dieser Abschnittstitel ist ein Scherz. Ich sage meinen Freunden oft, wenn jemand sagt "wir sollten X machen, weil Best Practices sagen das", sind sie es in 90% der Fälle nicht Sprechen Sie über etwas wie SQL-Injection oder Passwort-Hashing oder was auch immer - einseitige Best Practices - und so kann die Aussage in 90% der Fälle in "Wir sollten X machen, weil ich sage es übersetzt werden =. " Wie sie vielleicht einen Blog-Artikel von einem Unternehmen haben, das mit X einen besseren Job gemacht hat als mit X ', aber es gibt im Allgemeinen keine Garantie dafür, dass Ihr Unternehmen diesem Geschäft ähnelt, und es gibt im Allgemeinen einen anderen Artikel von einem anderen Unternehmen, das einen besseren Job gemacht hat mit X 'statt X. Nehmen Sie den Titel also bitte nicht zu ernst.)

Was ich empfehlen würde, basiert auf einem Vortrag von Jack Diederich mit dem Titel Stop Writing Classes (youtube.com) . In diesem Vortrag macht er einige wichtige Punkte geltend: Zum Beispiel, dass man wissen kann, dass eine Klasse wirklich nur eine Funktion ist, wenn sie nur zwei öffentliche Methoden hat, und eine davon ist der Konstruktor/Initialisierer. In einem Fall spricht er jedoch darüber, wie eine hypothetische Bibliothek, die er für den Vortrag als "Muffin" ersetzt hat, ihre eigene Klasse "MuffinHash" deklarierte, die eine Unterklasse des eingebauten Typs dict war, der Python hat. Die Implementierung war völlig leer - jemand hatte gerade gedacht: "Möglicherweise müssen wir Python Wörterbüchern später benutzerdefinierte Funktionen hinzufügen. Lassen Sie uns jetzt für alle Fälle eine Abstraktion einführen."

Und seine trotzige Antwort war einfach: "Wir können das immer später tun, wenn wir müssen."

Ich denke, wir tun manchmal so, als wären wir in Zukunft schlechtere Programmierer als jetzt, also möchten wir vielleicht etwas einfügen, das uns in Zukunft glücklich machen könnte. Wir antizipieren die Bedürfnisse der Zukunft. "Wenn der Verkehr 100-mal größer ist als wir glauben, wird dieser Ansatz nicht skaliert, daher müssen wir die Vorabinvestition in diesen schwierigeren Ansatz investieren, der skaliert." Sehr verdächtig.

Wenn wir diesen Rat ernst nehmen, müssen wir feststellen, wann „später“ gekommen ist. Am naheliegendsten wäre es wahrscheinlich, aus Stilgründen eine Obergrenze für die Länge der Dinge festzulegen. Und ich denke, der verbleibende beste Rat wäre, DRY zu verwenden - wiederholen Sie sich nicht - mit diesen Heuristiken über Linienlängen, um ein Loch in den SOLID Prinzipien zu flicken. Basierend auf der Heuristik von 30 Zeilen als "Seite" des Textes und einer Analogie zur Prosa,

  1. Refaktorieren Sie einen Check in eine Funktion/Methode, wenn Sie sie kopieren und einfügen möchten. Es gibt zwar gelegentlich gültige Gründe für das Kopieren und Einfügen, aber Sie sollten sich immer schmutzig fühlen. Echte Autoren lassen Sie nicht 50 Mal in der gesamten Erzählung einen großen langen Satz nachlesen, es sei denn, sie versuchen wirklich, ein Thema hervorzuheben.
  2. Eine Funktion/Methode sollte idealerweise ein "Absatz" sein. Die meisten Funktionen sollten ungefähr eine halbe Seite oder 1-15 Codezeilen lang sein, und vielleicht sollten nur 10% Ihrer Funktionen eineinhalb Seiten, 45 Zeilen oder mehr umfassen dürfen. Sobald Sie mehr als 120 Codezeilen und Kommentare haben, muss das Ding in Teile zerlegt werden.
  3. Eine Datei sollte idealerweise ein "Kapitel" sein. Die meisten Dateien sollten 12 Seiten oder weniger lang sein, also 360 Codezeilen und Kommentare. Nur vielleicht 10% Ihrer Dateien sollten eine Länge von 50 Seiten oder 1500 Codezeilen und Kommentare haben.
  4. Im Idealfall sollte der größte Teil Ihres Codes mit der Grundlinie der Funktion oder einer Ebene tief eingerückt sein. Basierend auf einigen Heuristiken zum Linux-Quellbaum sollten, wenn Sie religiös sind, nur 10% Ihres Codes innerhalb der Grundlinie um 2 oder mehr Ebenen eingerückt sein, weniger als 5% um 3 oder mehr Ebenen. Dies bedeutet insbesondere, dass Dinge, die ein anderes Problem "einschließen" müssen, wie die Fehlerbehandlung bei einem großen Versuch/Fang, aus der eigentlichen Logik herausgezogen werden sollten.

Wie ich dort oben erwähnt habe, habe ich diese Statistiken mit dem aktuellen Linux-Quellbaum verglichen, um diese ungefähren Prozentsätze zu finden, aber sie sind auch in der literarischen Analogie vernünftig.

1
CR Drost

Nehmen wir an, Sie arbeiten derzeit mit der Klasse Contact. Die Tatsache, dass Sie eine andere Methode zur Validierung der E-Mail-Adresse schreiben, ist ein Beweis dafür, dass die Klasse Contact keine einzige Verantwortung übernimmt.

Es übernimmt auch eine gewisse E-Mail-Verantwortung, die im Idealfall eine eigene Klasse sein sollte.


Ein weiterer Beweis dafür, dass Ihr Code eine Fusion der Klassen Contact und Email ist, ist, dass Sie den E-Mail-Validierungscode nicht einfach testen können. Es werden viele Manöver erforderlich sein, um den E-Mail-Validierungscode in einer großen Methode mit den richtigen Werten zu erreichen. Siehe die Methode unten.

private void LargeMethod() {
    //A lot of code which modifies a lot of values. You do all sorts of tricks here.
    //Code.
    //Code..
    //Code...

    //Email validation code becoming very difficult to test as it will be difficult to ensure 
    //that you have the right data till you reach here in the method
    ValidateEmail();

    //Another whole lot of code that modifies all sorts of values.
    //Extra work to preserve the result of ValidateEmail() for your asserts later.
}

Wenn Sie dagegen eine separate E-Mail-Klasse mit einer Methode zur E-Mail-Validierung hätten, würden Sie zum Testen Ihres Validierungscodes nur einen einfachen Aufruf von Email.Validation() mit Ihren Testdaten durchführen.


Bonusinhalt: MFeathers Vortrag über die tiefe Synergie zwischen Testbarkeit und gutem Design.

1
displayName