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