Sie lesen einen Auszug aus meinem Buch über sauberen Code „Washing your code“. Erhältlich als PDF, EPUB sowie als Taschenbuch- und Kindle-Ausgabe. Sichern Sie sich jetzt Ihr Exemplar.
Cleverer Code ist etwas, das wir möglicherweise in Fragen zu Vorstellungsgesprächen oder Sprachtests sehen, wenn von uns erwartet wird, dass wir wissen, wie eine Sprachfunktion funktioniert, die wir wahrscheinlich noch nie zuvor gesehen haben. Meine Antwort auf all diese Fragen lautet: „Es wird die Codeüberprüfung nicht bestehen.“
Manche Leute verwechseln Kürze mit Klarheit. Der kurze Code (Kürze) ist nicht immer der klarste Code (Klarheit), oft ist das Gegenteil der Fall. Das Bestreben, Ihren Code kürzer zu machen, ist ein hehres Ziel, sollte aber niemals auf Kosten der Lesbarkeit gehen.
Es gibt viele Möglichkeiten, dieselbe Idee im Code auszudrücken, und einige sind leichter zu verstehen als andere. Wir sollten immer darauf abzielen, die kognitive Belastung des nächsten Entwicklers, der unseren Code liest, zu reduzieren. Jedes Mal, wenn wir über etwas stolpern, das nicht sofort offensichtlich ist, verschwenden wir die Ressourcen unseres Gehirns.
Info:Ich habe den Namen dieses Kapitels aus Steve Krugs gleichnamigem Buch über Web-Usability „gestohlen“.
Schauen wir uns einige Beispiele an. Versuchen Sie, die Antworten abzudecken und zu erraten, was diese Codeausschnitte bewirken. Zählen Sie dann, wie viele Sie richtig erraten haben.
Beispiel 1:
const percent = 5; const percentString = percent.toString().concat('%');
Dieser Code fügt nur das %-Zeichen zu einer Zahl hinzu und sollte wie folgt umgeschrieben werden:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Beispiel 2:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Das ~-Symbol wird als bitweises NICHT-Operator bezeichnet. Der nützliche Effekt besteht hier darin, dass nur dann ein falscher Wert zurückgegeben wird, wenn indexOf() -1 zurückgibt. Dieser Code sollte wie folgt umgeschrieben werden:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Beispiel 3:
const value = ~~3.14;
Eine weitere obskure Verwendung des bitweisen NOT-Operators besteht darin, den Bruchteil einer Zahl zu verwerfen. Verwenden Sie stattdessen Math.floor():
const value = Math.floor(3.14); // → 3
Beispiel 4:
if (dogs.length + cats.length > 0) { // Something fishy here… }
Dieses ist nach einem Moment verständlich: Es prüft, ob eines der beiden Arrays Elemente enthält. Es ist jedoch besser, es klarer zu formulieren:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Beispiel 5:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Es hat eine Weile gedauert, bis ich es verstanden habe. Stellen Sie sich vor, wir haben einen Teil einer URL, z. B. filename="pizza". Zuerst teilen wir die Zeichenfolge durch = und nehmen den zweiten Teil, „pizza“. Dann schneiden wir den ersten und den letzten Charakter in Scheiben, um Pizza zu erhalten.
Ich würde hier wahrscheinlich einen regulären Ausdruck verwenden:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Oder, noch besser, die URLSearchParams API:
const header = 'filename="pizza.rar"'; const filename = new URLSearchParams(header) .get('filename') .replaceAll(/^"|"$/g, ''); // → 'pizza'
Diese Zitate sind allerdings seltsam. Normalerweise benötigen wir keine Anführungszeichen für URL-Parameter, daher könnte es eine gute Idee sein, mit dem Backend-Entwickler zu sprechen.
Beispiel 6:
const percent = 5; const percentString = percent.toString().concat('%');
Im obigen Code fügen wir einem Objekt eine Eigenschaft hinzu, wenn die Bedingung wahr ist, andernfalls tun wir nichts. Die Absicht wird deutlicher, wenn wir explizit zu zerstörende Objekte definieren, anstatt uns auf die Destrukturierung falscher Werte zu verlassen:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Normalerweise bevorzuge ich, wenn Objekte ihre Form nicht ändern, also würde ich die Bedingung in das Wertefeld verschieben:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Beispiel 7:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Dieser wunderbare Einzeiler erstellt ein Array, das mit Zahlen von 0 bis 9 gefüllt ist. Array(10) erstellt ein Array mit 10 leeren Elementen, dann gibt die Methode „keys()“ die Schlüssel (Zahlen von 0) zurück bis 9) als Iterator, den wir dann mithilfe der Spread-Syntax in ein einfaches Array umwandeln. Explodierender Kopf-Emoji…
Wir können es mit einer for-Schleife umschreiben:
const value = ~~3.14;
Obwohl ich Schleifen in meinem Code gerne vermeide, ist die Schleifenversion für mich besser lesbar.
Irgendwo in der Mitte würde die Methode Array.from() verwendet werden:
const value = Math.floor(3.14); // → 3
Array.from({length: 10}) erstellt ein Array mit 10 undefinierten Elementen, dann füllen wir das Array mit der Methode map() mit Zahlen von 0 bis 9.
Wir können es kürzer schreiben, indem wir den Map-Callback von Array.from() verwenden:
if (dogs.length + cats.length > 0) { // Something fishy here… }
Explicit map() ist etwas besser lesbar und wir müssen uns nicht daran erinnern, was das zweite Argument von Array.from() bewirkt. Darüber hinaus ist Array.from({length: 10}) etwas besser lesbar als Array(10). Allerdings nur geringfügig.
Also, wie hoch ist deine Punktzahl? Ich denke, meiner würde bei etwa 3/7 liegen.
Einige Muster bewegen sich auf der Grenze zwischen Cleverness und Lesbarkeit.
Zum Beispiel die Verwendung von Boolean zum Herausfiltern falscher Array-Elemente (null und 0 in diesem Beispiel):
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Ich finde dieses Muster akzeptabel; Obwohl es etwas Lernen erfordert, ist es besser als die Alternative:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Beachten Sie jedoch, dass beide Varianten falsche Werte herausfiltern. Wenn also Nullen oder leere Zeichenfolgen wichtig sind, müssen wir explizit nach undefiniert oder null filtern:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Wenn ich zwei Zeilen kniffligen Codes sehe, die identisch erscheinen, gehe ich davon aus, dass sie sich in irgendeiner Weise unterscheiden, aber ich sehe den Unterschied noch nicht. Andernfalls würde ein Programmierer wahrscheinlich eine Variable oder eine Funktion für den wiederholten Code erstellen, anstatt ihn zu kopieren.
Zum Beispiel haben wir einen Code, der Test-IDs für zwei verschiedene Tools generiert, die wir in einem Projekt verwenden, Enzyme und Codeception:
const header = 'filename="pizza.rar"'; const filename = new URLSearchParams(header) .get('filename') .replaceAll(/^"|"$/g, ''); // → 'pizza'
Es ist schwierig, Unterschiede zwischen diesen beiden Codezeilen sofort zu erkennen. Erinnern Sie sich an die Bildpaare, bei denen Sie zehn Unterschiede finden mussten? Das macht dieser Code mit dem Leser.
Obwohl ich gegenüber extremem Code-DRYing generell skeptisch bin, ist dies ein gutes Argument dafür.
Info:Wir sprechen mehr über das Prinzip „Wiederhole dich nicht“ im Kapitel „Teile und herrsche“ oder „Zusammenführung und Entspannung“.
const percent = 5; const percentString = percent.toString().concat('%');
Nun besteht kein Zweifel daran, dass der Code für beide Test-IDs genau derselbe ist.
Sehen wir uns ein kniffligeres Beispiel an. Angenommen, wir verwenden für jedes Testtool unterschiedliche Namenskonventionen:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Der Unterschied zwischen diesen beiden Codezeilen ist schwer zu erkennen und wir können nie sicher sein, dass das Namenstrennzeichen (- oder _) hier der einzige Unterschied ist.
In einem Projekt mit einer solchen Anforderung wird dieses Muster wahrscheinlich an vielen Stellen auftauchen. Eine Möglichkeit zur Verbesserung besteht darin, Funktionen zu erstellen, die Test-IDs für jedes Tool generieren:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Das ist schon viel besser, aber noch nicht perfekt – der wiederholte Code ist immer noch zu groß. Lassen Sie uns auch das beheben:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Dies ist ein extremer Fall der Verwendung kleiner Funktionen, und ich versuche im Allgemeinen, eine so große Aufteilung des Codes zu vermeiden. In diesem Fall funktioniert es jedoch gut, insbesondere wenn es bereits viele Stellen im Projekt gibt, an denen wir die neue Funktion getTestIdProps() verwenden können.
Manchmal weist Code, der nahezu identisch aussieht, subtile Unterschiede auf:
const value = ~~3.14;
Der einzige Unterschied besteht hier in dem Parameter, den wir mit einem sehr langen Namen an die Funktion übergeben. Wir können die Bedingung innerhalb des Funktionsaufrufs verschieben:
const value = Math.floor(3.14); // → 3
Dadurch wird der ähnliche Code eliminiert, wodurch das gesamte Snippet kürzer und leichter verständlich wird.
Immer wenn wir auf eine Bedingung stoßen, die den Code etwas anders macht, sollten wir uns fragen: Ist diese Bedingung wirklich notwendig? Wenn die Antwort „Ja“ lautet, sollten wir uns noch einmal fragen. Oft besteht keine echte Notwendigkeit für eine bestimmte Erkrankung. Warum müssen wir beispielsweise Test-IDs für verschiedene Tools überhaupt separat hinzufügen? Können wir nicht eines der Tools so konfigurieren, dass es die Test-IDs des anderen verwendet? Wenn wir tief genug graben, stellen wir möglicherweise fest, dass niemand die Antwort kennt oder dass der ursprüngliche Grund nicht mehr relevant ist.
Betrachten Sie dieses Beispiel:
if (dogs.length + cats.length > 0) { // Something fishy here… }
Dieser Code behandelt zwei Randfälle: wenn „assetsDir“ nicht existiert und wenn „assetsDir“ kein Array ist. Außerdem wird der Objektgenerierungscode dupliziert. (Und reden wir nicht über verschachtelte Ternäre...)Wir können die Duplizierung und mindestens eine Bedingung loswerden:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Mir gefällt nicht, dass die castArray()-Methode von Lodash undefinierte Elemente in ein Array einschließt, was nicht das ist, was ich erwartet hätte, aber trotzdem ist das Ergebnis einfacher.
CSS verfügt über abgekürzte Eigenschaften, die von Entwicklern häufig häufig verwendet werden. Die Idee ist, dass eine einzelne Eigenschaft mehrere Eigenschaften gleichzeitig definieren kann. Hier ist ein gutes Beispiel:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Das ist dasselbe wie:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Eine Codezeile statt vier, und es ist immer noch klar, was passiert: Wir legen auf allen vier Seiten eines Elements den gleichen Rand fest.
Sehen Sie sich nun dieses Beispiel an:
const percent = 5; const percentString = percent.toString().concat('%');
Um zu verstehen, was sie tun, müssen wir Folgendes wissen:
Dies führt zu einer unnötigen kognitiven Belastung und erschwert das Lesen, Bearbeiten und Überprüfen des Codes. Ich vermeide solche Abkürzungen.
Ein weiteres Problem bei abgekürzten Eigenschaften besteht darin, dass sie Werte für Eigenschaften festlegen können, die wir nicht ändern wollten. Betrachten Sie dieses Beispiel:
const percent = 5; const percentString = `${percent}%`; // → '5%'
Diese Deklaration legt die Helvetica-Schriftfamilie und die Schriftgröße von 2rem fest und macht den Text kursiv und fett. Was wir hier nicht sehen, ist, dass dadurch auch die Zeilenhöhe auf den Standardwert „Normal“ geändert wird.
Meine Faustregel lautet, Abkürzungseigenschaften nur zu verwenden, wenn ein einzelner Wert festgelegt wird. Ansonsten bevorzuge ich Langschrift-Eigenschaften.
Hier sind einige gute Beispiele:
const url = 'index.html?id=5'; if (~url.indexOf('id')) { // Something fishy here… }
Und hier sind einige Beispiele, die Sie vermeiden sollten:
const url = 'index.html?id=5'; if (url.includes('id')) { // Something fishy here… }
Auch wenn Kurzschrifteigenschaften den Code tatsächlich kürzer machen, machen sie ihn oft deutlich schwerer lesbar, also verwenden Sie sie mit Vorsicht.
Die Beseitigung von Erkrankungen ist nicht immer möglich. Es gibt jedoch Möglichkeiten, Unterschiede in Codezweigen leichter erkennbar zu machen. Einer meiner Lieblingsansätze ist das, was ich parallele Codierung nenne.
Betrachten Sie dieses Beispiel:
const value = ~~3.14;
Es mag ein persönlicher Ärgernis sein, aber ich mag es nicht, wenn die Renditeabrechnungen auf unterschiedlichen Ebenen erfolgen, was den Vergleich erschwert. Fügen wir eine else-Anweisung hinzu, um dieses Problem zu beheben:
const value = Math.floor(3.14); // → 3
Jetzt haben beide Rückgabewerte die gleiche Einrückungsebene, was den Vergleich erleichtert. Dieses Muster funktioniert, wenn keiner der Bedingungszweige Fehler behandelt. In diesem Fall wäre eine frühe Rückkehr ein besserer Ansatz.
Info:Über vorzeitige Rückgaben sprechen wir im Kapitel „Bedingungen vermeiden“.
Hier ist ein weiteres Beispiel:
if (dogs.length + cats.length > 0) { // Something fishy here… }
In diesem Beispiel haben wir eine Schaltfläche, die sich wie ein Link im Browser verhält und ein Bestätigungsmodal in einer App anzeigt. Die umgekehrte Bedingung für die onPress-Requisite macht diese Logik schwer zu erkennen.
Machen wir beide Bedingungen positiv:
if (dogs.length > 0 && cats.length > 0) { // Something fishy here… }
Jetzt ist klar, dass wir je nach Plattform entweder onPress- oder Link-Requisiten setzen.
Wir können hier aufhören oder noch einen Schritt weiter gehen, abhängig von der Anzahl der Platform.OS === 'web'-Bedingungen in der Komponente oder wie viele Requisiten wir bedingt festlegen müssen
Wir können die bedingten Requisiten in eine separate Variable extrahieren:
const header = 'filename="pizza.rar"'; const filename = header.split('filename=')[1].slice(1, -1);
Dann verwenden Sie es, anstatt jedes Mal die gesamte Bedingung fest zu codieren:
const header = 'filename="pizza.rar"'; const filename = header.match(/filename="(.*?)"/)[1]; // → 'pizza'
Ich habe auch die Ziel-Requisite in den Webzweig verschoben, da sie von der App ohnehin nicht verwendet wird.
Als ich in meinen Zwanzigern war, war es für mich kein großes Problem, mich an Dinge zu erinnern. Ich konnte mich an Bücher erinnern, die ich gelesen hatte, und an alle Funktionen in einem Projekt, an dem ich arbeitete. Jetzt, wo ich in meinen Vierzigern bin, ist das nicht mehr der Fall. Ich lege jetzt Wert auf einfachen Code, der keine Tricks verwendet; Ich schätze Suchmaschinen, schnellen Zugriff auf die Dokumentation und Tools, die mir helfen, über den Code nachzudenken und durch das Projekt zu navigieren, ohne alles im Kopf zu behalten.
Wir sollten Code nicht für uns selbst schreiben, sondern für das, was wir in ein paar Jahren sein werden. Denken ist schwer und das Programmieren erfordert viel davon, auch ohne kniffligen oder unklaren Code entschlüsseln zu müssen.
Denken Sie darüber nach:
Wenn Sie Feedback haben, senden Sie mir eine Nachricht, twittern Sie mich, eröffnen Sie ein Problem auf GitHub oder senden Sie mir eine E-Mail an artem@sapegin.ru. Holen Sie sich Ihr Exemplar.
Das obige ist der detaillierte Inhalt vonWaschen Sie Ihren Code: Bringen Sie mich nicht zum Nachdenken. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!