Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

🛁 Clean Code principi prilagođeni za JavaScript

License

NotificationsYou must be signed in to change notification settings

doskovicmilos/clean-code-javascript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 

Repository files navigation

Sadržaj

  1. Uvod
  2. Promenljive
  3. Funkcije
  4. Objekti i strukture podataka
  5. Klase
  6. SOLID
  7. Testiranje
  8. Asihronost
  9. Rad sa greškama
  10. Formatiranje
  11. Komentari
  12. Prevod

Uvod

Humorous image of software quality estimation as a count of how many expletives you shout when reading code

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! :)

Promenljive

Koristite imena koja otkrivaju namenu

Loše:

constyyyymmdstr=moment().format("YYYY/MM/DD");

Dobro:

constcurrentDate=moment().format("YYYY/MM/DD");

⬆ vrati se na početak

Koristite istu metodu za isti tip promenljive

Loše:

getUserInfo();getClientData();getCustomerRecord();

Dobro:

getUser();

⬆ vrati se na početak

Koristite imena koja se mogu lako pretraživati

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);

⬆ vrati se na početak

Koristite imena koja imaju smisleno značenje

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);

⬆ vrati se na početak

Izbegavajte mentalno mapiranje

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);});

⬆ vrati se na početak

Nemojte dodavati nepotrebno značenje

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";}

⬆ vrati se na početak

Koristite podrazumevane vrednosti

Loše:

functioncreateMicrobrewery(name){constbreweryName=name||"Hipster Brew Co.";// ...}

Dobro:

functioncreateMicrobrewery(name="Hipster Brew Co."){// ...}

⬆ vrati se na početak

Funkcije

Argumenti funkcije

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});

⬆ vrati se na početak

Funkcije treba da rade jednu stvar

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();}

⬆ vrati se na početak

Koristite opisna imena funkcija

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);

⬆ vrati se na početak

Jedan nivo apstrakcije po funkciji

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;}

⬆ vrati se na početak

Rešite se dupliranog koda

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);});}

⬆ vrati se na početak

Podesite podrazumevane vrednosti objekta pomoću Object.assign metode

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);

⬆ vrati se na početak

Ne koristite argumente pokazivače

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}`);}

⬆ vrati se na početak

Izbegavajte neželjene efekte (deo 1)

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'];

⬆ vrati se na početak

Izbegavajte neželjene efekte (deo 2)

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:

  1. 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.

  2. 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()}];};

⬆ vrati se na početak

Nemojte dodavati globalne funkcije

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));}}

⬆ vrati se na početak

Dajte prednost funkcionalnom programiranju nad imperativnim programiranjem

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);

⬆ vrati se na početak

Enkapsulacija uslova

Loše:

if(fsm.state==="fetching"&&isEmpty(listNode)){// ...}

Dobro:

functionshouldShowSpinner(fsm,listNode){returnfsm.state==="fetching"&&isEmpty(listNode);}if(shouldShowSpinner(fsmInstance,listNodeInstance)){// ...}

⬆ vrati se na početak

Izbegavajte negativne uslove

Loše:

functionisDOMNodeNotPresent(node){// ...}if(!isDOMNodeNotPresent(node)){// ...}

Dobro:

functionisDOMNodePresent(node){// ...}if(isDOMNodePresent(node)){// ...}

⬆ vrati se na početak

Izbegavajte uslove

Č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();}}

⬆ vrati se na početak

Izbegavajte proveru tipa (deo 1)

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"));}

⬆ vrati se na početak

Izbegavajte proveru tipa (deo 2)

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;}

⬆ vrati se na početak

Ne optimizujte preterano

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++){// ...}

⬆ vrati se na početak

Uklonite kod koji se ne koristi

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");

⬆ vrati se na početak

Objekti i strukture podataka

Koristite getters i setters

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 nivouset metode
  • Enkapsulaciju promenljivih unutar objekta
  • Jednostavnije je rukovanje greškama na nivouget 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);

⬆ vrati se na početak

Koristite privatne članove

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

⬆ vrati se na početak

Classes

Preferirajte klase ES2015/ES6 u odnosu na konstruktor funkcije ES5

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(){/* ... */}}

⬆ vrati se na početak

Koristitemethod chaining

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();

⬆ vrati se na početak

Dajte prednost kompoziciji (composition) nad nasleđivanjem (inheritance)

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);}// ...}

⬆ vrati se na početak

SOLID

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.

Single Responsibility Principle (SRP)

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()){// ...}}}

⬆ vrati se na početak

Open/Closed Principle (OCP)

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});}}

⬆ vrati se na početak

Liskov Substitution Principle (LSP)

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);

⬆ vrati se na početak

Interface Segregation Principle (ISP)

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(){}}});

⬆ vrati se na početak

Dependency Inversion Principle (DIP)

Princip inverzije zavisnosti naglašava dve bitne stvari:

  1. Moduli višeg nivoa ne treba da zavise od modula nižeg nivoa.
  2. 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();

⬆ vrati se na početak

Testiranje

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.

Jedna tvrdnja po testu (single concept per test)

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);});});

⬆ vrati se na početak

Asihronost

Koristite Obećanja (Promises), umesto povratnih poziva (callbacks)

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);});

⬆ vrati se na početak

Async/Await čini kod čistijim više od Promisa

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()

⬆ vrati se na početak

Rad sa greškama

Ne ignorišite uhvaćene greške

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!}

Nemojte zanemariti ni odbijene (rejected) promise

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!});

⬆ vrati se na početak

Formatiranje

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.

Budite dosledni prilikom imenovanja

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{}

⬆ vrati se na početak

Srodne funkcije bi trebale biti u blizini

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();

⬆ vrati se na početak

Komentari

Kometarišite samo stvari koje imaju složenu poslovnu logiku

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;}}

⬆ vrati se na početak

Ne ostavljate zakomentarisan kod

Ostavite stari kod u istoriji kontroli verzije(version control)

Loše:

doStuff();// doOtherStuff();// doSomeMoreStuff();// doSoMuchStuff();

Dobro:

doStuff();

⬆ vrati se na početak

Ne vodite dnevnik komentara

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;}

⬆ vrati se na početak

Izbegavajte pozicione markere

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(){// ...};

⬆ vrati se na početak

Prevod

Dostupno i na drugim jezicima

⬆ vrati se na početak

About

🛁 Clean Code principi prilagođeni za JavaScript

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp