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 cho #"/hienvd/clean-code-javascript/blob/master/LICENSE" data-analytics-event="{"category":"Repository Overview","action":"click","label":"location:sidebar;file:license"}" > MIT license

NotificationsYou must be signed in to change notification settings

hienvd/clean-code-javascript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 

Repository files navigation

Original Repository:ryanmcdermott/clean-code-javascript

clean-code-javascript

Mục lục

  1. Giới thiệu
  2. Biến
  3. Hàm
  4. Đối tượng và Cấu trúc dữ liệu
  5. Lớp
  6. SOLID
  7. Testing
  8. Xử lí đồng thời
  9. Xử lí lỗi
  10. Định dạng
  11. Viết chú thích
  12. Các ngôn ngữ khác

Giới thiệu

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

Những nguyên tắc kỹ thuật phần mềm, từ cuốn sáchClean Codecủa Robert C. Martin's, được áp dụng cho ngôn ngữ JavaScript. Đây không phải là một hướng dẫn về cách viết code Javascript mà là hướng dẫn về cách viết các đoạn code dễ đọc hiểu, tái sử dụng và tái cấu trúc được trong Javascript.

Không phải mọi nguyên tắc ở đây phải được tuân thủ một cách nghiêm ngặt,và thậm chí chỉ có một ít trong số đó được sử dụng phổ biến. Ở đây, nó chỉ là mộthướng dẫn - không hơn không kém, nhưng chúng được hệ thống hóa thông qua kinhnghiệm thu thập được qua nhiều năm của các tác giả của cuốn sáchClean Code

Ngành kỹ thuật phần mềm chỉ phát triển được hơn 50 năm, và chúng ta vẫnđang học rất nhiều. Một khi kiến trúc phần mềm trở thành phổ biến, có lẽ sau đóchúng ta sẽ có thêm nhiều luật lệ khó hơn phải tuân theo. Còn giờ đây,hãy để những hướng dẫn này như là một tiêu chuẩn để đánh giá chất lượng các đoạncode Javascript mà bạn và team của bạn tạo ra.

Biết những hướng dẫn này thôi sẽ không thể ngay lập tức làm bạn trở thành mộtlập trình viên phần mềm tốt hơn được, và làm việc với chúng trong nhiều nămcũng không có nghĩa bạn sẽ không gặp bất cứ sai lầm nào. Mỗi đoạn code bắt đầunhư một bản thảo đầu tiên, giống như đất sét được nặn nhào và cho tới cuối cùngthì nó sẽ lộ diện hình hài. Cuối cùng, chúng ta gọt tỉa những khuyết điểm khichúng ta xem xét lại nó cùng với các đồng nghiệp.Đừng để bản thân bạn bị đánh bại bởi những bản thảo đầu tiên,thứ mà vẫn cần phải được chỉnh sửa. Thay vào đó hãy đánh bại những dòng code.

Biến

Sử dụng tên biến có nghĩa và dễ phát âm

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Sử dụng cùng từ vựng cho cùng loại biến

Không tốt:

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

Tốt:

getUser();

⬆ về đầu trang

Sử dụng các tên có thể tìm kiếm được

Chúng ta sẽ đọc code nhiều hơn là viết chúng. Điều quan trọng là code chúng taviết có thể đọc được và tìm kiếm được. Việc đặt tên các biếnkhông có ngữnghĩa so với chương trình, chúng ta có thể sẽ làm người đọc code bị tổn thươngtinh thần.Hãy làm cho các tên biến của bạn có thể tìm kiếm được. Các công cụ nhưbuddy.jsESLintcó thể giúp nhận ra các hằng chưa được đặt tên.

Không tốt:

// 86400000 là cái quái gì thế?setTimeout(blastOff,86400000);

Tốt:

// Khai báo chúng như một biến global.constMILLISECONDS_IN_A_DAY=86400000;setTimeout(blastOff,MILLISECONDS_IN_A_DAY);

⬆ về đầu trang

Sử dụng những biến có thể giải thích được

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Tránh hại não người khác

Tường minh thì tốt hơn là ẩn.

Không tốt:

