├── .gitattributes ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=JavaScript 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan McDermott 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 | 2 | # clean-code-javascript 3 | 4 | ## Table of Contents 5 | 1. [Introduction](#introduction) 6 | 2. [Variables](#variables) 7 | 3. [Functions](#functions) 8 | 4. [Objects and Data Structures](#objects-and-data-structures) 9 | 5. [Classes](#classes) 10 | 6. [Testing](#testing) 11 | 7. [Concurrency](#concurrency) 12 | 8. [Error Handling](#error-handling) 13 | 9. [Formatting](#formatting) 14 | 10. [Comments](#comments) 15 | 11. [Translation](#translation) 16 | 17 | ## Introduction 18 | ![Humorous image of software quality estimation as a count of how many expletives 19 | you shout when reading code](http://www.osnews.com/images/comics/wtfm.jpg) 20 | 21 | Software engineering principles, from Robert C. Martin's book 22 | [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882), 23 | adapted for JavaScript. This is not a style guide. It's a guide to producing 24 | readable, reusable, and refactorable software in JavaScript. 25 | 26 | Not every principle herein has to be strictly followed, and even fewer will be 27 | universally agreed upon. These are guidelines and nothing more, but they are 28 | ones codified over many years of collective experience by the authors of 29 | *Clean Code*. 30 | 31 | Our craft of software engineering is just a bit over 50 years old, and we are 32 | still learning a lot. When software architecture is as old as architecture 33 | itself, maybe then we will have harder rules to follow. For now, let these 34 | guidelines serve as a touchstone by which to assess the quality of the 35 | JavaScript code that you and your team produce. 36 | 37 | One more thing: knowing these won't immediately make you a better software 38 | developer, and working with them for many years doesn't mean you won't make 39 | mistakes. Every piece of code starts as a first draft, like wet clay getting 40 | shaped into its final form. Finally, we chisel away the imperfections when 41 | we review it with our peers. Don't beat yourself up for first drafts that need 42 | improvement. Beat up the code instead! 43 | 44 | ## **Variables** 45 | ### Use meaningful and pronounceable variable names 46 | 47 | **Bad:** 48 | ```javascript 49 | const yyyymmdstr = moment().format('YYYY/MM/DD'); 50 | ``` 51 | 52 | **Good:** 53 | ```javascript 54 | const currentDate = moment().format('YYYY/MM/DD'); 55 | ``` 56 | **[⬆ back to top](#table-of-contents)** 57 | 58 | ### Use the same vocabulary for the same type of variable 59 | 60 | **Bad:** 61 | ```javascript 62 | getUserInfo(); 63 | getClientData(); 64 | getCustomerRecord(); 65 | ``` 66 | 67 | **Good:** 68 | ```javascript 69 | getUser(); 70 | ``` 71 | **[⬆ back to top](#table-of-contents)** 72 | 73 | ### Use searchable names 74 | We will read more code than we will ever write. It's important that the code we 75 | do write is readable and searchable. By *not* naming variables that end up 76 | being meaningful for understanding our program, we hurt our readers. 77 | Make your names searchable. Tools like 78 | [buddy.js](https://github.com/danielstjules/buddy.js) and 79 | [ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) 80 | can help identify unnamed constants. 81 | 82 | **Bad:** 83 | ```javascript 84 | // What the heck is 86400000 for? 85 | setTimeout(blastOff, 86400000); 86 | 87 | ``` 88 | 89 | **Good:** 90 | ```javascript 91 | // Declare them as capitalized `const` globals. 92 | const MILLISECONDS_IN_A_DAY = 86400000; 93 | 94 | setTimeout(blastOff, MILLISECONDS_IN_A_DAY); 95 | 96 | ``` 97 | **[⬆ back to top](#table-of-contents)** 98 | 99 | ### Use explanatory variables 100 | **Bad:** 101 | ```javascript 102 | const address = 'One Infinite Loop, Cupertino 95014'; 103 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 104 | saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]); 105 | ``` 106 | 107 | **Good:** 108 | ```javascript 109 | const address = 'One Infinite Loop, Cupertino 95014'; 110 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 111 | const [, city, zipCode] = address.match(cityZipCodeRegex) || []; 112 | saveCityZipCode(city, zipCode); 113 | ``` 114 | **[⬆ back to top](#table-of-contents)** 115 | 116 | ### Avoid Mental Mapping 117 | Explicit is better than implicit. 118 | 119 | **Bad:** 120 | ```javascript 121 | const locations = ['Austin', 'New York', 'San Francisco']; 122 | locations.forEach((l) => { 123 | doStuff(); 124 | doSomeOtherStuff(); 125 | // ... 126 | // ... 127 | // ... 128 | // Wait, what is `l` for again? 129 | dispatch(l); 130 | }); 131 | ``` 132 | 133 | **Good:** 134 | ```javascript 135 | const locations = ['Austin', 'New York', 'San Francisco']; 136 | locations.forEach((location) => { 137 | doStuff(); 138 | doSomeOtherStuff(); 139 | // ... 140 | // ... 141 | // ... 142 | dispatch(location); 143 | }); 144 | ``` 145 | **[⬆ back to top](#table-of-contents)** 146 | 147 | ### Don't add unneeded context 148 | If your class/object name tells you something, don't repeat that in your 149 | variable name. 150 | 151 | **Bad:** 152 | ```javascript 153 | const Car = { 154 | carMake: 'Honda', 155 | carModel: 'Accord', 156 | carColor: 'Blue' 157 | }; 158 | 159 | function paintCar(car) { 160 | car.carColor = 'Red'; 161 | } 162 | ``` 163 | 164 | **Good:** 165 | ```javascript 166 | const Car = { 167 | make: 'Honda', 168 | model: 'Accord', 169 | color: 'Blue' 170 | }; 171 | 172 | function paintCar(car) { 173 | car.color = 'Red'; 174 | } 175 | ``` 176 | **[⬆ back to top](#table-of-contents)** 177 | 178 | ### Use default arguments instead of short circuiting or conditionals 179 | 180 | **Bad:** 181 | ```javascript 182 | function createMicrobrewery(name) { 183 | const breweryName = name || 'Hipster Brew Co.'; 184 | // ... 185 | } 186 | 187 | ``` 188 | 189 | **Good:** 190 | ```javascript 191 | function createMicrobrewery(breweryName = 'Hipster Brew Co.') { 192 | // ... 193 | } 194 | 195 | ``` 196 | **[⬆ back to top](#table-of-contents)** 197 | 198 | ## **Functions** 199 | ### Function arguments (2 or fewer ideally) 200 | Limiting the amount of function parameters is incredibly important because it 201 | makes testing your function easier. Having more than three leads to a 202 | combinatorial explosion where you have to test tons of different cases with 203 | each separate argument. 204 | 205 | One or two arguments is the ideal case, and three should be avoided if possible. 206 | Anything more than that should be consolidated. Usually, if you have 207 | more than two arguments then your function is trying to do too much. In cases 208 | where it's not, most of the time a higher-level object will suffice as an 209 | argument. 210 | 211 | Since JavaScript allows you to make objects on the fly, without a lot of class 212 | boilerplate, you can use an object if you are finding yourself needing a 213 | lot of arguments. 214 | 215 | To make it obvious what properties the function expects, you can use the es6 216 | destructuring syntax. This has a few advantages: 217 | 218 | 1. When someone looks at the function signature, it's immediately clear what 219 | properties are being used. 220 | 2. Destructuring also clones the specified primitive values of the argument 221 | object passed into the function. This can help prevent side effects. Note: 222 | objects and arrays that are destructured from the argument object are NOT 223 | cloned. 224 | 3. Linters can warn you about unused properties, which would be impossible 225 | without destructuring. 226 | 227 | **Bad:** 228 | ```javascript 229 | function createMenu(title, body, buttonText, cancellable) { 230 | // ... 231 | } 232 | ``` 233 | 234 | **Good:** 235 | ```javascript 236 | function createMenu({ title, body, buttonText, cancellable }) { 237 | // ... 238 | } 239 | 240 | createMenu({ 241 | title: 'Foo', 242 | body: 'Bar', 243 | buttonText: 'Baz', 244 | cancellable: true 245 | }); 246 | ``` 247 | **[⬆ back to top](#table-of-contents)** 248 | 249 | 250 | ### Functions should do one thing 251 | This is by far the most important rule in software engineering. When functions 252 | do more than one thing, they are harder to compose, test, and reason about. 253 | When you can isolate a function to just one action, they can be refactored 254 | easily and your code will read much cleaner. If you take nothing else away from 255 | this guide other than this, you'll be ahead of many developers. 256 | 257 | **Bad:** 258 | ```javascript 259 | function emailClients(clients) { 260 | clients.forEach((client) => { 261 | const clientRecord = database.lookup(client); 262 | if (clientRecord.isActive()) { 263 | email(client); 264 | } 265 | }); 266 | } 267 | ``` 268 | 269 | **Good:** 270 | ```javascript 271 | function emailClients(clients) { 272 | clients 273 | .filter(isClientActive) 274 | .forEach(email); 275 | } 276 | 277 | function isClientActive(client) { 278 | const clientRecord = database.lookup(client); 279 | return clientRecord.isActive(); 280 | } 281 | ``` 282 | **[⬆ back to top](#table-of-contents)** 283 | 284 | ### Function names should say what they do 285 | 286 | **Bad:** 287 | ```javascript 288 | function addToDate(date, month) { 289 | // ... 290 | } 291 | 292 | const date = new Date(); 293 | 294 | // It's hard to to tell from the function name what is added 295 | addToDate(date, 1); 296 | ``` 297 | 298 | **Good:** 299 | ```javascript 300 | function addMonthToDate(month, date) { 301 | // ... 302 | } 303 | 304 | const date = new Date(); 305 | addMonthToDate(1, date); 306 | ``` 307 | **[⬆ back to top](#table-of-contents)** 308 | 309 | ### Functions should only be one level of abstraction 310 | When you have more than one level of abstraction your function is usually 311 | doing too much. Splitting up functions leads to reusability and easier 312 | testing. 313 | 314 | **Bad:** 315 | ```javascript 316 | function parseBetterJSAlternative(code) { 317 | const REGEXES = [ 318 | // ... 319 | ]; 320 | 321 | const statements = code.split(' '); 322 | const tokens = []; 323 | REGEXES.forEach((REGEX) => { 324 | statements.forEach((statement) => { 325 | // ... 326 | }); 327 | }); 328 | 329 | const ast = []; 330 | tokens.forEach((token) => { 331 | // lex... 332 | }); 333 | 334 | ast.forEach((node) => { 335 | // parse... 336 | }); 337 | } 338 | ``` 339 | 340 | **Good:** 341 | ```javascript 342 | function tokenize(code) { 343 | const REGEXES = [ 344 | // ... 345 | ]; 346 | 347 | const statements = code.split(' '); 348 | const tokens = []; 349 | REGEXES.forEach((REGEX) => { 350 | statements.forEach((statement) => { 351 | tokens.push( /* ... */ ); 352 | }); 353 | }); 354 | 355 | return tokens; 356 | } 357 | 358 | function lexer(tokens) { 359 | const ast = []; 360 | tokens.forEach((token) => { 361 | ast.push( /* ... */ ); 362 | }); 363 | 364 | return ast; 365 | } 366 | 367 | function parseBetterJSAlternative(code) { 368 | const tokens = tokenize(code); 369 | const ast = lexer(tokens); 370 | ast.forEach((node) => { 371 | // parse... 372 | }); 373 | } 374 | ``` 375 | **[⬆ back to top](#table-of-contents)** 376 | 377 | ### Remove duplicate code 378 | Do your absolute best to avoid duplicate code. Duplicate code is bad because it 379 | means that there's more than one place to alter something if you need to change 380 | some logic. 381 | 382 | Imagine if you run a restaurant and you keep track of your inventory: all your 383 | tomatoes, onions, garlic, spices, etc. If you have multiple lists that 384 | you keep this on, then all have to be updated when you serve a dish with 385 | tomatoes in them. If you only have one list, there's only one place to update! 386 | 387 | Oftentimes you have duplicate code because you have two or more slightly 388 | different things, that share a lot in common, but their differences force you 389 | to have two or more separate functions that do much of the same things. Removing 390 | duplicate code means creating an abstraction that can handle this set of 391 | different things with just one function/module/class. 392 | 393 | Getting the abstraction right is critical, that's why you should follow the 394 | SOLID principles laid out in the *Classes* section. Bad abstractions can be 395 | worse than duplicate code, so be careful! Having said this, if you can make 396 | a good abstraction, do it! Don't repeat yourself, otherwise you'll find yourself 397 | updating multiple places anytime you want to change one thing. 398 | 399 | **Bad:** 400 | ```javascript 401 | function showDeveloperList(developers) { 402 | developers.forEach((developer) => { 403 | const expectedSalary = developer.calculateExpectedSalary(); 404 | const experience = developer.getExperience(); 405 | const githubLink = developer.getGithubLink(); 406 | const data = { 407 | expectedSalary, 408 | experience, 409 | githubLink 410 | }; 411 | 412 | render(data); 413 | }); 414 | } 415 | 416 | function showManagerList(managers) { 417 | managers.forEach((manager) => { 418 | const expectedSalary = manager.calculateExpectedSalary(); 419 | const experience = manager.getExperience(); 420 | const portfolio = manager.getMBAProjects(); 421 | const data = { 422 | expectedSalary, 423 | experience, 424 | portfolio 425 | }; 426 | 427 | render(data); 428 | }); 429 | } 430 | ``` 431 | 432 | **Good:** 433 | ```javascript 434 | function showList(employees) { 435 | employees.forEach((employee) => { 436 | const expectedSalary = employee.calculateExpectedSalary(); 437 | const experience = employee.getExperience(); 438 | 439 | let portfolio = employee.getGithubLink(); 440 | 441 | if (employee.type === 'manager') { 442 | portfolio = employee.getMBAProjects(); 443 | } 444 | 445 | const data = { 446 | expectedSalary, 447 | experience, 448 | portfolio 449 | }; 450 | 451 | render(data); 452 | }); 453 | } 454 | ``` 455 | **[⬆ back to top](#table-of-contents)** 456 | 457 | ### Set default objects with Object.assign 458 | 459 | **Bad:** 460 | ```javascript 461 | const menuConfig = { 462 | title: null, 463 | body: 'Bar', 464 | buttonText: null, 465 | cancellable: true 466 | }; 467 | 468 | function createMenu(config) { 469 | config.title = config.title || 'Foo'; 470 | config.body = config.body || 'Bar'; 471 | config.buttonText = config.buttonText || 'Baz'; 472 | config.cancellable = config.cancellable === undefined ? config.cancellable : true; 473 | } 474 | 475 | createMenu(menuConfig); 476 | ``` 477 | 478 | **Good:** 479 | ```javascript 480 | const menuConfig = { 481 | title: 'Order', 482 | // User did not include 'body' key 483 | buttonText: 'Send', 484 | cancellable: true 485 | }; 486 | 487 | function createMenu(config) { 488 | config = Object.assign({ 489 | title: 'Foo', 490 | body: 'Bar', 491 | buttonText: 'Baz', 492 | cancellable: true 493 | }, config); 494 | 495 | // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} 496 | // ... 497 | } 498 | 499 | createMenu(menuConfig); 500 | ``` 501 | **[⬆ back to top](#table-of-contents)** 502 | 503 | 504 | ### Don't use flags as function parameters 505 | Flags tell your user that this function does more than one thing. Functions should do one thing. Split out your functions if they are following different code paths based on a boolean. 506 | 507 | **Bad:** 508 | ```javascript 509 | function createFile(name, temp) { 510 | if (temp) { 511 | fs.create(`./temp/${name}`); 512 | } else { 513 | fs.create(name); 514 | } 515 | } 516 | ``` 517 | 518 | **Good:** 519 | ```javascript 520 | function createFile(name) { 521 | fs.create(name); 522 | } 523 | 524 | function createTempFile(name) { 525 | createFile(`./temp/${name}`); 526 | } 527 | ``` 528 | **[⬆ back to top](#table-of-contents)** 529 | 530 | ### Avoid Side Effects (part 1) 531 | A function produces a side effect if it does anything other than take a value in 532 | and return another value or values. A side effect could be writing to a file, 533 | modifying some global variable, or accidentally wiring all your money to a 534 | stranger. 535 | 536 | Now, you do need to have side effects in a program on occasion. Like the previous 537 | example, you might need to write to a file. What you want to do is to 538 | centralize where you are doing this. Don't have several functions and classes 539 | that write to a particular file. Have one service that does it. One and only one. 540 | 541 | The main point is to avoid common pitfalls like sharing state between objects 542 | without any structure, using mutable data types that can be written to by anything, 543 | and not centralizing where your side effects occur. If you can do this, you will 544 | be happier than the vast majority of other programmers. 545 | 546 | **Bad:** 547 | ```javascript 548 | // Global variable referenced by following function. 549 | // If we had another function that used this name, now it'd be an array and it could break it. 550 | let name = 'Ryan McDermott'; 551 | 552 | function splitIntoFirstAndLastName() { 553 | name = name.split(' '); 554 | } 555 | 556 | splitIntoFirstAndLastName(); 557 | 558 | console.log(name); // ['Ryan', 'McDermott']; 559 | ``` 560 | 561 | **Good:** 562 | ```javascript 563 | function splitIntoFirstAndLastName(name) { 564 | return name.split(' '); 565 | } 566 | 567 | const name = 'Ryan McDermott'; 568 | const newName = splitIntoFirstAndLastName(name); 569 | 570 | console.log(name); // 'Ryan McDermott'; 571 | console.log(newName); // ['Ryan', 'McDermott']; 572 | ``` 573 | **[⬆ back to top](#table-of-contents)** 574 | 575 | ### Avoid Side Effects (part 2) 576 | In JavaScript, primitives are passed by value and objects/arrays are passed by 577 | reference. In the case of objects and arrays, if our function makes a change 578 | in a shopping cart array, for example, by adding an item to purchase, 579 | then any other function that uses that `cart` array will be affected by this 580 | addition. That may be great, however it can be bad too. Let's imagine a bad 581 | situation: 582 | 583 | The user clicks the "Purchase", button which calls a `purchase` function that 584 | spawns a network request and sends the `cart` array to the server. Because 585 | of a bad network connection, the `purchase` function has to keep retrying the 586 | request. Now, what if in the meantime the user accidentally clicks "Add to Cart" 587 | button on an item they don't actually want before the network request begins? 588 | If that happens and the network request begins, then that purchase function 589 | will send the accidentally added item because it has a reference to a shopping 590 | cart array that the `addItemToCart` function modified by adding an unwanted 591 | item. 592 | 593 | A great solution would be for the `addItemToCart` to always clone the `cart`, 594 | edit it, and return the clone. This ensures that no other functions that are 595 | holding onto a reference of the shopping cart will be affected by any changes. 596 | 597 | Two caveats to mention to this approach: 598 | 1. There might be cases where you actually want to modify the input object, 599 | but when you adopt this programming practice you will find that those case 600 | are pretty rare. Most things can be refactored to have no side effects! 601 | 602 | 2. Cloning big objects can be very expensive in terms of performance. Luckily, 603 | this isn't a big issue in practice because there are 604 | [great libraries](https://facebook.github.io/immutable-js/) that allow 605 | this kind of programming approach to be fast and not as memory intensive as 606 | it would be for you to manually clone objects and arrays. 607 | 608 | **Bad:** 609 | ```javascript 610 | const addItemToCart = (cart, item) => { 611 | cart.push({ item, date: Date.now() }); 612 | }; 613 | ``` 614 | 615 | **Good:** 616 | ```javascript 617 | const addItemToCart = (cart, item) => { 618 | return [...cart, { item, date : Date.now() }]; 619 | }; 620 | ``` 621 | 622 | **[⬆ back to top](#table-of-contents)** 623 | 624 | ### Don't write to global functions 625 | Polluting globals is a bad practice in JavaScript because you could clash with another 626 | library and the user of your API would be none-the-wiser until they get an 627 | exception in production. Let's think about an example: what if you wanted to 628 | extend JavaScript's native Array method to have a `diff` method that could 629 | show the difference between two arrays? You could write your new function 630 | to the `Array.prototype`, but it could clash with another library that tried 631 | to do the same thing. What if that other library was just using `diff` to find 632 | the difference between the first and last elements of an array? This is why it 633 | would be much better to just use ES2015/ES6 classes and simply extend the `Array` global. 634 | 635 | **Bad:** 636 | ```javascript 637 | Array.prototype.diff = function diff(comparisonArray) { 638 | const hash = new Set(comparisonArray); 639 | return this.filter(elem => !hash.has(elem)); 640 | }; 641 | ``` 642 | 643 | **Good:** 644 | ```javascript 645 | class SuperArray extends Array { 646 | diff(comparisonArray) { 647 | const hash = new Set(comparisonArray); 648 | return this.filter(elem => !hash.has(elem)); 649 | } 650 | } 651 | ``` 652 | **[⬆ back to top](#table-of-contents)** 653 | 654 | ### Favor functional programming over imperative programming 655 | JavaScript isn't a functional language in the way that Haskell is, but it has 656 | a functional flavor to it. Functional languages are cleaner and easier to test. 657 | Favor this style of programming when you can. 658 | 659 | **Bad:** 660 | ```javascript 661 | const programmerOutput = [ 662 | { 663 | name: 'Uncle Bobby', 664 | linesOfCode: 500 665 | }, { 666 | name: 'Suzie Q', 667 | linesOfCode: 1500 668 | }, { 669 | name: 'Jimmy Gosling', 670 | linesOfCode: 150 671 | }, { 672 | name: 'Gracie Hopper', 673 | linesOfCode: 1000 674 | } 675 | ]; 676 | 677 | let totalOutput = 0; 678 | 679 | for (let i = 0; i < programmerOutput.length; i++) { 680 | totalOutput += programmerOutput[i].linesOfCode; 681 | } 682 | ``` 683 | 684 | **Good:** 685 | ```javascript 686 | const programmerOutput = [ 687 | { 688 | name: 'Uncle Bobby', 689 | linesOfCode: 500 690 | }, { 691 | name: 'Suzie Q', 692 | linesOfCode: 1500 693 | }, { 694 | name: 'Jimmy Gosling', 695 | linesOfCode: 150 696 | }, { 697 | name: 'Gracie Hopper', 698 | linesOfCode: 1000 699 | } 700 | ]; 701 | 702 | const INITIAL_VALUE = 0; 703 | 704 | const totalOutput = programmerOutput 705 | .map((programmer) => programmer.linesOfCode) 706 | .reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE); 707 | ``` 708 | **[⬆ back to top](#table-of-contents)** 709 | 710 | ### Encapsulate conditionals 711 | 712 | **Bad:** 713 | ```javascript 714 | if (fsm.state === 'fetching' && isEmpty(listNode)) { 715 | // ... 716 | } 717 | ``` 718 | 719 | **Good:** 720 | ```javascript 721 | function shouldShowSpinner(fsm, listNode) { 722 | return fsm.state === 'fetching' && isEmpty(listNode); 723 | } 724 | 725 | if (shouldShowSpinner(fsmInstance, listNodeInstance)) { 726 | // ... 727 | } 728 | ``` 729 | **[⬆ back to top](#table-of-contents)** 730 | 731 | ### Avoid negative conditionals 732 | 733 | **Bad:** 734 | ```javascript 735 | function isDOMNodeNotPresent(node) { 736 | // ... 737 | } 738 | 739 | if (!isDOMNodeNotPresent(node)) { 740 | // ... 741 | } 742 | ``` 743 | 744 | **Good:** 745 | ```javascript 746 | function isDOMNodePresent(node) { 747 | // ... 748 | } 749 | 750 | if (isDOMNodePresent(node)) { 751 | // ... 752 | } 753 | ``` 754 | **[⬆ back to top](#table-of-contents)** 755 | 756 | ### Avoid conditionals 757 | This seems like an impossible task. Upon first hearing this, most people say, 758 | "how am I supposed to do anything without an `if` statement?" The answer is that 759 | you can use polymorphism to achieve the same task in many cases. The second 760 | question is usually, "well that's great but why would I want to do that?" The 761 | answer is a previous clean code concept we learned: a function should only do 762 | one thing. When you have classes and functions that have `if` statements, you 763 | are telling your user that your function does more than one thing. Remember, 764 | just do one thing. 765 | 766 | **Bad:** 767 | ```javascript 768 | class Airplane { 769 | // ... 770 | getCruisingAltitude() { 771 | switch (this.type) { 772 | case '777': 773 | return this.getMaxAltitude() - this.getPassengerCount(); 774 | case 'Air Force One': 775 | return this.getMaxAltitude(); 776 | case 'Cessna': 777 | return this.getMaxAltitude() - this.getFuelExpenditure(); 778 | } 779 | } 780 | } 781 | ``` 782 | 783 | **Good:** 784 | ```javascript 785 | class Airplane { 786 | // ... 787 | } 788 | 789 | class Boeing777 extends Airplane { 790 | // ... 791 | getCruisingAltitude() { 792 | return this.getMaxAltitude() - this.getPassengerCount(); 793 | } 794 | } 795 | 796 | class AirForceOne extends Airplane { 797 | // ... 798 | getCruisingAltitude() { 799 | return this.getMaxAltitude(); 800 | } 801 | } 802 | 803 | class Cessna extends Airplane { 804 | // ... 805 | getCruisingAltitude() { 806 | return this.getMaxAltitude() - this.getFuelExpenditure(); 807 | } 808 | } 809 | ``` 810 | **[⬆ back to top](#table-of-contents)** 811 | 812 | ### Avoid type-checking (part 1) 813 | JavaScript is untyped, which means your functions can take any type of argument. 814 | Sometimes you are bitten by this freedom and it becomes tempting to do 815 | type-checking in your functions. There are many ways to avoid having to do this. 816 | The first thing to consider is consistent APIs. 817 | 818 | **Bad:** 819 | ```javascript 820 | function travelToTexas(vehicle) { 821 | if (vehicle instanceof Bicycle) { 822 | vehicle.peddle(this.currentLocation, new Location('texas')); 823 | } else if (vehicle instanceof Car) { 824 | vehicle.drive(this.currentLocation, new Location('texas')); 825 | } 826 | } 827 | ``` 828 | 829 | **Good:** 830 | ```javascript 831 | function travelToTexas(vehicle) { 832 | vehicle.move(this.currentLocation, new Location('texas')); 833 | } 834 | ``` 835 | **[⬆ back to top](#table-of-contents)** 836 | 837 | ### Avoid type-checking (part 2) 838 | If you are working with basic primitive values like strings, integers, and arrays, 839 | and you can't use polymorphism but you still feel the need to type-check, 840 | you should consider using TypeScript. It is an excellent alternative to normal 841 | JavaScript, as it provides you with static typing on top of standard JavaScript 842 | syntax. The problem with manually type-checking normal JavaScript is that 843 | doing it well requires so much extra verbiage that the faux "type-safety" you get 844 | doesn't make up for the lost readability. Keep your JavaScript clean, write 845 | good tests, and have good code reviews. Otherwise, do all of that but with 846 | TypeScript (which, like I said, is a great alternative!). 847 | 848 | **Bad:** 849 | ```javascript 850 | function combine(val1, val2) { 851 | if (typeof val1 === 'number' && typeof val2 === 'number' || 852 | typeof val1 === 'string' && typeof val2 === 'string') { 853 | return val1 + val2; 854 | } 855 | 856 | throw new Error('Must be of type String or Number'); 857 | } 858 | ``` 859 | 860 | **Good:** 861 | ```javascript 862 | function combine(val1, val2) { 863 | return val1 + val2; 864 | } 865 | ``` 866 | **[⬆ back to top](#table-of-contents)** 867 | 868 | ### Don't over-optimize 869 | Modern browsers do a lot of optimization under-the-hood at runtime. A lot of 870 | times, if you are optimizing then you are just wasting your time. [There are good 871 | resources](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) 872 | for seeing where optimization is lacking. Target those in the meantime, until 873 | they are fixed if they can be. 874 | 875 | **Bad:** 876 | ```javascript 877 | 878 | // On old browsers, each iteration with uncached `list.length` would be costly 879 | // because of `list.length` recomputation. In modern browsers, this is optimized. 880 | for (let i = 0, len = list.length; i < len; i++) { 881 | // ... 882 | } 883 | ``` 884 | 885 | **Good:** 886 | ```javascript 887 | for (let i = 0; i < list.length; i++) { 888 | // ... 889 | } 890 | ``` 891 | **[⬆ back to top](#table-of-contents)** 892 | 893 | ### Remove dead code 894 | Dead code is just as bad as duplicate code. There's no reason to keep it in 895 | your codebase. If it's not being called, get rid of it! It will still be safe 896 | in your version history if you still need it. 897 | 898 | **Bad:** 899 | ```javascript 900 | function oldRequestModule(url) { 901 | // ... 902 | } 903 | 904 | function newRequestModule(url) { 905 | // ... 906 | } 907 | 908 | const req = newRequestModule; 909 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 910 | 911 | ``` 912 | 913 | **Good:** 914 | ```javascript 915 | function newRequestModule(url) { 916 | // ... 917 | } 918 | 919 | const req = newRequestModule; 920 | inventoryTracker('apples', req, 'www.inventory-awesome.io'); 921 | ``` 922 | **[⬆ back to top](#table-of-contents)** 923 | 924 | ## **Objects and Data Structures** 925 | ### Use getters and setters 926 | JavaScript doesn't have interfaces or types so it is very hard to enforce this 927 | pattern, because we don't have keywords like `public` and `private`. As it is, 928 | using getters and setters to access data on objects is far better than simply 929 | looking for a property on an object. "Why?" you might ask. Well, here's an 930 | unorganized list of reasons why: 931 | 932 | * When you want to do more beyond getting an object property, you don't have 933 | to look up and change every accessor in your codebase. 934 | * Makes adding validation simple when doing a `set`. 935 | * Encapsulates the internal representation. 936 | * Easy to add logging and error handling when getting and setting. 937 | * Inheriting this class, you can override default functionality. 938 | * You can lazy load your object's properties, let's say getting it from a 939 | server. 940 | 941 | 942 | **Bad:** 943 | ```javascript 944 | class BankAccount { 945 | constructor() { 946 | this.balance = 1000; 947 | } 948 | } 949 | 950 | const bankAccount = new BankAccount(); 951 | 952 | // Buy shoes... 953 | bankAccount.balance -= 100; 954 | ``` 955 | 956 | **Good:** 957 | ```javascript 958 | class BankAccount { 959 | constructor(balance = 1000) { 960 | this._balance = balance; 961 | } 962 | 963 | // It doesn't have to be prefixed with `get` or `set` to be a getter/setter 964 | set balance(amount) { 965 | if (this.verifyIfAmountCanBeSetted(amount)) { 966 | this._balance = amount; 967 | } 968 | } 969 | 970 | get balance() { 971 | return this._balance; 972 | } 973 | 974 | verifyIfAmountCanBeSetted(val) { 975 | // ... 976 | } 977 | } 978 | 979 | const bankAccount = new BankAccount(); 980 | 981 | // Buy shoes... 982 | bankAccount.balance -= shoesPrice; 983 | 984 | // Get balance 985 | let balance = bankAccount.balance; 986 | 987 | ``` 988 | **[⬆ back to top](#table-of-contents)** 989 | 990 | 991 | ### Make objects have private members 992 | This can be accomplished through closures (for ES5 and below). 993 | 994 | **Bad:** 995 | ```javascript 996 | 997 | const Employee = function(name) { 998 | this.name = name; 999 | }; 1000 | 1001 | Employee.prototype.getName = function getName() { 1002 | return this.name; 1003 | }; 1004 | 1005 | const employee = new Employee('John Doe'); 1006 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 1007 | delete employee.name; 1008 | console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined 1009 | ``` 1010 | 1011 | **Good:** 1012 | ```javascript 1013 | const Employee = function (name) { 1014 | this.getName = function getName() { 1015 | return name; 1016 | }; 1017 | }; 1018 | 1019 | const employee = new Employee('John Doe'); 1020 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 1021 | delete employee.name; 1022 | console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe 1023 | ``` 1024 | **[⬆ back to top](#table-of-contents)** 1025 | 1026 | 1027 | ## **Classes** 1028 | ### Single Responsibility Principle (SRP) 1029 | As stated in Clean Code, "There should never be more than one reason for a class 1030 | to change". It's tempting to jam-pack a class with a lot of functionality, like 1031 | when you can only take one suitcase on your flight. The issue with this is 1032 | that your class won't be conceptually cohesive and it will give it many reasons 1033 | to change. Minimizing the amount of times you need to change a class is important. 1034 | It's important because if too much functionality is in one class and you modify a piece of it, 1035 | it can be difficult to understand how that will affect other dependent modules in 1036 | your codebase. 1037 | 1038 | **Bad:** 1039 | ```javascript 1040 | class UserSettings { 1041 | constructor(user) { 1042 | this.user = user; 1043 | } 1044 | 1045 | changeSettings(settings) { 1046 | if (this.verifyCredentials()) { 1047 | // ... 1048 | } 1049 | } 1050 | 1051 | verifyCredentials() { 1052 | // ... 1053 | } 1054 | } 1055 | ``` 1056 | 1057 | **Good:** 1058 | ```javascript 1059 | class UserAuth { 1060 | constructor(user) { 1061 | this.user = user; 1062 | } 1063 | 1064 | verifyCredentials() { 1065 | // ... 1066 | } 1067 | } 1068 | 1069 | 1070 | class UserSettings { 1071 | constructor(user) { 1072 | this.user = user; 1073 | this.auth = new UserAuth(user); 1074 | } 1075 | 1076 | changeSettings(settings) { 1077 | if (this.auth.verifyCredentials()) { 1078 | // ... 1079 | } 1080 | } 1081 | } 1082 | ``` 1083 | **[⬆ back to top](#table-of-contents)** 1084 | 1085 | ### Open/Closed Principle (OCP) 1086 | As stated by Bertrand Meyer, "software entities (classes, modules, functions, 1087 | etc.) should be open for extension, but closed for modification." What does that 1088 | mean though? This principle basically states that you should allow users to 1089 | add new functionalities without changing existing code. 1090 | 1091 | **Bad:** 1092 | ```javascript 1093 | class AjaxAdapter extends Adapter { 1094 | constructor() { 1095 | super(); 1096 | this.name = 'ajaxAdapter'; 1097 | } 1098 | } 1099 | 1100 | class NodeAdapter extends Adapter { 1101 | constructor() { 1102 | super(); 1103 | this.name = 'nodeAdapter'; 1104 | } 1105 | } 1106 | 1107 | class HttpRequester { 1108 | constructor(adapter) { 1109 | this.adapter = adapter; 1110 | } 1111 | 1112 | fetch(url) { 1113 | if (this.adapter.name === 'ajaxAdapter') { 1114 | return makeAjaxCall(url).then((response) => { 1115 | // transform response and return 1116 | }); 1117 | } else if (this.adapter.name === 'httpNodeAdapter') { 1118 | return makeHttpCall(url).then((response) => { 1119 | // transform response and return 1120 | }); 1121 | } 1122 | } 1123 | } 1124 | 1125 | function makeAjaxCall(url) { 1126 | // request and return promise 1127 | } 1128 | 1129 | function makeHttpCall(url) { 1130 | // request and return promise 1131 | } 1132 | ``` 1133 | 1134 | **Good:** 1135 | ```javascript 1136 | class AjaxAdapter extends Adapter { 1137 | constructor() { 1138 | super(); 1139 | this.name = 'ajaxAdapter'; 1140 | } 1141 | 1142 | request(url) { 1143 | // request and return promise 1144 | } 1145 | } 1146 | 1147 | class NodeAdapter extends Adapter { 1148 | constructor() { 1149 | super(); 1150 | this.name = 'nodeAdapter'; 1151 | } 1152 | 1153 | request(url) { 1154 | // request and return promise 1155 | } 1156 | } 1157 | 1158 | class HttpRequester { 1159 | constructor(adapter) { 1160 | this.adapter = adapter; 1161 | } 1162 | 1163 | fetch(url) { 1164 | return this.adapter.request(url).then((response) => { 1165 | // transform response and return 1166 | }); 1167 | } 1168 | } 1169 | ``` 1170 | **[⬆ back to top](#table-of-contents)** 1171 | 1172 | 1173 | ### Liskov Substitution Principle (LSP) 1174 | This is a scary term for a very simple concept. It's formally defined as "If S 1175 | is a subtype of T, then objects of type T may be replaced with objects of type S 1176 | (i.e., objects of type S may substitute objects of type T) without altering any 1177 | of the desirable properties of that program (correctness, task performed, 1178 | etc.)." That's an even scarier definition. 1179 | 1180 | The best explanation for this is if you have a parent class and a child class, 1181 | then the base class and child class can be used interchangeably without getting 1182 | incorrect results. This might still be confusing, so let's take a look at the 1183 | classic Square-Rectangle example. Mathematically, a square is a rectangle, but 1184 | if you model it using the "is-a" relationship via inheritance, you quickly 1185 | get into trouble. 1186 | 1187 | **Bad:** 1188 | ```javascript 1189 | class Rectangle { 1190 | constructor() { 1191 | this.width = 0; 1192 | this.height = 0; 1193 | } 1194 | 1195 | setColor(color) { 1196 | // ... 1197 | } 1198 | 1199 | render(area) { 1200 | // ... 1201 | } 1202 | 1203 | setWidth(width) { 1204 | this.width = width; 1205 | } 1206 | 1207 | setHeight(height) { 1208 | this.height = height; 1209 | } 1210 | 1211 | getArea() { 1212 | return this.width * this.height; 1213 | } 1214 | } 1215 | 1216 | class Square extends Rectangle { 1217 | setWidth(width) { 1218 | this.width = width; 1219 | this.height = width; 1220 | } 1221 | 1222 | setHeight(height) { 1223 | this.width = height; 1224 | this.height = height; 1225 | } 1226 | } 1227 | 1228 | function renderLargeRectangles(rectangles) { 1229 | rectangles.forEach((rectangle) => { 1230 | rectangle.setWidth(4); 1231 | rectangle.setHeight(5); 1232 | const area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20. 1233 | rectangle.render(area); 1234 | }); 1235 | } 1236 | 1237 | const rectangles = [new Rectangle(), new Rectangle(), new Square()]; 1238 | renderLargeRectangles(rectangles); 1239 | ``` 1240 | 1241 | **Good:** 1242 | ```javascript 1243 | class Shape { 1244 | setColor(color) { 1245 | // ... 1246 | } 1247 | 1248 | render(area) { 1249 | // ... 1250 | } 1251 | } 1252 | 1253 | class Rectangle extends Shape { 1254 | constructor() { 1255 | super(); 1256 | this.width = 0; 1257 | this.height = 0; 1258 | } 1259 | 1260 | setWidth(width) { 1261 | this.width = width; 1262 | } 1263 | 1264 | setHeight(height) { 1265 | this.height = height; 1266 | } 1267 | 1268 | getArea() { 1269 | return this.width * this.height; 1270 | } 1271 | } 1272 | 1273 | class Square extends Shape { 1274 | constructor() { 1275 | super(); 1276 | this.length = 0; 1277 | } 1278 | 1279 | setLength(length) { 1280 | this.length = length; 1281 | } 1282 | 1283 | getArea() { 1284 | return this.length * this.length; 1285 | } 1286 | } 1287 | 1288 | function renderLargeShapes(shapes) { 1289 | shapes.forEach((shape) => { 1290 | switch (shape.constructor.name) { 1291 | case 'Square': 1292 | shape.setLength(5); 1293 | break; 1294 | case 'Rectangle': 1295 | shape.setWidth(4); 1296 | shape.setHeight(5); 1297 | } 1298 | 1299 | const area = shape.getArea(); 1300 | shape.render(area); 1301 | }); 1302 | } 1303 | 1304 | const shapes = [new Rectangle(), new Rectangle(), new Square()]; 1305 | renderLargeShapes(shapes); 1306 | ``` 1307 | **[⬆ back to top](#table-of-contents)** 1308 | 1309 | ### Interface Segregation Principle (ISP) 1310 | JavaScript doesn't have interfaces so this principle doesn't apply as strictly 1311 | as others. However, it's important and relevant even with JavaScript's lack of 1312 | type system. 1313 | 1314 | ISP states that "Clients should not be forced to depend upon interfaces that 1315 | they do not use." Interfaces are implicit contracts in JavaScript because of 1316 | duck typing. 1317 | 1318 | A good example to look at that demonstrates this principle in JavaScript is for 1319 | classes that require large settings objects. Not requiring clients to setup 1320 | huge amounts of options is beneficial, because most of the time they won't need 1321 | all of the settings. Making them optional helps prevent having a "fat interface". 1322 | 1323 | **Bad:** 1324 | ```javascript 1325 | class DOMTraverser { 1326 | constructor(settings) { 1327 | this.settings = settings; 1328 | this.setup(); 1329 | } 1330 | 1331 | setup() { 1332 | this.rootNode = this.settings.rootNode; 1333 | this.animationModule.setup(); 1334 | } 1335 | 1336 | traverse() { 1337 | // ... 1338 | } 1339 | } 1340 | 1341 | const $ = new DOMTraverser({ 1342 | rootNode: document.getElementsByTagName('body'), 1343 | animationModule() {} // Most of the time, we won't need to animate when traversing. 1344 | // ... 1345 | }); 1346 | 1347 | ``` 1348 | 1349 | **Good:** 1350 | ```javascript 1351 | class DOMTraverser { 1352 | constructor(settings) { 1353 | this.settings = settings; 1354 | this.options = settings.options; 1355 | this.setup(); 1356 | } 1357 | 1358 | setup() { 1359 | this.rootNode = this.settings.rootNode; 1360 | this.setupOptions(); 1361 | } 1362 | 1363 | setupOptions() { 1364 | if (this.options.animationModule) { 1365 | // ... 1366 | } 1367 | } 1368 | 1369 | traverse() { 1370 | // ... 1371 | } 1372 | } 1373 | 1374 | const $ = new DOMTraverser({ 1375 | rootNode: document.getElementsByTagName('body'), 1376 | options: { 1377 | animationModule() {} 1378 | } 1379 | }); 1380 | ``` 1381 | **[⬆ back to top](#table-of-contents)** 1382 | 1383 | ### Dependency Inversion Principle (DIP) 1384 | This principle states two essential things: 1385 | 1. High-level modules should not depend on low-level modules. Both should 1386 | depend on abstractions. 1387 | 2. Abstractions should not depend upon details. Details should depend on 1388 | abstractions. 1389 | 1390 | This can be hard to understand at first, but if you've worked with Angular.js, 1391 | you've seen an implementation of this principle in the form of Dependency 1392 | Injection (DI). While they are not identical concepts, DIP keeps high-level 1393 | modules from knowing the details of its low-level modules and setting them up. 1394 | It can accomplish this through DI. A huge benefit of this is that it reduces 1395 | the coupling between modules. Coupling is a very bad development pattern because 1396 | it makes your code hard to refactor. 1397 | 1398 | As stated previously, JavaScript doesn't have interfaces so the abstractions 1399 | that are depended upon are implicit contracts. That is to say, the methods 1400 | and properties that an object/class exposes to another object/class. In the 1401 | example below, the implicit contract is that any Request module for an 1402 | `InventoryTracker` will have a `requestItems` method. 1403 | 1404 | **Bad:** 1405 | ```javascript 1406 | class InventoryRequester { 1407 | constructor() { 1408 | this.REQ_METHODS = ['HTTP']; 1409 | } 1410 | 1411 | requestItem(item) { 1412 | // ... 1413 | } 1414 | } 1415 | 1416 | class InventoryTracker { 1417 | constructor(items) { 1418 | this.items = items; 1419 | 1420 | // BAD: We have created a dependency on a specific request implementation. 1421 | // We should just have requestItems depend on a request method: `request` 1422 | this.requester = new InventoryRequester(); 1423 | } 1424 | 1425 | requestItems() { 1426 | this.items.forEach((item) => { 1427 | this.requester.requestItem(item); 1428 | }); 1429 | } 1430 | } 1431 | 1432 | const inventoryTracker = new InventoryTracker(['apples', 'bananas']); 1433 | inventoryTracker.requestItems(); 1434 | ``` 1435 | 1436 | **Good:** 1437 | ```javascript 1438 | class InventoryTracker { 1439 | constructor(items, requester) { 1440 | this.items = items; 1441 | this.requester = requester; 1442 | } 1443 | 1444 | requestItems() { 1445 | this.items.forEach((item) => { 1446 | this.requester.requestItem(item); 1447 | }); 1448 | } 1449 | } 1450 | 1451 | class InventoryRequesterV1 { 1452 | constructor() { 1453 | this.REQ_METHODS = ['HTTP']; 1454 | } 1455 | 1456 | requestItem(item) { 1457 | // ... 1458 | } 1459 | } 1460 | 1461 | class InventoryRequesterV2 { 1462 | constructor() { 1463 | this.REQ_METHODS = ['WS']; 1464 | } 1465 | 1466 | requestItem(item) { 1467 | // ... 1468 | } 1469 | } 1470 | 1471 | // By constructing our dependencies externally and injecting them, we can easily 1472 | // substitute our request module for a fancy new one that uses WebSockets. 1473 | const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); 1474 | inventoryTracker.requestItems(); 1475 | ``` 1476 | **[⬆ back to top](#table-of-contents)** 1477 | 1478 | ### Prefer ES2015/ES6 classes over ES5 plain functions 1479 | It's very difficult to get readable class inheritance, construction, and method 1480 | definitions for classical ES5 classes. If you need inheritance (and be aware 1481 | that you might not), then prefer classes. However, prefer small functions over 1482 | classes until you find yourself needing larger and more complex objects. 1483 | 1484 | **Bad:** 1485 | ```javascript 1486 | const Animal = function(age) { 1487 | if (!(this instanceof Animal)) { 1488 | throw new Error('Instantiate Animal with `new`'); 1489 | } 1490 | 1491 | this.age = age; 1492 | }; 1493 | 1494 | Animal.prototype.move = function move() {}; 1495 | 1496 | const Mammal = function(age, furColor) { 1497 | if (!(this instanceof Mammal)) { 1498 | throw new Error('Instantiate Mammal with `new`'); 1499 | } 1500 | 1501 | Animal.call(this, age); 1502 | this.furColor = furColor; 1503 | }; 1504 | 1505 | Mammal.prototype = Object.create(Animal.prototype); 1506 | Mammal.prototype.constructor = Mammal; 1507 | Mammal.prototype.liveBirth = function liveBirth() {}; 1508 | 1509 | const Human = function(age, furColor, languageSpoken) { 1510 | if (!(this instanceof Human)) { 1511 | throw new Error('Instantiate Human with `new`'); 1512 | } 1513 | 1514 | Mammal.call(this, age, furColor); 1515 | this.languageSpoken = languageSpoken; 1516 | }; 1517 | 1518 | Human.prototype = Object.create(Mammal.prototype); 1519 | Human.prototype.constructor = Human; 1520 | Human.prototype.speak = function speak() {}; 1521 | ``` 1522 | 1523 | **Good:** 1524 | ```javascript 1525 | class Animal { 1526 | constructor(age) { 1527 | this.age = age; 1528 | } 1529 | 1530 | move() { /* ... */ } 1531 | } 1532 | 1533 | class Mammal extends Animal { 1534 | constructor(age, furColor) { 1535 | super(age); 1536 | this.furColor = furColor; 1537 | } 1538 | 1539 | liveBirth() { /* ... */ } 1540 | } 1541 | 1542 | class Human extends Mammal { 1543 | constructor(age, furColor, languageSpoken) { 1544 | super(age, furColor); 1545 | this.languageSpoken = languageSpoken; 1546 | } 1547 | 1548 | speak() { /* ... */ } 1549 | } 1550 | ``` 1551 | **[⬆ back to top](#table-of-contents)** 1552 | 1553 | 1554 | ### Use method chaining 1555 | This pattern is very useful in JavaScript and you see it in many libraries such 1556 | as jQuery and Lodash. It allows your code to be expressive, and less verbose. 1557 | For that reason, I say, use method chaining and take a look at how clean your code 1558 | will be. In your class functions, simply return `this` at the end of every function, 1559 | and you can chain further class methods onto it. 1560 | 1561 | **Bad:** 1562 | ```javascript 1563 | class Car { 1564 | constructor() { 1565 | this.make = 'Honda'; 1566 | this.model = 'Accord'; 1567 | this.color = 'white'; 1568 | } 1569 | 1570 | setMake(make) { 1571 | this.make = make; 1572 | } 1573 | 1574 | setModel(model) { 1575 | this.model = model; 1576 | } 1577 | 1578 | setColor(color) { 1579 | this.color = color; 1580 | } 1581 | 1582 | save() { 1583 | console.log(this.make, this.model, this.color); 1584 | } 1585 | } 1586 | 1587 | const car = new Car(); 1588 | car.setColor('pink'); 1589 | car.setMake('Ford'); 1590 | car.setModel('F-150'); 1591 | car.save(); 1592 | ``` 1593 | 1594 | **Good:** 1595 | ```javascript 1596 | class Car { 1597 | constructor() { 1598 | this.make = 'Honda'; 1599 | this.model = 'Accord'; 1600 | this.color = 'white'; 1601 | } 1602 | 1603 | setMake(make) { 1604 | this.make = make; 1605 | // NOTE: Returning this for chaining 1606 | return this; 1607 | } 1608 | 1609 | setModel(model) { 1610 | this.model = model; 1611 | // NOTE: Returning this for chaining 1612 | return this; 1613 | } 1614 | 1615 | setColor(color) { 1616 | this.color = color; 1617 | // NOTE: Returning this for chaining 1618 | return this; 1619 | } 1620 | 1621 | save() { 1622 | console.log(this.make, this.model, this.color); 1623 | // NOTE: Returning this for chaining 1624 | return this; 1625 | } 1626 | } 1627 | 1628 | const car = new Car() 1629 | .setColor('pink') 1630 | .setMake('Ford') 1631 | .setModel('F-150') 1632 | .save(); 1633 | ``` 1634 | **[⬆ back to top](#table-of-contents)** 1635 | 1636 | ### Prefer composition over inheritance 1637 | As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, 1638 | you should prefer composition over inheritance where you can. There are lots of 1639 | good reasons to use inheritance and lots of good reasons to use composition. 1640 | The main point for this maxim is that if your mind instinctively goes for 1641 | inheritance, try to think if composition could model your problem better. In some 1642 | cases it can. 1643 | 1644 | You might be wondering then, "when should I use inheritance?" It 1645 | depends on your problem at hand, but this is a decent list of when inheritance 1646 | makes more sense than composition: 1647 | 1648 | 1. Your inheritance represents an "is-a" relationship and not a "has-a" 1649 | relationship (Human->Animal vs. User->UserDetails). 1650 | 2. You can reuse code from the base classes (Humans can move like all animals). 1651 | 3. You want to make global changes to derived classes by changing a base class. 1652 | (Change the caloric expenditure of all animals when they move). 1653 | 1654 | **Bad:** 1655 | ```javascript 1656 | class Employee { 1657 | constructor(name, email) { 1658 | this.name = name; 1659 | this.email = email; 1660 | } 1661 | 1662 | // ... 1663 | } 1664 | 1665 | // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee 1666 | class EmployeeTaxData extends Employee { 1667 | constructor(ssn, salary) { 1668 | super(); 1669 | this.ssn = ssn; 1670 | this.salary = salary; 1671 | } 1672 | 1673 | // ... 1674 | } 1675 | ``` 1676 | 1677 | **Good:** 1678 | ```javascript 1679 | class EmployeeTaxData { 1680 | constructor(ssn, salary) { 1681 | this.ssn = ssn; 1682 | this.salary = salary; 1683 | } 1684 | 1685 | // ... 1686 | } 1687 | 1688 | class Employee { 1689 | constructor(name, email) { 1690 | this.name = name; 1691 | this.email = email; 1692 | } 1693 | 1694 | setTaxData(ssn, salary) { 1695 | this.taxData = new EmployeeTaxData(ssn, salary); 1696 | } 1697 | // ... 1698 | } 1699 | ``` 1700 | **[⬆ back to top](#table-of-contents)** 1701 | 1702 | ## **Testing** 1703 | Testing is more important than shipping. If you have no tests or an 1704 | inadequate amount, then every time you ship code you won't be sure that you 1705 | didn't break anything. Deciding on what constitutes an adequate amount is up 1706 | to your team, but having 100% coverage (all statements and branches) is how 1707 | you achieve very high confidence and developer peace of mind. This means that 1708 | in addition to having a great testing framework, you also need to use a 1709 | [good coverage tool](http://gotwarlost.github.io/istanbul/). 1710 | 1711 | There's no excuse to not write tests. There's [plenty of good JS test frameworks] 1712 | (http://jstherightway.org/#testing-tools), so find one that your team prefers. 1713 | When you find one that works for your team, then aim to always write tests 1714 | for every new feature/module you introduce. If your preferred method is 1715 | Test Driven Development (TDD), that is great, but the main point is to just 1716 | make sure you are reaching your coverage goals before launching any feature, 1717 | or refactoring an existing one. 1718 | 1719 | ### Single concept per test 1720 | 1721 | **Bad:** 1722 | ```javascript 1723 | const assert = require('assert'); 1724 | 1725 | describe('MakeMomentJSGreatAgain', () => { 1726 | it('handles date boundaries', () => { 1727 | let date; 1728 | 1729 | date = new MakeMomentJSGreatAgain('1/1/2015'); 1730 | date.addDays(30); 1731 | date.shouldEqual('1/31/2015'); 1732 | 1733 | date = new MakeMomentJSGreatAgain('2/1/2016'); 1734 | date.addDays(28); 1735 | assert.equal('02/29/2016', date); 1736 | 1737 | date = new MakeMomentJSGreatAgain('2/1/2015'); 1738 | date.addDays(28); 1739 | assert.equal('03/01/2015', date); 1740 | }); 1741 | }); 1742 | ``` 1743 | 1744 | **Good:** 1745 | ```javascript 1746 | const assert = require('assert'); 1747 | 1748 | describe('MakeMomentJSGreatAgain', () => { 1749 | it('handles 30-day months', () => { 1750 | const date = new MakeMomentJSGreatAgain('1/1/2015'); 1751 | date.addDays(30); 1752 | date.shouldEqual('1/31/2015'); 1753 | }); 1754 | 1755 | it('handles leap year', () => { 1756 | const date = new MakeMomentJSGreatAgain('2/1/2016'); 1757 | date.addDays(28); 1758 | assert.equal('02/29/2016', date); 1759 | }); 1760 | 1761 | it('handles non-leap year', () => { 1762 | const date = new MakeMomentJSGreatAgain('2/1/2015'); 1763 | date.addDays(28); 1764 | assert.equal('03/01/2015', date); 1765 | }); 1766 | }); 1767 | ``` 1768 | **[⬆ back to top](#table-of-contents)** 1769 | 1770 | ## **Concurrency** 1771 | ### Use Promises, not callbacks 1772 | Callbacks aren't clean, and they cause excessive amounts of nesting. With ES2015/ES6, 1773 | Promises are a built-in global type. Use them! 1774 | 1775 | **Bad:** 1776 | ```javascript 1777 | const request = require('request'); 1778 | const fs = require('fs'); 1779 | 1780 | request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => { 1781 | if (requestErr) { 1782 | console.error(requestErr); 1783 | } else { 1784 | fs.writeFile('article.html', response.body, (writeErr) => { 1785 | if (writeErr) { 1786 | console.error(writeErr); 1787 | } else { 1788 | console.log('File written'); 1789 | } 1790 | }); 1791 | } 1792 | }); 1793 | 1794 | ``` 1795 | 1796 | **Good:** 1797 | ```javascript 1798 | const requestPromise = require('request-promise'); 1799 | const fsPromise = require('fs-promise'); 1800 | 1801 | requestPromise.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1802 | .then((response) => { 1803 | return fsPromise.writeFile('article.html', response); 1804 | }) 1805 | .then(() => { 1806 | console.log('File written'); 1807 | }) 1808 | .catch((err) => { 1809 | console.error(err); 1810 | }); 1811 | 1812 | ``` 1813 | **[⬆ back to top](#table-of-contents)** 1814 | 1815 | ### Async/Await are even cleaner than Promises 1816 | Promises are a very clean alternative to callbacks, but ES2017/ES8 brings async and await 1817 | which offer an even cleaner solution. All you need is a function that is prefixed 1818 | in an `async` keyword, and then you can write your logic imperatively without 1819 | a `then` chain of functions. Use this if you can take advantage of ES2017/ES8 features 1820 | today! 1821 | 1822 | **Bad:** 1823 | ```javascript 1824 | const requestPromise = require('request-promise'); 1825 | const fsPromise = require('fs-promise'); 1826 | 1827 | requestPromise.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') 1828 | .then((response) => { 1829 | return fsPromise.writeFile('article.html', response); 1830 | }) 1831 | .then(() => { 1832 | console.log('File written'); 1833 | }) 1834 | .catch((err) => { 1835 | console.error(err); 1836 | }); 1837 | 1838 | ``` 1839 | 1840 | **Good:** 1841 | ```javascript 1842 | const requestPromise = require('request-promise'); 1843 | const fsPromise = require('fs-promise'); 1844 | 1845 | async function getCleanCodeArticle() { 1846 | try { 1847 | const response = await requestPromise.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin'); 1848 | await fsPromise.writeFile('article.html', response); 1849 | console.log('File written'); 1850 | } catch(err) { 1851 | console.error(err); 1852 | } 1853 | } 1854 | ``` 1855 | **[⬆ back to top](#table-of-contents)** 1856 | 1857 | 1858 | ## **Error Handling** 1859 | Thrown errors are a good thing! They mean the runtime has successfully 1860 | identified when something in your program has gone wrong and it's letting 1861 | you know by stopping function execution on the current stack, killing the 1862 | process (in Node), and notifying you in the console with a stack trace. 1863 | 1864 | ### Don't ignore caught errors 1865 | Doing nothing with a caught error doesn't give you the ability to ever fix 1866 | or react to said error. Logging the error to the console (`console.log`) 1867 | isn't much better as often times it can get lost in a sea of things printed 1868 | to the console. If you wrap any bit of code in a `try/catch` it means you 1869 | think an error may occur there and therefore you should have a plan, 1870 | or create a code path, for when it occurs. 1871 | 1872 | **Bad:** 1873 | ```javascript 1874 | try { 1875 | functionThatMightThrow(); 1876 | } catch (error) { 1877 | console.log(error); 1878 | } 1879 | ``` 1880 | 1881 | **Good:** 1882 | ```javascript 1883 | try { 1884 | functionThatMightThrow(); 1885 | } catch (error) { 1886 | // One option (more noisy than console.log): 1887 | console.error(error); 1888 | // Another option: 1889 | notifyUserOfError(error); 1890 | // Another option: 1891 | reportErrorToService(error); 1892 | // OR do all three! 1893 | } 1894 | ``` 1895 | 1896 | ### Don't ignore rejected promises 1897 | For the same reason you shouldn't ignore caught errors 1898 | from `try/catch`. 1899 | 1900 | **Bad:** 1901 | ```javascript 1902 | getdata() 1903 | .then((data) => { 1904 | functionThatMightThrow(data); 1905 | }) 1906 | .catch((error) => { 1907 | console.log(error); 1908 | }); 1909 | ``` 1910 | 1911 | **Good:** 1912 | ```javascript 1913 | getdata() 1914 | .then((data) => { 1915 | functionThatMightThrow(data); 1916 | }) 1917 | .catch((error) => { 1918 | // One option (more noisy than console.log): 1919 | console.error(error); 1920 | // Another option: 1921 | notifyUserOfError(error); 1922 | // Another option: 1923 | reportErrorToService(error); 1924 | // OR do all three! 1925 | }); 1926 | ``` 1927 | 1928 | **[⬆ back to top](#table-of-contents)** 1929 | 1930 | 1931 | ## **Formatting** 1932 | Formatting is subjective. Like many rules herein, there is no hard and fast 1933 | rule that you must follow. The main point is DO NOT ARGUE over formatting. 1934 | There are [tons of tools](http://standardjs.com/rules.html) to automate this. 1935 | Use one! It's a waste of time and money for engineers to argue over formatting. 1936 | 1937 | For things that don't fall under the purview of automatic formatting 1938 | (indentation, tabs vs. spaces, double vs. single quotes, etc.) look here 1939 | for some guidance. 1940 | 1941 | ### Use consistent capitalization 1942 | JavaScript is untyped, so capitalization tells you a lot about your variables, 1943 | functions, etc. These rules are subjective, so your team can choose whatever 1944 | they want. The point is, no matter what you all choose, just be consistent. 1945 | 1946 | **Bad:** 1947 | ```javascript 1948 | const DAYS_IN_WEEK = 7; 1949 | const daysInMonth = 30; 1950 | 1951 | const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1952 | const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1953 | 1954 | function eraseDatabase() {} 1955 | function restore_database() {} 1956 | 1957 | class animal {} 1958 | class Alpaca {} 1959 | ``` 1960 | 1961 | **Good:** 1962 | ```javascript 1963 | const DAYS_IN_WEEK = 7; 1964 | const DAYS_IN_MONTH = 30; 1965 | 1966 | const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; 1967 | const artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; 1968 | 1969 | function eraseDatabase() {} 1970 | function restoreDatabase() {} 1971 | 1972 | class Animal {} 1973 | class Alpaca {} 1974 | ``` 1975 | **[⬆ back to top](#table-of-contents)** 1976 | 1977 | 1978 | ### Function callers and callees should be close 1979 | If a function calls another, keep those functions vertically close in the source 1980 | file. Ideally, keep the caller right above the callee. We tend to read code from 1981 | top-to-bottom, like a newspaper. Because of this, make your code read that way. 1982 | 1983 | **Bad:** 1984 | ```javascript 1985 | class PerformanceReview { 1986 | constructor(employee) { 1987 | this.employee = employee; 1988 | } 1989 | 1990 | lookupPeers() { 1991 | return db.lookup(this.employee, 'peers'); 1992 | } 1993 | 1994 | lookupManager() { 1995 | return db.lookup(this.employee, 'manager'); 1996 | } 1997 | 1998 | getPeerReviews() { 1999 | const peers = this.lookupPeers(); 2000 | // ... 2001 | } 2002 | 2003 | perfReview() { 2004 | this.getPeerReviews(); 2005 | this.getManagerReview(); 2006 | this.getSelfReview(); 2007 | } 2008 | 2009 | getManagerReview() { 2010 | const manager = this.lookupManager(); 2011 | } 2012 | 2013 | getSelfReview() { 2014 | // ... 2015 | } 2016 | } 2017 | 2018 | const review = new PerformanceReview(user); 2019 | review.perfReview(); 2020 | ``` 2021 | 2022 | **Good:** 2023 | ```javascript 2024 | class PerformanceReview { 2025 | constructor(employee) { 2026 | this.employee = employee; 2027 | } 2028 | 2029 | perfReview() { 2030 | this.getPeerReviews(); 2031 | this.getManagerReview(); 2032 | this.getSelfReview(); 2033 | } 2034 | 2035 | getPeerReviews() { 2036 | const peers = this.lookupPeers(); 2037 | // ... 2038 | } 2039 | 2040 | lookupPeers() { 2041 | return db.lookup(this.employee, 'peers'); 2042 | } 2043 | 2044 | getManagerReview() { 2045 | const manager = this.lookupManager(); 2046 | } 2047 | 2048 | lookupManager() { 2049 | return db.lookup(this.employee, 'manager'); 2050 | } 2051 | 2052 | getSelfReview() { 2053 | // ... 2054 | } 2055 | } 2056 | 2057 | const review = new PerformanceReview(employee); 2058 | review.perfReview(); 2059 | ``` 2060 | 2061 | **[⬆ back to top](#table-of-contents)** 2062 | 2063 | ## **Comments** 2064 | ### Only comment things that have business logic complexity. 2065 | Comments are an apology, not a requirement. Good code *mostly* documents itself. 2066 | 2067 | **Bad:** 2068 | ```javascript 2069 | function hashIt(data) { 2070 | // The hash 2071 | let hash = 0; 2072 | 2073 | // Length of string 2074 | const length = data.length; 2075 | 2076 | // Loop through every character in data 2077 | for (let i = 0; i < length; i++) { 2078 | // Get character code. 2079 | const char = data.charCodeAt(i); 2080 | // Make the hash 2081 | hash = ((hash << 5) - hash) + char; 2082 | // Convert to 32-bit integer 2083 | hash &= hash; 2084 | } 2085 | } 2086 | ``` 2087 | 2088 | **Good:** 2089 | ```javascript 2090 | 2091 | function hashIt(data) { 2092 | let hash = 0; 2093 | const length = data.length; 2094 | 2095 | for (let i = 0; i < length; i++) { 2096 | const char = data.charCodeAt(i); 2097 | hash = ((hash << 5) - hash) + char; 2098 | 2099 | // Convert to 32-bit integer 2100 | hash &= hash; 2101 | } 2102 | } 2103 | 2104 | ``` 2105 | **[⬆ back to top](#table-of-contents)** 2106 | 2107 | ### Don't leave commented out code in your codebase 2108 | Version control exists for a reason. Leave old code in your history. 2109 | 2110 | **Bad:** 2111 | ```javascript 2112 | doStuff(); 2113 | // doOtherStuff(); 2114 | // doSomeMoreStuff(); 2115 | // doSoMuchStuff(); 2116 | ``` 2117 | 2118 | **Good:** 2119 | ```javascript 2120 | doStuff(); 2121 | ``` 2122 | **[⬆ back to top](#table-of-contents)** 2123 | 2124 | ### Don't have journal comments 2125 | Remember, use version control! There's no need for dead code, commented code, 2126 | and especially journal comments. Use `git log` to get history! 2127 | 2128 | **Bad:** 2129 | ```javascript 2130 | /** 2131 | * 2016-12-20: Removed monads, didn't understand them (RM) 2132 | * 2016-10-01: Improved using special monads (JP) 2133 | * 2016-02-03: Removed type-checking (LI) 2134 | * 2015-03-14: Added combine with type-checking (JR) 2135 | */ 2136 | function combine(a, b) { 2137 | return a + b; 2138 | } 2139 | ``` 2140 | 2141 | **Good:** 2142 | ```javascript 2143 | function combine(a, b) { 2144 | return a + b; 2145 | } 2146 | ``` 2147 | **[⬆ back to top](#table-of-contents)** 2148 | 2149 | ### Avoid positional markers 2150 | They usually just add noise. Let the functions and variable names along with the 2151 | proper indentation and formatting give the visual structure to your code. 2152 | 2153 | **Bad:** 2154 | ```javascript 2155 | //////////////////////////////////////////////////////////////////////////////// 2156 | // Scope Model Instantiation 2157 | //////////////////////////////////////////////////////////////////////////////// 2158 | $scope.model = { 2159 | menu: 'foo', 2160 | nav: 'bar' 2161 | }; 2162 | 2163 | //////////////////////////////////////////////////////////////////////////////// 2164 | // Action setup 2165 | //////////////////////////////////////////////////////////////////////////////// 2166 | const actions = function() { 2167 | // ... 2168 | }; 2169 | ``` 2170 | 2171 | **Good:** 2172 | ```javascript 2173 | $scope.model = { 2174 | menu: 'foo', 2175 | nav: 'bar' 2176 | }; 2177 | 2178 | const actions = function() { 2179 | // ... 2180 | }; 2181 | ``` 2182 | **[⬆ back to top](#table-of-contents)** 2183 | 2184 | ## Translation 2185 | 2186 | This is also available in other languages: 2187 | 2188 | - ![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) 2189 | - ![cn](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/China.png) **Chinese**: [alivebao/clean-code-js](https://github.com/alivebao/clean-code-js) 2190 | - ![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) 2191 | - ![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) 2192 | 2193 | **[⬆ back to top](#table-of-contents)** 2194 | --------------------------------------------------------------------------------