Kapitel 26. Ein Meta-Styleguide für Code
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 26. Ein Meta-Styleguide für Code

JavaScript hat viele gute Styleguides. Daher besteht keine Notwendigkeit, noch einen zu schreiben. Stattdessen beschreibt dieses Kapitel Meta-Stilregeln und gibt einen Überblick über bestehende Styleguides und etablierte Best Practices. Es erwähnt auch Praktiken, die mir gefallen, die aber umstrittener sind. Die Idee ist, bestehende Styleguides zu ergänzen, anstatt sie zu ersetzen.

Bestehende Styleguides

Dies sind Styleguides, die mir gefallen:

Zusätzlich gibt es zwei Styleguides, die ins Meta gehen

Allgemeine Tipps

Dieser Abschnitt behandelt einige allgemeine Tipps zum Schreiben von Code.

Code sollte konsistent sein

Es gibt zwei wichtige Regeln für das Schreiben von konsistentem Code. Die erste Regel ist, dass Sie, wenn Sie ein neues Projekt beginnen, einen Stil festlegen, ihn dokumentieren und überall befolgen sollten. Je größer das Team, desto wichtiger ist es, die Einhaltung des Stils automatisch zu überprüfen, z. B. mit Tools wie JSHint. Wenn es um Stil geht, gibt es viele Entscheidungen zu treffen. Die meisten davon haben allgemein anerkannte Antworten. Andere müssen projektbezogen definiert werden. Zum Beispiel:

  • Wie viel Leerzeichen (nach Klammern, zwischen Anweisungen usw.)
  • Einrückung (z. B. wie viele Leerzeichen pro Einrückungsstufe)
  • Wie und wo var-Anweisungen geschrieben werden

Die zweite Regel ist, dass Sie, wenn Sie einem bestehenden Projekt beitreten, dessen Regeln strikt befolgen sollten (auch wenn Sie ihnen nicht zustimmen).

Code sollte leicht verständlich sein

Für den größten Teil des Codes ist die Zeit, die zum Lesen benötigt wird, viel größer als die Zeit, die zum Schreiben benötigt wird. Es ist daher wichtig, ersteres so einfach wie möglich zu gestalten. Hier sind einige Richtlinien dafür

Kürzer ist nicht immer besser
Manchmal bedeutet das Schreiben von mehr, dass die Dinge tatsächlich schneller zu lesen sind. Betrachten wir zwei Beispiele. Erstens sind vertraute Dinge leichter zu verstehen. Das kann bedeuten, dass die Verwendung vertrauter, etwas ausführlicherer Konstrukte vorzuziehen ist. Zweitens lesen Menschen Token, nicht Zeichen. Daher ist redBalloon leichter zu lesen als rdBlln.
Guter Code ist wie ein Lehrbuch

Die meisten Codebasen sind voller neuer Ideen und Konzepte. Das bedeutet, dass Sie diese Ideen und Konzepte lernen müssen, wenn Sie mit einer Codebasis arbeiten möchten. Im Gegensatz zu Lehrbüchern besteht die zusätzliche Herausforderung bei Code darin, dass die Leute ihn nicht linear lesen werden. Sie werden irgendwo einsteigen und grob verstehen können, was vor sich geht. Drei Teile einer Codebasis helfen

Seien Sie nicht clever; lassen Sie mich nicht denken
Es gibt viel cleveren Code, der tiefgreifendes Wissen über die Sprache nutzt, um beeindruckende Kürze zu erreichen. Solcher Code ist normalerweise wie ein Rätsel und schwer zu durchschauen. Sie werden auf die Meinung stoßen, dass Leute, die solchen Code nicht verstehen, vielleicht zuerst JavaScript lernen sollten. Aber darum geht es hier nicht. Egal wie clever Sie sind, das Eindringen in die Gedankenwelten anderer Menschen ist immer eine Herausforderung. Daher ist einfacher Code nicht dumm, sondern Code, bei dem der meiste Aufwand darauf verwendet wurde, alles leicht verständlich zu machen. Beachten Sie, dass "andere Leute" auch Ihre zukünftigen Ichs einschließen. Ich stelle oft fest, dass clevere Gedanken, die ich in der Vergangenheit hatte, für mein jetziges Ich keinen Sinn ergeben.
Vermeiden Sie Optimierungen für Geschwindigkeit oder Code-Größe