constlocations=['Austin','New York','San Francisco'];locations.forEach((l)=>{doStuff();doSomeOtherStuff();// ...// ...// ...// Khoan, `l` làm cái gì vậy?dispatch(l);});

Tốt:

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

⬆ về đầu trang

Đừng thêm những ngữ cảnh không cần thiết

Nếu tên của lớp hay đối tượng của bạn đã nói lên điều gì đó rồi, đừng lặplại điều đó trong tên biến nữa.

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Sử dụng những tham số mặc định thay vì kiểm tra các điều kiện lòng vòng

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Hàm

Đối số của hàm (lý tưởng là ít hơn hoặc bằng 2)

Giới hạn số lượng param của hàm là một điều cực kì quan trọng bởi vì nó làm chohàm của bạn trở nên dễ test hơn. Trường hợp có nhiều hơn 3 params có thể dẫnđến việc bạn phải test hàng tấn test case khác nhau với những đối số riêng biệt.

1 hoặc 2 đối số là trường hợp lý tưởng, còn trường hợp 3 đối số thì nên tránhnếu có thể. Những trường hợp khác (từ 3 params trở lên) thì nên được gộp lại.Thông thường nếu có nhiều hơn 2 đối số thì hàm của bạn đang cố thực hiện quánhiều việc rồi đấy. Trong trường hợp ngược lại, phần lớn thời gian một đốitượng cấp cao sẽ là đủ để làm đối số.

Kể từ khi Javascript cho phép tạo nhiều đối tượng một cách nhanh chóng, màkhông cần nhiều lớp có sẵn, bạn có thể sử dụng một đối tượng nếu bạn cần truyềnnhiều đối số.

Để làm cho rõ ràng một hàm mong đợi những thuộc tính gì, bạn có thể sử dụngcấu trúc destructuring của ES6. Điều này có một số ưu điểm:

  1. Khi một ai đó nhìn vào hàm, những thuộc tính nào được sử dụng sẽ trở nênrõ ràng ngay lập tức.
  2. Destructuring cũng sao chép lại các giá trị ban đầu được chỉ định của đốitượng đối số được truyền vào hàm. Điều này có thể giúp ngăn chặn các ảnh hưởngphụ. Chú ý: các đối tượng và mảng được destructure từ đối tượng đối số thì khôngđược sao chép lại.
  3. Linter có thể sẽ cảnh báo bạn về những thuộc tính không sử dụng, điều mà khôngthể xảy ra nếu không có destructuring.

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Hàm chỉ nên giải quyết một vấn đề

Đây là quy định quan trọng nhất của kỹ thuật phần mềm. Khi một hàm thực hiệnnhiều hơn 1 việc, chúng sẽ trở nên khó khăn hơn để viết code, test, và suy luận.Khi bạn có thể tách biệt một hàm để chỉ thực hiện một hành động, thì sẽ dễ dànghơn để tái cấu trúc và code của bạn sẽ dễ đọc hơn nhiều. Nếu bạn chỉ cần làm theohướng dẫn này thôi mà không cần làm gì khác thì bạn cũng đã giỏi hơn nhiềudeveloper khác rồi.

Không tốt:

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

Tốt:

functionemailClients(clients){clients.filter(isClientActive).forEach(email);}functionisClientActive(client){constclientRecord=database.lookup(client);returnclientRecord.isActive();}

⬆ Về đầu trang

Tên hàm phải nói ra được những gì chúng làm

Không tốt:

functionaddToDate(date,month){// ...}constdate=newDate();// Khó để biết được hàm này thêm gì thông qua tên hàm.addToDate(date,1);

Tốt:

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

⬆ Về đầu trang

Hàm chỉ nên có một lớp trừu tượng

Khi có nhiều hơn một lớp trừu tượng thì hàm của bạn đang làm quá nhiều. Chianhỏ các hàm ra sẽ làm cho việc test và tái sử dụng dễ dàng hơn.

Không tốt:

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

Tốt:

functiontokenize(code){constREGEXES=[// ...];conststatements=code.split(' ');consttokens=[];REGEXES.forEach((REGEX)=>{statements.forEach((statement)=>{tokens.push(/* ... */);});});returntokens;}functionlexer(tokens){constast=[];tokens.forEach((token)=>{ast.push(/* ... */);});returnast;}functionparseBetterJSAlternative(code){consttokens=tokenize(code);constast=lexer(tokens);ast.forEach((node)=>{// parse...});}

⬆ Về đầu trang

Xóa code trùng lặp

Tuyệt đối tránh những dòng code trùng lặp. Code trùng lặp thì không tốt bởi vìnếu bạn cần thay đổi cùng một logic, bạn phải sửa ở nhiều hơn một nơi.

Hãy tưởng tượng nếu bạn điều hành một nhà hàng và bạn theo dõi hàng tồn kho:bao gồm cà chua, hành tây, tỏi, gia vị, vv.... Nếu bạn có nhiều danh sáchquản lý, thì tất cả chúng phải được thay đổi khi bạn phục vụ một món ăn cóchứa cà chua. Nếu bạn chỉ có 1 danh sách, thì việc cập nhật ở một nơi thôi.

Thông thường, bạn có những dòng code lặp lại bởi vì bạn có 2 hay nhiều hơnnhững thứ chỉ khác nhau chút ít, mà chia sẻ nhiều thứ chung, nhưng sự khácnhau của chúng buộc bạn phải có 2 hay nhiều hàm riêng biệt để làm nhiều điềutương tự nhau. Xóa đi những dòng code trùng có nghĩa là tạo ra một abstractioncó thể xử lý tập những điểm khác biệt này chỉ với một hàm/module hay class.

Có được một abstraction đúng thì rất quan trọng, đó là lý do tại sao bạn nêntuân thủ các nguyên tắc SOLID được đặt ra trong phầnLớp. Những abstractionkhông tốt có thể còn tệ hơn cả những dòng code bị trùng lặp, vì thế hãy cẩnthận! Nếu bạn có thể tạo ra một abstraction tốt, hãy làm nó! Đừng lặp lại chínhmình, nếu bạn không muốn đi cập nhật nhiều nơi bất cứ khi nào bạn muốn thay đổimột thứ gì đó.

Không tốt:

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

Tốt:

functionshowEmployeeList(employees){employees.forEach((employee)=>{constexpectedSalary=employee.calculateExpectedSalary();constexperience=employee.getExperience();letportfolio=employee.getGithubLink();if(employee.type==='manager'){portfolio=employee.getMBAProjects();}constdata={      expectedSalary,      experience,      portfolio};render(data);});}

⬆ Về đầu trang

Thiết lập những đối tượng mặc định với Object.assign

Không tốt:

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

Tốt:

constmenuConfig={title:'Order',// User did not include 'body' keybuttonText:'Send',cancellable:true};functioncreateMenu(config){config=Object.assign({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true},config);// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}// ...}createMenu(menuConfig);

⬆ Về đầu trang

Đừng sử dụng các cờ như đối số của hàm

Các biến cờ cho người dùng của bạn biết rằng hàm thực hiện nhiều hơn một việc. Hàmchỉ nên làm một nhiệm vụ. Vì vậy hãy tách hàm của bạn nếu chúng đang làm cho coderẽ nhánh dựa trên một biến boolean.

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Tránh những ảnh hưởng phụ (phần 1)

Một hàm tạo ra ảnh hưởng phụ nếu nó làm bất kì điều gì khác hơn là nhận một giátrị đầu vào và trả về một hoặc nhiều giá trị. Ảnh hưởng phụ có thể là ghi mộtfile, thay đổi vài biến toàn cục, hoặc vô tình đưa tất cả tiền của bạn cho mộtngười lạ.

Bây giờ, cũng có khi bạn cần ảnh hưởng phụ trong một chương trình. Giống như ví dụtrước, bạn cần ghi một file. Những gì bạn cần làm là tập trung vào nơi bạn sẽ làmnó. Đừng viết hàm và lớp riêng biệt để tạo ra một file cụ thể. Hãy có một serviceđể viết nó. Một và chỉ một.

Điểm chính là để tránh những lỗi chung như chia sẻ trạng thái giữa những đối tượngmà không có bất kì cấu trúc nào, sử dụng các kiểu dữ liệu có thể thay đổi được màcó thể được ghi bởi bất cứ thứ gì, và không tập trung nơi có thể xảy ra các ảnh hưởngphụ. Nếu bạn có thể làm điều đó, bạn sẽ hạnh phúc hơn so với phần lớn các lập trìnhviên khác đấy.

Không tốt:

// Biến toàn cục được tham chiếu bởi hàm dưới đây.// Nếu chúng ta có một hàm khác sử dụng name, nó sẽ trở thành một arrayletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();console.log(name);// ['Ryan', 'McDermott'];

Tốt:

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

⬆ Về đầu trang

Tránh những ảnh hưởng phụ (phần 2)

Trong JavaScript, các kiểu cơ bản được truyền theo giá trị và các đốitượng/mảng được truyền theo tham chiếu. Trong trường hợp các đối tượng và mảng,ví dụ nếu hàm của chúng ta tạo ra thay đổi trong một mảng giỏ mua hàng, ví dụthêm một sản phẩm để mua, thì bất kì hàm khác mà sử dụng mảng 'giỏ hàng' sẽ bịảnh hưởng bởi việc thêm này. Điều này có thể tốt, tuy nhiên nó cũng có thể trởnên tồi tệ. Hãy tưởng tượng trường hợp xấu sau:

Người sử dụng nhấp chuột "Mua hàng", nút mua hàng sẽ gọi tới hàmmua, cái màsinh ra một yêu cầu mạng và gửi mảnggiỏ lên server. Do kết nối chậm, hàmmuacó thể giữ việc thử lại yêu cầu. Bây giờ, nếu trong thời gian đó người sử dụng vôtình nhấn chuột vào nút "Thêm vào giỏ hàng" ở một sản phẩm mà họ không thực sự muốntrước khi mạng thực hiện yêu cầu? Nếu điều đó xảy ra và mạng bắt đầu gửi yêu cầuthì hàm mua sẽ vô tình thêm một sản phẩm vì nó có một tham chiếu đế mảng giỏ hàngmà hàmthêm sản phẩm vào giỏ hàng đã thay đổi bằng cách thêm một sản phẩm mà họkhông muốn.

Một giải pháp tốt là hàmthêm sản phẩm vào giỏ hàng luôn luôn tạo một bản sao củagiỏ, thay đổi nó, và trả về bản sao đó. Điều này đảm bảo rằng không một hàm nào cónắm giữ tham chiếu của giỏ mua hàng bị ảnh hưởng bởi bất kì thay đổi.

Hai lưu ý cho cách tiếp cận này:

  1. Có thể có những trường hợp mà bạn thực sự muốn thay đổi đối tượng đầu vào, nhưngkhi bạn áp dụng phương pháp này bạn sẽ thấy những trường hợp này thì hiếm. Hầu hếtcác vấn đề có thể được cấu trúc lại để không còn ảnh hưởng phụ.
  2. Nhân bản các đối tượng lớn có thể ảnh hưởng đến hiệu năng. May mắn thay, đó khôngphải là một vấn đề lớn trong thực tế bởi vì cóimmutable-jscho phép cách tiếp cận này trở nên nhanh và ít tốn bộ nhớ so với khi bạn tự sao chépnhững đối tượng và mảng.

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Đừng ghi lên những hàm toàn cục

Gây ảnh hưởng đến các biến toàn cục là một bad practice trong JavaScript vì bạncó thể xung đột với các thư viện khác và người dùng API của bạn sẽ không biếttrước được cho đến khi cho một lỗi xảy ra trên sản phẩm. Hãy suy nghĩ ví dụ này:điều gì xảy ra nếu bạn muốn mở rộng phương thức của Array trong JavaScript nativeđể có thể có một hàmdiff chỉ ra sự khác nhau giữa hai mảng? Bạn có thể viết mộthàm mới vớiArray.prototype, nhưng nó có thể xung đột với một thư viện khác màđã làm những điều tương tự. Điều gì xảy ra nếu thư viện đó chỉ sử dụngdiff đểtìm sự khác biệt giữa phần tử đầu tiên và cuối cùng của một mảng? Đó là lý do tạisao sẽ là tốt hơn nhiều khi chỉ sử dụng các lớp ES2015/ES6 và đơn giản mở rộngArray toàn cục.

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Ủng hộ lập trình hàm hơn là lập trình mệnh lệnh

JavaScript không phải là ngôn ngữ lập trình hàm giống như là Haskell, nhưng nó cóđặc trưng hàm của nó. Những ngôn ngữ lập trình hàm thì gọn gàng hơn và dễ test hơn.Hãy dùng cách lập trình này khi bạn có thể.

Không tốt:

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

Tốt:

constprogrammerOutput=[{name:'Uncle Bobby',linesOfCode:500},{name:'Suzie Q',linesOfCode:1500},{name:'Jimmy Gosling',linesOfCode:150},{name:'Gracie Hopper',linesOfCode:1000}];constINITIAL_VALUE=0;consttotalOutput=programmerOutput.map((programmer)=>programmer.linesOfCode).reduce((acc,linesOfCode)=>acc+linesOfCode,INITIAL_VALUE);

⬆ Về đầu trang

Đóng gói các điều kiện

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Tránh điều kiện tiêu cực

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Tránh điều kiện

Đây dường như là một việc bất khả thi. Khi nghe điều này đầu tiên, hầu hết mọingười đều nói, "Làm sao tôi cần phải làm gì mà không có mệnh đềif?"Câu trả lời là bạn có thể sử dụng tính đa hình để đạt được công việc tương tựtrong rất nhiều trường hợp. Câu hỏi thứ hai thường là "Đó là điều tốt nhưng tạisao tôi lại muốn làm điều đó?" Câu trả lời là khái niệm mà ta đã học ở phầntrước: một hàm chỉ nên thực hiện một việc. Khi bạn có nhiều lớp và hàm mà cónhiều mệnh đềif, bạn đang cho người dùng của bạn biết rằng hàm của bạn đanglàm nhiều hơn một việc. Hãy nhớ, chỉ làm một công việc thôi.

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Tránh kiểm tra kiểu (phần 1)

JavaScript không định kiểu, có nghĩa hàm của bạn có thể nhận bất kì đối sốkiểu nào. Đôi khi bạn bị cám dỗ bởi sự tự do này và dễ dẫn đến việc đi kiểmtra kiểu trong hàm của mình. Có nhiều cách để tránh phải làm điều này. Điềuđầu tiên là xem xét sử dụng các API nhất quán.

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Tránh kiểm tra kiểu (phần 2)

Nếu bạn làm việc với các kiểu cơ bản như chuỗi, số nguyên và mảng, và bạn khôngthể sử dụng đa hình nhưng bạn vẫn cảm thấy cần phải kiểm tra kiểu, bạn nên xemxét sử dụng TypeScript. Nó là một phương pháp thay thế tuyệt vời cho JavaScriptthường, vì nó cung cấp kiểu tĩnh ngoài cú pháp JavaScript chuẩn. Vấn đề với việckiểm tra kiểu thủ công là để làm tốt việc này đòi hỏi nhiều sự dài dòng mà"kiểu an toàn" giả này không thay thế được cho việc mất đi tính dễ đọc của code.Hãy giữ code JavaScript của bạn sạch sẽ, viết test tốt và có reviews code tốt.Nếu không thì thực hiện tất cả những điều đó nhưng với TypeScript (giống như tôiđã nói, đó là sự thay thế tốt!).

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Đừng quá tối ưu

Những trình duyệt hiện đại làm rất nhiều tối ưu hóa bên dưới trong thời gianchạy. Rất nhiều lần, nếu bạn đang tối ưu thì bạn đang làm tốn thời gian củachính mình.Xem ở đâyđể biết khi nào việc tối ưu hóa là thiếu. Hãy thực hiện những tối ưu đó và chođến khi chúng được sửa nếu có thể.

Không tốt:

// Trên các trình duyệt cũ, mỗi lần lặp với 'list.length` chưa được cache sẽ tốn kém// vì `list.length` sẽ được tính lại. Trong các trình duyệt hiện đại, điều này đã được tối ưu.for(leti=0,len=list.length;i<len;i++){// ...}

Tốt:

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

⬆ Về đầu trang

Xóa code chết (dead code)

Dead code cũng tệ như code trùng lặp. Không có lý do gì để giữ chúng lại trongcodebase của bạn. Nếu nó không được gọi nữa, hãy bỏ nó đi! Nó vẫn sẽ nằm tronglịch sử phiên bản của bạn nếu bạn vẫn cần nó.

Không tốt:

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

Tốt:

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

⬆ Về đầu trang

Đối tượng và Cấu trúc dữ liệu

Sử dụng getter và setter

JavaScript không có interface hoặc kiểu vì vậy rất khó để thực hiện mô hình này,bởi vì chúng ta không có các từ khoá nhưpublicprivate. Vì vậy, sử dụnggetters và setters để truy cập dữ liệu trên các đối tượng thì tốt hơn là chỉ đơngiản tìm kiếm một thuộc tính trên một đối tượng. Bạn có thể hỏi "Tại sao?".Đây là một danh sách các lí do tại sao:

  • Khi bạn muốn thực hiện nhiều hơn việc lấy một thuộc tính của đối tượng, bạn khôngcần phải tìm kiếm và thay đổi mỗi accessor trong codebase của bạn.
  • Làm cho việc thêm các validation đơn giản khi thực hiện trên mộttập hợp.
  • Đóng gói các biểu diễn nội bộ.
  • Dễ dàng thêm log và xử lí lỗi khi getting và setting.
  • Kế thừa lớp này, bạn có thể override những hàm mặc định.
  • Bạn có thể lazy load các thuộc tính của một đối tượng, lấy nó từ server.

Không tốt:

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

Tốt:

functionmakeBankAccount(){// this one is privateletbalance=0;// Một "getter", thiết lập public thông qua đối tượng được trả về dưới đâyfunctiongetBalance(){returnbalance;}// Một "setter", thiết lập public thông qua đối tượng được trả về dưới đâyfunctionsetBalance(amount){// ... validate before updating the balancebalance=amount;}return{// ...    getBalance,    setBalance,};}constaccount=makeBankAccount();account.setBalance(100);

⬆ về đầu trang

Làm cho các đối tượng có thành viên private

Điều này có thể được thực hiện thông qua closures (cho ES5 và cũ hơn).

Không tốt:

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

Tốt:

constEmployee=function(name){this.getName=functiongetName(){returnname;};};constemployee=newEmployee('John Doe');console.log(`Employee name:${employee.getName()}`);// Employee name: John Doedeleteemployee.name;console.log(`Employee name:${employee.getName()}`);// Employee name: John Doe

⬆ về đầu trang

Lớp

Ưu tiên lớp ES2015/ES6 hơn các chức năng thuần ES5

Rất khó khăn để có thể đọc được lớp thừa kế, lớp khởi tạo, và các định nghĩa phương thứctrong các lớp ES5 cổ điển. Nếu bạn cần kế thừa (và lưu ý rằng bạn có thể không),tốt hơn là nên sử dụng lớp. Tuy nhiên ưu tiên sử dụng những hàm nhỏ hơn là lớp chođến khi bạn cần những đối tượng lớn và phức tạp hơn.

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Sử dụng các hàm liên tiếp nhau

Đây là một pattern rất hữu ích trong JavaScript và bạn thấy nó trong rấtnhiều thư viện chẳng hạn như jQuery và Lodash. Nó cho phép code của bạncó tính truyền tải và ngắn gọn. Vì lý do đó, theo tôi, sử dụng phương phápcác hàm liên tiếp nhau và hãy xem code của bạn sẽ sạch sẽ như thế nào. Trongcác hàm của lớp, đơn giản là trả vềthis ở cuối mỗi hàm, và bạn có thể xâuchuỗi các phương thức khác vào trong nó.

Không tốt:

classCar{constructor(){this.make='Honda';this.model='Accord';this.color='white';}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();car.setColor('pink');car.setMake('Ford');car.setModel('F-150');car.save();

Tốt:

classCar{constructor(){this.make='Honda';this.model='Accord';this.color='white';}setMake(make){this.make=make;// Ghi chú: Trả về this để xâu chuỗi các phương thứcreturnthis;}setModel(model){this.model=model;// Ghi chú: Trả về this để xâu chuỗi các phương thứcreturnthis;}setColor(color){this.color=color;// Ghi chú: Trả về this để xâu chuỗi các phương thứcreturnthis;}save(){console.log(this.make,this.model,this.color);// Ghi chú: Trả về this để xâu chuỗi các phương thứcreturnthis;}}constcar=newCar().setColor('pink').setMake('Ford').setModel('F-150').save();

⬆ về đầu trang

Ưu tiên thành phần hơn là kế thừa

Như đã được nhấn mạnh nhiều trongDesign Patternscủa Gang of Four, bạn nên sử dụng cấu trúc thành phần hơn là thừa kế nếu có thể.Có rất nhiều lý do tốt để sử dụng kế thừa cũng như sử dụng thành phần.Điểm nhấn cho phương châm này đó là nếu tâm trí của bạn đi theo bản năng thừa kế,thử nghĩ nếu thành phần có thể mô hình vấn đề của bạn tốt hơn. Trong một số trườnghợp nó có thể.

Bạn có thể tự hỏi, "khi nào tôi nên sử dụng thừa kế?" Nó phụ thuộc vàovấn đề trong tầm tay của bạn, nhưng đây là một danh sách manh nha khi kế thừacó ý nghĩa hơn thành phần:

  1. Kế thừa của bạn đại diện cho mỗi quan hệ "is-a" và không có mỗi quan hệ "has-a"(Human->Animal vs. User->UserDetails).
  2. Bạn có thể sử dụng lại code từ lớp cơ bản (Humans có thể di chuyển giống tất cả Animals).
  3. Bạn muốn làm thay đổi toàn cục đến các lớp dẫn xuất bằng cách thay đổi lớp cơ bản.(Thay đổi lượng calo của tất cả animal khi chúng di chuyển)

Không tốt:

classEmployee{constructor(name,email){this.name=name;this.email=email;}// ...}// Không tốt bởi vì Employees "có" dữ liệu thuế.// EmployeeTaxData không phải là một loại của EmployeeclassEmployeeTaxDataextendsEmployee{constructor(ssn,salary){super();this.ssn=ssn;this.salary=salary;}// ...}

Tốt:

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

⬆ về đầu trang

SOLID

Nguyên lí đơn trách nhiệm (Single Responsibility Principle)

Như đã được nói đến trong cuốn Clean Code, "Chỉ có thể thay đổi một lớp vì một lído duy nhất". Thật là hấp dẫn để nhồi nhét nhiều chức năng vào cho một lớp, giốngnhư là khi bạn chỉ có thể lấy một chiếc vali cho chuyến bay vậy. Vấn đề là lớp củabạn sẽ không được hiểu gắn kết về mặt khái niệm của nó và sẽ có rất nhiều lí dođể thay đổi. Việc làm giảm thiểu số lần bạn cần phải thay đổi một lớp là một việcquan trọng. Nó quan trọng bởi vì nếu có quá nhiều chức năng trong một lớp và bạnchỉ muốn thay đổi một chút xíu của lớp đó, thì có thể sẽ rất khó để hiểu được việcthay đổi đó sẽ ảnh hưởng đến những module khác trong codebase của bạn như thế nào.

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Nguyên lí đóng mở (Open/Closed Principle)

Betrand Meyer đã nói "có thể thoải mái mở rộng một module, nhưng hạn chế sửađổi bên trong module đó". Điều đó nghĩa là gì? Nguyên tắc này cơ bản nhấn mạnhrằng bạn phải cho phép người dùng thêm các chức năng mới mà không làm thayđổi các code đang có.

Không tốt:

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==='httpNodeAdapter'){returnmakeHttpCall(url).then((response)=>{// transform response and return});}}}functionmakeAjaxCall(url){// request and return promise}functionmakeHttpCall(url){// request and return promise}

Tốt:

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

⬆ về đầu trang

Nguyên lí thay thế Liskov (Liskov Substitution Principle)

Đây là một thuật ngữ đáng sợ cho một khái niệm rất đơn giản. Nó được định nghĩamột cách chính thức là: "Nếu S là một kiểu con của T, thì các đối tượng của kiểuT có thể được thay thế bằng các đối tượng của kiểu S (ví dụ các đối tượng củakiểu S có thể thay thế các đối tượng của kiểu T) mà không làm thay đổi bất kì thuộctính mong muốn nào của chương trình đó (tính chính xác, thực hiện tác vụ, ..).Đó thậm chí còn là một định nghĩa đáng sợ hơn.

Sự giải thích tốt nhất cho nguyên lí này là, nếu bạn có một lớp cha và một lớp con,thì lớp cơ sở và lớp con có thể được sử dụng thay thế cho nhau mà không làm thayđổi tính đúng đắn của chương trình. Có thể vẫn còn hơi rối ở đây, vậy hãy xemcái ví dụ cổ điển hình vuông-hình chữ nhật (Square-Rectangle) dưới đây. Về mặttoán học, một hình vuông là một hình chữ nhật, tuy nhiên nếu bạn mô hình hoá điềunày sử dụng quan hệ "is a" thông qua việc kế thừa, bạn sẽ nhanh chóng gặp phảirắc rối đấy.

Không tốt:

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: Will return 25 for Square. Should be 20.rectangle.render(area);});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);

Tốt:

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

⬆ về đầu trang

Nguyên lí phân tách interface (Interface Segregation Principle)

JavaScript không có interface vì vậy nguyên lí này không áp dụng một cáchchặt chẽ như các nguyên lí khác. Tuy nhiên, nó cũng quan trọng và liên quanngay cả với hệ thống thiếu định kiểu của JavaScript.

Nguyên lí phân tách interface nhấn mạnh rằng "Người dùng không nên bị bắtbuộc phải phụ thuộc vào các interfaces mà họ không sử dụng." Interface lànhững ràng buộc ẩn trong JavaScript bởi vì duck typing.

Một ví dụ tốt để minh hoạ cho nguyên lí này trong JavaScript là các lớp mà yêucầu cài đặt các đối tượng lớn. Việc không yêu cầu người dùng thiết lập một sốlượng lớn các tuỳ chọn là một ích lợi, bởi vì đa số thời gian họ không cần tấtcả các cài đặt. Làm cho chúng trở thành tuỳ chọn giúp tránh được việc có một"fat interface".

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Nguyên lí đảo ngược dependency (Dependency Inversion Principle)

Nguyên lí này khẳng định hai điều cần thiết sau:

  1. Nhưng module cấp cao không nên phụ thuộc vào những module cấp thấp. Cảhai nên phụ thuộc vào abstraction.
  2. Abstraction (interface) không nên phụ thuộc vào chi tiết, mà ngược lại.

Điều này có thể khó hiểu lúc ban đầu, nhưng nếu bạn đã từng làm việc với Angular.js,bạn đã thấy một sự hiện thực của nguyên lí này trong dạng của Dependency Injection(DI). Khi chúng không phải là các khái niệm giống nhau, DIP giữ cho module cấpcao không biết chi tiết các module cấp thấp của nó và thiết lập chúng. Có thể đạtđược điều này thông qua DI. Một lợi ích to lớn của DIP là nó làm giảm sự phụ thuộclẫn nhau giữa các module. Sự phụ thuộc lẫn nhau là một kiểu mẫu không tốt, vì nólàm cho việc tái cấu trúc code trở nên khó khăn.

Như đã khẳng định ở trước, JavaScript không có interface vì vậy các abstractionmà bị phụ thuộc là những ràng buộc ẩn. Đó là để nói, các phương thức và thuộc tínhmà một đối tượng/lớp làm phơi bày đối tượng/lớp khác. Trong ví dụ bên dưới, sự ràngbuộc ẩn là bất cứ module Request cho mộtInventoryRequester sẽ có một phương thứcrequestItems.

Không tốt:

classInventoryRequester{constructor(){this.REQ_METHODS=['HTTP'];}requestItem(item){// ...}}classInventoryTracker{constructor(items){this.items=items;// Không tốt: chúng ta đã tạo một phụ thuộc vào một hiện thực của một request cụ thể// Chúng ta nên có những requestItems phụ thuộc vào một phương thức request `request`this.requester=newInventoryRequester();}requestItems(){this.items.forEach((item)=>{this.requester.requestItem(item);});}}constinventoryTracker=newInventoryTracker(['apples','bananas']);inventoryTracker.requestItems();

Tốt:

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){// ...}}// Bằng cách xây dựng các phụ thuộc ở ngoài và thêm chúng vào, chúng ta có thể// dễ dàng thay thế module request bằng một module mới lạ sử dụng WebSockets.constinventoryTracker=newInventoryTracker(['apples','bananas'],newInventoryRequesterV2());inventoryTracker.requestItems();

⬆ về đầu trang

Testing

Testing thì quan trọng hơn shipping. Nếu bạn không có test hoặc không đủ,thì mỗi lần ship code bạn sẽ không chắc là mình có làm hư hại thứ gì không.Việc quyết định những gì để tạo thành số lượng test đủ là do team của bạn,nhưng việc có 100% độ bao phủ (tất cả các câu lệnh và rẽ nhánh) là cách đểbạn đạt được sự tự tin cao. Điều này có nghĩa ngoài việc có được một frameworkđể test tốt, bạn cũng cần sử dụng mộtcông cụ bao phủ tốt.

Không có lí do gì để không viết test. Córất nhiều framework test JS tốt,vì thế hãy tìm một framework mà team bạn thích. Khi đã tìm được một cái thíchhợp với team của mình, hãy đặt mục tiêu để luôn luôn viết test cho mỗi tínhnăng hoặc module mới của bạn. Nếu phương pháp test ưa thích của bạn là TestDriven Development (TDD), điều đó thật tuyệt, nhưng điểm quan trọng là phảichắc chắn bạn đạt được mục tiêu về độ bao phủ trước khi launch một tính nănghoặc refactor một tính năng cũ nào đó.

Một khái niệm duy nhất cho mỗi đơn vị test

Không tốt:

constassert=require('assert');describe('MakeMomentJSGreatAgain',()=>{it('handles date boundaries',()=>{letdate;date=newMakeMomentJSGreatAgain('1/1/2015');date.addDays(30);date.shouldEqual('1/31/2015');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);});});

Tốt:

constassert=require('assert');describe('MakeMomentJSGreatAgain',()=>{it('handles 30-day months',()=>{constdate=newMakeMomentJSGreatAgain('1/1/2015');date.addDays(30);date.shouldEqual('1/31/2015');});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);});});

⬆ về đầu trang

Xử lí đồng thời

Hãy dùng Promise, đừng dùng callback

Callback thì không được 'sạch sẽ' cho lắm, chúng gây ra quá nhiều đoạn code lồng nhau(callback hell). Từ ES2015/ES6, Promise đã được đưa vào Javascript. Hãy sử dụng chúng!

Không tốt:

require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin',(requestErr,response)=>{if(requestErr){console.error(requestErr);}else{require('fs').writeFile('article.html',response.body,(writeErr)=>{if(writeErr){console.error(writeErr);}else{console.log('File written');}});}});

Tốt:

require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin').then((response)=>{returnrequire('fs-promise').writeFile('article.html',response);}).then(()=>{console.log('File written');}).catch((err)=>{console.error(err);});

⬆ về đầu trang

Async/Await thì 'sạch sẽ' hơn Promise

Promise là một sự thay thế 'sạch sẽ' cho callback, nhưng ES2017/ES8 giới thiệuasync và await, đó thậm chí còn là một giải pháp tốt hơn Promise nữa. Những gìbạn cần phải làm là một hàm có tiếp đầu ngữ là từ khoáasync, và bạn có thể viếtcác lệnh logic mà không cần một chuỗithen của các hàm. Hãy sử dụng điều này nếubạn có thể tận dụng các tính năng của ES2017/ES8 ngay hôm nay!

Không tốt:

require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin').then((response)=>{returnrequire('fs-promise').writeFile('article.html',response);}).then(()=>{console.log('File written');}).catch((err)=>{console.error(err);});

Tốt:

asyncfunctiongetCleanCodeArticle(){try{constresponse=awaitrequire('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');awaitrequire('fs-promise').writeFile('article.html',response);console.log('File written');}catch(err){console.error(err);}}

⬆ về đầu trang

Xử lí lỗi

Thông báo lỗi là một điều tốt! Nghĩa là chương trình của bạn nhận dạngđược khi có một cái gì đó chạy không đúng và nó sẽ cho bạn biết bằng việcdừng chức năng mà nó đang thực thi, huỷ tiến trình (trong Node), và thôngbáo cho bạn trong console với một stack để theo dấu.

Đừng bỏ qua những lỗi đã bắt được

Nếu không làm gì với lỗi đã bắt được, bạn sẽ không thể sửa hoặc phản ứnglại được với lỗi đó. Ghi lỗi ra console (console.log) cũng không tốt hơnbao nhiêu vì đa số nó có thể bị trôi mất trong một đống những thứ được hiểnthị ra ở console. Nếu bạn đặt bất cứ đoạn code nào trong một blocktry/catch,tức là bạn nghĩ một lỗi có thể xảy ra ở đây, do đó bạn nên có một giải pháphoặc tạo một luồng code để xử lí lỗi khi nó xảy ra.

Không tốt:

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

Tốt:

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

Đừng bỏ qua những promise bị lỗi (rejected)

Cùng nguyên nhân với phần trên.

Không tốt:

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

Tốt:

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

⬆ về trang chủ

Định dạng

Việc định dạng code mang tính chủ quan. Giống như nhiều quy tắc được trìnhbày trong tài liệu này, không có quy tắc nào cứng nhắc và nhanh chóng mà bạnbắt buộc phải tuân theo. Điểm chính của phần này là ĐỪNG BAO GIỜ TRANH CÃIvề việc định dạng code như thế nào. Cóhàng tá công cụđể tự động hoá việc này. Hãy sử dụng một công cụ nào đó! Thật tốn thời gian vàtiền bạc chỉ để tranh cãi về vấn đề định dạng code.

Đối với những thứ không thuộc phạm vi của việc tự động định dạng code (thụt đầudòng, tab và space, nháy đơn và nháy kép,..) hãy xem một số hướng dẫn ở đây.

Sử dụng thống nhất cách viết hoa

Javascript là một ngôn ngữ không định kiểu, vì vậy việc viết hoa sẽ nói lên rấtnhiều về các biến, hàm,.. của bạn. Những quy tắc này thì mang tính chủ quan,vì thế team bạn có thể chọn quy tắc nào họ muốn. Tuy nhiên điều quan trọng làdù bạn chọn cách viết như thế nào, thì cũng hãy sử dụng thống nhất nó trongcodebase của bạn.

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Các hàm gọi và hàm được gọi nên nằm gần nhau

Nếu một hàm gọi một hàm khác, hãy giữ những hàm này nằm gần theo chiều dọc trongfile. Lí tưởng là, hãy giữ cho hàm gọi ở trên hàm được gọi. Chúng ta có xu hướngđọc code từ trên xuống, giống như đọc báo vậy. Do đó, hãy làm cho code của chúngta cũng được đọc theo cách đó.

Không tốt:

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(user);review.perfReview();

Tốt:

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

⬆ về đầu trang

Viết chú thích

Chỉ nên viết chú thích cho những thứ có logic phức tạp.

Các chú thích thường là lời xin lỗi, chứ không phải là yêu cầu.Những đoạn code tốt thìđa số tự nó đã là tài liệu rồi.

Không tốt:

functionhashIt(data){// Khai báo hashlethash=0;// Lấy chiều dài của chuỗiconstlength=data.length;// Lặp qua mỗi kí tựfor(leti=0;i<length;i++){// Lấy mã của kí tựconstchar=data.charCodeAt(i);// Gán giá trị cho hashhash=((hash<<5)-hash)+char;// Chuyển thành định dạng số nguyên 32 bithash&=hash;}}

Tốt:

functionhashIt(data){lethash=0;constlength=data.length;for(leti=0;i<length;i++){constchar=data.charCodeAt(i);hash=((hash<<5)-hash)+char;// Chuyển thành định dạng số nguyên 32 bithash&=hash;}}

⬆ về đầu trang

Đừng giữ lại những đoạn code bị chú thích trong codebase của bạn.

Những công cụ quản lí phiên bản sinh ra để làm nhiệm vụ của chúng.Hãy để code cũ của bạn nằm lại trong dĩ vãng đi.

Không tốt:

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

Tốt:

doStuff();

⬆ về đầu trang

Đừng viết các chú thích nhật ký.

Hãy nhớ, sử dụng công cụ quản lí phiên bản! Chúng ta không cần những đoạn codevô dụng, bị chú thích và đặc biệt là những chú thích dạng nhật ký...Sử dụnggit log để xem lịch sử được mà!

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Tránh những đánh dấu vị trí

Chúng thường xuyên làm nhiễu code. Hãy để những tên hàm, biến cùng với cácđịnh dạng thích hợp tự tạo thành cấu trúc trực quan cho code của bạn.

Không tốt:

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

Tốt:

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

⬆ về đầu trang

Các ngôn ngữ khác

Tài liệu này cũng có sẵn ở các ngôn ngữ sau:

⬆ về đầu trang

About

🛁 Clean Code cho #"/topics/javascript" title="Topic: javascript" data-view-component="true"> javascript clean-code composition inheritance clean-architecture principles

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors3

  •  
  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp