- Notifications
You must be signed in to change notification settings - Fork8
marcbruederlin/clean-code-javascript
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Original Repository:ryanmcdermott/clean-code-javascript
- Einführung
- Variablen
- Funktionen
- Objekte und Datenstrukturen
- Klassen
- SOLID
- Testing
- Parallelität
- Fehlerbehandlung
- Formatierung
- Kommentare
- Übersetzungen
Softwareentwicklungs-Prinzipien aus Robert C. Martin's BuchClean Code,adaptiert für JavaScript. Dies ist kein Styleguide. Es ist ein Leitfaden um lesbare, wiederverwendbareund wartbare Software mit JavaScript zu entwickeln.
Nicht jede Regel hier muss streng befolgt werden und noch weniger Regeln werden universellanwendbar sein. Dies sind Richtlinien und nicht mehr. Sie sind jedoch das Ergebnisjahrelang gesammelter Erfahrung des Autors vonClean Code.
Unser Handwerk der Softwareentwicklung ist nur etwas mehr als 50 Jahre alt und wir lernenimmer noch eine Menge. Wenn Softwarearchitektur ebenso alt ist wie die Architektur, haben wirwomöglich strengere Regeln denen wir zu folgen haben. Für den Moment, lass diese Richtlinienein Maßstab sein um die Qualität des JavaScript-Codes den du und dein Team produzierst,beurteilen zu können.
Eine Sache noch: Diese Regeln zu kennen macht dich nicht unverzüglich zu einem besserenSoftwareentwickler. Und mit ihnen für viele Jahre zu arbeiten heißt nicht, dass du keineFehler machst. Jedes Stück Code startet mit einem ersten Entwurf – wie nasser Ton der inseine finale Form ausgestaltet wird. Wir meißeln endlich die Unvollkommenheiten weg, wennwir unseren Code mit Kollegen überprüfen. Mach dich wegen ersten Entwürfen die Verbesserungenbenötigen nicht fertig. Verprügel stattdessen den Code!
Schlecht:
constyyyymmdstr=moment().format('YYYY/MM/DD');
Gut:
constcurrentDate=moment().format('YYYY/MM/DD');
Schlecht:
getUserInfo();getClientData();getCustomerRecord();
Gut:
getUser();
Wir lesen mehr Code als wir jemals schreiben werden. Es ist daher wichtig, dassder Code den wir schreiben les- und durchsuchbar ist. Wir erschweren den Lesernunseres Codes die Arbeit, wenn wir unsere Variablennicht aussagekräftig benennenund somit das Verständnis unseres Programms erschweren.Mache deine Namen suchbar. Tools wiebuddy.js undESLintkönnen dabei helfen, unbenannte Konstanten zu finden.
Schlecht:
// Wofür steht 86400000?setTimeout(blastOff,86400000);
Gut:
// Deklariere diese als großgeschriebene `const` Globale.constMILLISECONDS_IN_A_DAY=86400000;setTimeout(blastOff,MILLISECONDS_IN_A_DAY);
Schlecht:
constaddress='One Infinite Loop, Cupertino 95014';constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;saveCityZipCode(address.match(cityZipCodeRegex)[1],address.match(cityZipCodeRegex)[2]);
Gut:
constaddress='One Infinite Loop, Cupertino 95014';constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;const[,city,zipCode]=address.match(cityZipCodeRegex)||[];saveCityZipCode(city,zipCode);
Explizites ist besser als Implizites.
Schlecht:
constlocations=['Austin','New York','San Francisco'];locations.forEach((l)=>{doStuff();doSomeOtherStuff();// ...// ...// ...// Moment, wofür steht `l` nochmal?dispatch(l);});
Gut:
constlocations=['Austin','New York','San Francisco'];locations.forEach((location)=>{doStuff();doSomeOtherStuff();// ...// ...// ...dispatch(location);});
Wenn dir deine Klasse/Objekt etwas sagt, wiederhole das nicht in deinemVariablennamen.
Schlecht:
constCar={carMake:'Honda',carModel:'Accord',carColor:'Blue'};functionpaintCar(car){car.carColor='Red';}
Gut:
constCar={make:'Honda',model:'Accord',color:'Blue'};functionpaintCar(car){car.color='Red';}
Default-Werte sind meistens sauberer als Short-Circuiting. Sei dir aber bewusst, dassdeine Funktion nur Default-Werte fürundefined
-Argumente vergibt. Andere „falsche“ Wertewie beispielsweise''
,false
,null
,0
undNaN
werden nicht durch Default-Werte ersetzt.
Schlecht:
functioncreateMicrobrewery(name){constbreweryName=name||'Hipster Brew Co.';// ...}
Gut:
functioncreateMicrobrewery(breweryName='Hipster Brew Co.'){// ...}
Die Anzahl der Funktionsargumente zu limitieren ist unglaublich wichtig, weildadurch die Testbarkeit deiner Funktion erleichtert wird. Mehr als drei Argumenteführen zu einer kombinatorischen Explosion und du musst unzählige verschiedeneFälle mit jedem einzelnen Argument testen.
Ein bis zwei Argumente sind ideal, drei sollten aber wenn möglich vermieden werden.Alles darüber sollte zusammengefasst werden. Meistens erledigt deine Funktion zu viel,wenn du mehr als zwei Argumente benötigst. Falls nicht, reicht es ein übergeordnetesObjekt als Argument zu übergeben.
Weil uns JavaScript erlaubt Objekte on-the-fly und ohne viel Klassen-Boilerplate zuerstellen, kannst du Objekte verwenden wenn du merkst, dass du eine Menge Argumentebenötigst.
Um deutlich zu machen, welche Eigenschaften eine Funktion erwartet, kannst du die ES6destruktierende Zuweisung verwenden. Diese hat einige Vorteile:
- Wenn sich jemand die Methodensignatur anschaut, wird unmittelbar klar, welche Eigenschaftenverwendet werden.
- Die destruktierende Zuweisung klont die spezifiziert primitiven Datentypen, die in die Funktionübergeben werden. Das kann helfen, Nebeneffekte zu verhindern. Anmerkung: Objekte und Arrays werdenNICHT geklont.
- Linter können dich vor unbenutzten Eigenschaften warnen. Das wäre ohne die destruktierende Zuweisungunmöglich.
Schlecht:
functioncreateMenu(title,body,buttonText,cancellable){// ...}
Gut:
functioncreateMenu({ title, body, buttonText, cancellable}){// ...}createMenu({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true});
Dies ist bei weitem die wichtigste Regel in der Softwareentwicklung. Wenn Funktionenmehr als eine Sache erledigen sind sie schwerer zu verfassen, zu testen und zu begründen.Wenn du eine Funktion abgrenzen kannst nur eine Aktion auszuführen, dann kann dieseproblemlos überarbeitet werden und dein Code liest sich sehr viel einfacher. Wenn duaus diesem Leitfaden nichts außer diese Regel ziehen kannst, wirst du vielen Entwicklerneinen Schritt voraus sein.
Schlecht:
functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}
Gut:
functionemailActiveClients(clients){clients.filter(isActiveClient).forEach(email);}functionisActiveClient(client){constclientRecord=database.lookup(client);returnclientRecord.isActive();}
Schlecht:
functionaddToDate(date,month){// ...}constdate=newDate();// Es ist schwierig zu sagen was laut Funktionsnamen hinzugefügt wirdaddToDate(date,1);
Gut:
functionaddMonthToDate(month,date){// ...}constdate=newDate();addMonthToDate(1,date);
Wenn du mehr als eine Ebene der Abstraktion hast, erledigt deine Funktionmöglicherweise zu viel. Funktionen in mehrere verschiedene Funktionenaufzuteilen sorgt für eine bessere Wiederverwendbarkeit und einfacheresTesting.
Schlecht:
functionparseBetterJSAlternative(code){constREGEXES=[// ...];conststatements=code.split(' ');consttokens=[];REGEXES.forEach((REGEX)=>{statements.forEach((statement)=>{// ...});});constast=[];tokens.forEach((token)=>{// lex...});ast.forEach((node)=>{// parse...});}
Gut:
functiontokenize(code){constREGEXES=[// ...];conststatements=code.split(' ');consttokens=[];REGEXES.forEach((REGEX)=>{statements.forEach((statement)=>{tokens.push(/* ... */);});});returntokens;}functionlexer(tokens){constast=[];tokens.forEach((token)=>{ast.push(/* ... */);});returnast;}functionparseBetterJSAlternative(code){consttokens=tokenize(code);constast=lexer(tokens);ast.forEach((node)=>{// parse...});}
Gib dein absolut bestes um doppelten Code zu vermeiden. Doppelter Code ist schlechtweil es bedeutet, dass es mehr als eine Stelle gibt um etwas anzupassen, wenn an dieserLogik etwas geändert werden soll.
Stelle dir vor du betreibst ein Restaurant und willst den Überblick über deinen Vorrat behalten:All deine Tomaten, Zwiebeln, Knoblauch, Gewürze, etc. Wenn du deinen Lagerbestand auf mehrerenListen festhälst, dann musst du all diese Listen aktualisieren wenn du einen Teller – auf demsich Tomaten befinden – servierst. Wenn du nur eine List hast, dann musst du auch nur diese eine Listeaktualisieren!
Häufig hast du doppelten Code weil du zwei oder mehr nur geringfügig unterschiedliche Dinge hast, diezwar eine Menge exakt gleich erledigen aber ihre Unterschiede zwingen dich dazu zwei oder mehr seperate Funktionenzu schreiben. Doppelten Code zu entfernen heißt Abstraktionen zu erstellen, die diese Reihe von unterschiedlichenDingen mit nur einer Funktion/Modul/Klasse handhaben können.
Diese Abstraktion gut hinzubekommen ist entscheidend und darum solltest du den SOLID-Regeln imKlassen-Kapitelfolgen. Schlechte Abstraktionen können schlimmer als doppelter Code sein, also sei vorsichtig!In diesem Sinne, wenn du eine gute Abstraktion hinbekommst - mach es! Wiederhole dich nicht selbst, ansonstenwirst du jedes Mal verschiedene Stellen anpassen müssen, wenn du eine Sache ändern möchtest.
Schlecht:
functionshowDeveloperList(developers){developers.forEach((developer)=>{constexpectedSalary=developer.calculateExpectedSalary();constexperience=developer.getExperience();constgithubLink=developer.getGithubLink();constdata={ expectedSalary, experience, githubLink};render(data);});}functionshowManagerList(managers){managers.forEach((manager)=>{constexpectedSalary=manager.calculateExpectedSalary();constexperience=manager.getExperience();constportfolio=manager.getMBAProjects();constdata={ expectedSalary, experience, portfolio};render(data);});}
Gut:
functionshowEmployeeList(employees){employees.forEach((employee)=>{constexpectedSalary=employee.calculateExpectedSalary();constexperience=employee.getExperience();constdata={ expectedSalary, experience};switch(employee.type){case'manager':data.portfolio=employee.getMBAProjects();break;case'developer':data.githubLink=employee.getGithubLink();break;}render(data);});}
Schlecht:
constmenuConfig={title:null,body:'Bar',buttonText:null,cancellable:true};functioncreateMenu(config){config.title=config.title||'Foo';config.body=config.body||'Bar';config.buttonText=config.buttonText||'Baz';config.cancellable=config.cancellable!==undefined ?config.cancellable :true;}createMenu(menuConfig);
Gut:
constmenuConfig={title:'Order',// Der User hat den 'body'-Key nicht angegebenbuttonText:'Send',cancellable:true};functioncreateMenu(config){config=Object.assign({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true},config);// config entspricht nun: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}// ...}createMenu(menuConfig);
Flags zeigen dem Anwender dass diese Funktion mehr als eine Sache macht. Funktionen solltennur eine Sache erledigen. Teile deine Funktionen auf, wenn diese beispielsweise auf Basis einesBoolean andere Pfade verwendet.
Schlecht:
functioncreateFile(name,temp){if(temp){fs.create(`./temp/${name}`);}else{fs.create(name);}}
Gut:
functioncreateFile(name){fs.create(name);}functioncreateTempFile(name){createFile(`./temp/${name}`);}
Eine Funktion erzeugt einen Nebeneffekt, wenn sie mehr macht als einen Wert entgegenzunehmenund einen oder mehrere Werte zurückzugeben. Ein Nebeneffekt könnte das Schreiben einer Datei,das modifizieren einiger globaler Variablen oder das versehentliche Übertragen deines ganzen Geldesan einen Fremden sein.
Jetzt brauchst du bei einer Gelegenheit einen Nebeneffekt. Ähnlich wie im vorangegangen Beispiel möchtestdu möglicherweise eine Datei schreiben. Fasse dies zusammen um nicht etliche Funktionen und Klassen, die aufbestimmte Datei bezogen sind zu schreiben. Habe einen Service der dies erledigt. Einen und auch nur einen.
Der Punkt ist, üblich Tücken wie beispielsweise das Teilen von State zwischen Objekten ohne jeglicheStruktur mit veränderbaren Datentypen – die überall überschrieben werden können – zu verhindern und die Stellenan denen Nebeneffekte auftreten können zusammenzufassen. Wenn du das schaffst, wirst du glücklicher als eineVielzahl der anderen Programmierer sein.
Schlecht:
// Globale Variable die von der nachfolgenden Funktion referenziert wird.// Wenn wir eine andere Funktion hätten, die diese Variable verwendet, könnte es sein, dass diese nun nicht mehr funktioniert, weil diese Variable nun ein Array ist.letname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();console.log(name);// ['Ryan', 'McDermott'];
Gut:
functionsplitIntoFirstAndLastName(name){returnname.split(' ');}constname='Ryan McDermott';constnewName=splitIntoFirstAndLastName(name);console.log(name);// 'Ryan McDermott';console.log(newName);// ['Ryan', 'McDermott'];
In JavaScript werden primitive Datentypen als Werte und Objekte/Arrays alsReferenzen übergeben. Wenn deine Funktion beispielsweise im Falle von Objektenund Arrays eine Änderung an einem Warenkorb-Array – durch das Hinzufügen einesItems – durchführt. Dann werden alle anderen Funktionen die diesescart
-Arrayverwenden von dieser Ergänzung betroffen sein. Das mag in manchen Fällen gewünschtsein. Es kann aber auch schlecht sein. Stellen wir uns folgende Situation vor:
Der User klickt auf den "Kaufen"-Button, der einepurchase
-Funktion aufruft.Diese wiederum erstellt einen Netzwerk-Request und sendet dascart
-Array zu unseremServer. Aufgrund einer schlechten Internetverbindung muss diepurchase
-Funktion diesen Request wiederholen. Was passiert nun, wenn der User in derZwischenzeit versehentlich auf den „Kaufen“-Button eines Produkts klickt, dasser eigentlich gar nicht wollte? Wenn das passiert und der Request beginnt, dannwird diesepurchase
-Funktion das versehentlich hinzugefügte Produkt senden, weildiese Funktion eine Referenz zum Warenkorb-Array hat, dass dieaddItemToCart
-Funktion– durch das Hinzufügen eines ungewollten Produkts – modifiziert hat.
Eine tolle Lösung wäre für dieaddItemToCart
-Funktion jedesmalcart
zu kopieren,zu bearbeiten und die Kopie zurückzugeben. Dies stellt sicher, dass andere Funktionendie eine Referenz zum Warenkorb besitzen, keine Möglichkeit haben Änderungen durchzuführen.
Zwei Einsprüche die zu diesem Ansatz erwähnt werden sollten:
Möglicherweise gibt es Fälle in denen du wirklich das Input-Objekt modifizieren willst.Allerdings werden diese Fälle sehr selten sein. Die meisten Dinge können so überarbeitet werden,dass keine Nebeneffekte auftreten!
Große Objekte zu kopieren kann sich hinsichtlich der Performance sehr negativ auswirken. Glücklicherweiseist das in der Praxis kein großes Problem. Es gibttolle Bibliothekendie es erlauben diese Art des Programmieransatzes schneller und nicht so speicherintensiv auszuführenwie es wäre wenn du manuell Objekte und Arrays kopierst.
Schlecht:
constaddItemToCart=(cart,item)=>{cart.push({ item,date:Date.now()});};
Gut:
constaddItemToCart=(cart,item)=>{return[...cart,{ item,date:Date.now()}];};
Den globalen Namespace zu verschmutzen ist in JavaScript eine schlechte Angewohnheit,weil du dadurch mit anderen Bibliotheken aneinandergeraten könntest und der Nutzer deinerSchnittstelle würde nichts davon mitbekommen, bis er einen Fehler bei der Veröffentlichungerhält. Stelle dir folgendes Beispiel vor: Was wenn du JavaScripts ursprüngliche Array-Methodenum eine weiterediff
-Methode – die dir die Unterschiede zwischen zwei Arrays darstellt – ergänzenmöchtest? Du könntest deine neue Funktion mitArray.prototype
schreiben. Allerdings könnte diesmit einer anderen Bibliothek aneinandergeraten, die das selbe versucht hat. Was ist, wenn dieseandere Bibliothekdiff
nur verwendet um das erste und letzte Element eines Arrays zu finden?Das ist der Grund warum es viel besser wäre die Klassen in ES2015/ES6 zu verwenden und einfach dieArray
-Globale zu ergänzen.
Schlecht:
Array.prototype.diff=functiondiff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));};
Gut:
classSuperArrayextendsArray{diff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));}}
JavaScript ist keine funktionale Sprache wie es beispielsweise Haskell ist.JavaScript hat aber eine funktionale Variante. Funktionale Sprachen sind saubererund einfacher zu testen. Ziehe diesen Stil der Programmierung vor, wenn du kannst.
Schlecht:
constprogrammerOutput=[{name:'Uncle Bobby',linesOfCode:500},{name:'Suzie Q',linesOfCode:1500},{name:'Jimmy Gosling',linesOfCode:150},{name:'Gracie Hopper',linesOfCode:1000}];lettotalOutput=0;for(leti=0;i<programmerOutput.length;i++){totalOutput+=programmerOutput[i].linesOfCode;}
Gut:
constprogrammerOutput=[{name:'Uncle Bobby',linesOfCode:500},{name:'Suzie Q',linesOfCode:1500},{name:'Jimmy Gosling',linesOfCode:150},{name:'Gracie Hopper',linesOfCode:1000}];consttotalOutput=programmerOutput.map(output=>output.linesOfCode).reduce((totalLines,lines)=>totalLines+lines);
Schlecht:
if(fsm.state==='fetching'&&isEmpty(listNode)){// ...}
Gut:
functionshouldShowSpinner(fsm,listNode){returnfsm.state==='fetching'&&isEmpty(listNode);}if(shouldShowSpinner(fsmInstance,listNodeInstance)){// ...}
Schlecht:
functionisDOMNodeNotPresent(node){// ...}if(!isDOMNodeNotPresent(node)){// ...}
Gut:
functionisDOMNodePresent(node){// ...}if(isDOMNodePresent(node)){// ...}
Das hört sich nach einer unmöglichen Aufgabe an. Menschen, die dies zum erstenMal hören, fragen sich: „Wie soll ich etwas ohne eineif
-Anweisung tun?“ DieAntwort ist, dass du Polymorphie verwenden kannst um in vielen Fällen die selbeAufgabe zu erledigen. Die zweite Frage ist dann oft: „Gut, das ist toll aberwarum sollte ich das wollen?“ Die Antwort ist ein Clean-Code-Konzept, das wir bereitskennengelernt haben: Eine Funktion sollte lediglich eine Aufgabe erledigen. Wenn du Klassenund Funktionen mitif
-Anweisungen schreibst, teilst du dem Nutzer deines Codes mit, dassdeine Funktion mehr als eine Sache erledigt. Vergiss nicht, mache nur eine Sache.
Schlecht:
classAirplane{// ...getCruisingAltitude(){switch(this.type){case'777':returnthis.getMaxAltitude()-this.getPassengerCount();case'Air Force One':returnthis.getMaxAltitude();case'Cessna':returnthis.getMaxAltitude()-this.getFuelExpenditure();}}}
Gut:
classAirplane{// ...}classBoeing777extendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getPassengerCount();}}classAirForceOneextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude();}}classCessnaextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getFuelExpenditure();}}
JavaScript besitzt keine Datentypen. Das bedeutet, dass deine Funktionen jegliche Datentypenals Argument entgegennehmen. Gelegentlich wirst du dich mit dieser Freiheit unwohl fühlenund es wird dich dazu verleiten in deinen Funktionen Datentypen zu prüfen. Es gibt vieleMöglichkeiten, dies zu vermeiden. Der erste Schritt ist, auf einheitliche Schnittstellen zu achten.
Schlecht:
functiontravelToTexas(vehicle){if(vehicleinstanceofBicycle){vehicle.pedal(this.currentLocation,newLocation('texas'));}elseif(vehicleinstanceofCar){vehicle.drive(this.currentLocation,newLocation('texas'));}}
Gut:
functiontravelToTexas(vehicle){vehicle.move(this.currentLocation,newLocation('texas'));}
Wenn du mit grundlegenden einfachen Werten wie Strings, Integer und Arrays arbeitest,du Polymorphie nicht anwenden kannst und weiterhin das Bedüfniss verspürst, Datentypenzu prüfen. Dann solltest du dir überlegen TypeScript zu verwenden. TypeScript ist eineausgezeichnet Alternative zu normalem JavaScript, weil es statische Typen auf Grundlageder normalen JavaScript-Syntax anbietet. Das Problem mit dem manuellen Prüfen von Typenin JavaScript ist, dass dieser Mehraufwand um diese "falsche" Typensicherheitzu erreichen die verlorene Lesbarkeit nicht wieder gut macht. Halte dein JavaScriptsauber, schreibe gute Tests und habe gute Code-Reviews. Anderenfalls erledige dies mitTypeScript (was wie gesagt, eine ausgezeichnete Alternative ist!).
Schlecht:
functioncombine(val1,val2){if(typeofval1==='number'&&typeofval2==='number'||typeofval1==='string'&&typeofval2==='string'){returnval1+val2;}thrownewError('Must be of type String or Number');}
Gut:
functioncombine(val1,val2){returnval1+val2;}
Moderne Browser optimieren eine Menge während der Laufzeit. Du verschwendest häufigdeine Zeit, wenn du Dinge optimierst.Es gibt gute Ressourcenum herauszufinden, wo die Optimierungen des Browsers mangelhaft sind. Fokusiere dichderweil auf diese Dinge bis sie behoben sind – wenn sie behoben werden können.
Schlecht:
// In alten Browsern würde jeder Durchlauf mit einem ungespeicherten `list.length` speicherintensiv sein,// weil `list.length` in jedem Durchlauf neu berechnet wird. In modernen Browsern ist das optimiert.for(leti=0,len=list.length;i<len;i++){// ...}
Gut:
for(leti=0;i<list.length;i++){// ...}
Veralteter Code ist genau so schlecht wie doppelter Code. Es gibt keinen Grund diesenin deiner Code-Basis zu lassen. Wenn etwas nicht aufgerufen wird, werde es los! Eswird weiterhin in deiner Versionsverwaltung stehen, falls du es nochmal benötigst.
Schlecht:
functionoldRequestModule(url){// ...}functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker('apples',req,'www.inventory-awesome.io');
Gut:
functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker('apples',req,'www.inventory-awesome.io');
Getter und Setter zu verwenden, um Zugriff auf Daten eines Objekts zu bekommen, ist umeiniges besser als einfach nach der Eigenschaft eines Objekts zu schauen. „Warum?“ fragstdu dich vielleicht. Nun, hier ist eine ungeordnete Liste mit Gründen:
- Wenn du mehr als nur das Erhalten eines Objekt-Eigenschaft erledigen möchtest, muss dunicht alle Zugrifssmethoden in deinem Code anpassen.
- Macht es einfach, innerhalb eines
set
-Aufrufs eine Validierung hinzuzufügen. - Verschachtelt die interne Darstellung.
- Macht es einfach, Logging und Fehlerbehandlung hinzuzufügen, wenn Daten gespeichertoder abgerufen werden.
- Du kannst die Eigenschaften eines Objekts mittels lazy-loading laden. Beispielsweise voneinem Server.
Schlecht:
functionmakeBankAccount(){// ...return{balance:0,// ...};}constaccount=makeBankAccount();account.balance=100;
Gut:
functionmakeBankAccount(){// Diese Variable ist privatletbalance=0;// Ein "Getter", wird duch das unten zurückgegebene Objekt public gemachtfunctiongetBalance(){returnbalance;}// Ein "Setter", wird duch das unten zurückgegebene Objekt public gemachtfunctionsetBalance(amount){// ... validieren bevor der Kontostand aktualisiert wirdbalance=amount;}return{// ... getBalance, setBalance,};}constaccount=makeBankAccount();account.setBalance(100);
Dies kann durch Closures erreicht werden (für ES5 und darunter).
Schlecht:
constEmployee=function(name){this.name=name;};Employee.prototype.getName=functiongetName(){returnthis.name;};constemployee=newEmployee('John Doe');console.log(`Employee name:${employee.getName()}`);// Employee name: John Doedeleteemployee.name;console.log(`Employee name:${employee.getName()}`);// Employee name: undefined
Gut:
functionmakeEmployee(name){return{getName(){returnname;},};}constemployee=makeEmployee('John Doe');console.log(`Employee name:${employee.getName()}`);// Employee name: John Doedeleteemployee.name;console.log(`Employee name:${employee.getName()}`);// Employee name: John Doe
Es ist sehr schwer, lesbare Klassen-Vererbungen, Konstruktionen und Methoden-Definitionen mitklassischen ES5-Klassen zu schreiben. Wenn du Vererbungen benötigst (und sei dir bewusst, dassdu es möglicherweise doch nicht brauchst), dann bevorzuge Klassen. Verwende schlankeFunktionen bis du dir sicher bist, größere und komplexere Objekte zu benötigen.
Schlecht:
constAnimal=function(age){if(!(thisinstanceofAnimal)){thrownewError('Instantiate Animal with `new`');}this.age=age;};Animal.prototype.move=functionmove(){};constMammal=function(age,furColor){if(!(thisinstanceofMammal)){thrownewError('Instantiate Mammal with `new`');}Animal.call(this,age);this.furColor=furColor;};Mammal.prototype=Object.create(Animal.prototype);Mammal.prototype.constructor=Mammal;Mammal.prototype.liveBirth=functionliveBirth(){};constHuman=function(age,furColor,languageSpoken){if(!(thisinstanceofHuman)){thrownewError('Instantiate Human with `new`');}Mammal.call(this,age,furColor);this.languageSpoken=languageSpoken;};Human.prototype=Object.create(Mammal.prototype);Human.prototype.constructor=Human;Human.prototype.speak=functionspeak(){};
Gut:
classAnimal{constructor(age){this.age=age;}move(){/* ... */}}classMammalextendsAnimal{constructor(age,furColor){super(age);this.furColor=furColor;}liveBirth(){/* ... */}}classHumanextendsMammal{constructor(age,furColor,languageSpoken){super(age,furColor);this.languageSpoken=languageSpoken;}speak(){/* ... */}}
Dieses Entwurfsmuster ist in JavaScript sehr nützlich und du wirst es in vielenBibliotheken, wie beispielsweise in jQuery und Lodash finden. Es erlaubt deinemCode mehr aussagekräftig und weniger langatmig zu sein. Aus diesem Grund würdeich sagen, verwende die Methoden-Verkettung und schaue dir an, wie sauber deinCode sein wird. Gib in deinen Klassen-Methoden am Ende jeder Funktioneinfachthis
zurück und du wirst weitere Methoden verketten können.
Schlecht:
classCar{constructor(make,model,color){this.make=make;this.model=model;this.color=color;}setMake(make){this.make=make;}setModel(model){this.model=model;}setColor(color){this.color=color;}save(){console.log(this.make,this.model,this.color);}}constcar=newCar('Ford','F-150','red');car.setColor('pink');car.save();
Gut:
classCar{constructor(make,model,color){this.make=make;this.model=model;this.color=color;}setMake(make){this.make=make;// Anmerkung: Gib „this“ für die Verkettung zurückreturnthis;}setModel(model){this.model=model;// Anmerkung: Gib „this“ für die Verkettung zurückreturnthis;}setColor(color){this.color=color;// Anmerkung: Gib „this“ für die Verkettung zurückreturnthis;}save(){console.log(this.make,this.model,this.color);// Anmerkung: Gib „this“ für die Verkettung zurückreturnthis;}}constcar=newCar('Ford','F-150','red').setColor('pink').save();
Wie bekanntermaßen inDesign Patterns von der Viererbande (Gang of Four)gesagt wurde, sollst du die Komposition der Vererbung vorziehen, wenn du kannst. Es gibt viele gute Gründe die Vererbung zu verwendenund eine Menge guter Gründe warum du die Komposition verwenden sollst. Der Kernpunkt dieses Denkansatzes ist, dass wenn dein Gehirninstinktiv für die Vererbung ist, du versuchen sollst darüber nachzudenken, ob die Komposition dein Problem nicht besser lösen wird.In manchen Fällen kann es das.
Du fragst dich vielleicht, wann du Vererbungen verwenden sollst? Es ist von deinemvorliegenden Problem anhängig. Hier ist eine Liste mit Gründen die mehr für die Vererbungals für die Komposition sprechen:
- Deine Vererbung repräsentiert eine „ist-ein“-Beziehung und keine „hat-ein“-Beziehung(Mensch->Tier vs. User->UserDetails).
- Du kannst Code von der Elternklasse verwenden (Menschen können sich wie alle Tiere bewegen).
- Du willst globale Änderungen – durch das Ändern der Elternklasse – auf abgeleitete Klassen übertragen.(Den Kalorienverbrauch aller Tiere ändern wenn sie sich bewegen).
Schlecht:
classEmployee{constructor(name,email){this.name=name;this.email=email;}// ...}// Schlecht, weil Angestellte Steuerdaten "besitzen". EmployeeTaxData ist keine Art eines AngestelltenclassEmployeeTaxDataextendsEmployee{constructor(ssn,salary){super();this.ssn=ssn;this.salary=salary;}// ...}
Gut:
classEmployeeTaxData{constructor(ssn,salary){this.ssn=ssn;this.salary=salary;}// ...}classEmployee{constructor(name,email){this.name=name;this.email=email;}setTaxData(ssn,salary){this.taxData=newEmployeeTaxData(ssn,salary);}// ...}
Wie in Clean Code bereits genannt: „Es sollte niemals mehr als einen Grund geben,eine Klasse zu ändern“. Es ist verlockend, eine Klasse bis obenhin mit Funktionalitätvoll zu stopfen, wie wenn du nur einen Koffer für deinen Flug mitnehmen kannst.Das Problem damit ist einfach, dass deine Klasse konzeptionell nicht unabhängig istund es viele Gründe geben wird, deine Klasse zu ändern. Die Anzahl der Änderungenan einer Klasse zu reduzieren ist wichtig. Es ist wichtig, weil zu viel Funktionalitäteiner Klasse bedeutet, dass es schwierig ist zu verstehen wie die Änderung andereabhängige Module in deinem Code beeinflusst.
Schlecht:
classUserSettings{constructor(user){this.user=user;}changeSettings(settings){if(this.verifyCredentials()){// ...}}verifyCredentials(){// ...}}
Gut:
classUserAuth{constructor(user){this.user=user;}verifyCredentials(){// ...}}classUserSettings{constructor(user){this.user=user;this.auth=newUserAuth(user);}changeSettings(settings){if(this.auth.verifyCredentials()){// ...}}}
Wie von Bertrand Meyer angegeben: „Software-Einheiten (Klassen, Module, Funktionen,etc.) sollten offen für Erweiterungen aber geschlossen für Modifizierungen sein.“Was soll das bedeuten? Diese Prinzip bedeutet, dass du es Anwendern ermöglichensollst neue Funktionalitäten hinzuzufügen ohne bestehenden Code ändern zu müssen.
Schlecht:
classAjaxAdapterextendsAdapter{constructor(){super();this.name='ajaxAdapter';}}classNodeAdapterextendsAdapter{constructor(){super();this.name='nodeAdapter';}}classHttpRequester{constructor(adapter){this.adapter=adapter;}fetch(url){if(this.adapter.name==='ajaxAdapter'){returnmakeAjaxCall(url).then((response)=>{// transformiere die Response und gib sie zurück});}elseif(this.adapter.name==='httpNodeAdapter'){returnmakeHttpCall(url).then((response)=>{// transformiere die Response und gib sie zurück});}}}functionmakeAjaxCall(url){// führe den Request aus und gib eine Promise zurück}functionmakeHttpCall(url){// führe den Request aus und gib eine Promise zurück}
Gut:
classAjaxAdapterextendsAdapter{constructor(){super();this.name='ajaxAdapter';}request(url){// führe den Request aus und gib eine Promise zurück}}classNodeAdapterextendsAdapter{constructor(){super();this.name='nodeAdapter';}request(url){// führe den Request aus und gib eine Promise zurück}}classHttpRequester{constructor(adapter){this.adapter=adapter;}fetch(url){returnthis.adapter.request(url).then((response)=>{// transformiere die Response und gib sie zurück});}}
Das ist ein beängstigender Begriff für ein sehr einfaches Konzept. Es istformell definiert als: „Wenn S ein Subtyp von T ist, dann können Objekte vom TypT möglicherweise mit Objekten vom Typ S ersetzt werden. (D.h., Objekte vom TypS ersetzen möglicherweise Objekte vom Typ T) ohne irgendwelche gewünschtenEigenschaften des Programms (Korrektheit, Durchführen der Aufgabe, etc.) zuverändern.“. Das ist eine noch viel erschreckendere Definition.
Die beste Erklärung für dieses Konzept ist, wenn du eine Eltern- und Kindklassehast und die Basisklasse abwechselnd mit der Kindklasse verwendet werden kann ohnefalsche Ergebnisse zu bekommen. Möglicherweise ist das immer noch verwirrend. Alsoschauen wir uns das klassische Quadrat-Rechteck-Beispiel an. Mathematisch gesehenist ein Quadrat ein Rechteck. Wenn du es aber mit einer „ist-ein“-Beziehung durchVererbung darstellen willst, kommst du schnell in Schwierigkeiten.
Schlecht:
classRectangle{constructor(){this.width=0;this.height=0;}setColor(color){// ...}render(area){// ...}setWidth(width){this.width=width;}setHeight(height){this.height=height;}getArea(){returnthis.width*this.height;}}classSquareextendsRectangle{setWidth(width){this.width=width;this.height=width;}setHeight(height){this.width=height;this.height=height;}}functionrenderLargeRectangles(rectangles){rectangles.forEach((rectangle)=>{rectangle.setWidth(4);rectangle.setHeight(5);constarea=rectangle.getArea();// SCHLECHT: Es wird 25 für ein Quadrat zurückgeben. Sollte aber 20 sein.rectangle.render(area);});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);
Gut:
classShape{setColor(color){// ...}render(area){// ...}}classRectangleextendsShape{constructor(width,height){super();this.width=width;this.height=height;}getArea(){returnthis.width*this.height;}}classSquareextendsShape{constructor(length){super();this.length=length;}getArea(){returnthis.length*this.length;}}functionrenderLargeShapes(shapes){shapes.forEach((shape)=>{constarea=shape.getArea();shape.render(area);});}constshapes=[newRectangle(4,5),newRectangle(4,5),newSquare(5)];renderLargeShapes(shapes);
JavaScript besitzt keine Interfaces. Dieses Prinzip ist also nicht so strenganwendbar wie die anderen. Wie auch immer, es ist wichtig und bedeutend trotzJavaScripts fehlendem Typen-System.
ISP heißt, dass „Anwender nicht dazu gezwungen werden sollten, sich auf Interfaceszu stützen die sie nicht verwenden“. Interfaces sind wegen des Duck-Typings indirekteVerträge.
Gute Beispiele, die dieses Prinzip in JavaScript anschaulich demonstrieren sind Klassen,die umfangreiche Konfigurationsobjekte benötigen. Den Anwender nicht dazu zwingen, unzähligeOptionen einzurichten ist vorteilhaft, weil sie in den meisten Fällen nicht alle Konfigurationsmöglichkeitenbrauchen. Diese optional anzubieten verhindert „üppige Interfaces“.
Schlecht:
classDOMTraverser{constructor(settings){this.settings=settings;this.setup();}setup(){this.rootNode=this.settings.rootNode;this.animationModule.setup();}traverse(){// ...}}const$=newDOMTraverser({rootNode:document.getElementsByTagName('body'),animationModule(){}// In den meisten Fällen werden wir nicht animieren müssen.// ...});
Gut:
classDOMTraverser{constructor(settings){this.settings=settings;this.options=settings.options;this.setup();}setup(){this.rootNode=this.settings.rootNode;this.setupOptions();}setupOptions(){if(this.options.animationModule){// ...}}traverse(){// ...}}const$=newDOMTraverser({rootNode:document.getElementsByTagName('body'),options:{animationModule(){}}});
Dieses Prinzip steht für zwei essentielle Dinge:
- übergeordnete Module sollten nicht von untergeordneten Modulen abhängigsein. Beide sollten von Abstraktionen abhängig sein.
- Abstraktionen sollten nicht von Details abhängig sein. Details solltenvon Abstraktionen abhängig sein.
Das kann im ersten Moment schwer zu verstehen sein. Aber wenn du bereits mit Angular.jsgearbeitet hast, hast du eine Implementierung dieses Prinzips in Form derDependency Injection (DI) bereits kennengelernt. Obwohl diese keine komplettidentischen Konzepte sind, sorgt DIP dafür, dass übergeordnete Module nichts vonden Details ihrer untergeordneten Module wissen. Das kann durch DI erreicht werden.Ein großer Vorteil davon ist, dass es die Verknüpfungen zwischen Modulen reduziert.Verknüpfungen sind ein sehr schlechtes Entwurfsmuster weil dein Code dadurchschwieriger zu überarbeiten ist.
Wie schon davor angemerkt, besitzt JavaScript keine Interfaces. Die Abstraktionensind also abhängig von indirekten Vereinbarungen. Das betrifft die Methoden undEigenschaften die ein Objekt/Klasse anderen Objekten/Klassen zur Verfügung stellt.In dem untenstehenden Beispiel ist die indirekte Vereinbarung, dass jedes Request-Modulfür einenInventoryTracker
einerequestItems
-Methode besitzt.
Schlecht:
classInventoryRequester{constructor(){this.REQ_METHODS=['HTTP'];}requestItem(item){// ...}}classInventoryTracker{constructor(items){this.items=items;// SCHLECHT: Wir haben eine Abhänigkeit zu einer spezifischen Request-Implementierung geschaffen.// Wir hätten requestItems von einer Request-Methode: `request` abhängig machen sollenthis.requester=newInventoryRequester();}requestItems(){this.items.forEach((item)=>{this.requester.requestItem(item);});}}constinventoryTracker=newInventoryTracker(['apples','bananas']);inventoryTracker.requestItems();
Gut:
classInventoryTracker{constructor(items,requester){this.items=items;this.requester=requester;}requestItems(){this.items.forEach((item)=>{this.requester.requestItem(item);});}}classInventoryRequesterV1{constructor(){this.REQ_METHODS=['HTTP'];}requestItem(item){// ...}}classInventoryRequesterV2{constructor(){this.REQ_METHODS=['WS'];}requestItem(item){// ...}}// Durch das externe Konstruieren und das Injektieren unserer Abhängigkeiten könnten wir unser// Request-Modul einfach durch ein raffiniertes neues – das WebSockets verwendet – austauschen.constinventoryTracker=newInventoryTracker(['apples','bananas'],newInventoryRequesterV2());inventoryTracker.requestItems();
Testing ist wichtiger als das Veröffentlichen von Code. Wenn du keine oder eine unzureichendeAnzahl an Tests hast, dann wirst du jedes Mal unsicher sein, ob du nicht irgendetwaskaputt gemacht hast. Die Entscheidung welche Anzahl angemessen ist, entscheidet deinTeam. Aber eine 100%ige Abdeckung (alle Anweisungen und Branches) wird dir ein sehrhohes Vertrauen und Seelenfrieden geben. Das bedeutet, dass du als Ergänzung zu einemguten Testing-Framework auch eingutes Coverage-Toolverwenden musst.
Es gibt keine Ausrede um keine Tests zu schreiben. Es gibt [zahlreiche gute JS-Test-Frameworks](http://jstherightway.org/#testing-tools). Also finde eines das dein Team bevorzugt.Wenn du eines gefunden hast, dass für dein Team funktioniert, dann ziele darauf ab, immereinen Test für jedes neue Feature/Modul zu schreiben. Wenn deine bevorzugte Arbeitsweise dietestgetriebene Entwicklung ist, umso besser. Aber die Hauptsache ist, sicher zu stellen, dassdu die Abdeckungsziele erreichst bevor ein Feature veröffentlicht oder vorhandener Codeüberarbeitet wird.
Schlecht:
importassertfrom'assert';describe('MakeMomentJSGreatAgain',()=>{it('handles date boundaries',()=>{letdate;date=newMakeMomentJSGreatAgain('1/1/2015');date.addDays(30);assert.equal('1/31/2015',date);date=newMakeMomentJSGreatAgain('2/1/2016');date.addDays(28);assert.equal('02/29/2016',date);date=newMakeMomentJSGreatAgain('2/1/2015');date.addDays(28);assert.equal('03/01/2015',date);});});
Gut:
importassertfrom'assert';describe('MakeMomentJSGreatAgain',()=>{it('handles 30-day months',()=>{constdate=newMakeMomentJSGreatAgain('1/1/2015');date.addDays(30);assert.equal('1/31/2015',date);});it('handles leap year',()=>{constdate=newMakeMomentJSGreatAgain('2/1/2016');date.addDays(28);assert.equal('02/29/2016',date);});it('handles non-leap year',()=>{constdate=newMakeMomentJSGreatAgain('2/1/2015');date.addDays(28);assert.equal('03/01/2015',date);});});
Callbacks sind keine saubere Lösung und sie verursachen eine übermäßige Verschachtelung.Mit ES2015/ES6 sind Promises ein integrierter globaler Typ. Verwende sie!
Schlecht:
import{get}from'request';import{writeFile}from'fs';get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin',(requestErr,response)=>{if(requestErr){console.error(requestErr);}else{writeFile('article.html',response.body,(writeErr)=>{if(writeErr){console.error(writeErr);}else{console.log('File written');}});}});
Gut:
import{get}from'request';import{writeFile}from'fs';get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin').then((response)=>{returnwriteFile('article.html',response);}).then(()=>{console.log('File written');}).catch((err)=>{console.error(err);});
Promises sind eine sehr saubere Alternative zu Callbacks. ES2017/ES8 beinhaltet allerdingsasync und await, die eine viel sauberere Lösung bieten. Alles was du benötigst ist eine Funktion,der dasasync
-Keyword vorangestellt ist. Anschließend kannst du deine Logik imperativ ohne eineKette vonthen
-Funktionen schreiben. Verwende dies, wenn du die Vorteile von ES2017/ES8 jetztschon nutzen kannst!
Schlecht:
import{get}from'request-promise';import{writeFile}from'fs-promise';get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin').then((response)=>{returnwriteFile('article.html',response);}).then(()=>{console.log('File written');}).catch((err)=>{console.error(err);});
Gut:
import{get}from'request-promise';import{writeFile}from'fs-promise';asyncfunctiongetCleanCodeArticle(){try{constresponse=awaitget('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');awaitwriteFile('article.html',response);console.log('File written');}catch(err){console.error(err);}}
Ausgegebene Fehler sind eine gute Sache! Das heißt, dass die Laufzeitumgebungerfolgreich erkannt hat, dass etwas in deinem Programm schiefgegangen ist und lässtdich das wissen indem deine Funktion im aktuellen Stack beendet wurde, den Prozesszerstört hat (in Node) und dich in der Konsole mit einem Stacktrace darüber benachrichtigt.
Mit einem abgefangenem Fehler nichts anzufangen, wird dir niemals die Möglichkeitgeben auf diesen zu reagieren oder ihn zu beheben. Den Fehler in der Konsole auszugeben(console.log
) ist nicht viel besser. Er wird oft in den unzähligen Dingen, diein der Konsole ausgegeben werden untergehen. Wenn du ein Stückchen Code in einertry/catch
-Anweisung verschachtelst, heißt dass, das du davon ausgehst, dass andieser Stelle ein Fehler auftreten kann und daher solltest du einen Plan haben odereine Anweisung schreiben, wenn dieser Fehler erscheint.
Schlecht:
try{functionThatMightThrow();}catch(error){console.log(error);}
Gut:
try{functionThatMightThrow();}catch(error){// Eine Option (Erweckt mehr Aufmerksamkeit als console.log):console.error(error);// Eine andere Möglichkeit:notifyUserOfError(error);// Eine andere Möglichkeit:reportErrorToService(error);// ODER wende alle drei an!}
Aus dem selben Grund, weshalb du keine Fehler vontry/catch
-Anweisungenignorieren solltest.
Schlecht:
getdata().then((data)=>{functionThatMightThrow(data);}).catch((error)=>{console.log(error);});
Gut:
getdata().then((data)=>{functionThatMightThrow(data);}).catch((error)=>{// Eine Option (Erweckt mehr Aufmerksamkeit als console.log):console.error(error);// Eine andere Möglichkeit:notifyUserOfError(error);// Eine andere Möglichkeit:reportErrorToService(error);// ODER wende alle drei an!});
Formatierungen sind subjektiv. Wie bei vielen anderen Regeln hier gibt es keinRichtig oder Falsch dem du folgen musst. Der Punkt ist, STREITE NICHT wegenFormatierungen. Es gibtunzählige Tools diedies automatisieren. Verwende eines! Es ist für Entwickler eine Verschwendungvon Zeit und Geld darüber zu streiten.
Für Dinge die nicht in den Bereich der automatischen Formatierung (Einrückungen,Tabs vs. Leerzeichen, doppelte vs. einfache Anführungszeichen, etc.) fallen schauehier für ein paar Ratschläge.
JavaScript hat keine Typen. Die Schreibweise deiner Variablen und Funktionensagt viel über diese aus. Dieses Regeln sind subjektiv, dein Team kann wählenwas sie bevorzugen. Die Hauptsache ist, egal was ihr wählt, wendet es konsequent an.
Schlecht:
constDAYS_IN_WEEK=7;constdaysInMonth=30;constsongs=['Back In Black','Stairway to Heaven','Hey Jude'];constArtists=['ACDC','Led Zeppelin','The Beatles'];functioneraseDatabase(){}functionrestore_database(){}classanimal{}classAlpaca{}
Gut:
constDAYS_IN_WEEK=7;constDAYS_IN_MONTH=30;constSONGS=['Back In Black','Stairway to Heaven','Hey Jude'];constARTISTS=['ACDC','Led Zeppelin','The Beatles'];functioneraseDatabase(){}functionrestoreDatabase(){}classAnimal{}classAlpaca{}
Wenn eine Funktion eine andere aufruft, halte diese Funktionen im Codevertikal nahe beieinander. Behalte idealerweise die Funktion die von einer anderenFunktion aufgerufen wird genau über dieser. Wir neigen dazu Code von oben nach untenwie eine Zeitung zu lesen. Aus diesem Grund sorge dafür, dass dein Code auf diese Weiselesbar ist.
Schlecht:
classPerformanceReview{constructor(employee){this.employee=employee;}lookupPeers(){returndb.lookup(this.employee,'peers');}lookupManager(){returndb.lookup(this.employee,'manager');}getPeerReviews(){constpeers=this.lookupPeers();// ...}perfReview(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();}getManagerReview(){constmanager=this.lookupManager();}getSelfReview(){// ...}}constreview=newPerformanceReview(employee);review.perfReview();
Gut:
classPerformanceReview{constructor(employee){this.employee=employee;}perfReview(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();}getPeerReviews(){constpeers=this.lookupPeers();// ...}lookupPeers(){returndb.lookup(this.employee,'peers');}getManagerReview(){constmanager=this.lookupManager();}lookupManager(){returndb.lookup(this.employee,'manager');}getSelfReview(){// ...}}constreview=newPerformanceReview(employee);review.perfReview();
Kommentare sind eine Entschuldigung, keine Anforderung. Guter Code dokumentiertsichmeistens selbst.
Schlecht:
functionhashIt(data){// The hashlethash=0;// Length of stringconstlength=data.length;// Loop through every character in datafor(leti=0;i<length;i++){// Get character code.constchar=data.charCodeAt(i);// Make the hashhash=((hash<<5)-hash)+char;// Convert to 32-bit integerhash&=hash;}}
Gut:
functionhashIt(data){lethash=0;constlength=data.length;for(leti=0;i<length;i++){constchar=data.charCodeAt(i);hash=((hash<<5)-hash)+char;// Zu einem 32-bit Integer konvertierenhash&=hash;}}
Versionsverwaltungen existieren aus einem Grund. Lasse alten Code in derHistory deines Repositories.
Schlecht:
doStuff();// doOtherStuff();// doSomeMoreStuff();// doSoMuchStuff();
Gut:
doStuff();
Denke daran, verwende eine Versionsverwaltung! Veralteter Code, auskommentierterCode und insbesondere Journal-Kommentare werden nicht benötigt. Verwendegit log
um vergangen Änderungen einzusehen.
Schlecht:
/** * 2016-12-20: Removed monads, didn't understand them (RM) * 2016-10-01: Improved using special monads (JP) * 2016-02-03: Removed type-checking (LI) * 2015-03-14: Added combine with type-checking (JR) */functioncombine(a,b){returna+b;}
Gut:
functioncombine(a,b){returna+b;}
Positionsmarker sorgen meistens für einen gestörten Lesefluss. Lasse Funktionenund Variablen in der korrekten Einrückung und Formatierung stehen. Diese werdendeinem Code ausreichend visuelle Struktur geben.
Schlecht:
////////////////////////////////////////////////////////////////////////////////// Scope Model Instantiation////////////////////////////////////////////////////////////////////////////////$scope.model={menu:'foo',nav:'bar'};////////////////////////////////////////////////////////////////////////////////// Action setup////////////////////////////////////////////////////////////////////////////////constactions=function(){// ...};
Gut:
$scope.model={menu:'foo',nav:'bar'};constactions=function(){// ...};
Dieser Leitfaden ist in den folgenden Sprachen verfügbar:
Brasilianisches Portugiesisch:fesnt/clean-code-javascript
Spanisch:andersontr15/clean-code-javascript
Englisch:ryanmcdermott/clean-code-javascript
Chinesisch:
Koreanisch:qkraudghgh/clean-code-javascript-ko
Polnisch:greg-dev/clean-code-javascript-pl
Russisch:
Vietnamesisch:hienvd/clean-code-javascript/
Japanisch:mitsuruog/clean-code-javascript/
Indonesisch:andirkh/clean-code-javascript/
About
🛁 Clean Code-Konzepte adaptiert für JavaScript.
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.