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