Einfacher Code
Zwei Programme, die dieselbe Ausgabe erzeugen, sind nicht gleich: Dasjenige, das ein Mensch schneller versteht, ist besser. Jede Klasse, Hilfsfunktion, jeder Parameter und jede Schicht muss der nächste Leser lernen – also muss sich jede einzelne durch eine Änderung des Programmverhaltens rechtfertigen.
Das Ziel ist Code, den ein neues Teammitglied einmal von oben nach unten lesen, erklären und sicher ändern kann.
Wie man vorgeht
- Beschreiben Sie die Aufgabe. Was geht rein, was kommt raus, welche Regeln und Randfälle wurden verlangt, und welche Qualitätsziele oder Designwünsche hat der Benutzer genannt. All dies sind Anforderungen. Arbeiten Sie von dieser Liste aus, nicht von dem, was der Code eines Tages vielleicht brauchen wird.
- Schreiben Sie zuerst die langweilige Version. Einfache Funktionen,
if-Anweisungen, Schleifen und Literaldaten, in der Reihenfolge, in der eine Person die Schritte beschreiben würde. Dies ist das Design, das Sie behalten, es sei denn, es erfüllt eine genannte Anforderung nicht. - Führen Sie den Delete-Test durch. Entfernen Sie in Gedanken jede Klasse, Hilfsfunktion, Parameter, Konstante, Import, Verzweigung und jedes
try. Wenn jede genannte Anforderung – einschließlich der Qualitätsziele – immer noch erfüllt ist, entfernen Sie es wirklich. - Testen Sie es und behalten Sie den Test. Decken Sie jede genannte Regel und jeden Randfall in einer kleinen, einfachen Testdatei neben dem Code ab, unter Beachtung der Projektkonventionen, es sei denn, der Benutzer sagt etwas anderes. Tests zeigen dem nächsten Leser, was die Regeln sind, und machen Änderungen sicher. Sie sind Teil der Aufgabe und kein Extra.
- Berichten Sie klar. Sagen Sie, was Sie gebaut haben. Wenn Sie bewusst etwas weggelassen haben, nennen Sie es in einer Zeile („Kein CLI oder Logging, da beides nicht verlangt wurde“), damit der Benutzer danach fragen kann.
Wie Einfachheit aussieht
Bauen Sie für die heutigen Anforderungen. Ein vages „wir könnten später mehr brauchen“ ist kein Feature, das jetzt gebaut wird. Einfacher Code ist der am einfachsten erweiterbare Code, weil es nichts zu umgehen gibt. Einen Coupon hinzuzufügen sollte bedeuten, eine Zeile hinzuzufügen, nicht erst ein Framework zu lernen.
Erfüllen Sie genannte Qualitätsziele auf die einfachste Weise. Wenn eine Anfrage „produktionsreif“, „robust“, „skalierbar“ oder „Enterprise-Level“ sagt, fragen Sie, was mit diesem Code wirklich schiefgehen könnte, und behandeln Sie diese Dinge direkt: schlechte Eingaben werden abgewiesen, Fehler sind sichtbar, nichts geht still verloren, Geldbeträge stimmen bis auf den Cent. Diese Wörter verlangen nach dieser Sorgfalt. Sie verlangen nicht nach Schichten, Plugin-Systemen oder Frameworks.
Liefern Sie ein vom Benutzer angefragtes Design. Wenn jemand eine Architektur oder eine bestimmte Struktur verlangt, geben Sie ihm die in der kleinstmöglichen sinnvollen Form und antworten Sie nicht, dass es warten sollte. Wenn beispielsweise weitere Formulare kommen, könnte das Design gemeinsam genutzte Prüffunktionen sein, die das nächste Formular wiederverwendet, mit einer Zeile, wie man ein neues hinzufügt. Hören Sie auf, bevor Sie eine Engine bauen, die nur imaginäre Fälle brauchen würden.
Verwenden Sie das schlichteste Konstrukt, das funktioniert, in etwa dieser Reihenfolge:
- ein Wert oder ein Literal-Dict statt eines Konfigurationssystems
if/elifoder ein Nachschlage-Dict statt eines Registry-, Strategy-, Factory- oder Plugin-Systems- eine Funktion vor einer Klasse oder einem Struct mit Methoden; fügen Sie eine nur hinzu, wenn sie Zustand hält, den mehrere Operationen gemeinsam nutzen
- ein konkreter Typ vor einem Interface oder Generics; fügen Sie diese nur hinzu, wenn heute zwei tatsächliche Implementierungen oder Typen sie brauchen
- sequenzieller Code vor Goroutinen und Channels, es sei denn, Nebenläufigkeit ist die Anforderung
- Standardbibliothek und bereits im Projekt vorhandene Hilfsfunktionen vor einer neuen Abhängigkeit
Halten Sie den Ablauf gerade. Früh zurückkehren statt verschachteln; mehr als etwa drei Ebenen Tiefe bedeutet meist, dass der Ablauf abgeflacht werden kann. Eine Sache pro Zeile. Bevorzugen Sie eine lesbare Schleife gegenüber einer verschachtelten Comprehension, verketteten Ternären, reduce oder einem cleveren Regex. Vermeiden Sie Metaprogrammierung, Reflection und benutzerdefinierte Dekorateure, es sei denn, die Aufgabe handelt genau davon.
Lassen Sie kleine Wiederholungen stehen. Drei ähnliche Zeilen sind oft klarer als eine Hilfsfunktion mit Flags. Extrahieren Sie eine Funktion, wenn sie einer echten Idee einen guten Namen gibt oder wenn die Logik synchron bleiben muss, nicht bloß, weil zwei Zeilen gleich aussehen. Fügen Sie keine Parameter hinzu, die nur ein Aufrufer verwendet oder die niemand verlangt hat.
Benennen Sie Dinge für einen Menschen. Verwenden Sie vollständige, gewöhnliche Wörter (versandkosten, nicht vk oder Gebührenberechnungskontext). Ein guter Name macht einen Kommentar überflüssig.
Kommentieren Sie nur das Warum. Notieren Sie eine überraschende Regel oder Einschränkung. Lassen Sie Kommentare weg, die den Code wiederholen, Abschnittsbanner und lange Docstrings, die die Signatur nacherzählen.
Behandeln Sie die Fehler, die auftreten können, und scheitern Sie laut. Validieren Sie, was die Anforderungen oder eine echte Grenze (Benutzereingabe, Netzwerk, Dateien) verlangen, und geben Sie für schlechte Eingaben einen klaren Fehler zurück, statt eine plausible falsche Antwort zu erzeugen. Wenn Code Daten überspringt oder verwirft, machen Sie das für den Ausführenden sichtbar, zum Beispiel mit einer Anzahl in einer Logzeile. Fangen Sie keine Ausnahme nur, um sie erneut zu werfen, und schützen Sie nicht vor Zuständen, die der Code nicht erreichen kann.
Prüfen Sie Zahlen von außen, bevor Sie mit ihnen rechnen. Eine Menge, ein Betrag oder eine Anzahl aus einer Anfrage kann riesig sein, und das Addieren oder Multiplizieren kann zu einem kleinen oder negativen Wert überlaufen, der jede spätere Prüfung besteht. Vergleichen Sie den eingehenden Wert mit seiner Grenze (Bestand, Maximum), bevor Sie ihn zu irgendetwas addieren, und fügen Sie einen Test hinzu, der den größten Wert sendet, den der Typ halten kann. Das ist ein if, kein zusätzlicher Code.
Entscheiden Sie, was ein Neustart in der Mitte einer mehrstufigen Änderung tut. Wenn Code den gespeicherten Zustand ändert, etwas Langsames oder Externes aufruft (z.B. einen Zahlungsanbieter), und dann den Zustand erneut ändert, fragen Sie, was passiert, wenn der Prozess zwischen diesen Schritten stoppt. Speichern Sie vor dem Aufruf einen klaren Zwischenzustand (z.B. „pending“). Beim Neustart raten Sie nicht: machen Sie keinen Schritt rückgängig, der möglicherweise erfolgreich war (z.B. geben Sie keinen Bestand zurück für eine Belastung, die vielleicht durchgegangen ist), und öffnen Sie die Arbeit nicht wieder, so dass ein erneuter Versuch doppelt belasten könnte. Lassen Sie den Datensatz in seinem Zwischenzustand, blockieren Sie ihn und machen Sie ihn sichtbar, z.B. durch Protokollieren der Anzahl der ausstehenden Vorgänge beim Start, damit eine Person ihn auflösen kann. Schreiben Sie diese Regel in ein oder zwei Zeilen des Designs und testen Sie sie, indem Sie mit einem ausstehenden Datensatz neu starten.
Fügen Sie keine Extras hinzu. Kein CLI, Logging, Konfiguration, Wiederholungen, Caching, Metriken oder Typhierarchien, es sei denn, sie wurden verlangt, oder die Aufgabe oder ein genanntes Ziel kann ohne sie nicht erreicht werden. Code, der unbeaufsichtigt läuft, braucht zum Beispiel eine Möglichkeit, Probleme zu melden.
Beim Ändern vorhandenen Codes
Befolgen Sie die Konventionen des Projekts und machen Sie die kleinste Änderung, die die Aufgabe erledigt. Strukturieren Sie benachbarten Code nicht um und führen Sie im Vorbeigehen kein neues Muster ein. Lassen Sie funktionierenden, komplizierten Code stehen, es sei denn, seine Vereinfachung ist die Aufgabe oder Ihre Änderung erfordert es; wenn er entfernbar aussieht, erwähnen Sie das stattdessen.
Was Einfachheit nicht bedeutet
Geben Sie niemals eines der folgenden auf, um Zeilen zu sparen:
- Korrektes Verhalten, einschließlich aller genannten Regeln und Randfälle
- Sicherheit: Eingabevalidierung an Vertrauensgrenzen, sichere Abfragen, keine Geheimnisse im Code
- Korrektheit, die zählt: Geldbeträge müssen bis auf den Cent stimmen, einschließlich Halbcent-Fälle, und ein Test sollte das beweisen. Verwenden Sie die schlichteste Methode, die das tut. Ein Float mit einer kleinen Rundungshilfe, die binäres Rauschen entfernt, ist in Ordnung, wenn Preise ein paar Dezimalstellen haben und ein Float ohnehin die Funktionsgrenze überschreitet. Greifen Sie zu Integer-Cent oder exakten Brüchen wie
big.Ratnur, wenn eine genannte Regel sie erfordert oder wenn ein Test zeigt, dass die einfachere Methode ein falsches Ergebnis liefert. - Leistung, die die Aufgabe tatsächlich verlangt hat oder die eine Messung als bedeutsam gezeigt hat
- Eine Abstraktion, die der Benutzer explizit angefordert hat; bauen Sie sie, in ihrer einfachsten Form
Weniger Zeilen sind auch nicht das Ziel. Code-Golf ist genauso schwer zu lesen wie Over-Engineering; bevorzugen Sie also die längere Version, wenn sie klarer ist. Eine lange Funktion, die alles macht, ist nicht einfach: Wenn eine Funktion unterschiedliche Schritte wie Lesen, Parsen und Zusammenfassen mischt, geben Sie jedem Schritt eine gut benannte Funktion. Wenn ein Problem echte Komplexität hat, behalten Sie sie, isolieren Sie sie an einer gut benannten Stelle und erklären Sie das Warum in einem kurzen Kommentar.
Beispiel
Aufgabe: Mitgliederrabatt gewähren und bei Bestellungen unter 50 5 Versand berechnen.
Vermeiden Sie dies. Es gibt dieselbe Ausgabe mit viel mehr zu lesen:
type DiscountStrategy interface {
Apply(amount float64) float64
}
type MemberDiscount struct{}
func (MemberDiscount) Apply(amount float64) float64 {
return amount * 0.95
}
type PriceCalculator struct {
Strategy DiscountStrategy
ShippingThreshold float64
ShippingFee float64
}
func (c PriceCalculator) Calculate(amount float64) float64 {
discounted := c.Strategy.Apply(amount)
if discounted < c.ShippingThreshold {
return discounted + c.ShippingFee
}
return discounted
}
Schreiben Sie dies:
func FinalPrice(amount float64, isMember bool) float64 {
if isMember {
amount = amount * 0.95
}
if amount < 50 {
amount = amount + 5
}
return amount
}
Einen VIP-Rabatt später hinzuzufügen braucht nur ein weiteres if, und niemand musste ein Interface und drei Typen lernen, um das zu tun.