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