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 concepts adapted for JavaScript

License

NotificationsYou must be signed in to change notification settings

hanumanum/clean-code-javascript

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

❗ թարգմանությունը ունի թերություններ, սխալներ և վրիպակներ։ Խմբագրումները, ուղղումները և առաջարկները բնականաբար ընդունվում են ։)

Բովանդակություն

  1. Ներածություն
  2. Փոփոխականներ
  3. Ֆունկցիաներ
  4. Օբյեկտներ և տվյալների ստրուկտուրաներ
  5. Կլասներ
  6. SOLID
  7. Թեստավորում
  8. Concurrency
  9. Սխալների մշակում
  10. Ֆորմատավորում
  11. Մեկնաբանություններ
  12. Թարգմանություններ

Ներածություն

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

Ծրագրային ապահովման սկզբունքները, Ռոբերտ ՄարտինիՊարզ կոդ գրքից,հարմարեցված JavaScript լեզվի համար: Սա ոճի ուղեցույց չէ։ Սա JavaScript֊ովընթեռնելի, վերաօգտագործելի և վերափոխելի ծրագրային ապահովում գրելու ուղեցույց է։

Ոչ բոլոր սկզբունքներին է պետք խիստ հետևել։ Ավելին, ոչ բոլորն են համընդհանուր ընդունված։ Այս ուղեցույցը ոչ ավելին է, քանՊարզ կոդ գրքի հեղինակների տարիներով հավաքած կոլեկտիվ փորձի արտացոլումը։

ԾԱ արհեստը միայն 50 տարեկան է և մենք դեռ շատ բան ենք սովորում։ Հնարավոր է, որ երբ ծրագրային ապահովման ճարտարապետությունը նույնքան ծեր լինի, որքան սովորական ճարտարապետությունը, մենք ավելի խիստ կանոնների կհետևենք։ Իսկ հիմա, դարձրու այս ուղեցույցը անկյունաքար, որով կբարձրացնես քո և քո թիմի գրած Javascript կոդի որակը։

Մի բան էլ․ սրանց իմացությունը քեզ անմիջապես չի դարձնի ավելի լավ ԾԱ նախագծող և սրանցով տարիներ շարունակ աշխատելը չի նշանակում, որ դու սխալներ չես անի։ Կոդի ամեն կտորը սկսվում է որպես փոքր նախագիծ․ այն ձևավորվում է խոնավ կավի պես հասնելով իր վերջնական ձևին։ Վերջապես մենք հեռացնում ենք անկատարությունը մեր ընկերների հետ այն վերանայելիս։ Մի՛ ինքնախարազանվիր սկզբնական և փոփոխության ենթակածրագրերի համար։ Խարազանի՛ր ծրագրերը։

Փոփոխականներ

Օգտագործիր իմաստալից և արտասանելի փոփոխականների անուններ

Վատ:

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

Լավ:

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

⬆ վեր

Օգտագործիր նույն բառարանը նույն փոփոխականների տեսակի համար

Վատ՝

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

Լավ՝

getUser();

⬆ վեր

Օգտագործիր որոնելի փոփոխականների անուններ

Մենք պիտի ավելի շատ կոդ կարդանք, քան գրենք։ Կարևոր է մեր կոդը այնպես գրել, որայն լինի ընթեռնելի և որոնելի։ Ծրագրում փոփոխականների լավ անունները կօգնեն հասկանալծրագրի նշանակությունը այն մարդկանց, ովքեր կցանկանան ուսումնասիրել քո ծրագրիը։Փոփոխականների անունները որոնելի գրիր։ Հետևյալ գործիքները՝buddy.js,ESLintկօգնեն հայտնաբերել չանվանված հաստատունները։

Վատ՝

// Ինչ թիվ է 86400000֊ը?setTimeout(blastOff,86400000);

Լավ՝

// Հայտարարիր դրանք որպես հաստատուններconstMILLISECONDS_IN_A_DAY=86400000;setTimeout(blastOff,MILLISECONDS_IN_A_DAY);

⬆ վեր

Օգտագործիր փոփոխականների "բացատրող" անուններ

Վատ՝

constaddress='One Infinite Loop, Cupertino 95014';constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;saveCityZipCode(address.match(cityZipCodeRegex)[1],address.match(cityZipCodeRegex)[2]);

Լավ՝

constaddress='One Infinite Loop, Cupertino 95014';constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;const[,city,zipCode]=address.match(cityZipCodeRegex)||[];saveCityZipCode(city,zipCode);

⬆ վեր

Խուսափիր մտավոր կապ ստեղծելուց (Avoid Mental Mapping)

Բացահայտը ավելի է քան թաքնվածը։

Վատ՝

