- Notifications
You must be signed in to change notification settings - Fork2
🛁 Clean Code principi prilagođeni za JavaScript
License
doskovicmilos/clean-code-javascript
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
- Uvod
- Promenljive
- Funkcije
- Objekti i strukture podataka
- Klase
- SOLID
- Testiranje
- Asihronost
- Rad sa greškama
- Formatiranje
- Komentari
- Prevod
Principi programiranja, iz knjige Roberta C. MartinaClean Code, prilagođeni za JavaScript. Ovo nije style guide. Ovo je vodič za pisanječitljivog, ponovno upotrebljivog, i održivog programa u JavaScript-u.
Ne mora se svaki pricip koji je ovde opisan poštovati. Ovo su smernice i ništa više, ali smernice koje su autoriClean Code napisali tokom dugogodišnjeg iskustva.
Zanat programiranja star je više od 50 godina, a mi još uvek mnogo učimo. Kada softverska arhitektura bude stara kao i sama arhitektura, možda ćemo tada imati strožija pravila koja će biti neophodna. Za sada neka ove smernice služe kao kamen temeljac uz pomoć koga ćete oceniti kvalitet JavaScript koda koji pišete.
Još nešto: poznavanje ovih principa neće vas odmah učiniti boljim programerom, a pridržavanje ovih principa dugi niz godina ne znači da nećete pogrešiti. Svaki komad koda započinje kao grubi nacrt, kao i komad mokre gline koji poprima svoj konačan oblik. Nedostatke ćemo ispraviti kada sa kolegama pregledamo kod. Ne udarajte po sebi zbog prvih nacrta kojima su potrebna poboljšanja. Umesto toga udrite po kodu! :)
Loše:
constyyyymmdstr=moment().format("YYYY/MM/DD");
Dobro:
constcurrentDate=moment().format("YYYY/MM/DD");
Loše:
getUserInfo();getClientData();getCustomerRecord();
Dobro:
getUser();
Pročitaćemo više koda nego što ćemo ga ikada napisati. Važno je da kod koji mi pišemo bude čitljiv i da ga je lako pretražiti. Alati kao što subuddy.js iESLint nam mogu pomoći u identifikovanju neimenovanih konstanti.
Loše:
// What the heck is 86400000 for?setTimeout(blastOff,86400000);
Dobro:
// Declare them as capitalized named constants.constMILLISECONDS_IN_A_DAY=86_400_000;setTimeout(blastOff,MILLISECONDS_IN_A_DAY);
Loše:
constaddress="One Infinite Loop, Cupertino 95014";constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;saveCityZipCode(address.match(cityZipCodeRegex)[1],address.match(cityZipCodeRegex)[2]);
Dobro:
constaddress="One Infinite Loop, Cupertino 95014";constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;const[_,city,zipCode]=address.match(cityZipCodeRegex)||[];saveCityZipCode(city,zipCode);
Eksplicitno je bolje od implicitnog. Čitaoci koda ne bi trebalo mentalno da prevode imena u druga imena koja već znaju. To je problem sa imenima promeljivih sa jednim slovom.
Loše:
constlocations=["Austin","New York","San Francisco"];locations.forEach(l=>{doStuff();doSomeOtherStuff();// ...// ...// ...// Wait, what is `l` for again?dispatch(l);});
Dobro:
constlocations=["Austin","New York","San Francisco"];locations.forEach(location=>{doStuff();doSomeOtherStuff();// ...// ...// ...dispatch(location);});
Ako vam ime klase / objekta nešto govori, nemote to ponavljati u imenama promenljivih.
Loše:
constCar={carMake:"Honda",carModel:"Accord",carColor:"Blue"};functionpaintCar(car){car.carColor="Red";}
Dobro:
constCar={make:"Honda",model:"Accord",color:"Blue"};functionpaintCar(car){car.color="Red";}
Loše:
functioncreateMicrobrewery(name){constbreweryName=name||"Hipster Brew Co.";// ...}
Dobro:
functioncreateMicrobrewery(name="Hipster Brew Co."){// ...}
Ograničavanje broja argumenata za funkciju je od velike važnosti jer olakšava testiranje funkcije. Imati više od tri argumenta dovodi do velikog broja kombinacija pri testiranju u kojem moramo da ponovimo veliki broj različitih slučajeva za svaki pojedinačni argument.
Idealan broj argumenata za funkciju je nula. Zatim jedan i dva. Tri argumenta treba izbegavati gde god je moguće. Kada se čini da je za funkciju potrebno više od dva ili tri argumenata, onda bi neki od njih trebalo da budu zamotani u objekat.
Da biste učinili očiglednim koje argumente funkcija očekuje, možete koristiti sintaksu destruktuiranja ES2015/ES6.
Loše:
functioncreateMenu(title,body,buttonText,cancellable){// ...}createMenu("Foo","Bar","Baz",true);
Dobro:
functioncreateMenu({ title, body, buttonText, cancellable}){// ...}createMenu({title:"Foo",body:"Bar",buttonText:"Baz",cancellable:true});
Ovo je ubedljivo najvažnije pravilo u razvoju softvera. Kada funkcije rade više stvari, teže ih je kombinovati, testirati i razumeti. Kada funkciju svedemo samo na jednu radnju, mnogo je lakše refaktorizati je i kod postaje mnogo čitljiviji.
Loše:
functionemailClients(clients){clients.forEach(client=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}
Dobro:
functionemailActiveClients(clients){clients.filter(isActiveClient).forEach(email);}functionisActiveClient(client){constclientRecord=database.lookup(client);returnclientRecord.isActive();}
Loše:
functionaddToDate(date,month){// ...}constdate=newDate();// It's hard to tell from the function name what is addedaddToDate(date,1);
Dobro:
functionaddMonthToDate(month,date){// ...}constdate=newDate();addMonthToDate(1,date);
Ako funkcija ima više od jednog nivoa apstrakcije, ima tendenciju da radi previše. Odvajanjem takvih funkcija dovodi do ponovne upotrebe i lakšeg testiranja.
Loše:
functionparseBetterJSAlternative(code){constREGEXES=[// ...];conststatements=code.split(" ");consttokens=[];REGEXES.forEach(REGEX=>{statements.forEach(statement=>{// ...});});constast=[];tokens.forEach(token=>{// lex...});ast.forEach(node=>{// parse...});}
Dobro:
functionparseBetterJSAlternative(code){consttokens=tokenize(code);constsyntaxTree=parse(tokens);syntaxTree.forEach(node=>{// parse...});}functiontokenize(code){constREGEXES=[// ...];conststatements=code.split(" ");consttokens=[];REGEXES.forEach(REGEX=>{statements.forEach(statement=>{tokens.push(/* ... */);});});returntokens;}functionparse(tokens){constsyntaxTree=[];tokens.forEach(token=>{syntaxTree.push(/* ... */);});returnsyntaxTree;}
Potrudite se da izbegnete duplirani kod. Duplirani kod je štetan zato što potrazumeva više mesta koja treba izmetiti ako se algoritam promeni.
Zamislite da vodite restoran i pratite potrošnju namernica: paradajiz, luk, začine itd. Ako imate više spiskova na kojima ovo pratite, za servisiranje bilo kog jela sa paradajizom biće potrebne promene na svakom spisku. Ako postoji samo jedan spisak, biće potrebno samo jedno ažuriranje!
Loše:
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);});}
Dobro:
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);});}
Loše:
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);
Dobro:
constmenuConfig={title:"Order",// User did not include 'body' keybuttonText:"Send",cancellable:true};functioncreateMenu(config){letfinalConfig=Object.assign({title:"Foo",body:"Bar",buttonText:"Baz",cancellable:true},config);returnfinalConfig// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}// ...}createMenu(menuConfig);
Argumenti pokazivači, zastavice (engl.flags) su ružni.Prosleđivanje logičke vrednosti u funkciju je zaista loša praksa. To odmah otežava razumevanje metode, naglašavajući da funkcija obavlja više stvari. Jedna stvar je ako je vrednost pokazivača istinita (engl.true), a druga ako je lažna (engl.false).
Loše:
functioncreateFile(name,temp){if(temp){fs.create(`./temp/${name}`);}else{fs.create(name);}}
Dobro:
functioncreateFile(name){fs.create(name);}functioncreateTempFile(name){createFile(`./temp/${name}`);}
Funkcija će imati sporedan efekat ako radi bilo šta drugo osim što uzima neku vrednost i vraća drugu vrednost ili vrednosti. Neželjeni efekti se mogu javiti u vidu izmena globalne varijable i slično.
Loše:
// Global variable referenced by following function.// If we had another function that used this name, now it'd be an array and it could break it.letname="Ryan McDermott";functionsplitIntoFirstAndLastName(){name=name.split(" ");}splitIntoFirstAndLastName();console.log(name);// ['Ryan', 'McDermott'];
Dobro:
functionsplitIntoFirstAndLastName(name){returnname.split(" ");}constname="Ryan McDermott";constnewName=splitIntoFirstAndLastName(name);console.log(name);// 'Ryan McDermott';console.log(newName);// ['Ryan', 'McDermott'];
U JavaScript-u primitivni tipovi se čuvaju po vrednosti, a objketi i nizovi po referenci.Referentani tip vrednosti se čuva u sporijem delu memorije i za razliku od primitivnog tipa vrednosti referentni tip vrednost može da se menja tokom vremena.
Ukoliko neka funkcija izvrši promenu nad ulaznim nizom ili objketom, to će uticati na sve ostale funkcije koje koriste isti taj niz ili objekat kao ulazni parametar. Odlično rešenje bi bilo da nizove i objekte kao ulazne parametre, unutar funkcije uvek kloniramo.
Treba istaći i dva upozorenja ovom pristupu:
Možda postoje slučajevi kada želimo da izmenimo ulazni objekat, ali kada usvojite ovu programsku praksu, otkrićete da su ti slučajevi prilično retki.
Kloniranje velikih objekata može biti veoma skupo u pogledu performansi. Srećom ovo nije veliko pitanje u praksi, obzirom da postojesjajne biblioteke koje rešavaju probleme ovakvog pristupa.
Loše:
constaddItemToCart=(cart,item)=>{cart.push({ item,date:Date.now()});};
Dobro:
constaddItemToCart=(cart,item)=>{return[...cart,{ item,date:Date.now()}];};
Dodavanje funkcija globalnom objketu je loša praksa u Javascript-u jer može doći do sukoba sa drugim bibliotekama. Šta ako želimo da proširimo globalni objekatArray
tako da imadiff
metodu koja će prikazivati razliku između dva niza? Mogli bismo da napišemo novu metodu naArray.prototype
, ali možda će početi da se sukobljava sa drugom bibliotekom koja pokušava da učini isto. Šta ako druga biblioteka koristidiff
da pokaže razliku između prvog i poslednjeg elementa u nizu? Zbog toga je mnogo bolje koristiti klase ES2015/ES6 i proširiti globalni objekatArray
.
Loše:
Array.prototype.diff=functiondiff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));};
Dobro:
classSuperArrayextendsArray{diff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));}}
JavaScript nije toliko funkcionalan kao Haskell, ali ima predispoziciju za to. Funkcionalni jezici su čistiji i lakši za testiranje. Preferirajte ovaj stil programiranja kad god možete.
Loše:
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;}
Dobro:
constprogrammerOutput=[{name:"Uncle Bobby",linesOfCode:500},{name:"Suzie Q",linesOfCode:1500},{name:"Jimmy Gosling",linesOfCode:150},{name:"Gracie Hopper",linesOfCode:1000}];consttotalOutput=programmerOutput.reduce((totalLines,output)=>totalLines+output.linesOfCode,0);
Loše:
if(fsm.state==="fetching"&&isEmpty(listNode)){// ...}
Dobro:
functionshouldShowSpinner(fsm,listNode){returnfsm.state==="fetching"&&isEmpty(listNode);}if(shouldShowSpinner(fsmInstance,listNodeInstance)){// ...}
Loše:
functionisDOMNodeNotPresent(node){// ...}if(!isDOMNodeNotPresent(node)){// ...}
Dobro:
functionisDOMNodePresent(node){// ...}if(isDOMNodePresent(node)){// ...}
Čini se kao nemoguć zadatak. Većina ljudi, kada ovo prvi put čuje, kaže: "Kako da radim nešto bezif
?" Odgovor je da u mnogim slučajevima možemo koristiti polimorfizam da bi postigli isti cilj. Zašto bi ovo radili leži u jednom od prethodnih principa: funkcija treba da radi samo jednu stvar. Čim funkcija ima uslovif
to znači da ta funkcija radi više od jedne stvari.
Loše:
classAirplane{// ...getCruisingAltitude(){switch(this.type){case"777":returnthis.getMaxAltitude()-this.getPassengerCount();case"Air Force One":returnthis.getMaxAltitude();case"Cessna":returnthis.getMaxAltitude()-this.getFuelExpenditure();}}}
Dobro:
classAirplane{// ...}classBoeing777extendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getPassengerCount();}}classAirForceOneextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude();}}classCessnaextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getFuelExpenditure();}}
Loše:
functiontravelToTexas(vehicle){if(vehicleinstanceofBicycle){vehicle.pedal(this.currentLocation,newLocation("texas"));}elseif(vehicleinstanceofCar){vehicle.drive(this.currentLocation,newLocation("texas"));}}
Dobro:
functiontravelToTexas(vehicle){vehicle.move(this.currentLocation,newLocation("texas"));}
Ukoliko osećate da imate potrebu za proverom tipa, trebalo bi da razmislite o korišćenju TypeScript-a. TypeScript je programski jezik koji je baziran na JavaScript jeziku, ali je postavljen kao nad-jezik, tj. jezik koji proširuje funkcionalnosti JavaScript-a. Posebna osobina TypeScript jezika, u odnosu na JavaScript je ta što koristi mehanizam statički izričito definisanih tipova za promenljive, parametre funkcija, povratne tipove funkcija itd.
Loše:
functioncombine(val1,val2){if((typeofval1==="number"&&typeofval2==="number")||(typeofval1==="string"&&typeofval2==="string")){returnval1+val2;}thrownewError("Must be of type String or Number");}
Dobro:
functioncombine(val1,val2){returnval1+val2;}
Moderni pretraživači rade puno optimizacije tokom izvršavanja koda.Postoje dobri resuri za otkrivanje nedostataka optimizacije, koristite ih.
Loše:
// On old browsers, each iteration with uncached `list.length` would be costly// because of `list.length` recomputation. In modern browsers, this is optimized.for(leti=0,len=list.length;i<len;i++){// ...}
Dobro:
for(leti=0;i<list.length;i++){// ...}
Kod koji se ne koristi je jednako loš kao i duplirani kod. Nema razloga da čuvamo kod koji više ne koristimo.
Loše:
functionoldRequestModule(url){// ...}functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker("apples",req,"www.inventory-awesome.io");
Dobro:
functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker("apples",req,"www.inventory-awesome.io");
Bolje je koristitiget
iset
metode kada pristupamo svojstvima objekta nego direktno pristupiti njima. Ako se pitate "Zašto?" Evo nekoliko razloga:
- Validacija je lako primeljiva na nivou
set
metode - Enkapsulaciju promenljivih unutar objekta
- Jednostavnije je rukovanje greškama na nivou
get
iset
metoda
Loše:
functionmakeBankAccount(){// ...return{balance:0// ...};}constaccount=makeBankAccount();account.balance=100;
Dobro:
functionmakeBankAccount(){// this one is privateletbalance=0;// a "getter", made public via the returned object belowfunctiongetBalance(){returnbalance;}// a "setter", made public via the returned object belowfunctionsetBalance(amount){// ... validate before updating the balancebalance=amount;}return{// ... getBalance, setBalance};}constaccount=makeBankAccount();account.setBalance(100);
To se može postići pomoćuclosure
Loše:
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
Dobro:
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
Pomoću ES5, veoma je teško napisati čitljivu konstruktor funkciju koja nasleđuje neke metode od druge konstruktor funkcije. Ukoliko je potrebno da koristite nasleđivanje, onda koristite klase iz ES2015/ES6. Najbolje je raditi sa malim funkcijama sve dok ne vidite potrebu za većim i složenijim objektom.
Loše:
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(){};
Dobro:
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(){/* ... */}}
Ovaj pattern je veoma koristan u JavaScript-u i možete ga videti u mnogim bibliotekama kao što su jQuery i Lodash.Jednostavno u metodama vratitethis
na kraju svake funkcije i zatim kod poziva metoda, možete nadovezivati ostale metode.
Loše:
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();
Dobro:
classCar{constructor(make,model,color){this.make=make;this.model=model;this.color=color;}setMake(make){this.make=make;// NOTE: Returning this for chainingreturnthis;}setModel(model){this.model=model;// NOTE: Returning this for chainingreturnthis;}setColor(color){this.color=color;// NOTE: Returning this for chainingreturnthis;}save(){console.log(this.make,this.model,this.color);// NOTE: Returning this for chainingreturnthis;}}constcar=newCar("Ford","F-150","red").setColor("pink").save();
Postoji mnogo razloga kada treba upotrebiti nasleđivanje i mnogo razloga kada treba upotrebiti kompoziciju.Ukoliko mislite da treba da implementirate nasleđivanje, pokušajte da razmislite da li bi kompozicija bila bolje rešenje. U nekim slučajevima će sigurno biti.
Ukoliko za primer uzmemo automobil: točkovi, motor, menjač itd. mogu biti posmatrani kao posebne klase. Klasa automobil bi predstavljala kompoziciju ovih pojedinačnih klasa.
Loše:
classEmployee{constructor(name,email){this.name=name;this.email=email;}// ...}// Bad because Employees "have" tax data. EmployeeTaxData is not a type of EmployeeclassEmployeeTaxDataextendsEmployee{constructor(ssn,salary){super();this.ssn=ssn;this.salary=salary;}// ...}
Dobro:
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);}// ...}
SOLID je akronim koji se sastoji iz 5 principa: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation i Dependency Inversion. Ovih 5 principa se najčešće koriste i imaju veoma velike pozitivne efekte na softver i kod u kome se primene.
Princip jednostruke odgovornosti govori o tome da svaka klasa treba da ima samo jednu odgovornost. To ne znači da treba da ima samo jednu funkciju. Ona može imati i više funkcija ako one zajedno izvršavaju jedan zadatak i zajedno čine samo jedan razlog za promenu te klase.
Loše:
classUserSettings{constructor(user){this.user=user;}changeSettings(settings){if(this.verifyCredentials()){// ...}}verifyCredentials(){// ...}}
Dobro:
classUserAuth{constructor(user){this.user=user;}verifyCredentials(){// ...}}classUserSettings{constructor(user){this.user=user;this.auth=newUserAuth(user);}changeSettings(settings){if(this.auth.verifyCredentials()){// ...}}}
Otvoren-zatvoren princip govori o tome da klasa treba biti otvorena za proširenja, a zatvorena za izmene. To znači da ako je potrebno uneti neke nove funkcionalnosti, onda ne treba menjati klasu i postojeće metode, već je proširiti.
Loše:
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=>{// transform response and return});}elseif(this.adapter.name==="nodeAdapter"){returnmakeHttpCall(url).then(response=>{// transform response and return});}}}functionmakeAjaxCall(url){// request and return promise}functionmakeHttpCall(url){// request and return promise}
Dobro:
classAjaxAdapterextendsAdapter{constructor(){super();this.name="ajaxAdapter";}request(url){// request and return promise}}classNodeAdapterextendsAdapter{constructor(){super();this.name="nodeAdapter";}request(url){// request and return promise}}classHttpRequester{constructor(adapter){this.adapter=adapter;}fetch(url){returnthis.adapter.request(url).then(response=>{// transform response and return});}}
Princip Liskove zamene kaže da sve podklase neke nadklase mogu da se zamene svojom nadklasom, a da se pri tome ponašanje programa ne promeni.
Loše:
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();// Bad: Returns 25 for Square. Should be 20.rectangle.render(area);});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);
Dobro:
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);
JavaScrip nema intefejse, pa se ovaj princip ne može u potpunosti iskoristiti. U ISP se naglašava da "Klijente ne treba prisiljavati da zavise od interfejsa koje ne koriste". Dobar primer koji se može primeniti u JavaScript-u jeste da prilikom kreiranja neke klase ne zahtevamo veliki broj ulaznih parametra koji se neće puno koristiti, već da ih ostavimo kao opcione.
Loše:
classDOMTraverser{constructor(settings){this.settings=settings;this.setup();}setup(){this.rootNode=this.settings.rootNode;this.settings.animationModule.setup();}traverse(){// ...}}const$=newDOMTraverser({rootNode:document.getElementsByTagName("body"),animationModule(){}// Most of the time, we won't need to animate when traversing.// ...});
Dobro:
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(){}}});
Princip inverzije zavisnosti naglašava dve bitne stvari:
- Moduli višeg nivoa ne treba da zavise od modula nižeg nivoa.
- Apstrakcije ne treba da zavise od detalja, već detalji od apstrakcija.
Kao što smo ranije napomenuli, JavaScript nema iterfejse, to znači da treba da se kreiraju apstrakcije koje predstavljaju ugovor.Apstrakcija samo govori šta neka klasa treba da radi, ne i kako. Klase koje će naslediti tu apstraktu klasu treba da znaju šta sve ta apstraktna klasa ima od funkcija i na taj način zapravo detalji zavise od apstrakcija, a apstrakcije ne zavise od detalja.
Loše:
classInventoryRequester{constructor(){this.REQ_METHODS=["HTTP"];}requestItem(item){// ...}}classInventoryTracker{constructor(items){this.items=items;// Bad: We have created a dependency on a specific request implementation.// We should just have requestItems depend on a request method: `request`this.requester=newInventoryRequester();}requestItems(){this.items.forEach(item=>{this.requester.requestItem(item);});}}constinventoryTracker=newInventoryTracker(["apples","bananas"]);inventoryTracker.requestItems();
Dobro:
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){// ...}}// By constructing our dependencies externally and injecting them, we can easily// substitute our request module for a fancy new one that uses WebSockets.constinventoryTracker=newInventoryTracker(["apples","bananas"],newInventoryRequesterV2());inventoryTracker.requestItems();
Testovi su jednako važni za zdravlje projekta kao i sam kod. Možda su još i važniji, jer testovi čuvaju i poboljšavaju felksibilnost, održavanje i naknadnu upotrebu proizvodnog koda. Ako imamo testove ne plašimo se promene koda! Bez testova svaka promena predstavlja moguću grešku.
Loše:
importassertfrom"assert";describe("MomentJS",()=>{it("handles date boundaries",()=>{letdate;date=newMomentJS("1/1/2015");date.addDays(30);assert.equal("1/31/2015",date);date=newMomentJS("2/1/2016");date.addDays(28);assert.equal("02/29/2016",date);date=newMomentJS("2/1/2015");date.addDays(28);assert.equal("03/01/2015",date);});});
Dobro:
importassertfrom"assert";describe("MomentJS",()=>{it("handles 30-day months",()=>{constdate=newMomentJS("1/1/2015");date.addDays(30);assert.equal("1/31/2015",date);});it("handles leap year",()=>{constdate=newMomentJS("2/1/2016");date.addDays(28);assert.equal("02/29/2016",date);});it("handles non-leap year",()=>{constdate=newMomentJS("2/1/2015");date.addDays(28);assert.equal("03/01/2015",date);});});
Povratni pozivi (callbacks) dovode do prekomernog gnježđenja i loše čitljivosti koda.U ES2015/ES6 Promisi su ugrađeni kao globalni tipovi. Koristite ih!
Loše:
import{get}from"request";import{writeFile}from"fs";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin",(requestErr,response,body)=>{if(requestErr){console.error(requestErr);}else{writeFile("article.html",body,writeErr=>{if(writeErr){console.error(writeErr);}else{console.log("File written");}});}});
Dobro:
import{get}from"request-promise";import{writeFile}from"fs-extra";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin").then(body=>{returnwriteFile("article.html",body);}).then(()=>{console.log("File written");}).catch(err=>{console.error(err);});
Promisi su vrlo dobra alternativa za callback, ali ES2017/ES8 uvode async/await koje predstavlja još bolje rešenje. Sve što treba da uradite jeste da napišete funkciju saasync
prefiksom u kojoj možete da koristite svoju asihronu logiku.
Loše:
import{get}from"request-promise";import{writeFile}from"fs-extra";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin").then(body=>{returnwriteFile("article.html",body);}).then(()=>{console.log("File written");}).catch(err=>{console.error(err);});
Dobro:
import{get}from"request-promise";import{writeFile}from"fs-extra";asyncfunctiongetCleanCodeArticle(){try{constbody=awaitget("https://en.wikipedia.org/wiki/Robert_Cecil_Martin");awaitwriteFile("article.html",body);console.log("File written");}catch(err){console.error(err);}}getCleanCodeArticle()
Ne radeći ništa sa uhvaćenom greškom, gubimo priliku da je ispravimo ili da na nju ikada reagujemo. Ako obmotovamo deo koda utry/catch
onda sumnjamo da se tu može javiti greška, tada trebamo imati plan šta ćemo uraditi sa njom.
Loše:
try{functionThatMightThrow();}catch(error){console.log(error);}
Dobro:
try{functionThatMightThrow();}catch(error){// One option (more noisy than console.log):console.error(error);// Another option:notifyUserOfError(error);// Another option:reportErrorToService(error);// OR do all three!}
Loše:
getdata().then(data=>{functionThatMightThrow(data);}).catch(error=>{console.log(error);});
Dobro:
getdata().then(data=>{functionThatMightThrow(data);}).catch(error=>{// One option (more noisy than console.log):console.error(error);// Another option:notifyUserOfError(error);// Another option:reportErrorToService(error);// OR do all three!});
Formatiranje je subjektivno. Kao i mnogo pravila u ovom dokumentu, ne postoji čvrsto i brzo pravilo koje morate poštovati. Glavna poenta je da se ne svađate oko formatiranja! :)Postojimnogo alata za automatizaciju formatiranja.
JavaScript je netipiziran, tako da vaš tim može izabrati imenovanje koje želi. Poenta je da bez obzira šta se odabere, budete dosledni.
Loše:
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{}
Dobro:
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{}
Ako funkcija poziva drugu funkciju, držite te funkcije vertikalno blizu izvornoj funkciji. Idealno bi bilo da funkcija koja koristi drugu funkciju bude tačno iznad nje. Zato što obično čitamo od vrha do dna.
Loše:
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();
Dobro:
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();
Komentari nisu obavezni. Dobar kod se sam opisuje.
Loše:
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;}}
Dobro:
functionhashIt(data){lethash=0;constlength=data.length;for(leti=0;i<length;i++){constchar=data.charCodeAt(i);hash=(hash<<5)-hash+char;// Convert to 32-bit integerhash&=hash;}}
Ostavite stari kod u istoriji kontroli verzije(version control)
Loše:
doStuff();// doOtherStuff();// doSomeMoreStuff();// doSoMuchStuff();
Dobro:
doStuff();
Zapamtite, koristite(version control)! Koristitegit log
da biste videli istoriju.
Loše:
/** * 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;}
Dobro:
functioncombine(a,b){returna+b;}
Neka funckije i imena promenljivih zajedno sa odgovarajućim formatiranjem daju vizalnu strukturu vašem kodu.
Loše:
////////////////////////////////////////////////////////////////////////////////// Scope Model Instantiation////////////////////////////////////////////////////////////////////////////////$scope.model={menu:"foo",nav:"bar"};////////////////////////////////////////////////////////////////////////////////// Action setup////////////////////////////////////////////////////////////////////////////////constactions=function(){// ...};
Dobro:
$scope.model={menu:"foo",nav:"bar"};constactions=function(){// ...};
Dostupno i na drugim jezicima
Armenian:hanumanum/clean-code-javascript/
Bangla(বাংলা):InsomniacSabbir/clean-code-javascript/
Brazilian Portuguese:fesnt/clean-code-javascript
Simplified Chinese:
Traditional Chinese:AllJointTW/clean-code-javascript
French:GavBaros/clean-code-javascript-fr
German:marcbruederlin/clean-code-javascript
Indonesia:andirkh/clean-code-javascript/
Italian:frappacchio/clean-code-javascript/
Japanese:mitsuruog/clean-code-javascript/
Korean:qkraudghgh/clean-code-javascript-ko
Polish:greg-dev/clean-code-javascript-pl
Russian:
Spanish:tureey/clean-code-javascript
Spanish:andersontr15/clean-code-javascript
Turkish:bsonmez/clean-code-javascript
Ukrainian:mindfr1k/clean-code-javascript-ua
Vietnamese:hienvd/clean-code-javascript/
About
🛁 Clean Code principi prilagođeni za JavaScript
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.