Viel Klugheit wird auf diese Optimierungen gerichtet. Normalerweise brauchen Sie sie jedoch nicht. Einerseits werden JavaScript-Engines immer intelligenter und optimieren automatisch die Geschwindigkeit von Code, der etablierten Mustern folgt. Andererseits schreiben Minifier-Tools (Kapitel 32) Ihren Code neu, sodass er so klein wie möglich ist. In beiden Fällen sind Tools für Sie clever, damit Sie es nicht sein müssen.

Manchmal haben Sie keine andere Wahl, als die Leistung Ihres Codes zu optimieren. Wenn Sie dies tun, stellen Sie sicher, dass Sie die richtigen Teile messen und optimieren. In Browsern beziehen sich die Probleme oft auf DOM und HTML und nicht auf die Sprache selbst.

Allgemein anerkannte Best Practices

Eine Mehrheit von JavaScript-Programmierern stimmt den folgenden Best Practices zu:

Klammerstile

In Sprachen, in denen Klammern Codeblöcke abgrenzen, bestimmt ein Klammerstil, wo Sie diese Klammern platzieren. Zwei Klammerstile sind in C-ähnlichen Sprachen (wie Java und JavaScript) am gebräuchlichsten: Allman-Stil und 1TBS.

1TBS (One True Brace Style)

Hier ist ein Block enger mit dem Header seiner Anweisung verbunden; er beginnt danach, in derselben Zeile. Die Körper von Kontrollflussanweisungen werden immer in Klammern gesetzt, auch wenn es nur eine einzige Anweisung gibt. Zum Beispiel:

// One True Brace Style
function foo(x, y, z) {
    if (x) {
        a();
    } else {
        b();
        c();
    }
}

1TBS ist eine Variante des (älteren) K&R (Kernighan und Ritchie)-Stils.[21] Im K&R-Stil werden Funktionen im Allman-Stil geschrieben und Klammern weggelassen, wo sie nicht notwendig sind – zum Beispiel um einzeilige then-Fälle

// K&R brace style
function foo(x, y, z)
{
    if (x)
        a();
    else {
        b();
        c();
    }
}

JavaScript

Der De-facto-Standard in der JavaScript-Welt ist 1TBS. Er wurde von Java übernommen und die meisten Styleguides empfehlen ihn. Ein Grund dafür ist objektiv. Wenn Sie ein Objektliteral zurückgeben, müssen Sie die öffnende Klammer in dieselbe Zeile wie das Schlüsselwort return setzen, wie hier (andernfalls fügt die automatische Semikolon-Einfügung ein Semikolon nach return ein, was bedeutet, dass nichts zurückgegeben wird; siehe Fallstrick: ASI kann unerwartet Anweisungen trennen)

return {
    name: 'Jane'
};

Offensichtlich ist ein Objektliteral kein Codeblock, aber die Dinge sehen konsistenter aus, und Sie machen weniger Fehler, wenn beides auf die gleiche Weise formatiert ist.

Mein persönlicher Stil und meine Vorliebe sind

  • 1TBS (was bedeutet, dass Sie Klammern verwenden, wann immer möglich).
  • Als Ausnahme lasse ich Klammern weg, wenn eine Anweisung in einer einzigen Zeile geschrieben werden kann. Zum Beispiel

    if (x) return x;

Literale gegenüber Konstruktoren bevorzugen

Mehrere Konstruktoren erzeugen Objekte, die auch durch Literale erstellt werden können. Letzteres ist normalerweise die bessere Wahl:

var obj = new Object(); // no
var obj = {}; // yes

var arr = new Array(); // no
var arr = []; // yes

var regex = new RegExp('abc'); // avoid if possible
var regex = /abc/; // yes

Verwenden Sie niemals den Konstruktor Array, um ein Array mit gegebenen Elementen zu erstellen. Initialisieren eines Arrays mit Elementen (vermeiden!) erklärt, warum

