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