├── .gitattributes ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=JavaScript 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hien Vuong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Original Repository: [ryanmcdermott/clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 2 | 3 | # clean-code-javascript 4 | 5 | ## Mục lục 6 | 1. [Giới thiệu](#giới-thiệu) 7 | 2. [Biến](#biến) 8 | 3. [Hàm](#hàm) 9 | 4. [Đối tượng và Cấu trúc dữ liệu](#Đối-tượng-và-cấu-trúc-dữ-liệu) 10 | 5. [Lớp](#lớp) 11 | 6. [SOLID](#solid) 12 | 7. [Testing](#testing) 13 | 8. [Xử lí đồng thời](#xử-lí-đồng-thời) 14 | 9. [Xử lí lỗi](#xử-lí-lỗi) 15 | 10. [Định dạng](#Định-dạng) 16 | 11. [Viết chú thích](#viết-chú-thích) 17 | 12. [Các ngôn ngữ khác](#các-ngôn-ngữ-khác) 18 | 19 | ## Giới thiệu 20 | ![Humorous image of software quality estimation as a count of how many expletives 21 | you shout when reading code](http://www.osnews.com/images/comics/wtfm.jpg) 22 | 23 | Những nguyên tắc kỹ thuật phần mềm, từ cuốn sách [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 24 | củ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. 25 | 26 | 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, 27 | và thậm chí chỉ có một ít trong số đó được sử dụng phổ biến. Ở đây, nó chỉ là một 28 | hướng dẫn - không hơn không kém, nhưng chúng được hệ thống hóa thông qua kinh 29 | nghiệm thu thập được qua nhiều năm của các tác giả của cuốn sách *Clean Code* 30 | 31 | 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 32 | đ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 đó 33 | chúng ta sẽ có thêm nhiều luật lệ khó hơn phải tuân theo. Còn giờ đây, 34 | 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ạn 35 | code Javascript mà bạn và team của bạn tạo ra. 36 | 37 | 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ột 38 | lậ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ăm 39 | cũ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 đầu 40 | như 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ùng 41 | thì 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 khi 42 | chúng ta xem xét lại nó cùng với các đồng nghiệp. 43 | Đừng để bản thân bạn bị đánh bại bởi những bản thảo đầu tiên, 44 | 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. 45 | 46 | ## **Biến** 47 | ### Sử dụng tên biến có nghĩa và dễ phát âm 48 | 49 | **Không tốt:** 50 | ```javascript 51 | const yyyymmdstr = moment().format('YYYY/MM/DD'); 52 | ``` 53 | 54 | **Tốt:** 55 | ```javascript 56 | const currentDate = moment().format('YYYY/MM/DD'); 57 | ``` 58 | **[⬆ về đầu trang](#mục-lục)** 59 | 60 | ### Sử dụng cùng từ vựng cho cùng loại biến 61 | 62 | **Không tốt:** 63 | ```javascript 64 | getUserInfo(); 65 | getClientData(); 66 | getCustomerRecord(); 67 | ``` 68 | 69 | **Tốt:** 70 | ```javascript 71 | getUser(); 72 | ``` 73 | **[⬆ về đầu trang](#mục-lục)** 74 | 75 | ### Sử dụng các tên có thể tìm kiếm được 76 | Chúng ta sẽ đọc code nhiều hơn là viết chúng. Điều quan trọng là code chúng ta 77 | viết có thể đọc được và tìm kiếm được. Việc đặt tên các biến *không* có ngữ 78 | 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ương 79 | tinh thần. 80 | 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ư 81 | [buddy.js](https://github.com/danielstjules/buddy.js) và 82 | [ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) 83 | có thể giúp nhận ra các hằng chưa được đặt tên. 84 | 85 | **Không tốt:** 86 | ```javascript 87 | // 86400000 là cái quái gì thế? 88 | setTimeout(blastOff, 86400000); 89 | 90 | ``` 91 | 92 | **Tốt:** 93 | ```javascript 94 | // Khai báo chúng như một biến global. 95 | const MILLISECONDS_IN_A_DAY = 86400000; 96 | 97 | setTimeout(blastOff, MILLISECONDS_IN_A_DAY); 98 | 99 | ``` 100 | **[⬆ về đầu trang](#mục-lục)** 101 | 102 | ### Sử dụng những biến có thể giải thích được 103 | **Không tốt:** 104 | ```javascript 105 | const address = 'One Infinite Loop, Cupertino 95014'; 106 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 107 | saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]); 108 | ``` 109 | 110 | **Tốt:** 111 | ```javascript 112 | const address = 'One Infinite Loop, Cupertino 95014'; 113 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 114 | const [, city, zipCode] = address.match(cityZipCodeRegex) || []; 115 | saveCityZipCode(city, zipCode); 116 | ``` 117 | **[⬆ về đầu trang](#mục-lục)** 118 | 119 | ### Tránh hại não người khác 120 | Tường minh thì tốt hơn là ẩn. 121 | 122 | **Không tốt:** 123 | ```javascript 124 | const locations = ['Austin', 'New York', 'San Francisco']; 125 | locations.forEach((l) => { 126 | doStuff(); 127 | doSomeOtherStuff(); 128 | // ... 129 | // ... 130 | // ... 131 | // Khoan, `l` làm cái gì vậy? 132 | dispatch(l); 133 | }); 134 | ``` 135 | 136 | **Tốt:** 137 | ```javascript 138 | const locations = ['Austin', 'New York', 'San Francisco']; 139 | locations.forEach((location) => { 140 | doStuff(); 141 | doSomeOtherStuff(); 142 | // ... 143 | // ... 144 | // ... 145 | dispatch(location); 146 | }); 147 | ``` 148 | **[⬆ về đầu trang](#mục-lục)** 149 | 150 | ### Đừng thêm những ngữ cảnh không cần thiết 151 | 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ặp 152 | lại điều đó trong tên biến nữa. 153 | 154 | **Không tốt:** 155 | ```javascript 156 | const Car = { 157 | carMake: 'Honda', 158 | carModel: 'Accord', 159 | carColor: 'Blue' 160 | }; 161 | 162 | function paintCar(car, color) { 163 | car.carColor = color; 164 | } 165 | ``` 166 | 167 | **Tốt:** 168 | ```javascript 169 | const Car = { 170 | make: 'Honda', 171 | model: 'Accord', 172 | color: 'Blue' 173 | }; 174 | 175 | function paintCar(car, color) { 176 | car.color = color; 177 | } 178 | ``` 179 | **[⬆ về đầu trang](#mục-lục)** 180 | 181 | ### 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 182 | 183 | **Không tốt:** 184 | ```javascript 185 | function createMicrobrewery(name) { 186 | const breweryName = name || 'Hipster Brew Co.'; 187 | // ... 188 | } 189 | 190 | ``` 191 | 192 | **Tốt:** 193 | ```javascript 194 | function createMicrobrewery(breweryName = 'Hipster Brew Co.') { 195 | // ... 196 | } 197 | 198 | ``` 199 | **[⬆ về đầu trang](#mục-lục)** 200 | 201 | ## **Hàm** 202 | ### Đối số của hàm (lý tưởng là ít hơn hoặc bằng 2) 203 | 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 cho 204 | hà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 205 | đế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. 206 | 207 | 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ánh 208 | nế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. 209 | 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á 210 | 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 đối 211 | tượng cấp cao sẽ là đủ để làm đối số. 212 | 213 | Kể từ khi Javascript cho phép tạo nhiều đối tượng một cách nhanh chóng, mà 214 | 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ền 215 | nhiều đối số. 216 | 217 | Để 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ụng 218 | cấu trúc destructuring của ES6. Điều này có một số ưu điểm: 219 | 220 | 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ên 221 | rõ ràng ngay lập tức. 222 | 2. Destructuring cũng sao chép lại các giá trị ban đầu được chỉ định của đối 223 | tượ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ưởng 224 | phụ. Chú ý: các đối tượng và mảng được destructure từ đối tượng đối số thì không 225 | được sao chép lại. 226 | 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ông 227 | thể xảy ra nếu không có destructuring. 228 | 229 | **Không tốt:** 230 | ```javascript 231 | function createMenu(title, body, buttonText, cancellable) { 232 | // ... 233 | } 234 | ``` 235 | 236 | **Tốt:** 237 | ```javascript 238 | function createMenu({ title, body, buttonText, cancellable }) { 239 | // ... 240 | } 241 | 242 | createMenu({ 243 | title: 'Foo', 244 | body: 'Bar', 245 | buttonText: 'Baz', 246 | cancellable: true 247 | }); 248 | ``` 249 | **[⬆ Về đầu trang](#mục-lục)** 250 | 251 | 252 | ### Hàm chỉ nên giải quyết một vấn đề 253 | Đâ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ện 254 | nhiề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. 255 | 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àng 256 | hơ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 theo 257 | hướ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ều 258 | developer khác rồi. 259 | 260 | **Không tốt:** 261 | ```javascript 262 | function emailClients(clients) { 263 | clients.forEach((client) => { 264 | const clientRecord = database.lookup(client); 265 | if (clientRecord.isActive()) { 266 | email(client); 267 | } 268 | }); 269 | } 270 | ``` 271 | 272 | **Tốt:** 273 | ```javascript 274 | function emailClients(clients) { 275 | clients 276 | .filter(isClientActive) 277 | .forEach(email); 278 | } 279 | 280 | function isClientActive(client) { 281 | const clientRecord = database.lookup(client); 282 | return clientRecord.isActive(); 283 | } 284 | ``` 285 | **[⬆ Về đầu trang](#mục-lục)** 286 | 287 | ### Tên hàm phải nói ra được những gì chúng làm 288 | 289 | **Không tốt:** 290 | ```javascript 291 | function addToDate(date, month) { 292 | // ... 293 | } 294 | 295 | const date = new Date(); 296 | 297 | // Khó để biết được hàm này thêm gì thông qua tên hàm. 298 | addToDate(date, 1); 299 | ``` 300 | 301 | **Tốt:** 302 | ```javascript 303 | function addMonthToDate(month, date) { 304 | // ... 305 | } 306 | 307 | const date = new Date(); 308 | addMonthToDate(1, date); 309 | ``` 310 | **[⬆ Về đầu trang](#mục-lục)** 311 | 312 | ### Hàm chỉ nên có một lớp trừu tượng 313 | 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. Chia 314 | nhỏ các hàm ra sẽ làm cho việc test và tái sử dụng dễ dàng hơn. 315 | 316 | **Không tốt:** 317 | ```javascript 318 | function parseBetterJSAlternative(code) { 319 | const REGEXES = [ 320 | // ... 321 | ]; 322 | 323 | const statements = code.split(' '); 324 | const tokens = []; 325 | REGEXES.forEach((REGEX) => { 326 | statements.forEach((statement) => { 327 | // ... 328 | }); 329 | }); 330 | 331 | const ast = []; 332 | tokens.forEach((token) => { 333 | // lex... 334 | }); 335 | 336 | ast.forEach((node) => { 337 | // parse... 338 | }); 339 | } 340 | ``` 341 | 342 | **Tốt:** 343 | ```javascript 344 | function tokenize(code) { 345 | const REGEXES = [ 346 | // ... 347 | ]; 348 | 349 | const statements = code.split(' '); 350 | const tokens = []; 351 | REGEXES.forEach((REGEX) => { 352 | statements.forEach((statement) => { 353 | tokens.push( /* ... */ ); 354 | }); 355 | }); 356 | 357 | return tokens; 358 | } 359 | 360 | function lexer(tokens) { 361 | const ast = []; 362 | tokens.forEach((token) => { 363 | ast.push( /* ... */ ); 364 | }); 365 | 366 | return ast; 367 | } 368 | 369 | function parseBetterJSAlternative(code) { 370 | const tokens = tokenize(code); 371 | const ast = lexer(tokens); 372 | ast.forEach((node) => { 373 | // parse... 374 | }); 375 | } 376 | ``` 377 | **[⬆ Về đầu trang](#mục-lục)** 378 | 379 | ### Xóa code trùng lặp 380 | 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ì 381 | 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. 382 | 383 | 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: 384 | bao gồm cà chua, hành tây, tỏi, gia vị, vv.... Nếu bạn có nhiều danh sách 385 | quả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ó 386 | 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. 387 | 388 | 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ơn 389 | những thứ chỉ khác nhau chút ít, mà chia sẻ nhiều thứ chung, nhưng sự khác 390 | nhau 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ều 391 | tương tự nhau. Xóa đi những dòng code trùng có nghĩa là tạo ra một abstraction 392 | có 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. 393 | 394 | Có được một abstraction đúng thì rất quan trọng, đó là lý do tại sao bạn nên 395 | tuân thủ các nguyên tắc SOLID được đặt ra trong phần *Lớp*. Những abstraction 396 | khô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ẩn 397 | thậ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ính 398 | mì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 đổi 399 | một thứ gì đó. 400 | 401 | **Không tốt:** 402 | ```javascript 403 | function showDeveloperList(developers) { 404 | developers.forEach((developer) => { 405 | const expectedSalary = developer.calculateExpectedSalary(); 406 | const experience = developer.getExperience(); 407 | const githubLink = developer.getGithubLink(); 408 | const data = { 409 | expectedSalary, 410 | experience, 411 | githubLink 412 | }; 413 | 414 | render(data); 415 | }); 416 | } 417 | 418 | function showManagerList(managers) { 419 | managers.forEach((manager) => { 420 | const expectedSalary = manager.calculateExpectedSalary(); 421 | const experience = manager.getExperience(); 422 | const portfolio = manager.getMBAProjects(); 423 | const data = { 424 | expectedSalary, 425 | experience, 426 | portfolio 427 | }; 428 | 429 | render(data); 430 | }); 431 | } 432 | ``` 433 | 434 | **Tốt:** 435 | ```javascript 436 | function showEmployeeList(employees) { 437 | employees.forEach((employee) => { 438 | const expectedSalary = employee.calculateExpectedSalary(); 439 | const experience = employee.getExperience(); 440 | 441 | let portfolio = employee.getGithubLink(); 442 | 443 | if (employee.type === 'manager') { 444 | portfolio = employee.getMBAProjects(); 445 | } 446 | 447 | const data = { 448 | expectedSalary, 449 | experience, 450 | portfolio 451 | }; 452 | 453 | render(data); 454 | }); 455 | } 456 | ``` 457 | **[⬆ Về đầu trang](#mục-lục)** 458 | 459 | ### Thiết lập những đối tượng mặc định với Object.assign 460 | 461 | **Không tốt:** 462 | ```javascript 463 | const menuConfig = { 464 | title: null, 465 | body: 'Bar', 466 | buttonText: null, 467 | cancellable: true 468 | }; 469 | 470 | function createMenu(config) { 471 | config.title = config.title || 'Foo'; 472 | config.body = config.body || 'Bar'; 473 | config.buttonText = config.buttonText || 'Baz'; 474 | config.cancellable = config.cancellable === undefined ? config.cancellable : true; 475 | } 476 | 477 | createMenu(menuConfig); 478 | ``` 479 | 480 | **Tốt:** 481 | ```javascript 482 | const menuConfig = { 483 | title: 'Order', 484 | // User did not include 'body' key 485 | buttonText: 'Send', 486 | cancellable: true 487 | }; 488 | 489 | function createMenu(config) { 490 | config = Object.assign({ 491 | title: 'Foo', 492 | body: 'Bar', 493 | buttonText: 'Baz', 494 | cancellable: true 495 | }, config); 496 | 497 | // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} 498 | // ... 499 | } 500 | 501 | createMenu(menuConfig); 502 | ``` 503 | **[⬆ Về đầu trang](#mục-lục)** 504 | 505 | 506 | ### Đừng sử dụng các cờ như đối số của hàm 507 | 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àm 508 | chỉ 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 code 509 | rẽ nhánh dựa trên một biến boolean. 510 | 511 | **Không tốt:** 512 | ```javascript 513 | function createFile(name, temp) { 514 | if (temp) { 515 | fs.create(`./temp/${name}`); 516 | } else { 517 | fs.create(name); 518 | } 519 | } 520 | ``` 521 | 522 | **Tốt:** 523 | ```javascript 524 | function createFile(name) { 525 | fs.create(name); 526 | } 527 | 528 | function createTempFile(name) { 529 | createFile(`./temp/${name}`); 530 | } 531 | ``` 532 | **[⬆ Về đầu trang](#mục-lục)** 533 | 534 | ### Tránh những ảnh hưởng phụ (phần 1) 535 | 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á 536 | trị đầu vào và trả về một hoặc nhiều giá trị. Ảnh hưởng phụ có thể là ghi một 537 | file, 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ột 538 | người lạ. 539 | 540 | 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ụ 541 | 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àm 542 | nó. Đừ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 543 | để viết nó. Một và chỉ một. 544 | 545 | Đ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ượng 546 | mà 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à 547 | 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ưởng 548 | phụ. 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ình 549 | viên khác đấy. 550 | 551 | **Không tốt:** 552 | ```javascript 553 | // Biến toàn cục được tham chiếu bởi hàm dưới đây. 554 | // Nếu chúng ta có một hàm khác sử dụng name, nó sẽ trở thành một array 555 | let name = 'Ryan McDermott'; 556 | 557 | function splitIntoFirstAndLastName() { 558 | name = name.split(' '); 559 | } 560 | 561 | splitIntoFirstAndLastName(); 562 | 563 | console.log(name); // ['Ryan', 'McDermott']; 564 | ``` 565 | 566 | **Tốt:** 567 | ```javascript 568 | function splitIntoFirstAndLastName(name) { 569 | return name.split(' '); 570 | } 571 | 572 | const name = 'Ryan McDermott'; 573 | const newName = splitIntoFirstAndLastName(name); 574 | 575 | console.log(name); // 'Ryan McDermott'; 576 | console.log(newName); // ['Ryan', 'McDermott']; 577 | ``` 578 | **[⬆ Về đầu trang](#mục-lục)** 579 | 580 | ### Tránh những ảnh hưởng phụ (phần 2) 581 | Trong JavaScript, các kiểu cơ bản được truyền theo giá trị và các đối 582 | tượng/mảng được truyền theo tham chiếu. Trong trường hợp các đối tượng và mảng, 583 | 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ụ 584 | 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ị 585 | ả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ở 586 | nên tồi tệ. Hãy tưởng tượng trường hợp xấu sau: 587 | 588 | Người sử dụng nhấp chuột "Mua hàng", nút mua hàng sẽ gọi tới hàm `mua`, cái mà 589 | sinh ra một yêu cầu mạng và gửi mảng `giỏ` lên server. Do kết nối chậm, hàm `mua` 590 | có 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ô 591 | 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ốn 592 | trướ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ầu 593 | thì 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àng 594 | mà hàm `thê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ọ 595 | không muốn. 596 | 597 | Một giải pháp tốt là hàm `thêm sản phẩm vào giỏ hàng` luôn luôn tạo một bản sao của 598 | `giỏ`, 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ó 599 | nắm giữ tham chiếu của giỏ mua hàng bị ảnh hưởng bởi bất kì thay đổi. 600 | 601 | Hai lưu ý cho cách tiếp cận này: 602 | 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ưng 603 | khi 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ết 604 | các vấn đề có thể được cấu trúc lại để không còn ảnh hưởng phụ. 605 | 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ông 606 | phải là một vấn đề lớn trong thực tế bởi vì có [immutable-js](https://facebook.github.io/immutable-js/) 607 | cho 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ép 608 | những đối tượng và mảng. 609 | 610 | **Không tốt:** 611 | ```javascript 612 | const addItemToCart = (cart, item) => { 613 | cart.push({ item, date: Date.now() }); 614 | }; 615 | ``` 616 | 617 | **Tốt:** 618 | ```javascript 619 | const addItemToCart = (cart, item) => { 620 | return [...cart, { item, date : Date.now() }]; 621 | }; 622 | ``` 623 | 624 | **[⬆ Về đầu trang](#mục-lục)** 625 | 626 | ### Đừng ghi lên những hàm toàn cục 627 | Gây ảnh hưởng đến các biến toàn cục là một bad practice trong JavaScript vì bạn 628 | có 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ết 629 | trướ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: 630 | đ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 631 | để có thể có một hàm `diff` chỉ ra sự khác nhau giữa hai mảng? Bạn có thể viết một 632 | hàm mới với `Array.prototype`, nhưng nó có thể xung đột với một thư viện khác mà 633 | đã làm những điều tương tự. Điều gì xảy ra nếu thư viện đó chỉ sử dụng `diff` để 634 | 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ại 635 | sao 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ộng 636 | `Array` toàn cục. 637 | 638 | **Không tốt:** 639 | ```javascript 640 | Array.prototype.diff = function diff(comparisonArray) { 641 | const hash = new Set(comparisonArray); 642 | return this.filter(elem => !hash.has(elem)); 643 | }; 644 | ``` 645 | 646 | **Tốt:** 647 | ```javascript 648 | class SuperArray extends Array { 649 | diff(comparisonArray) { 650 | const hash = new Set(comparisonArray); 651 | return this.filter(elem => !hash.has(elem)); 652 | } 653 | } 654 | ``` 655 | **[⬆ Về đầu trang](#mục-lục)** 656 | 657 | ### Ủng hộ lập trình hàm hơn là lập trình mệnh lệnh 658 | JavaScript không phải là ngôn ngữ lập trình hàm giống như là Haskell, nhưng nó có 659 | đặ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. 660 | Hãy dùng cách lập trình này khi bạn có thể. 661 | 662 | **Không tốt:** 663 | ```javascript 664 | const programmerOutput = [ 665 | { 666 | name: 'Uncle Bobby', 667 | linesOfCode: 500 668 | }, { 669 | name: 'Suzie Q', 670 | linesOfCode: 1500 671 | }, { 672 | name: 'Jimmy Gosling', 673 | linesOfCode: 150 674 | }, { 675 | name: 'Gracie Hopper', 676 | linesOfCode: 1000 677 | } 678 | ]; 679 | 680 | let totalOutput = 0; 681 | 682 | for (let i = 0; i < programmerOutput.length; i++) { 683 | totalOutput += programmerOutput[i].linesOfCode; 684 | } 685 | ``` 686 | 687 | **Tốt:** 688 | ```javascript 689 | const programmerOutput = [ 690 | { 691 | name: 'Uncle Bobby', 692 | linesOfCode: 500 693 | }, { 694 | name: 'Suzie Q', 695 | linesOfCode: 1500 696 | }, { 697 | name: 'Jimmy Gosling', 698 | linesOfCode: 150 699 | }, { 700 | name: 'Gracie Hopper', 701 | linesOfCode: 1000 702 | } 703 | ]; 704 | 705 | const INITIAL_VALUE = 0; 706 | 707 | const totalOutput = programmerOutput 708 | .map((programmer) => programmer.linesOfCode) 709 | .reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE); 710 | ``` 711 | **[⬆ Về đầu trang](#mục-lục)** 712 | 713 | ### Đóng gói các điều kiện 714 | 715 | **Không tốt:** 716 | ```javascript 717 | if (fsm.state === 'fetching' && isEmpty(listNode)) { 718 | // ... 719 | } 720 | ``` 721 | 722 | **Tốt:** 723 | ```javascript 724 | function shouldShowSpinner(fsm, listNode) { 725 | return fsm.state === 'fetching' && isEmpty(listNode); 726 | } 727 | 728 | if (shouldShowSpinner(fsmInstance, listNodeInstance)) { 729 | // ... 730 | } 731 | ``` 732 | **[⬆ Về đầu trang](#mục-lục)** 733 | 734 | ### Tránh điều kiện tiêu cực 735 | 736 | **Không tốt:** 737 | ```javascript 738 | function isDOMNodeNotPresent(node) { 739 | // ... 740 | } 741 | 742 | if (!isDOMNodeNotPresent(node)) { 743 | // ... 744 | } 745 | ``` 746 | 747 | **Tốt:** 748 | ```javascript 749 | function isDOMNodePresent(node) { 750 | // ... 751 | } 752 | 753 | if (isDOMNodePresent(node)) { 754 | // ... 755 | } 756 | ``` 757 | **[⬆ Về đầu trang](#mục-lục)** 758 | 759 | ### Tránh điều kiện 760 | Đâ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ọi 761 | người đều nói, "Làm sao tôi cần phải làm gì mà không có mệnh đề `if`?" 762 | 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ự 763 | 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ại 764 | sao 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ần 765 | trướ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ó 766 | 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 đang 767 | làm nhiều hơn một việc. Hãy nhớ, chỉ làm một công việc thôi. 768 | 769 | **Không tốt:** 770 | ```javascript 771 | class Airplane { 772 | // ... 773 | getCruisingAltitude() { 774 | switch (this.type) { 775 | case '777': 776 | return this.getMaxAltitude() - this.getPassengerCount(); 777 | case 'Air Force One': 778 | return this.getMaxAltitude(); 779 | case 'Cessna': 780 | return this.getMaxAltitude() - this.getFuelExpenditure(); 781 | } 782 | } 783 | } 784 | ``` 785 | 786 | **Tốt:** 787 | ```javascript 788 | class Airplane { 789 | // ... 790 | } 791 | 792 | class Boeing777 extends Airplane { 793 | // ... 794 | getCruisingAltitude() { 795 | return this.getMaxAltitude() - this.getPassengerCount(); 796 | } 797 | } 798 | 799 | class AirForceOne extends Airplane { 800 | // ... 801 | getCruisingAltitude() { 802 | return this.getMaxAltitude(); 803 | } 804 | } 805 | 806 | class Cessna extends Airplane { 807 | // ... 808 | getCruisingAltitude() { 809 | return this.getMaxAltitude() - this.getFuelExpenditure(); 810 | } 811 | } 812 | ``` 813 | **[⬆ Về đầu trang](#mục-lục)** 814 | 815 | ### Tránh kiểm tra kiểu (phần 1) 816 | JavaScript không định kiểu, có nghĩa hàm của bạn có thể nhận bất kì đối số 817 | 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ểm 818 | tra 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 819 | đầu tiên là xem xét sử dụng các API nhất quán. 820 | 821 | **Không tốt:** 822 | ```javascript 823 | function travelToTexas(vehicle) { 824 | if (vehicle instanceof Bicycle) { 825 | vehicle.peddle(this.currentLocation, new Location('texas')); 826 | } else if (vehicle instanceof Car) { 827 | vehicle.drive(this.currentLocation, new Location('texas')); 828 | } 829 | } 830 | ``` 831 | 832 | **Tốt:** 833 | ```javascript 834 | function travelToTexas(vehicle) { 835 | vehicle.move(this.currentLocation, new Location('texas')); 836 | } 837 | ``` 838 | **[⬆ Về đầu trang](#mục-lục)** 839 | 840 | ### Tránh kiểm tra kiểu (phần 2) 841 | 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ông 842 | thể 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 xem 843 | xét sử dụng TypeScript. Nó là một phương pháp thay thế tuyệt vời cho JavaScript 844 | thường, vì nó cung cấp kiểu tĩnh ngoài cú pháp JavaScript chuẩn. Vấn đề với việc 845 | kiể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à 846 | "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. 847 | Hãy giữ code JavaScript của bạn sạch sẽ, viết test tốt và có reviews code tốt. 848 | 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 849 | đã nói, đó là sự thay thế tốt!). 850 | 851 | **Không tốt:** 852 | ```javascript 853 | function combine(val1, val2) { 854 | if (typeof val1 === 'number' && typeof val2 === 'number' || 855 | typeof val1 === 'string' && typeof val2 === 'string') { 856 | return val1 + val2; 857 | } 858 | 859 | throw new Error('Must be of type String or Number'); 860 | } 861 | ``` 862 | 863 | **Tốt:** 864 | ```javascript 865 | function combine(val1, val2) { 866 | return val1 + val2; 867 | } 868 | ``` 869 | **[⬆ Về đầu trang](#mục-lục)** 870 | 871 | ### Đừng quá tối ưu 872 | 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 gian 873 | chạ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ủa 874 | chính mình. [Xem ở đây](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) 875 | để 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 876 | đến khi chúng được sửa nếu có thể. 877 | 878 | **Không tốt:** 879 | ```javascript 880 | 881 | // 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 882 | // 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. 883 | for (let i = 0, len = list.length; i < len; i++) { 884 | // ... 885 | } 886 | ``` 887 | 888 | **Tốt:** 889 | ```javascript 890 | for (let i = 0; i < list.length; i++) { 891 | // ... 892 | } 893 | ``` 894 | **[⬆ Về đầu trang](#mục-lục)** 895 | 896 | ### Xóa code chết (dead code) 897 | Dead code cũng tệ như code trùng lặp. Không có lý do gì để giữ chúng lại trong 898 | codebase 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 trong 899 | lịch sử phiên bản của bạn nếu bạn vẫn cần nó. 900 | 901 | **Không tốt:** 902 | ```javascript 903 | function oldRequestModule(url) { 904 | // ... 905 | } 906 | 907 | function newRequestModule(url) { 908 | // ... 909 | } 910 | 911 | const req = newRequestModule; 912 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 913 | 914 | ``` 915 | 916 | **Tốt:** 917 | ```javascript 918 | function newRequestModule(url) { 919 | // ... 920 | } 921 | 922 | const req = newRequestModule; 923 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 924 | ``` 925 | **[⬆ Về đầu trang](#mục-lục)** 926 | 927 | ## **Đối tượng và Cấu trúc dữ liệu** 928 | ### Sử dụng getter và setter 929 | JavaScript không có interface hoặc kiểu vì vậy rất khó để thực hiện mô hình này, 930 | bởi vì chúng ta không có các từ khoá như `public` và `private`. Vì vậy, sử dụng 931 | getters và setters để truy cập dữ liệu trên các đối tượng thì tốt hơn là chỉ đơn 932 | giả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?". 933 | Đây là một danh sách các lí do tại sao: 934 | 935 | * 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ông 936 | cần phải tìm kiếm và thay đổi mỗi accessor trong codebase của bạn. 937 | * Làm cho việc thêm các validation đơn giản khi thực hiện trên một `tập hợp`. 938 | * Đóng gói các biểu diễn nội bộ. 939 | * Dễ dàng thêm log và xử lí lỗi khi getting và setting. 940 | * Kế thừa lớp này, bạn có thể override những hàm mặc định. 941 | * Bạn có thể lazy load các thuộc tính của một đối tượng, lấy nó từ server. 942 | 943 | 944 | **Không tốt:** 945 | ```javascript 946 | function makeBankAccount() { 947 | // ... 948 | 949 | return { 950 | balance: 0, 951 | // ... 952 | }; 953 | } 954 | 955 | const account = makeBankAccount(); 956 | account.balance = 100; 957 | ``` 958 | 959 | **Tốt:** 960 | ```javascript 961 | function makeBankAccount() { 962 | // this one is private 963 | let balance = 0; 964 | 965 | // Một "getter", thiết lập public thông qua đối tượng được trả về dưới đây 966 | function getBalance() { 967 | return balance; 968 | } 969 | 970 | // Một "setter", thiết lập public thông qua đối tượng được trả về dưới đây 971 | function setBalance(amount) { 972 | // ... validate before updating the balance 973 | balance = amount; 974 | } 975 | 976 | return { 977 | // ... 978 | getBalance, 979 | setBalance, 980 | }; 981 | } 982 | 983 | const account = makeBankAccount(); 984 | account.setBalance(100); 985 | 986 | ``` 987 | **[⬆ về đầu trang](#mục-lục)** 988 | 989 | 990 | ### Làm cho các đối tượng có thành viên private 991 | Điều này có thể được thực hiện thông qua closures (cho ES5 và cũ hơn). 992 | 993 | **Không tốt:** 994 | ```javascript 995 | 996 | const Employee = function(name) { 997 | this.name = name; 998 | }; 999 | 1000 | Employee.prototype.getName = function getName() { 1001 | return this.name; 1002 | }; 1003 | 1004 | const employee = new Employee('John Doe'); 1005 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 1006 | delete employee.name; 1007 | console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined 1008 | ``` 1009 | 1010 | **Tốt:** 1011 | ```javascript 1012 | const Employee = function (name) { 1013 | this.getName = function getName() { 1014 | return name; 1015 | }; 1016 | }; 1017 | 1018 | const employee = new Employee('John Doe'); 1019 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 1020 | delete employee.name; 1021 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 1022 | ``` 1023 | **[⬆ về đầu trang](#mục-lục)** 1024 | 1025 | 1026 | ## **Lớp** 1027 | 1028 | ### Ưu tiên lớp ES2015/ES6 hơn các chức năng thuần ES5 1029 | 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ức 1030 | trong 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), 1031 | 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 1032 | đến khi bạn cần những đối tượng lớn và phức tạp hơn. 1033 | 1034 | **Không tốt:** 1035 | ```javascript 1036 | const Animal = function(age) { 1037 | if (!(this instanceof Animal)) { 1038 | throw new Error('Instantiate Animal with `new`'); 1039 | } 1040 | 1041 | this.age = age; 1042 | }; 1043 | 1044 | Animal.prototype.move = function move() {}; 1045 | 1046 | const Mammal = function(age, furColor) { 1047 | if (!(this instanceof Mammal)) { 1048 | throw new Error('Instantiate Mammal with `new`'); 1049 | } 1050 | 1051 | Animal.call(this, age); 1052 | this.furColor = furColor; 1053 | }; 1054 | 1055 | Mammal.prototype = Object.create(Animal.prototype); 1056 | Mammal.prototype.constructor = Mammal; 1057 | Mammal.prototype.liveBirth = function liveBirth() {}; 1058 | 1059 | const Human = function(age, furColor, languageSpoken) { 1060 | if (!(this instanceof Human)) { 1061 | throw new Error('Instantiate Human with `new`'); 1062 | } 1063 | 1064 | Mammal.call(this, age, furColor); 1065 | this.languageSpoken = languageSpoken; 1066 | }; 1067 | 1068 | Human.prototype = Object.create(Mammal.prototype); 1069 | Human.prototype.constructor = Human; 1070 | Human.prototype.speak = function speak() {}; 1071 | ``` 1072 | 1073 | **Tốt:** 1074 | ```javascript 1075 | class Animal { 1076 | constructor(age) { 1077 | this.age = age; 1078 | } 1079 | 1080 | move() { /* ... */ } 1081 | } 1082 | 1083 | class Mammal extends Animal { 1084 | constructor(age, furColor) { 1085 | super(age); 1086 | this.furColor = furColor; 1087 | } 1088 | 1089 | liveBirth() { /* ... */ } 1090 | } 1091 | 1092 | class Human extends Mammal { 1093 | constructor(age, furColor, languageSpoken) { 1094 | super(age, furColor); 1095 | this.languageSpoken = languageSpoken; 1096 | } 1097 | 1098 | speak() { /* ... */ } 1099 | } 1100 | ``` 1101 | **[⬆ về đầu trang](#mục-lục)** 1102 | 1103 | ### Sử dụng các hàm liên tiếp nhau 1104 | Đây là một pattern rất hữu ích trong JavaScript và bạn thấy nó trong rất 1105 | nhiều thư viện chẳng hạn như jQuery và Lodash. Nó cho phép code của bạn 1106 | có tính truyền tải và ngắn gọn. Vì lý do đó, theo tôi, sử dụng phương pháp 1107 | cá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. Trong 1108 | cá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âu 1109 | chuỗi các phương thức khác vào trong nó. 1110 | 1111 | **Không tốt:** 1112 | ```javascript 1113 | class Car { 1114 | constructor() { 1115 | this.make = 'Honda'; 1116 | this.model = 'Accord'; 1117 | this.color = 'white'; 1118 | } 1119 | 1120 | setMake(make) { 1121 | this.make = make; 1122 | } 1123 | 1124 | setModel(model) { 1125 | this.model = model; 1126 | } 1127 | 1128 | setColor(color) { 1129 | this.color = color; 1130 | } 1131 | 1132 | save() { 1133 | console.log(this.make, this.model, this.color); 1134 | } 1135 | } 1136 | 1137 | const car = new Car(); 1138 | car.setColor('pink'); 1139 | car.setMake('Ford'); 1140 | car.setModel('F-150'); 1141 | car.save(); 1142 | ``` 1143 | 1144 | **Tốt:** 1145 | ```javascript 1146 | class Car { 1147 | constructor() { 1148 | this.make = 'Honda'; 1149 | this.model = 'Accord'; 1150 | this.color = 'white'; 1151 | } 1152 | 1153 | setMake(make) { 1154 | this.make = make; 1155 | // Ghi chú: Trả về this để xâu chuỗi các phương thức 1156 | return this; 1157 | } 1158 | 1159 | setModel(model) { 1160 | this.model = model; 1161 | // Ghi chú: Trả về this để xâu chuỗi các phương thức 1162 | return this; 1163 | } 1164 | 1165 | setColor(color) { 1166 | this.color = color; 1167 | // Ghi chú: Trả về this để xâu chuỗi các phương thức 1168 | return this; 1169 | } 1170 | 1171 | save() { 1172 | console.log(this.make, this.model, this.color); 1173 | // Ghi chú: Trả về this để xâu chuỗi các phương thức 1174 | return this; 1175 | } 1176 | } 1177 | 1178 | const car = new Car() 1179 | .setColor('pink') 1180 | .setMake('Ford') 1181 | .setModel('F-150') 1182 | .save(); 1183 | ``` 1184 | **[⬆ về đầu trang](#mục-lục)** 1185 | 1186 | ### Ưu tiên thành phần hơn là kế thừa 1187 | Như đã được nhấn mạnh nhiều trong [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) 1188 | củ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ể. 1189 | 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. 1190 | Đ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ế, 1191 | 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ường 1192 | hợp nó có thể. 1193 | 1194 | 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ào 1195 | vấn đề trong tầm tay của bạn, nhưng đây là một danh sách manh nha khi kế thừa 1196 | có ý nghĩa hơn thành phần: 1197 | 1198 | 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" 1199 | (Human->Animal vs. User->UserDetails). 1200 | 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). 1201 | 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. 1202 | (Thay đổi lượng calo của tất cả animal khi chúng di chuyển) 1203 | 1204 | **Không tốt:** 1205 | ```javascript 1206 | class Employee { 1207 | constructor(name, email) { 1208 | this.name = name; 1209 | this.email = email; 1210 | } 1211 | 1212 | // ... 1213 | } 1214 | 1215 | // Không tốt bởi vì Employees "có" dữ liệu thuế. 1216 | // EmployeeTaxData không phải là một loại của Employee 1217 | class EmployeeTaxData extends Employee { 1218 | constructor(ssn, salary) { 1219 | super(); 1220 | this.ssn = ssn; 1221 | this.salary = salary; 1222 | } 1223 | 1224 | // ... 1225 | } 1226 | ``` 1227 | 1228 | **Tốt:** 1229 | ```javascript 1230 | class EmployeeTaxData { 1231 | constructor(ssn, salary) { 1232 | this.ssn = ssn; 1233 | this.salary = salary; 1234 | } 1235 | 1236 | // ... 1237 | } 1238 | 1239 | class Employee { 1240 | constructor(name, email) { 1241 | this.name = name; 1242 | this.email = email; 1243 | } 1244 | 1245 | setTaxData(ssn, salary) { 1246 | this.taxData = new EmployeeTaxData(ssn, salary); 1247 | } 1248 | // ... 1249 | } 1250 | ``` 1251 | **[⬆ về đầu trang](#mục-lục)** 1252 | 1253 | ## **SOLID** 1254 | ### Nguyên lí đơn trách nhiệm (Single Responsibility Principle) 1255 | Như đã được nói đến trong cuốn Clean Code, "Chỉ có thể thay đổi một lớp vì một lí 1256 | 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ống 1257 | như 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ủa 1258 | bạ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 1259 | để 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ệc 1260 | quan 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ạn 1261 | chỉ 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ệc 1262 | thay đổi đó sẽ ảnh hưởng đến những module khác trong codebase của bạn như thế nào. 1263 | 1264 | **Không tốt:** 1265 | ```javascript 1266 | class UserSettings { 1267 | constructor(user) { 1268 | this.user = user; 1269 | } 1270 | 1271 | changeSettings(settings) { 1272 | if (this.verifyCredentials()) { 1273 | // ... 1274 | } 1275 | } 1276 | 1277 | verifyCredentials() { 1278 | // ... 1279 | } 1280 | } 1281 | ``` 1282 | 1283 | **Tốt:** 1284 | ```javascript 1285 | class UserAuth { 1286 | constructor(user) { 1287 | this.user = user; 1288 | } 1289 | 1290 | verifyCredentials() { 1291 | // ... 1292 | } 1293 | } 1294 | 1295 | 1296 | class UserSettings { 1297 | constructor(user) { 1298 | this.user = user; 1299 | this.auth = new UserAuth(user); 1300 | } 1301 | 1302 | changeSettings(settings) { 1303 | if (this.auth.verifyCredentials()) { 1304 | // ... 1305 | } 1306 | } 1307 | } 1308 | ``` 1309 | **[⬆ về đầu trang](#mục-lục)** 1310 | 1311 | ### Nguyên lí đóng mở (Open/Closed Principle) 1312 | Betrand Meyer đã nói "có thể thoải mái mở rộng một module, nhưng hạn chế sửa 1313 | đổi bên trong module đó". Điều đó nghĩa là gì? Nguyên tắc này cơ bản nhấn mạnh 1314 | rằ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 1315 | đổi các code đang có. 1316 | 1317 | **Không tốt:** 1318 | ```javascript 1319 | class AjaxAdapter extends Adapter { 1320 | constructor() { 1321 | super(); 1322 | this.name = 'ajaxAdapter'; 1323 | } 1324 | } 1325 | 1326 | class NodeAdapter extends Adapter { 1327 | constructor() { 1328 | super(); 1329 | this.name = 'nodeAdapter'; 1330 | } 1331 | } 1332 | 1333 | class HttpRequester { 1334 | constructor(adapter) { 1335 | this.adapter = adapter; 1336 | } 1337 | 1338 | fetch(url) { 1339 | if (this.adapter.name === 'ajaxAdapter') { 1340 | return makeAjaxCall(url).then((response) => { 1341 | // transform response and return 1342 | }); 1343 | } else if (this.adapter.name === 'httpNodeAdapter') { 1344 | return makeHttpCall(url).then((response) => { 1345 | // transform response and return 1346 | }); 1347 | } 1348 | } 1349 | } 1350 | 1351 | function makeAjaxCall(url) { 1352 | // request and return promise 1353 | } 1354 | 1355 | function makeHttpCall(url) { 1356 | // request and return promise 1357 | } 1358 | ``` 1359 | 1360 | **Tốt:** 1361 | ```javascript 1362 | class AjaxAdapter extends Adapter { 1363 | constructor() { 1364 | super(); 1365 | this.name = 'ajaxAdapter'; 1366 | } 1367 | 1368 | request(url) { 1369 | // request and return promise 1370 | } 1371 | } 1372 | 1373 | class NodeAdapter extends Adapter { 1374 | constructor() { 1375 | super(); 1376 | this.name = 'nodeAdapter'; 1377 | } 1378 | 1379 | request(url) { 1380 | // request and return promise 1381 | } 1382 | } 1383 | 1384 | class HttpRequester { 1385 | constructor(adapter) { 1386 | this.adapter = adapter; 1387 | } 1388 | 1389 | fetch(url) { 1390 | return this.adapter.request(url).then((response) => { 1391 | // transform response and return 1392 | }); 1393 | } 1394 | } 1395 | ``` 1396 | **[⬆ về đầu trang](#mục-lục)** 1397 | 1398 | 1399 | ### Nguyên lí thay thế Liskov (Liskov Substitution Principle) 1400 | Đâ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ĩa 1401 | mộ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ểu 1402 | T 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ủa 1403 | kiể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ộc 1404 | tí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ụ, ..). 1405 | Đó thậm chí còn là một định nghĩa đáng sợ hơn. 1406 | 1407 | 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, 1408 | 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 1409 | đổ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 xem 1410 | cái ví dụ cổ điển hình vuông-hình chữ nhật (Square-Rectangle) dưới đây. Về mặt 1411 | toá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ều 1412 | nà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ải 1413 | rắc rối đấy. 1414 | 1415 | **Không tốt:** 1416 | ```javascript 1417 | class Rectangle { 1418 | constructor() { 1419 | this.width = 0; 1420 | this.height = 0; 1421 | } 1422 | 1423 | setColor(color) { 1424 | // ... 1425 | } 1426 | 1427 | render(area) { 1428 | // ... 1429 | } 1430 | 1431 | setWidth(width) { 1432 | this.width = width; 1433 | } 1434 | 1435 | setHeight(height) { 1436 | this.height = height; 1437 | } 1438 | 1439 | getArea() { 1440 | return this.width * this.height; 1441 | } 1442 | } 1443 | 1444 | class Square extends Rectangle { 1445 | setWidth(width) { 1446 | this.width = width; 1447 | this.height = width; 1448 | } 1449 | 1450 | setHeight(height) { 1451 | this.width = height; 1452 | this.height = height; 1453 | } 1454 | } 1455 | 1456 | function renderLargeRectangles(rectangles) { 1457 | rectangles.forEach((rectangle) => { 1458 | rectangle.setWidth(4); 1459 | rectangle.setHeight(5); 1460 | const area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20. 1461 | rectangle.render(area); 1462 | }); 1463 | } 1464 | 1465 | const rectangles = [new Rectangle(), new Rectangle(), new Square()]; 1466 | renderLargeRectangles(rectangles); 1467 | ``` 1468 | 1469 | **Tốt:** 1470 | ```javascript 1471 | class Shape { 1472 | setColor(color) { 1473 | // ... 1474 | } 1475 | 1476 | render(area) { 1477 | // ... 1478 | } 1479 | } 1480 | 1481 | class Rectangle extends Shape { 1482 | constructor(width, height) { 1483 | super(); 1484 | this.width = width; 1485 | this.height = height; 1486 | } 1487 | 1488 | getArea() { 1489 | return this.width * this.height; 1490 | } 1491 | } 1492 | 1493 | class Square extends Shape { 1494 | constructor(length) { 1495 | super(); 1496 | this.length = length; 1497 | } 1498 | 1499 | getArea() { 1500 | return this.length * this.length; 1501 | } 1502 | } 1503 | 1504 | function renderLargeShapes(shapes) { 1505 | shapes.forEach((shape) => { 1506 | const area = shape.getArea(); 1507 | shape.render(area); 1508 | }); 1509 | } 1510 | 1511 | const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)]; 1512 | renderLargeShapes(shapes); 1513 | ``` 1514 | **[⬆ về đầu trang](#mục-lục)** 1515 | 1516 | ### Nguyên lí phân tách interface (Interface Segregation Principle) 1517 | 1518 | JavaScript không có interface vì vậy nguyên lí này không áp dụng một cách 1519 | chặt chẽ như các nguyên lí khác. Tuy nhiên, nó cũng quan trọng và liên quan 1520 | ngay cả với hệ thống thiếu định kiểu của JavaScript. 1521 | 1522 | Nguyên lí phân tách interface nhấn mạnh rằng "Người dùng không nên bị bắt 1523 | buộc phải phụ thuộc vào các interfaces mà họ không sử dụng." Interface là 1524 | những ràng buộc ẩn trong JavaScript bởi vì duck typing. 1525 | 1526 | Một ví dụ tốt để minh hoạ cho nguyên lí này trong JavaScript là các lớp mà yêu 1527 | cầ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ố 1528 | 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ất 1529 | cả 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 1530 | "fat interface". 1531 | 1532 | **Không tốt:** 1533 | ```javascript 1534 | class DOMTraverser { 1535 | constructor(settings) { 1536 | this.settings = settings; 1537 | this.setup(); 1538 | } 1539 | 1540 | setup() { 1541 | this.rootNode = this.settings.rootNode; 1542 | this.animationModule.setup(); 1543 | } 1544 | 1545 | traverse() { 1546 | // ... 1547 | } 1548 | } 1549 | 1550 | const $ = new DOMTraverser({ 1551 | rootNode: document.getElementsByTagName('body'), 1552 | animationModule() {} // Most of the time, we won't need to animate when traversing. 1553 | // ... 1554 | }); 1555 | 1556 | ``` 1557 | 1558 | **Tốt:** 1559 | ```javascript 1560 | class DOMTraverser { 1561 | constructor(settings) { 1562 | this.settings = settings; 1563 | this.options = settings.options; 1564 | this.setup(); 1565 | } 1566 | 1567 | setup() { 1568 | this.rootNode = this.settings.rootNode; 1569 | this.setupOptions(); 1570 | } 1571 | 1572 | setupOptions() { 1573 | if (this.options.animationModule) { 1574 | // ... 1575 | } 1576 | } 1577 | 1578 | traverse() { 1579 | // ... 1580 | } 1581 | } 1582 | 1583 | const $ = new DOMTraverser({ 1584 | rootNode: document.getElementsByTagName('body'), 1585 | options: { 1586 | animationModule() {} 1587 | } 1588 | }); 1589 | ``` 1590 | **[⬆ về đầu trang](#mục-lục)** 1591 | 1592 | ### Nguyên lí đảo ngược dependency (Dependency Inversion Principle) 1593 | Nguyên lí này khẳng định hai điều cần thiết sau: 1594 | 1. Nhưng module cấp cao không nên phụ thuộc vào những module cấp thấp. Cả 1595 | hai nên phụ thuộc vào abstraction. 1596 | 2. Abstraction (interface) không nên phụ thuộc vào chi tiết, mà ngược lại. 1597 | 1598 | Đ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, 1599 | 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 1600 | (DI). Khi chúng không phải là các khái niệm giống nhau, DIP giữ cho module cấp 1601 | cao 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 1602 | đượ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ộc 1603 | lẫ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ó 1604 | làm cho việc tái cấu trúc code trở nên khó khăn. 1605 | 1606 | Như đã khẳng định ở trước, JavaScript không có interface vì vậy các abstraction 1607 | mà 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ính 1608 | mà 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àng 1609 | buộc ẩn là bất cứ module Request cho một `InventoryRequester` sẽ có một phương thức 1610 | `requestItems`. 1611 | 1612 | **Không tốt:** 1613 | ```javascript 1614 | class InventoryRequester { 1615 | constructor() { 1616 | this.REQ_METHODS = ['HTTP']; 1617 | } 1618 | 1619 | requestItem(item) { 1620 | // ... 1621 | } 1622 | } 1623 | 1624 | class InventoryTracker { 1625 | constructor(items) { 1626 | this.items = items; 1627 | 1628 | // 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ể 1629 | // Chúng ta nên có những requestItems phụ thuộc vào một phương thức request `request` 1630 | this.requester = new InventoryRequester(); 1631 | } 1632 | 1633 | requestItems() { 1634 | this.items.forEach((item) => { 1635 | this.requester.requestItem(item); 1636 | }); 1637 | } 1638 | } 1639 | 1640 | const inventoryTracker = new InventoryTracker(['apples', 'bananas']); 1641 | inventoryTracker.requestItems(); 1642 | ``` 1643 | 1644 | **Tốt:** 1645 | ```javascript 1646 | class InventoryTracker { 1647 | constructor(items, requester) { 1648 | this.items = items; 1649 | this.requester = requester; 1650 | } 1651 | 1652 | requestItems() { 1653 | this.items.forEach((item) => { 1654 | this.requester.requestItem(item); 1655 | }); 1656 | } 1657 | } 1658 | 1659 | class InventoryRequesterV1 { 1660 | constructor() { 1661 | this.REQ_METHODS = ['HTTP']; 1662 | } 1663 | 1664 | requestItem(item) { 1665 | // ... 1666 | } 1667 | } 1668 | 1669 | class InventoryRequesterV2 { 1670 | constructor() { 1671 | this.REQ_METHODS = ['WS']; 1672 | } 1673 | 1674 | requestItem(item) { 1675 | // ... 1676 | } 1677 | } 1678 | 1679 | // 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ể 1680 | // dễ dàng thay thế module request bằng một module mới lạ sử dụng WebSockets. 1681 | const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); 1682 | inventoryTracker.requestItems(); 1683 | ``` 1684 | **[⬆ về đầu trang](#mục-lục)** 1685 | 1686 | ## **Testing** 1687 | Testing thì quan trọng hơn shipping. Nếu bạn không có test hoặc không đủ, 1688 | 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. 1689 | Việc quyết định những gì để tạo thành số lượng test đủ là do team của bạn, 1690 | nhưng việc có 100% độ bao phủ (tất cả các câu lệnh và rẽ nhánh) là cách để 1691 | bạn đạt được sự tự tin cao. Điều này có nghĩa ngoài việc có được một framework 1692 | để test tốt, bạn cũng cần sử dụng một [công cụ bao phủ tốt](http://gotwarlost.github.io/istanbul/). 1693 | 1694 | Không có lí do gì để không viết test. Có [rất nhiều framework test JS tốt](http://jstherightway.org/#testing-tools), 1695 | vì thế hãy tìm một framework mà team bạn thích. Khi đã tìm được một cái thích 1696 | hợ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ính 1697 | nă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à Test 1698 | Driven Development (TDD), điều đó thật tuyệt, nhưng điểm quan trọng là phải 1699 | chắc chắn bạn đạt được mục tiêu về độ bao phủ trước khi launch một tính năng 1700 | hoặc refactor một tính năng cũ nào đó. 1701 | 1702 | ### Một khái niệm duy nhất cho mỗi đơn vị test 1703 | 1704 | **Không tốt:** 1705 | ```javascript 1706 | const assert = require('assert'); 1707 | 1708 | describe('MakeMomentJSGreatAgain', () => { 1709 | it('handles date boundaries', () => { 1710 | let date; 1711 | 1712 | date = new MakeMomentJSGreatAgain('1/1/2015'); 1713 | date.addDays(30); 1714 | date.shouldEqual('1/31/2015'); 1715 | 1716 | date = new MakeMomentJSGreatAgain('2/1/2016'); 1717 | date.addDays(28); 1718 | assert.equal('02/29/2016', date); 1719 | 1720 | date = new MakeMomentJSGreatAgain('2/1/2015'); 1721 | date.addDays(28); 1722 | assert.equal('03/01/2015', date); 1723 | }); 1724 | }); 1725 | ``` 1726 | 1727 | **Tốt:** 1728 | ```javascript 1729 | const assert = require('assert'); 1730 | 1731 | describe('MakeMomentJSGreatAgain', () => { 1732 | it('handles 30-day months', () => { 1733 | const date = new MakeMomentJSGreatAgain('1/1/2015'); 1734 | date.addDays(30); 1735 | date.shouldEqual('1/31/2015'); 1736 | }); 1737 | 1738 | it('handles leap year', () => { 1739 | const date = new MakeMomentJSGreatAgain('2/1/2016'); 1740 | date.addDays(28); 1741 | assert.equal('02/29/2016', date); 1742 | }); 1743 | 1744 | it('handles non-leap year', () => { 1745 | const date = new MakeMomentJSGreatAgain('2/1/2015'); 1746 | date.addDays(28); 1747 | assert.equal('03/01/2015', date); 1748 | }); 1749 | }); 1750 | ``` 1751 | **[⬆ về đầu trang](#mục-lục)** 1752 | 1753 | ## **Xử lí đồng thời** 1754 | ### Hãy dùng Promise, đừng dùng callback 1755 | 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 1756 | (callback hell). Từ ES2015/ES6, Promise đã được đưa vào Javascript. Hãy sử dụng chúng! 1757 | 1758 | **Không tốt:** 1759 | ```javascript 1760 | require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => { 1761 | if (requestErr) { 1762 | console.error(requestErr); 1763 | } else { 1764 | require('fs').writeFile('article.html', response.body, (writeErr) => { 1765 | if (writeErr) { 1766 | console.error(writeErr); 1767 | } else { 1768 | console.log('File written'); 1769 | } 1770 | }); 1771 | } 1772 | }); 1773 | 1774 | ``` 1775 | 1776 | **Tốt:** 1777 | ```javascript 1778 | require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1779 | .then((response) => { 1780 | return require('fs-promise').writeFile('article.html', response); 1781 | }) 1782 | .then(() => { 1783 | console.log('File written'); 1784 | }) 1785 | .catch((err) => { 1786 | console.error(err); 1787 | }); 1788 | 1789 | ``` 1790 | **[⬆ về đầu trang](#mục-lục)** 1791 | 1792 | ### Async/Await thì 'sạch sẽ' hơn Promise 1793 | Promise là một sự thay thế 'sạch sẽ' cho callback, nhưng ES2017/ES8 giới thiệu 1794 | async và await, đó thậm chí còn là một giải pháp tốt hơn Promise nữa. Những gì 1795 | 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ết 1796 | các lệnh logic mà không cần một chuỗi `then` của các hàm. Hãy sử dụng điều này nếu 1797 | bạn có thể tận dụng các tính năng của ES2017/ES8 ngay hôm nay! 1798 | 1799 | **Không tốt:** 1800 | ```javascript 1801 | require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1802 | .then((response) => { 1803 | return require('fs-promise').writeFile('article.html', response); 1804 | }) 1805 | .then(() => { 1806 | console.log('File written'); 1807 | }) 1808 | .catch((err) => { 1809 | console.error(err); 1810 | }); 1811 | 1812 | ``` 1813 | 1814 | **Tốt:** 1815 | ```javascript 1816 | async function getCleanCodeArticle() { 1817 | try { 1818 | const response = await require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin'); 1819 | await require('fs-promise').writeFile('article.html', response); 1820 | console.log('File written'); 1821 | } catch(err) { 1822 | console.error(err); 1823 | } 1824 | } 1825 | ``` 1826 | **[⬆ về đầu trang](#mục-lục)** 1827 | 1828 | 1829 | ## **Xử lí lỗi** 1830 | 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 1831 | được khi có một cái gì đó chạy không đúng và nó sẽ cho bạn biết bằng việc 1832 | dừng chức năng mà nó đang thực thi, huỷ tiến trình (trong Node), và thông 1833 | báo cho bạn trong console với một stack để theo dấu. 1834 | 1835 | ### Đừng bỏ qua những lỗi đã bắt được 1836 | 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 ứng 1837 | lại được với lỗi đó. Ghi lỗi ra console (`console.log`) cũng không tốt hơn 1838 | bao nhiêu vì đa số nó có thể bị trôi mất trong một đống những thứ được hiển 1839 | thị ra ở console. Nếu bạn đặt bất cứ đoạn code nào trong một block `try/catch`, 1840 | 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áp 1841 | hoặc tạo một luồng code để xử lí lỗi khi nó xảy ra. 1842 | 1843 | **Không tốt:** 1844 | ```javascript 1845 | try { 1846 | functionThatMightThrow(); 1847 | } catch (error) { 1848 | console.log(error); 1849 | } 1850 | ``` 1851 | 1852 | **Tốt:** 1853 | ```javascript 1854 | try { 1855 | functionThatMightThrow(); 1856 | } catch (error) { 1857 | // One option (more noisy than console.log): 1858 | console.error(error); 1859 | // Another option: 1860 | notifyUserOfError(error); 1861 | // Another option: 1862 | reportErrorToService(error); 1863 | // OR do all three! 1864 | } 1865 | ``` 1866 | 1867 | ### Đừng bỏ qua những promise bị lỗi (rejected) 1868 | Cùng nguyên nhân với phần trên. 1869 | 1870 | **Không tốt:** 1871 | ```javascript 1872 | getdata() 1873 | .then((data) => { 1874 | functionThatMightThrow(data); 1875 | }) 1876 | .catch((error) => { 1877 | console.log(error); 1878 | }); 1879 | ``` 1880 | 1881 | **Tốt:** 1882 | ```javascript 1883 | getdata() 1884 | .then((data) => { 1885 | functionThatMightThrow(data); 1886 | }) 1887 | .catch((error) => { 1888 | // One option (more noisy than console.log): 1889 | console.error(error); 1890 | // Another option: 1891 | notifyUserOfError(error); 1892 | // Another option: 1893 | reportErrorToService(error); 1894 | // OR do all three! 1895 | }); 1896 | ``` 1897 | 1898 | **[⬆ về trang chủ](#mục-lục)** 1899 | 1900 | 1901 | ## **Định dạng** 1902 | Việc định dạng code mang tính chủ quan. Giống như nhiều quy tắc được trình 1903 | bà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ạn 1904 | bắt buộc phải tuân theo. Điểm chính của phần này là ĐỪNG BAO GIỜ TRANH CÃI 1905 | về việc định dạng code như thế nào. Có [hàng tá công cụ](http://standardjs.com/rules.html) 1906 | để 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à 1907 | tiền bạc chỉ để tranh cãi về vấn đề định dạng code. 1908 | 1909 | Đố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 đầu 1910 | dòng, tab và space, nháy đơn và nháy kép,..) hãy xem một số hướng dẫn ở đây. 1911 | 1912 | ### Sử dụng thống nhất cách viết hoa 1913 | 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ất 1914 | nhiề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, 1915 | 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à 1916 | 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ó trong 1917 | codebase của bạn. 1918 | 1919 | **Không tốt:** 1920 | ```javascript 1921 | const DAYS_IN_WEEK = 7; 1922 | const daysInMonth = 30; 1923 | 1924 | const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1925 | const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1926 | 1927 | function eraseDatabase() {} 1928 | function restore_database() {} 1929 | 1930 | class animal {} 1931 | class Alpaca {} 1932 | ``` 1933 | 1934 | **Tốt:** 1935 | ```javascript 1936 | const DAYS_IN_WEEK = 7; 1937 | const DAYS_IN_MONTH = 30; 1938 | 1939 | const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1940 | const artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1941 | 1942 | function eraseDatabase() {} 1943 | function restoreDatabase() {} 1944 | 1945 | class Animal {} 1946 | class Alpaca {} 1947 | ``` 1948 | **[⬆ về đầu trang](#mục-lục)** 1949 | 1950 | 1951 | ### Các hàm gọi và hàm được gọi nên nằm gần nhau 1952 | 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 trong 1953 | file. 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 1954 | đọ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úng 1955 | ta cũng được đọc theo cách đó. 1956 | 1957 | **Không tốt:** 1958 | ```javascript 1959 | class PerformanceReview { 1960 | constructor(employee) { 1961 | this.employee = employee; 1962 | } 1963 | 1964 | lookupPeers() { 1965 | return db.lookup(this.employee, 'peers'); 1966 | } 1967 | 1968 | lookupManager() { 1969 | return db.lookup(this.employee, 'manager'); 1970 | } 1971 | 1972 | getPeerReviews() { 1973 | const peers = this.lookupPeers(); 1974 | // ... 1975 | } 1976 | 1977 | perfReview() { 1978 | this.getPeerReviews(); 1979 | this.getManagerReview(); 1980 | this.getSelfReview(); 1981 | } 1982 | 1983 | getManagerReview() { 1984 | const manager = this.lookupManager(); 1985 | } 1986 | 1987 | getSelfReview() { 1988 | // ... 1989 | } 1990 | } 1991 | 1992 | const review = new PerformanceReview(user); 1993 | review.perfReview(); 1994 | ``` 1995 | 1996 | **Tốt:** 1997 | ```javascript 1998 | class PerformanceReview { 1999 | constructor(employee) { 2000 | this.employee = employee; 2001 | } 2002 | 2003 | perfReview() { 2004 | this.getPeerReviews(); 2005 | this.getManagerReview(); 2006 | this.getSelfReview(); 2007 | } 2008 | 2009 | getPeerReviews() { 2010 | const peers = this.lookupPeers(); 2011 | // ... 2012 | } 2013 | 2014 | lookupPeers() { 2015 | return db.lookup(this.employee, 'peers'); 2016 | } 2017 | 2018 | getManagerReview() { 2019 | const manager = this.lookupManager(); 2020 | } 2021 | 2022 | lookupManager() { 2023 | return db.lookup(this.employee, 'manager'); 2024 | } 2025 | 2026 | getSelfReview() { 2027 | // ... 2028 | } 2029 | } 2030 | 2031 | const review = new PerformanceReview(employee); 2032 | review.perfReview(); 2033 | ``` 2034 | 2035 | **[⬆ về đầu trang](#mục-lục)** 2036 | 2037 | ## **Viết chú thích** 2038 | ### Chỉ nên viết chú thích cho những thứ có logic phức tạp. 2039 | Các chú thích thường là lời xin lỗi, chứ không phải là yêu cầu. 2040 | Những đoạn code tốt thì *đa số* tự nó đã là tài liệu rồi. 2041 | 2042 | **Không tốt:** 2043 | ```javascript 2044 | function hashIt(data) { 2045 | // Khai báo hash 2046 | let hash = 0; 2047 | 2048 | // Lấy chiều dài của chuỗi 2049 | const length = data.length; 2050 | 2051 | // Lặp qua mỗi kí tự 2052 | for (let i = 0; i < length; i++) { 2053 | // Lấy mã của kí tự 2054 | const char = data.charCodeAt(i); 2055 | // Gán giá trị cho hash 2056 | hash = ((hash << 5) - hash) + char; 2057 | // Chuyển thành định dạng số nguyên 32 bit 2058 | hash &= hash; 2059 | } 2060 | } 2061 | ``` 2062 | 2063 | **Tốt:** 2064 | ```javascript 2065 | 2066 | function hashIt(data) { 2067 | let hash = 0; 2068 | const length = data.length; 2069 | 2070 | for (let i = 0; i < length; i++) { 2071 | const char = data.charCodeAt(i); 2072 | hash = ((hash << 5) - hash) + char; 2073 | 2074 | // Chuyển thành định dạng số nguyên 32 bit 2075 | hash &= hash; 2076 | } 2077 | } 2078 | 2079 | ``` 2080 | **[⬆ về đầu trang](#mục-lục)** 2081 | 2082 | ### Đừng giữ lại những đoạn code bị chú thích trong codebase của bạn. 2083 | Những công cụ quản lí phiên bản sinh ra để làm nhiệm vụ của chúng. 2084 | Hãy để code cũ của bạn nằm lại trong dĩ vãng đi. 2085 | 2086 | **Không tốt:** 2087 | ```javascript 2088 | doStuff(); 2089 | // doOtherStuff(); 2090 | // doSomeMoreStuff(); 2091 | // doSoMuchStuff(); 2092 | ``` 2093 | 2094 | **Tốt:** 2095 | ```javascript 2096 | doStuff(); 2097 | ``` 2098 | **[⬆ về đầu trang](#mục-lục)** 2099 | 2100 | ### Đừng viết các chú thích nhật ký. 2101 | 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 code 2102 | vô dụng, bị chú thích và đặc biệt là những chú thích dạng nhật ký... 2103 | Sử dụng `git log` để xem lịch sử được mà! 2104 | 2105 | **Không tốt:** 2106 | ```javascript 2107 | /** 2108 | * 2016-12-20: Removed monads, didn't understand them (RM) 2109 | * 2016-10-01: Improved using special monads (JP) 2110 | * 2016-02-03: Removed type-checking (LI) 2111 | * 2015-03-14: Added combine with type-checking (JR) 2112 | */ 2113 | function combine(a, b) { 2114 | return a + b; 2115 | } 2116 | ``` 2117 | 2118 | **Tốt:** 2119 | ```javascript 2120 | function combine(a, b) { 2121 | return a + b; 2122 | } 2123 | ``` 2124 | **[⬆ về đầu trang](#mục-lục)** 2125 | 2126 | ### Tránh những đánh dấu vị trí 2127 | 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 2128 | đị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. 2129 | 2130 | **Không tốt:** 2131 | ```javascript 2132 | //////////////////////////////////////////////////////////////////////////////// 2133 | // Scope Model Instantiation 2134 | //////////////////////////////////////////////////////////////////////////////// 2135 | $scope.model = { 2136 | menu: 'foo', 2137 | nav: 'bar' 2138 | }; 2139 | 2140 | //////////////////////////////////////////////////////////////////////////////// 2141 | // Action setup 2142 | //////////////////////////////////////////////////////////////////////////////// 2143 | const actions = function() { 2144 | // ... 2145 | }; 2146 | ``` 2147 | 2148 | **Tốt:** 2149 | ```javascript 2150 | $scope.model = { 2151 | menu: 'foo', 2152 | nav: 'bar' 2153 | }; 2154 | 2155 | const actions = function() { 2156 | // ... 2157 | }; 2158 | ``` 2159 | **[⬆ về đầu trang](#mục-lục)** 2160 | 2161 | ## Các ngôn ngữ khác 2162 | 2163 | Tài liệu này cũng có sẵn ở các ngôn ngữ sau: 2164 | 2165 | - ![en](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags-iso/shiny/24/US.png) **English**: [ryanmcdermott/clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 2166 | - ![am](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Armenia.png) **Armenian**: [hanumanum/clean-code-javascript/](https://github.com/hanumanum/clean-code-javascript) 2167 | - ![bd](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bangladesh.png) **Bangla(বাংলা)**: [InsomniacSabbir/clean-code-javascript/](https://github.com/InsomniacSabbir/clean-code-javascript/) 2168 | - ![br](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Brazil.png) **Brazilian Portuguese**: [fesnt/clean-code-javascript](https://github.com/fesnt/clean-code-javascript) 2169 | - ![cn](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/China.png) **Simplified Chinese**: 2170 | - [alivebao/clean-code-js](https://github.com/alivebao/clean-code-js) 2171 | - [beginor/clean-code-javascript](https://github.com/beginor/clean-code-javascript) 2172 | - ![tw](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Taiwan.png) **Traditional Chinese**: [AllJointTW/clean-code-javascript](https://github.com/AllJointTW/clean-code-javascript) 2173 | - ![fr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/France.png) **French**: [GavBaros/clean-code-javascript-fr](https://github.com/GavBaros/clean-code-javascript-fr) 2174 | - ![de](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Germany.png) **German**: [marcbruederlin/clean-code-javascript](https://github.com/marcbruederlin/clean-code-javascript) 2175 | - ![id](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Indonesia.png) **Indonesia**: [andirkh/clean-code-javascript/](https://github.com/andirkh/clean-code-javascript/) 2176 | - ![it](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Italy.png) **Italian**: [frappacchio/clean-code-javascript/](https://github.com/frappacchio/clean-code-javascript/) 2177 | - ![ja](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Japan.png) **Japanese**: [mitsuruog/clean-code-javascript/](https://github.com/mitsuruog/clean-code-javascript/) 2178 | - ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) **Korean**: [qkraudghgh/clean-code-javascript-ko](https://github.com/qkraudghgh/clean-code-javascript-ko) 2179 | - ![pl](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Poland.png) **Polish**: [greg-dev/clean-code-javascript-pl](https://github.com/greg-dev/clean-code-javascript-pl) 2180 | - ![ru](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Russia.png) **Russian**: 2181 | - [BoryaMogila/clean-code-javascript-ru/](https://github.com/BoryaMogila/clean-code-javascript-ru/) 2182 | - [maksugr/clean-code-javascript](https://github.com/maksugr/clean-code-javascript) 2183 | - ![es](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Spain.png) **Spanish**: [tureey/clean-code-javascript](https://github.com/tureey/clean-code-javascript) 2184 | - ![es](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Uruguay.png) **Spanish**: [andersontr15/clean-code-javascript](https://github.com/andersontr15/clean-code-javascript-es) 2185 | - ![rs](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Serbia.png) **Serbian**: [doskovicmilos/clean-code-javascript/](https://github.com/doskovicmilos/clean-code-javascript) 2186 | - ![tr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Turkey.png) **Turkish**: [bsonmez/clean-code-javascript](https://github.com/bsonmez/clean-code-javascript/tree/turkish-translation) 2187 | - ![ua](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Ukraine.png) **Ukrainian**: [mindfr1k/clean-code-javascript-ua](https://github.com/mindfr1k/clean-code-javascript-ua) 2188 | 2189 | **[⬆ về đầu trang](#mục-lục)** 2190 | --------------------------------------------------------------------------------