var arr = new Array('a', 'b', 'c'); // never ever
var arr = [ 'a', 'b', 'c' ]; // yes

Seien Sie nicht schlau

Dieser Abschnitt sammelt Beispiele für nicht empfohlene Klugheit.

Bedingungsoperator

Verschachteln Sie denBedingungsoperator nicht:

// Don’t:
return x === 0 ? 'red' : x === 1 ? 'green' : 'blue';

// Better:
if (x === 0) {
    return 'red';
} else if (x === 1) {
    return 'green';
} else {
    return 'blue';
}

// Best:
switch (x) {
    case 0:
        return 'red';
    case 1:
        return 'green';
    default:
        return 'blue';
}

Abkürzen von if-Anweisungen

Kürzen Sie if-Anweisungen nicht durch logische Operatoren:

foo && bar(); // no
if (foo) bar(); // yes

foo || bar(); // no
if (!foo) bar(); // yes

Inkrementoperator

Verwenden Sie, wenn möglich, den Inkrementoperator (++) und den Dekrementoperator (--) als Anweisungen; verwenden Sie sie nicht als Ausdrücke. In letzterem Fall geben sie einen Wert zurück, und obwohl es eine Eselsbrücke gibt, müssen Sie immer noch nachdenken, um herauszufinden, was vor sich geht:

// Unsure: what is happening?
return ++foo;

// Easy to understand
++foo;
return foo;

Prüfen auf undefiniert

if (x === void 0) x = 0; // not necessary in ES5
if (x === undefined) x = 0; // preferable

Ab ECMAScript 5 ist die zweite Methode besser. Ändern von undefined erklärt, warum.

Eine Zahl in eine Ganzzahl umwandeln

return x >> 0; // no
return Math.round(x); // yes

Der Shift-Operator kann verwendet werden, um eine Zahl in eine Ganzzahl umzuwandeln. Es ist jedoch normalerweise besser, eine explizitere Alternative wie Math.round() zu verwenden. Umwandlung in eine Ganzzahl gibt einen Überblick über alle Möglichkeiten der Umwandlung in eine Ganzzahl.

Akzeptable Klugheit

Manchmal kann man in JavaScript clever sein – wenn die Klugheit zu einem etablierten Muster geworden ist.

Standardwerte

Die Verwendung des Oder-Operators (||), um Standardwerte bereitzustellen, ist ein gängiges Muster – zum Beispiel für Parameter:

function f(x) {
    x = x || 0;
    ...
}

Für Details und weitere Beispiele konsultieren Sie bitte Muster: Bereitstellen eines Standardwerts.

Generische Methoden

Wenn Sie Methoden generisch verwenden, können Sie Object.prototype als {} abkürzen. Die folgenden beiden Ausdrücke sind äquivalent:

Object.prototype.hasOwnProperty.call(obj, propKey)
{}.hasOwnProperty.call(obj, propKey)

Und Array.prototype kann als [] abgekürzt werden:

Array.prototype.slice.call(arguments)
[].slice.call(arguments)

Ich bin hier zwiegespalten. Es ist ein Hack (Sie greifen über eine Instanz auf eine Prototyp-Eigenschaft zu). Aber es reduziert Unordnung, und ich erwarte, dass Engines dieses Muster irgendwann optimieren werden.

ECMAScript 5: nachgestellte Kommas

Nachgestellte Kommas in Objektliteralen sind in ECMAScript 5 legal:

var obj = {
    first: 'Jane',
    last: 'Doe', // legal: trailing comma
};

ECMAScript 5: reservierte Wörter

ECMAScript 5 erlaubt es Ihnen auch, reservierte Wörter (wie new) als Eigenschaftsschlüssel zu verwenden:

> var obj = { new: 'abc' };
> obj.new
'abc'

Umstrittene Regeln

Betrachten wir einige Konventionen, die mir gefallen und die etwas umstrittener sind.

Syntax

Wir beginnen mit syntaktischen Konventionen

Dichter Whitespace

Ich mag relativ dichten Whitespace. Das Modell ist geschriebenes Englisch: Es gibt keine Leerzeichen nach einer öffnenden Klammer und vor einer schließenden Klammer. Und es gibt Leerzeichen nach Kommas:

var result = foo('a', 'b');
var arr = [ 1, 2, 3 ];
if (flag) {
    ...
}

Für anonyme Funktionen befolge ich die Regel von Douglas Crockford, nach dem Schlüsselwort function ein Leerzeichen zu setzen. Die Begründung ist, dass dies so aussieht, wie ein benannter Funktionsausdruck aussieht, wenn Sie den Namen entfernen

function foo(arg) { ... }  // named function expression
function (arg) { ... }     // anonymous function expression
Vier Leerzeichen pro Einrückungsstufe
Die meisten Codes, die ich sehe, verwenden Leerzeichen für die Einrückung, da Tabs zwischen Anwendungen und Betriebssystemen unterschiedlich angezeigt werden. Ich bevorzuge vier Leerzeichen pro Einrückungsstufe, da dies die Einrückung besser sichtbar macht.
Setzen Sie den Bedingungsoperator in Klammern

Dies hilft beim Lesen, da der Geltungsbereich des Operators leichter zu erkennen ist:

return result ? result : theDefault;  // no
return (result ? result : theDefault);  // yes

Variablen

Als Nächstes behandle ich Konventionen für Variablen

Eine Variablendeklaration pro Zeile

Ich deklariere nicht mehrere Variablen mit einer einzigen Deklaration:

// no
var foo = 3,
    bar = 2,
    baz;

// yes
var foo = 3;
var bar = 2;
var baz;

Die Vorteile dieses Ansatzes sind, dass das Löschen, Einfügen und Umordnen von Zeilen einfacher ist und die Zeilen automatisch korrekt eingerückt werden.

Halten Sie Variablendeklarationen lokal
Wenn Ihre Funktion nicht zu lang ist (was sie ohnehin nicht sein sollte), können Sie sich weniger um das Hoisting kümmern und so tun, als wären var-Deklarationen block-scoped. Mit anderen Worten, Sie können eine Variable in dem Kontext deklarieren, in dem sie verwendet wird (innerhalb einer Schleife, innerhalb eines then-Blocks oder eines else-Blocks usw.). Diese Art der lokalen Kapselung macht ein Codefragment isoliert leichter verständlich. Es ist auch einfacher, das Codefragment zu entfernen oder woandershin zu verschieben.
Wenn Sie sich in einem Block befinden, bleiben Sie in diesem Block

Als Ergänzung zur vorherigen Regel: Deklarieren Sie nicht dieselbe Variable zweimal, in zwei verschiedenen Blöcken. Zum Beispiel

// Don’t do this
if (v) {
    var x = v;
} else {
    var x = 10;
}
doSomethingWith(x);

Der vorherige Code hat denselben Effekt und dieselbe Absicht wie der folgende Code, weshalb er so geschrieben werden sollte

var x;
if (v) {
    x = v;
} else {
    x = 10;
}
doSomethingWith(x);

Objektorientierung

Nun behandeln wir Konventionen bezüglich Objektorientierung.

Konstruktoren gegenüber anderen Instanzierungsmustern bevorzugen

Ich empfehle Ihnen,

  • Konstruktoren immer zu verwenden.
  • Verwenden Sie immer new, wenn Sie eine Instanz erstellen.

Die Hauptvorteile sind

  • Ihr Code passt besser in den JavaScript-Mainstream und ist wahrscheinlicher zwischen Frameworks portierbar.
  • In modernen Engines ist die Verwendung von Instanzen von Konstruktoren sehr schnell (z. B. über versteckte Klassen).
  • Klassen, der Standard-Vererbungskonstrukt in ECMAScript 6, das bald erscheinen wird, basieren auf Konstruktoren.

Bei Konstruktoren ist es wichtig, den Strict Mode zu verwenden, da er Sie vor dem Vergessen des new-Operators zur Instanziierung schützt. Und Sie sollten sich bewusst sein, dass Sie in einem Konstruktor jedes Objekt zurückgeben können. Weitere Tipps zur Verwendung von Konstruktoren werden in Tipps zur Implementierung von Konstruktoren erwähnt.