constlocations=['Austin','New York','San Francisco'];locations.forEach((l)=>{doStuff();doSomeOtherStuff();// ...// ...// ...// սպասիր, ի՞նչ է l-ըdispatch(l);});

Լավ՝

constlocations=['Austin','New York','San Francisco'];locations.forEach((location)=>{doStuff();doSomeOtherStuff();// ...// ...// ...dispatch(location);});

⬆ վեր

Խուսափիր ավելորդ բովանդակությունից

Եթե քո կլասը/օբյեկտը քեզ որևէ բան է ասում, մի կրկնիր նույնը փոփոխականների անուններում։

Վատ՝

constCar={carMake:'Honda',carModel:'Accord',carColor:'Blue'};functionpaintCar(car){car.carColor='Red';}

Լավ՝

constCar={make:'Honda',model:'Accord',color:'Blue'};functionpaintCar(car){car.color='Red';}

⬆ վեր

Օգտագործիր արգումենտների սկզբնարժեքավորում, խուսափիր պայմանների միջոցով սկզբնական արժեքներ դնելուց

Սկզբնարժեքավորված արգումենտները հաճախ ավելի հասկանալի են քան մյուս տարբերակները կարճ արժեքավորումը։(Այս նախադասությունը անհասկանալի է) Նաևիմացիր, որ եթե կարճ արժեքավորում օգտագործես, ապա քո ֆունկցիան կտրամադրի նախասահմանված արժեքներ միայն արգումենտիundefined արժեքի դեպքում։Արգումենտների այլ "կեղծոտ" արժեքները, ինչպիսիք են'',"",false,null,0, ևNaN , չեն փոխարինվի նախասահմանված արժեքներով։

Վատ՝

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

Լավ՝

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

⬆ վեր

Ֆունկցիաներ

Ֆունկցիաների արգումենտներ (իդեալական տարբերակում 2 կամ պակաս)

Ֆունկցիայի արգումենտների քանակը սահմանափակելը շատ կարևոր է, որովհետև դա հեշտացնում էֆունկցիայի աշխատանքի թեստավորումը։ Երեքից ավել արգումենտներ ունենալը բերում է այն տարբերակների բազմացմանը, որոնք պետք է ստուգել յուրաքանչյուր արգումենտի հետ։

Մեկ կամ երկու արգումենտ ունենալը իդեալական է իսկ երեք արգումենտից հնարավորության դեպքում պետք է խուսափել։Դրանցից ավելը պետք է խմբավորել։ Հաճախակի, եթե ունես երկու արգումենտից ավել, դա նշանակում է,որ ֆունկցիադ հավանաբար ավելի շատ բան է անում, քան անհրաժեշտ է։ Եթե ոչ, ապա օբյեկտը որպես արգումենտ փոխանցելըավելի ճիշտ տարբերակ է։

Քանի որ, JavaScript֊ը թույլատրում է ընթացքից օբյեկտներ ստեղծել (առանց նախապես class-ով նախագծելու), կարող եսօգտագործել օբյեկտ եթե ունես շատ արգումենտների կարիք։

Ֆունկցիայի պահանջած հատկանիշները ակնհայտ սարքելու համար, կարող ես օգտագործել ES2015/ES6֊իցդեստրուկտուրիզացիայի սինտաքսը (destructuring syntax) , այն ունի որոշակի առավելություններ՝

  1. Երբ մեկը նայում է ֆունկցիայի սիգնատուրային, միանգամից ակնհայտ է դառնում թե ինչ արգումենտներ են օգտագործվում

  2. Դեստրուկտուրիզացիան կլոնավորում է ֆունկցիային փոխանցված արգումենտի պրիմիտիվ արժեքները։ Սա օգնում է խուսափել կողմնակի էֆեկտներից։ՀԻՇԻՐ։ Դեստրուկտուրացված արժեքների օբյեկտները և զանգվածները չէն կլոնավորվում։

  3. Լինթերները կարող են նախազգուշացնել չօգտագործված հատկանիշների մասին, ինչը անհնար կլինի առանց դեստրուկտուրիզացիայի։

Վատ՝

functioncreateMenu(title,body,buttonText,cancellable){// ...}

Լավ՝

functioncreateMenu({ title, body, buttonText, cancellable}){// ...}createMenu({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true});

⬆ վեր

Ֆունկցիան պիտի միայն մի բան անի

Սա ԾԱ նախագծման կարևորագույն սկզբունքն է։ Մեկից ավելի բան անող ֆունկցիաները դժվար են նախագծվում, թեստավորվում և նաև դրանց մասին մտածելը նույնպես դժվարանում է։ Եթե կարող ես յուրաքանչյուր գործողություն իզոլացնել մեկ ֆունկցիայի ներսում, ապա այդ ֆունկցիաները հեշտ փոփոխելի (refactoring) են դառնում և քո ծրագիրը ավելի հեշտ է կարդացվում։ Եթե այս ուղեցույցից վերցնես միայն այս կանոնը, դու արդեն առաջ կանցնես բազմաթիվ ծրագրավորողներից։

Վատ՝

functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Լավ՝

functionemailActiveClients(clients){clients.filter(isActiveClient).forEach(email);}functionisActiveClient(client){constclientRecord=database.lookup(client);returnclientRecord.isActive();}

⬆ վեր

Ֆունկցիաների անունները պիտի ասեն, թե ինչ է ֆունկցիան անում

Վատ՝

functionaddToDate(date,month){// ...}constdate=newDate();// Դժվար է ֆունկցիայի անունից հասկանալ, թե ինչ է այն ավելացնում, օ՞ր, ամի՞ս, ի՜՞նչ ․․․addToDate(date,1);

Լավ՝

functionaddMonthToDate(month,date){// ...}constdate=newDate();addMonthToDate(1,date);

⬆ վեր

Ֆունկցիան պիտի արտահայտի աբստրակցիայի միայն մեկ մակարդակ

Աբստրակցիայի մեկ մակարդակից ավելին ունեցող ֆունկցիան, հավանաբար, անում է ավելին քան պետք է։Ֆունկցիան բաժանելը մի քանի ֆունկցիաների օգնում է ֆունկցիաների վերաօգտագործելիությանը և հեշտ թեստավորմանը։

Վատ՝

functionparseBetterJSAlternative(code){constREGEXES=[// ...];conststatements=code.split(' ');consttokens=[];REGEXES.forEach((REGEX)=>{statements.forEach((statement)=>{// ...});});constast=[];tokens.forEach((token)=>{// lex...});ast.forEach((node)=>{// parse...});}

Լավ՝

functionparseBetterJSAlternative(code){consttokens=tokenize(code);constast=lexer(tokens);ast.forEach((node)=>{// parse...});}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;}

⬆ վեր

Հեռացրու կրկնվող կոդը

Արա առավելագույնը կրկնվող կոդից խուսափելու համար։ Կրկնվող կոդը վատ է, որովհետև անրհաժեշտության դեպքում, փոփոխությունները անհրաժեշտ կլինի անել մեկից ավել տեղերում։

Պատկերացրու դու ռեստորան ունես և պահում ես տվյալներ բոլոր մթերքների մասին, բոլոր պոմիդորների, սոխերի, սխտորների, համեմունքների և այլն ․․․ Եթե ունենաս մի քանի ցանկ, ապաբոլոր այդ ցանկերը պիտի թարմանացվեն, երբ պոմիդորով որևէ ճաշատեսակ սարքես։ Միայն մեկ ցանկ ունենալու դեպքում միայն մի տեղ պիտի թարմացնես տվյալները։

Հաճախակի կոդը կրկնապատկվում է, որովհետև ունես իրարից մի քիչ տարբերվող բաներ, որոնք հիմնականում նույնն են, բայց նաև տարբերություններ կան։ Տարբերություննրը ստիպում են քեզ երկու կամ ավել առանձին ֆունկցիաներ սարքել այդ երկու բաների համար։ Կրկնվող կոդը հեռացնելը նշանակում է, որ պետք է ստեղծել աբստրակցիայի այնպիսի մակարդակ, որը կկարողանա աշխատել այդ բաների խմբի հետ միայն մեկ ֆունկցիայի (մոդուլի,կլասի) միջոցով:

Ճիշտ աբստրակցիա նկարագրելը կրիտիկական է, այդ պատճառով պետք է հետևեսԿլասներ բաժնում նկարագրված SOLID սկզբունքներին։ Վատ աբստրակցիան ավելի վատ է, քան կրկնվող կոդը․ զգույշ եղիր։ Մի կրկնիր ինքդ քեզ, հակառակ դեպքում կհայտնվես այնպիսի վիճակում, որ մեկ փոփոխության համար մի քանի տեղ պիտի կոդը փոփոխես։

Վատ՝

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

Լավ՝

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

⬆ վեր

Օբյեկտները սկզբարժեքավորիր Object.assign֊ի միջոցով

Վատ՝

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

Լավ՝

constmenuConfig={title:'Order',// 'body' բանալին չի արժեքավորվելbuttonText:'Send',cancellable:true};functioncreateMenu(config){config=Object.assign({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true},config);// config֊ը հիմա հավասաար է {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}// ...}createMenu(menuConfig);

⬆ վեր

Խուսափեք որպես ֆունկցիայի արգումենտ օգտագործել տրամաբանական փոփոխականներ

Տրամաբանական փոփոխականը որպես արգումենտ, օգտագործողին հուշում է, որ ձեր ֆունկցիան մեկից ավելի բաներ է անում։ Բայց լավ ֆունկցիան պետք է կատարի միայն մեկ բան։ Նման ֆունկցիաները կարող եք բաժանել երկու մասի։

Վատ՝

functioncreateFile(name,temp){if(temp){fs.create(`./temp/${name}`);}else{fs.create(name);}}

Լավ՝

functioncreateFile(name){fs.create(name);}functioncreateTempFile(name){createFile(`./temp/${name}`);}

⬆ վեր

Խուսափիր կողմնակի էֆեկտներից (մաս 1)

Եթե ֆունկցիան բացի արժեք վերցնելուց և վերադարձնելուց այլ բան է անում, ապա ասում են, որ այն ունի կողմնակի էֆետներ։ Կողմնակի էֆեկտները կարող են լինել՝

  • որևէ գլոբալ փոփոխականի արժեքը փոխելը
  • ֆայլի մեջ տեղեկություններ գրելը
  • պատահականորեն ձեր ամբողջ փողը անծանոթ մեկին ուղարկելը ։)

Հաճախ կարիք է լինում օգտագործել կողմնակի ազդեցություններ, ինչպիսիք են վերոշարադրյալները, քանի որ երբեմն պետք է գրել ֆայլի մեջ։ Նման դեպքերում դուք պետք է առանձնացնեք և կենտրոնացնեք ծրագրի այն մասերը որոնք ունեն կողմնակի ազդեցություն ու մի տեղից կատարել ֆայլերի մանիպուլյացիան։ Մի ստեղծեք մի քանի ֆունկցիա միևնույն ֆայլի վրա գրելու համար։ Գրեք մեկ և միայն մեկ service (ծառայություն) որն անում է այդ գործողությունը։

Հիմնական իմաստն այն է, որ չստեղծեք ընդհանուր օգտագործման օբյեկտներ առանց դրա վրա կառուցելու ձևափոխությունների կենտրոնացված կլաս կամ օբյեկտ։ Քանի որ նման դեպքում, ծրագրի տարբեր մասերում կարող է ձևափոխվել ձեր օբյեկտը և դուք ստանաք անկանխատեսելի արդյունքներ։ Եթե կարողանաք խուսափել այս տարածված խնդիրց, ապա շատ ավելի երջանիկ կլինեք, քան ծրագրավորողների մեծ մասը։

Վատ՝

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

Լավ՝

functionsplitIntoFirstAndLastName(name){returnname.split(' ');}constname='Ryan McDermott';constnewName=splitIntoFirstAndLastName(name);console.log(name);// 'Ryan McDermott';console.log(newName);// ['Ryan', 'McDermott'];

⬆ վեր

Խուսափիր կողմնակի էֆեկտներից (մաս 2)

JavaScript լեզվում տարրական տիպի տվյալները ֆունկցիաներին փոխանցվում են իրենց արժեքով, իսկ օբյեկտները/զանգվածները հղումով (by refreance)։ Օբյեկտների/զանգվածների դեպքում, եթե քո ֆունկցիան կատարում է փոփոխություն օրինակ զամբյուղում, ավելացնելով ապրանք, ապա մյուս ֆունկցիան որը կօգտագործի այդ զանգվածը կստանա փոփոխված տարբերակը։ Սա կարող է լավ լինել, սակայն կարող է նաև վատ լինել։ Պատկերացնենք վատ իրավիճակը։

Օգտատերը սեղմում է "Գնել" կոճակը, որը կանչում էpurchase ֆունկցիան, որը առաջացնում է ցանցային հարցում և ուղարկում էcart զանգվածը սերվերին։ Ցանցային վատ կապի պատճառովpurchase ֆունկցիան շարունակում է փորձել ուղարկել հարցումը։ Ի՞նչ կլինի եթե օգտատերը միևնույն ժամանակ պատահաբար սեղմի "Ավելացնել զամբյուղին" կոճակը և ավելացնի ապրանք, որը նա չէր ցանկանում։ Եթե դա պատահի և ցանցային հարցումը սկսվի, գնելու ֆունկցիան կուղարկի նաև սխալմամբ ավելացված ապրանքը, որովհետև այն ունի հղումcart զանգվածին որը արդեն փոփոխված էaddItemToCart ֆունկցիայի աշխատանքի պատճառով։

Լավագույն լուծումը կլինի այն, որaddItemToCart ֆունկցիան միշտ պատճենիcart֊ը, խմբագրի այն և վերադարձնի պատճենը։ Սա երաշխավորում է, որ ոչ մի այլ ֆունկցիա չի փոխի զամբյուղի արժեքը։

Երկու նախազգուշացում այս տեսանկյունի կապակցությամբ

  1. Կարող են լինել իրավիճակներ, երբ իրոք պետք կլինի փոփոխել մտնող օբյեկտը, սակայն եթե դու ընդունես վերոնշված պրակտիկան, ապա կտեսնես, որ նմանատիպ իրավիճակները շատ հազվադեպ են։ Շատ բաներ կարելի է վերափոխել (refactoring) հեռացնելով կողմնակի էֆեկտները։

  2. Մեծ օբյեկտների պատճենումը կարող է շատ ծախսատար լինել արագագործության տեսանկյունից։ Բարեբախտաբար գործանականում դա մեծ խնդիր չէ, որովհետև կանլավ գրադարաններ որոնք այս տիպի ծրագրավորումը դարձում են ավելի արագ և ոչ այդքան ծախսատար (հիշողության տեսանկյունից) , քան ձեռքով օբյետները և զանգվածները պատճենելու ժամանակ։

Վատ՝

constaddItemToCart=(cart,item)=>{cart.push({ item,date:Date.now()});};

Լավ՝

constaddItemToCart=(cart,item)=>{return[...cart,{ item,date:Date.now()}];};

⬆ վեր

Մի գրիր գլոբալ ֆունկցիաներ

Գլոբալ փոփոխականները աղտոտելը վատ պրակտիկա է JavaScript֊ում, որովհետև դու կարող ես ունենալ բախում այլ գրադարանների հետ։ Եվ քո API֊ի օգտատերը ոչինչ չի իմանա, քանի դեռ իրական ծրագրում չի ստանա բացառություն (exeption in production): Արի մտածենք հետևյալ օրինակի վրա։ Ի՞նչ կլինի եթե դու ուզենաս ընդլայնել JavaScript-ի հարազատ(native) Array֊ը ստեղծելովdiff մեթոդը, որը ցուցադրում է զանգվածների տարբերությունը։ Դու կարող ես գրել քո ֆունկցիանArray.prototype֊ի միջոցով, սակայն այն կարող է բախվել այլ գրադարանի նույնանուն ֆունկցիայի հետ, որը փորձում է անել նույն բանը։ Իսկ եթե մյուս գրադարանիdiff֊ը որոնում է տարբերությունը զանգվածի առաջին և վերջին անդամների միջև։ Ահա թե ինչու է ավելի լավ ուղակի օգտագործել ES2015/ES6 կլասները և հեշտությամբ ընդլայնելArray օբյեկտը։

Վատ՝

Array.prototype.diff=functiondiff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));};

Լավ՝

classSuperArrayextendsArray{diff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));}}

⬆ վեր

Նախընտրիր ֆունկցիոնալ ծրագրավորումը՝ ոչ իմպերատիվը

JavaScript֊ը ֆունկցիոնալ ծրագրավորման լեզու չի այն իմաստով, որով Haskell֊ն է, սակայն այն ունի ֆունկցիոնալ հատկանիշներ։ Ֆունկցիոնալ լեզուները կարող են լինել ավելի մաքուր և հեշտ թեստավորվող։ Նախընտրիր այս ոճը, երբ դա հնարավոր է։

Վատ՝

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

Լավ՝

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

⬆ վեր

Ինկապսուլացրու (Encapsulate) պայմանները

Վատ՝

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

Լավ՝

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

⬆ վեր

Խուսափիր բացասական պայմաններից

Վատ՝

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

Լավ՝

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

⬆ վեր

Խուսափիր պայմաններից

Սա կարծես անլուծելի մի բան լինի։ Առաջին անգամ սա լսելիս շատ մարդիկ ասում են․"Ինչպե՞ս կարող եմ անել որևէ բան առանցif֊ի"։ Պատասխանը այն է, որ շատ դեպքերում դու կարող ես օգտագործել բազմաձևությունը (պոլիմորֆիզմ) նույն խնդիրը լուծելու համար։Հաճախ տրվող երկրորդ հարցն է՝ "լավ, բայց ինչի համար է դա պետք անել"։ Պատասխանը այննախկինում սովորած մաքուր կոդի գաղափարներից մեկն է․ ֆունկցիան պիտի անի միայն մի բան։Երբ քո ֆունկցիաները և կլասները ունենif֊եր, դու ասում ես քո օգտատերերին, որ քոֆունկցիան անում է մեկ բանից ավել։ Հիշիր ՝ ֆունկցիան պիտի անի միայն մեկ բան։

Վատ՝

classAirplane{// ...getCruisingAltitude(){switch(this.type){case'777':returnthis.getMaxAltitude()-this.getPassengerCount();case'Air Force One':returnthis.getMaxAltitude();case'Cessna':returnthis.getMaxAltitude()-this.getFuelExpenditure();}}}

Լավ՝

classAirplane{// ...}classBoeing777extendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getPassengerCount();}}classAirForceOneextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude();}}classCessnaextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getFuelExpenditure();}}

⬆ վեր

Խուսափիր տիպերի ստուգումներից (մաս 1)

JavaScript֊ը տիպավորված չէ, ինչը նշանակում է, որ քո ֆունկցիաները կարող են ստանալցանկացած տիպի արգումենտ։ Հաճախ դու տուժում ես այս ազատությունից և ստիպված ես լինումստուգել տիպերը ֆունկցիաների ներսում։ Կան շատ եղանակներ սրանից խուսափելու համար։Առաջինը դիտարկիր հետևողական API֊ները։

Վատ՝

functiontravelToTexas(vehicle){if(vehicleinstanceofBicycle){vehicle.pedal(this.currentLocation,newLocation('texas'));}elseif(vehicleinstanceofCar){vehicle.drive(this.currentLocation,newLocation('texas'));}}

Լավ՝

functiontravelToTexas(vehicle){vehicle.move(this.currentLocation,newLocation('texas'));}

⬆ վեր

Խուսափիր տիպերի ստուգումներից (մաս 2)

Եթե աշխատում ես հիմնական տարրական արժեքների հետ (ինչպիսին են տողերը և ամբողջ թվերը),և չես կարող բազմաձևություն (պոլիմորֆիզմ) կիրառել, սակայն ունես տիպերի ստուգմանանհրաժեշտություն, պիտի դիտարկես TypeScript֊ի կիրառությունը։ Այն նորմալ JavasSript-իգերազանց այլընտրանք է, որովհետև առաջարկում է ստատիկ տիպավորում ի հավելումն ստանդարտJavaScript-ի սինտաքսին։ Նորմալ JavaScrpt-ում ձեռքով տիպերի ստուգման խնդիրը նրանում է,որ այն լավ անելու համար պահանջվում է շեղում կոդի ընթեռնելիությունից։ Պահիր քո JavaScript-ըմաքուր, գրիր լավ թեստեր և ունեցիր լավ կոդ-վերանայումներ (code reviews): Հակառակ դեպքում արանույնը, սակայն TypeScript֊ի օգտագործմամբ, որը ինչպես վերը նշվեց գերազանց այլընտրանք է։

Վատ՝

functioncombine(val1,val2){if(typeofval1==='number'&&typeofval2==='number'||typeofval1==='string'&&typeofval2==='string'){returnval1+val2;}thrownewError('Must be of type String or Number');}

Լավ՝

functioncombine(val1,val2){returnval1+val2;}

⬆ վեր

Մի արա գերօպտիմիզացիա

Ժամանակակից դիտարկիչները «տակից»(under-the-hood) կատարում են բազմապիսի օպտիմիզացիաներ։Հիմնականում, դու ժամանակ ես վատնում, երբ օպտիմիզացնում ես ծրագիրը։Կան լավ աղբյուրներհասկանալու համար, թե երբ է օպտիմիզացիան բացակայում։

Վատ՝

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

Լավ՝

for(leti=0;i<list.length;i++){// ...}

⬆ վեր

Հեռացրու մեռած կոդը

Մեռած կոդը նույնքան վատ է, որքան կրկնվող կոդը։ Ոչ մի պատճառ չկա այն քո ծրագրումպահելու համար։ Եթե այն չի աշխատելու, վերացրո՛ւ այն։ Այն կպահպանվի վարկածներիպատմության մեջ, եթե հանկարծ ապագայում անհրաժեշտ լինի։

Վատ՝

functionoldRequestModule(url){// ...}functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker('apples',req,'www.inventory-awesome.io');

Լավ՝

functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker('apples',req,'www.inventory-awesome.io');

⬆ վեր

Օբյեկտներ և տվյալների ստրուկտուրաներ

Օգտագործիր getter֊ներ և setter-ներ

Օբյեկնտերում տվյալներին հասնելու համար getter֊ներ և setter֊ներ օգտագործելը ավելիլավ է, քան օբյեկտի հատկանիշներին անմիջապես դիմելը։ Ինչու՞։ Ահա պատճառներիմի չդասակարգված ցանկ՝

  • եթե դու ուզում ես անել ավելին քան օբյեկտի հատկանիշից արժեքը ուղղակի վերցնելն է,քեզ պետք չի լինի փնտրել և փոփոխել այն ամեն ժառանգորդի մեջ։
  • Կարող ես հեշտությամբ անել վավերացում (validation) երբset կա
  • Ինկապսուլացնում ես ներքին իրականացումը
  • Հեշտանում է արժեքներ վերցնելու և փոխելու ժամանակ սխալների մշակումը և լոգավորումը
  • Կարող ես ալարկոտ (հետաձգված) արժեքավորում անել, օրինակ եթե վերցնում ես արժեքը սերվերից

Վատ՝

functionmakeBankAccount(){// ...return{balance:0,// ...};}constaccount=makeBankAccount();account.balance=100;

Լավ՝

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

⬆ վեր

Օբյեկտներում ունեցիր private անդամներ

Սա կարելի է իրականացնել closure֊ներով (ES5 և ցածր վարկածների համար)

Վատ՝

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

Լավ՝

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

⬆ վեր

կլասներ

Նախընտրիր ES2015/ES6 կլասները և ոչ ES5 ֆունկցիաները

Դասական ES5 կլասների համար շատ դժվար է գրել ընթեռնելի կլասների ժառանգում,կառուցում և մեթոդների հայտարարում։ Եթե քեզ անհրաժեշտ է ժառանգում՝ նախընտրիր ES2015/ES6 կլասները։Հակառակ դեպքում նախընտրիր փոքր ֆունկցիաներ, քանի դեռ չունես ավելի մեծ և բարդ օբյեկտներ կիրառելուանհրաժեշտություն։ Միշտ համոզվի՛ր, որ իրոք ժառանգության անհրաժեշտություն ունես։

Վատ՝

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

Լավ՝

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

⬆ վեր

Օգտագործիր մեթոդների շղթայավորում

Այս մոտեցումը շատ կիրառական է JavaScript֊ում և կարող ես տեսնել այն բազումգրադարաններում ինչպիսիք են jQuery֊ը և Lodash֊ը: Այն քո կոդը ավելի արտահայտիչև սակավաբառ է դարձնում։ Այդ պատճառով օգտագործիր մեթոդների շղթայավորում և կտեսնես թե որքանավելի մաքուր կդառնա կոդդ։ Քո կլասի ֆունկցիաներում վերադարձրուthis֊ը բոլորֆունկցիաների վերջում և հետագայում կարող ես շղթայավորել մեթոդները։

Վատ՝

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

Լավ՝

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

⬆ վեր

Կոմպոզիցիան նախընտրելի է ժառանգությունից

Ինչպես նշված է հայտնիDesign Patterns գրքում,դու պիտի նախընտրես կոմպոզիցիան ժառանգմանը երբ կարող ես։ Կան բազում պատճառներ թե կոմպոզիցիան և թե ժառանգումընախընտրելու համար։ Այս մտքի հիմնական կետը այն է, որ երբ քո միտքը բնազդորեն գնում է ժառանգում անելու, մտածես,որ գուցե կոմպոզիցիան կարող է ավելի լավ մոդելավորել քո խնդիրը։ Որոշ դեպքերում կարող է ․․․

Դու կարող ես զարմանալ․ «Ե՞րբ օգտագործեմ ժառանգում»։ Դա կախված է քո խնդիրց, բայց ահա մի համեստ ցանկ,թե երբ ժառանգումը ավելի իմաստալի է քան կոմպոզիցիան ՝

  1. Քո ժառանգումը նկարագրում է "է" (is-a) հարաբերությունը այլ ոչ "պատկանում է" (has-a) հարաբերությունը(Human->Animal vs. User->UserDetails)
  2. Դու կարող ես վերօգտագործել հիմնական կլասի կոդը (մարդիկ շարժվում են բոլոր կենդանիների պես)
  3. Դու ուզում ես կատարել գլոբալ փոփոխություններ ժառանգ կլասներում, փոխելով հիմնական կլասը։

Վատ՝

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

Լավ՝

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

Single Responsibility Principle (Միակ պատասխանատվության սկզբունք) (SRP)

Ինչպես ասված է «Մաքուր Կոդ»֊ում՝ "Կլասի փոփոխության համար պիտի լինի միայն մեկ պատճառ"։Շատ գայթակղիչ է մեկ կլասի մեջ շատ ֆունկցիաներ պահելը, կարծես այն թռիչքի համար միակ ճամպրուկը լինի։Խնդիրը նրանում է, որ քո կլասը չի լինի կոնցեպտուալ կապված ու ամեն պատեհ ու անպատեհ առիթով ստիպված կլինես փոփոխել այն։ Շատ կարևոր է կլասի ներսում փոփոխությունները հնարավորինս նվազեցնել։ Երբ դու փոփոխություններես կատարում մեծ ֆունկցիոնալությամբ կլասում, դժվար է վերահսկել այդ փոփոխությունների բոլոր հետևանքները։

Վատ՝

classUserSettings{constructor(user){this.user=user;}changeSettings(settings){if(this.verifyCredentials()){// ...}}verifyCredentials(){// ...}}

Լավ՝

classUserAuth{constructor(user){this.user=user;}verifyCredentials(){// ...}}classUserSettings{constructor(user){this.user=user;this.auth=newUserAuth(user);}changeSettings(settings){if(this.auth.verifyCredentials()){// ...}}}

⬆ վեր

Բացության/փակության սկզբունք (Open/Closed Principle (OCP))

Ինչպես նշել է Բերտրան Մեյերը․ «ծրագրային էությունները (կլասներ, մոդուլներ, ֆունկցիաներ, և այլն ․․․) պիտի բաց լինեն ընդլայնման համար, սակայն փակ՝ ձևափոխության համար»։ Ի՞նչ է սա նշանակում։ Այս սկզբունքըպնդում է, որ դու պիտի թույլատերս քո կոդի օգտատերերին* ընդլայնել հնարավորությունները առանց եղած կոդի ձևափոխության։

* օգտատերերը այստեղ մյուս ծրագրավորողներն են, ովքեր օգտագործում են քո կոդային բազան։

Վատ՝

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)=>{// տրանսֆորմացնել պատասխանը և վերադարձնել});}elseif(this.adapter.name==='httpNodeAdapter'){returnmakeHttpCall(url).then((response)=>{// տրանսֆորմացնել պատասխանը և վերադարձնել});}}}functionmakeAjaxCall(url){// հարցում անել և վերադարձնել promise}functionmakeHttpCall(url){// հարցում անել և վերադարձնել promise}

Լավ՝

classAjaxAdapterextendsAdapter{constructor(){super();this.name='ajaxAdapter';}request(url){// հարցում անել և վերադարձնել promise}}classNodeAdapterextendsAdapter{constructor(){super();this.name='nodeAdapter';}request(url){// հարցում անել և վերադարձնել promise}}classHttpRequester{constructor(adapter){this.adapter=adapter;}fetch(url){returnthis.adapter.request(url).then((response)=>{// տրանսֆորմացնել պատասխանը և վերադարձնել});}}

⬆ վեր

Լիսկովի փոխարինման սկզբունքը (Liskov Substitution Principle (LSP))

Սա շատ վախենալու տերմին է, որը հասարակ գաղափար է նկարագրում։ Ֆորմալ ձևակերպումը հետևյալն է․ «Եթե S-ը հանդիսանում է T-ի ենթատիպը, ապա T տիպիօբյեկտները կարող են փոխարինվել S տիպի օբյեկտներով» (այսինքն S տիպիօբյեկտները փոխարինում են T տիպի օբյեկտներին) առանց ծրագրի կարևոր հատկանիշների փոփոխության (ճշգրտություն, կատարվող առաջադրանք և այլն ․․․)

Լավագույն բացատրությունը այն է, որ եթե դու ունես մայրական և ժառանգ կլասներ, ապա մայրական և ժառանգ կլասները փոխարինելի են և փոխարինումը չի բերի սխալ արդյունքների։ Սա գուցե դեռ անհասկանալի է․ արի նայենք դասական Քառակուսի֊ՈՒղղանկյուն օրինակը։ Մաթեմատիկայի տեսանկյունից քառակուսին ուղղանկյուն է, և եթե դու մոդելավորես դրանք "է" (is-a) հարաբերությամբ ժառանգման միջոցով, կընկնես անախորժությունների մեջ։

Վատ՝

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();// Վատ: Վերադարձնում է 25  քառակուսու համար (Square). պիտի լինի 20.rectangle.render(area);});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);

Լավ՝

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

⬆ վեր

Ինտերֆեյսի բաժանման սկզբունք (Interface Segregation Principle (ISP))

JavaScript֊ը չունի ինտերֆեյսներ և այս սկզբունքը նույն խստությամբ չի կիրառվում։ Ամեն դեպքում այն կարևոր և էական է նույնիսկ JavaScript֊ում տիպերի բացակայության իրավիճակում։

ԻԲՍ (ISP) նշում է որ «օգտատերերը չպետք է ստիպված լինեն կախված լինել այն ինտերֆեյսներից, որոնք նրանք չեն օգտագործում»: Ինտերֆեյսներն անուղղակի պայմանագրեր են JavaScript-ում տիպերի համակարգի պատճառով:

Այս սկզբունքը ցուցադրող լավ օրինակ է այն դեպքը, երբ կլասը պահանջում էկարգավորումների մեծ օբյեկտ։ Օգտատերերին պետք է ազատել մեծաքանակ, այդ պահին իրենց համար ոչ անհրաժեշտ կարգավորումները գրելու անհրաժեշտությունից, քանի որ հաճախ, նրանք չունեն այդ բոլոր կարգավորումների կարիքը։ Այս կարգավորումները ընտրովի դարձնելը օգնում է խուսափել "չաղ ինտերֆեյս" ունենալուց։

Վատ՝

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(){}// Most of the time, we won't need to animate when traversing.// ...});

Լավ՝

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

⬆ վեր

Կախվածությունների շրջման սկզբունք (Dependency Inversion Principle (DIP))

Այս սկզբունքը սահմանում է երկու հիմնական բան՝

  1. Բարձր կարգի մոդուլները չպիտի կախվածություն ունենան ցածր կարգի մոդուլներից։ Երկուսն էլ պիտի կախված կլինեն աբստրակցիաներից։
  2. Աբստրակցիաները չպիտի կախված լինեն մանրամասնություններից, մանրամասնությունները պիտի կախված լինեն աբստրակցիաներից։

Սա սկզբից դժվար հասկանալի է, բայց եթե դու աշխատել ես AngularJS֊ով, ապա տեսել ես այս սկբունքի իրականացումը Կախվածության Ներարկման (Dependency Injection (DI)) տեսքով։ Չնայած որ DI֊ն և DIP-ը նույնական գաղափարներ չեն , DIP֊ը թաքցնում է ցածր կարգի մոդուլների իրականացման մանրամասները բարձր կարգի մոդուլներից։ Դա կարող է արվել DI֊ի միջոցով։ Այս մեթոդի օգտագործումը թուլացնում է շղթայակցումը մոդուլների միջև։ Մոդուլների շղթայակցումը շատ վատ նախագծման եղանակ է, որովհետև այն դժվարեցնում է կոդի վերափոխումը (refactoring)։

Ինչպես նախկինում նշվեց JavaScript֊ը չունի ինտերֆեյսներ, հետևաբար աբստրակցիաները անուղղակի պայմանագրերի վրա են հիմնվում։ Այսինքն մի կլասից/օբյեկտից մյուսին փոխանցվող մեթոդները և հատկանիշները։ Հաջորդ օրինակում անուղղակի պայմանագիրը այն է, որInventoryTracker֊ի համար Request մոդուլը պիտի ունենաrequestItems մեթոդը։

Վատ՝

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

Լավ՝

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

⬆ վեր

Թեստավորում

Թեստավորումը շատ կարևոր է։ Եթե դու չունես թեստերի բավարար քանակություն, ապա չես կարող վստահ լինել, որ վերջին փոփոխություններից հետո ոչինչ չի փչացել։

Թեստերի ադեկվատ քանակը կորոշի թիմը, սակայն կոդի 100%-ոց ծածկվածությունը թեստերով ավելացնում է վստահությունը և մտքի խաղաղությունը։

Սա նշանակում, որ թեստային լավ ֆրեյմվորք ունենալուն զուգահեռ քեզ անհրաժեշտ կլինի օգտագործել նաև լավծածկույթային գործիք:

Թեստեր չգրելու համար ոչ մի արդարացում չկա։ Կան բազում թեստավորմանֆրեյմվորքներ, այնպես, որ գտիր այն մեկը, որը հարմար է քո թիմին։ Երբ կգտնես հարմար մեկը, նպատակադրված գրիր թեստեր ցանկցած նոր մոդուլի կամ ֆունկցիայի համար։ Եթե քո նախընտրած մեթոդը "Թեստերով Առաջնորդվող Ծրագրավորումն է" (Test Driven Development (TDD)), դա լավ է, բայց հիմնական կետը դա համոզված լինելն է, որ ամեն ինչ աշխատում է այնպես ինչպես պիտի աշխատեր, ցանկացած նոր հնարավորություն ավելացնելուց կամ եղած կոդը վերափոխելուց հետո։

Ամեն թեստին միակ պարզ գաղափար

Վատ՝

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

Լավ՝

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

⬆ վեր

Concurrency

Օգտագործիր փրոմիսներ(Promises), ոչ քալբեքներ(callback)

Քալբեքները պարզ չեն և ստեղծում են մեծաքանակ ավելորդ ներդրվածություններ։ ES2015/ES6֊ով կարող եք օգտագործել Promises֊ներ, որոնք արդեն գլոբալ տիպ են։ Օգտագործի՛ր դրանք։

Վատ՝

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

Լավ՝

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

⬆ վեր

Async/Await փրոմիսներից ավելի հասկանալի է

Promises֊ը քալբեքների լավ այլընտրանք են, սակայն ES2017/ES8֊ը իր հետ բերել է async և await, որոնք ավելի մաքուր լուծումներ են առաջարկում։ Քեզ միայն անհրաժեշտ է ֆունկցիաների սահմանումը սկսելasync բառով և դու կարող ես գրել քո ֆունկցիայի տրամաբանությունը առանցthen շղթաների։

Վատ՝

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

Լավ՝

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

⬆ վեր

Սխալների մշակում

Գցված սխալները լավ բան են։ Դրանք նշանակում են որ ծրագրի աշխատանքի ժամանակ ինչ որ բան սխալ է գնացել և հնարավորություն են տալիս կանգնեցնել ֆունկցիայի կատարումը տվյալ սթեքում, սպանել պրոցեսը (Node-ում), և ծանուցել քեզ կոնսոլում stack trace ֊ով։

Մի անտեսիր բռնված սխալները

Բռնված սխալների հետ ոչինչ չանելը քեզ զրկում է սխալը ուղղելու և տվյալ սխալին արձագանքելու հնարավորությունից։Սխալները կոնսոլում տպելը (console.log) ավելի վատ է, քանի որ կարող ես խճճվել կոնսոլում տպված հաղորդագրությունների ծովում։ Եթե կոդիդ որևէ մասը «փաթաթես»try/catch֊երով, դա կնշանակի, որ դու մտածում ես, որ սխալը կարող է ի հայտ գալ այդ մասում և դու ունես պլան ինչ անել այդ իրավիճակում։

Վատ՝

try{functionThatMightThrow();}catch(error){console.log(error);}

Լավ՝

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

Մի անտեսիր մերժված փրոմիսները

Նույն պատճառով ինչtry/catch֊ի ժամանակ։

Վատ՝

getdata().then((data)=>{functionThatMightThrow(data);}).catch((error)=>{console.log(error);});

Լավ՝

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

⬆ վեր

Ֆորմատավորում

Ֆորմատավորումը սուբյեկտիվ է։ Ինչպես շատ այստեղի կանոնները, չկա կոշտ և արագ կանոն որին պիտի հետևես։ Կարևոր պայմանը ֆորմատավորման մասին չվիճելն է։ Կանբազում գործիքներ, որոնք ավտոմատացնում են այն։ Օգտագործի՛ր մեկն ու մեկը։ Վիճելը ծրագրավորողների ժամանակի և գումարի անիմաստ վատնում է։Այն բաների համար, որոնք ավտոմատ ֆորմատավորման գործիքների միջոցով չեն լուծվում (ինդենտացիան, թաբեր vs բացատներ, կրկնակի չակերտներ vs չակերտներ, և այլն ․․․), նայիր հետևյալ խորհուրդները։

Օգտագործիր հետևողական մեծատառացում (capitalization)

JavaScript֊ը տիպավորված չէ․ մեծատառացումը քո փոփոխականների, ֆունկցիաների և այլնի մասին շատ բան կարող է պատմել։ Այս կանոները սուբյեկտիվ են․ քո թիմը կարող է ընտրել այլ սկզբունքներ։ Կարևոր չի թե ինչ կընտրես, կարևորը, որ լինես հետևողական։

Վատ՝

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

Լավ՝

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

⬆ վեր

Ֆունկցիաների հայտարարումները և կանչերը դիր իրար մոտիկ

Եթե մի ֆունկցիան կանչում է մյուսին, պահիր այդ ֆունկցիաները
ուղղահայաց իրար մոտիկ։ Իդեալական տարբերակը կլինի, եթե կանչողը կանչվողի անմիջապես վերևում լինի։ Մենք կարդում ենք կոդը վերից֊վար, թերթի պես։ Այսպես կոդդ ավելի հեշտ կկարդացվի։

Վատ՝

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

Լավ՝

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

⬆ վեր

Մեկնաբանություններ

Մեկնաբանիր միայն այն մասերը, որոնք բիզնես տրամաբանության բարդություններ են պարունակում

Մեկնաբանությունը արդարացում են, ոչ պահանջ։ Լավ կոդըառավելապես մեկնաբանում է ինքն իրեն։

Վատ՝

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

Լավ՝

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

⬆ վեր

Մի պահիր մեկնաբանած կոդի կտորները քո կոդում։

Այդպիսի բաների համար կա վարկածների կառավարման համակարեր(version control)։ Թող հին կոդը պատմության մեջ։

Վատ՝

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

Լավ՝

doStuff();

⬆ վեր

Մի գրիր օրագրային մեկնաբանություններ

Հիշի՛ր, օգտագործիր վարկածի կառավարման համակարգեր։ Մեռած կոդը, մեկնաբանած կոդը և առավելապես օրագրային մեկնաբանությունները պահելու ոչ մի անհրաժեշտություն չկա։Պատմությունը տեսնելու համար օգտվիրgit log֊ից։

Վատ՝

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

Լավ՝

functioncombine(a,b){returna+b;}

⬆ վեր

Խուսափիր դիրքային նշումներից

Դրանք հաճախ ավելացնում են «աղմուկ»: Թող ֆունկցիաների և փոփոխականների անունները ճիշտ ֆորմատավորման հետ միասին ստեղծեն ծրագրիդ վիզուալ կառուցվածքը։

Վատ՝

////////////////////////////////////////////////////////////////////////////////// Scope Model Instantiation////////////////////////////////////////////////////////////////////////////////$scope.model={menu:'foo',nav:'bar'};////////////////////////////////////////////////////////////////////////////////// Action setup////////////////////////////////////////////////////////////////////////////////constactions=function(){// ...};

Լավ՝

$scope.model={menu:'foo',nav:'bar'};constactions=function(){// ...};

⬆ վեր

Թարգմանություններ

Հասանելի է նաև հետևյալ լեզուներով `

⬆ վեր

About

🛁 Clean Code concepts adapted for JavaScript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript100.0%

[8]ページ先頭

©2009-2025 Movatter.jp