Vermeiden Sie Closures für private Daten
Wenn Sie möchten, dass die privaten Daten eines Objekts vollständig sicher sind, müssen Sie Closures verwenden. Andernfalls können Sie normale Eigenschaften verwenden. Eine gängige Praxis ist, Namen von privaten Eigenschaften mit Unterstrichen zu präfixieren. Das Problem mit Closures ist, dass der Code komplizierter wird (es sei denn, Sie legen alle Methoden in die Instanz, was unidiomatisch und langsam ist) und langsamer (der Zugriff auf Daten in Closures ist derzeit langsamer als der Zugriff auf Eigenschaften). Daten privat halten behandelt dieses Thema ausführlicher.
Schreiben Sie Klammern, wenn ein Konstruktor keine Argumente hat

Ich finde, dass eine solche Konstruktoraufrufung mit Klammern sauberer aussieht

var foo = new Foo;  // no
var foo = new Foo();  // yes
Seien Sie vorsichtig mit der Operatorrangfolge

Verwenden Sie Klammern, damit sich zwei Operatoren nicht gegenseitig überlagern – das Ergebnis ist nicht immer das, was Sie vielleicht erwarten:

> false && true || true
true
> false && (true || true)
false
> (false && true) || true
true

instanceof ist besonders knifflig

> ! {} instanceof Array
false
> (!{}) instanceof Array
false
> !({} instanceof Array)
true

Methodenaufrufe nach einem Konstruktor finde ich jedoch unproblematisch

new Foo().bar().baz();  // ok
(new Foo()).bar().baz();  // not necessary

Verschiedenes

Dieser Abschnitt sammelt verschiedene Tipps

Kozeren

Konvertieren Sie einen Wert in einen Typ über Boolean, Number, String(), Object() (als Funktionen verwendet – verwenden Sie diese Funktionen niemals als Konstruktoren). Die Begründung ist, dass diese Konvention beschreibender ist:

> +'123'  // no
123
> Number('123')  // yes
123

> ''+true  // no
'true'
> String(true)  // yes
'true'
Vermeiden Sie this als impliziten Parameter

this sollte sich nur auf den Empfänger des aktuellen Methodenaufrufs beziehen; es sollte nicht als impliziter Parameter missbraucht werden. Die Begründung ist, dass solche Funktionen leichter aufzurufen und zu verstehen sind. Ich mag es auch, objektorientierte und funktionale Mechanismen getrennt zu halten:

// Avoid:
function handler() {
    this.logError(...);
}

// Prefer:
function handler(context) {
    context.logError(...);
}
Prüfen Sie die Existenz einer Eigenschaft über in und hasOwnProperty (siehe Iteration und Erkennung von Eigenschaften)

Dies ist selbsterklärender und sicherer als der Vergleich mit undefined oder die Prüfung auf Wahrheitswerte

// All properties:
if (obj.foo)  // no
if (obj.foo !== undefined)  // no
if ('foo' in obj) ... // yes

// Own properties:
if (obj.hasOwnProperty('foo')) ... // risky for arbitrary objects
if (Object.prototype.hasOwnProperty.call(obj, 'foo')) ... // safe
Schnell scheitern
Wenn Sie können, ist es am besten, schnell zu scheitern und nicht stillschweigend zu scheitern. JavaScript ist nur so fehlerverzeihend (z. B. Division durch Null), da die erste Version von ECMAScript keine Ausnahmen hatte. Konvertieren Sie beispielsweise keine Werte; werfen Sie eine Ausnahme. Sie müssen jedoch Wege finden, sich im Produktionsumfeld elegant von Fehlern zu erholen.

Schlussfolgerung

Wenn Sie eine Stilfrage in Betracht ziehen, fragen Sie sich: Was macht meinen Code leichter verständlich? Widerstehen Sie der Versuchung, schlau zu sein, und überlassen Sie die meiste mechanische Klugheit JavaScript-Engines und Minifiern (siehe Kapitel 32).



[21] Manche Leute sagen sogar, dass sie Synonyme sind, dass 1TBS eine scherzhafte Bezeichnung für K&R ist.

Weiter: 27. Sprachmechanismen zum Debugging