├── CODE_STYLE.md ├── GIT.md ├── JS.md ├── LICENSE ├── PHP.md └── README.md /CODE_STYLE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Infinitiweb/code-conventions/0424ca3c0a181be8b7830d2869cb22d3eed027ba/CODE_STYLE.md -------------------------------------------------------------------------------- /GIT.md: -------------------------------------------------------------------------------- 1 | ## Глобальный .gitignore 2 | 3 | На машине разработчика должен быть настроен глобальный гит игнор, в который вносятся данные специфичные для его 4 | окружения (ОС, используемой ide и проч.), чтобы в файле проекта не было лишних записей, только то что относится к проекту. 5 | Как это сделать: 6 | 7 | Создать файл `.gitignore` не в папке проекта (`~/.gitignore` или в папке со всеми проектами), например: 8 | 9 | ``` 10 | # phpstorm project files 11 | .idea 12 | 13 | # netbeans project files 14 | nbproject 15 | 16 | # zend studio for eclipse project files 17 | .buildpath 18 | .project 19 | .settings 20 | 21 | # windows thumbnail cache 22 | Thumbs.db 23 | 24 | # Mac DS_Store Files 25 | .DS_Store 26 | 27 | # phpunit itself is not needed 28 | phpunit.phar 29 | 30 | #ignore codeception phar archive 31 | codecept.phar 32 | ``` 33 | 34 | Назначить глобальным (комманды даны с расчетом что файл вы положиле в домашнюю папку пользователя от которого выполняете команды): 35 | 36 | *nix: 37 | ``` 38 | git config --global core.excludesfile '~/.gitignore' 39 | ``` 40 | Windows git bash: 41 | ``` 42 | git config --global core.excludesfile '~/.gitignore' 43 | ``` 44 | Windows cmd: 45 | ``` 46 | git config --global core.excludesfile "%USERPROFILE%\.gitignore" 47 | ``` 48 | 49 | ## Ветки 50 | 51 | Произойти может всё что угодно, поэтому коммиты сразу в master под запретом, а сама ветка master по умолчанию защищена 52 | от удаления и форс пуша. Любые изменения предлагаются через пулреквесты. 53 | 54 | ### Именование 55 | 56 | Для единообразия и не избежания путаницы ветки именуются номером задачи, без дополнительных префиксов: 57 | 58 | * %issue_number% — разделения на новый функционал и исправления нет. 59 | 60 | Любые исправления уже выкаченой на бой задачи оформляются в отдельную, на основании которой создается ПР. 61 | 62 | Дополнительная ветка hotfix, в нее заливаются быстрые заплатки. 63 | 64 | ## Работа с кодом 65 | 66 | ### Коммиты 67 | 68 | Один коммит решает одну небольшую задачу, а не объединяет под собой несколько. Так гораздо проще следить за изменениями 69 | и восстановить ход работы при необходимости. 70 | 71 | К сообщению коммита нужно прикрепить номер задачи (2349. Fixed file upload.). 72 | 73 | Если коммит не привязан к задаче, например вы заливаете быстрофикс в ветку hotfix, в комментарии максимально 74 | развернуто объяснить что он делает. 75 | 76 | ## Подготовка кода к пул-реквесту 77 | 78 | Перед отправкой кода на проверку в ПР, нужно его подготовить, разрешить все конфликты с текущим состоянием ветки `master`. Для этого нужно: 79 | 80 | 1. Обновить всю информацию о репозитории: 81 | 82 | ```git fetch --all``` 83 | 84 | 2. Обновить текущую ветку до мастера: 85 | 86 | ```git rebase -i origin/master``` 87 | 88 | Если необходимо — схлопнуть выбранные коммиты (`squash`) и оставить (`pick`). Например: 89 | 90 | ``` 91 | pick f7f3f6d 3548_fix 92 | squash 310154e 3548_change 93 | squash a5f4a0d 3548_change 94 | 95 | # Rebase 710f0f8..a5f4a0d onto 710f0f8 96 | # 97 | # Commands: 98 | # p, pick = use commit 99 | # e, edit = use commit, but stop for amending 100 | # s, squash = use commit, but meld into previous commit 101 | # 102 | # If you remove a line here THAT COMMIT WILL BE LOST. 103 | # However, if you remove everything, the rebase will be aborted. 104 | # 105 | ``` 106 | 107 | Теперь гит находится в состоянии ребейза о чем свидетельствует статус: 108 | 109 | ``` 110 | $ git status 111 | 112 | interactive rebase in progress; onto 52e4b371 113 | Last commands done (9 commands done): 114 | squash 8e56a9b6 3569. Working parser, but some trash in article text. 115 | squash 3cbbbc27 3569. Working parser, start tests. 116 | (see more in file .git/rebase-merge/done) 117 | Next commands to do (4 remaining commands): 118 | squash 7c905aa6 3569. Added Gazetaru parser, test for ContentSaverHandler, update test config. 119 | squash d7c0836d 3569. Fixed test, reformat Content saver. 120 | (use "git rebase --edit-todo" to view and edit) 121 | You are currently rebasing branch 'feature_3569' on '52e4b371'. 122 | (fix conflicts and then run "git rebase --continue") 123 | (use "git rebase --skip" to skip this patch) 124 | (use "git rebase --abort" to check out the original branch) 125 | 126 | Changes to be committed: 127 | (use "git reset HEAD ..." to unstage) 128 | 129 | modified: commands/CloneController.php 130 | modified: library/cloning/agents/cryptocurrency/Base.php 131 | new file: library/cloning/agents/svpressa/CleanHandler.php 132 | modified: library/cloning/agents/svpressa/Parser.php 133 | modified: library/cloning/base/PageListParser.php 134 | modified: library/cloning/base/Parser.php 135 | new file: library/cloning/base/exceptions/CloningExitException.php 136 | modified: library/cloning/handlers/ContentCrawlerHandler.php 137 | modified: library/cloning/handlers/ContentSaveHandler.php 138 | new file: tests/unit/library/cloning/svpressa/CleanerTest.php 139 | 140 | Unmerged paths: 141 | (use "git reset HEAD ..." to unstage) 142 | (use "git add ..." to mark resolution) 143 | 144 | both modified: config/test.php 145 | both modified: tests/unit/_bootstrap.php 146 | ``` 147 | 148 | В процессе ребейза могут возникнуть конфликты, после их решения нужно сделать: 149 | 150 | ```git rebase --continue``` 151 | 152 | После чего комиты сольются в один и нужно будет выполнить force push своей ветки: 153 | 154 | ```git push -f origin %task_number%``` 155 | 156 | Теперь можно создавать пулл реквест. -------------------------------------------------------------------------------- /JS.md: -------------------------------------------------------------------------------- 1 | ** документ в процессе наполнения... но некоторые правила уже можно и нужно использовать*** 2 | 3 | 4 | *Наиболее разумный подход к JavaScript* от *AirBnB, GitHub, & Google* 5 | 6 | Зачем нам это ¯\\_(ツ)_/¯ ? 7 | 8 | >Весь код в любой кодовой базе должен выглядеть так, как будто его набрал один человек, 9 | независимо от того, сколько человек внесло свой вклад. 10 | 11 | *Научитесь программировать как Google* 12 | 13 | ## Оглавление 14 | 15 | 1. [Типы](#types) 16 | 1. [Объявление переменных](#references) 17 | 1. [Объекты](#objects) 18 | 1. [Массивы](#arrays) 19 | 1. [Деструктуризация](#destructuring) 20 | 1. [Строки](#strings) 21 | 1. [Функции](#functions) 22 | 1. [Стрелочные функции](#arrow-functions) 23 | 1. [Классы и конструкторы](#classes--constructors) 24 | 1. [Модули](#modules) 25 | 1. [Итераторы и генераторы](#iterators-and-generators) 26 | 1. [Свойства](#properties) 27 | 1. [Переменные](#variables) 28 | 1. [Подъём](#hoisting) 29 | 1. [Операторы сравнения и равенства](#comparison-operators--equality) 30 | 1. [Блоки](#blocks) 31 | 1. [Управляющие операторы](#control-statements) 32 | 1. [Комментарии](#comments) 33 | 1. [Пробелы](#whitespace) 34 | 1. [Запятые](#commas) 35 | 1. [Точка с запятой](#semicolons) 36 | 1. [Приведение типов](#type-casting--coercion) 37 | 1. [Соглашение об именовании](#naming-conventions) 38 | 1. [Аксессоры](#accessors) 39 | 1. [События](#events) 40 | 1. [jQuery](#jquery) 41 | 1. [Поддержка ECMAScript 5](#ecmascript-5-compatibility) 42 | 1. [Возможности ECMAScript 6+ (ES 2015+)](#ecmascript-6-es-2015-styles) 43 | 1. [Стандартная библиотека](#standard-library) 44 | 1. [Тестирование](#testing) 45 | 1. [Производительность](#performance) 46 | 1. [Ресурсы](#resources) 47 | 48 | ## Типы 49 | 50 | 51 | - [1.1](#types--primitives) **Простые типы**: Когда вы взаимодействуете с простым типом, вы напрямую работаете с его значением. 52 | 53 | - `string` 54 | - `number` 55 | - `boolean` 56 | - `null` 57 | - `undefined` 58 | - `symbol` 59 | 60 | ```javascript 61 | const foo = 1; 62 | let bar = foo; 63 | 64 | bar = 9; 65 | 66 | console.log(foo, bar); // => 1, 9 67 | ``` 68 | 69 | - Символы не могут быть полностью заполифиллены, поэтому они не должны использоваться, если разработка ведётся для браузеров/сред, которые не поддерживают их нативно. 70 | 71 | 72 | - [1.2](#types--complex) **Сложные типы**: Когда вы взаимодействуете со сложным типом, вы работаете со ссылкой на его значение. 73 | 74 | - `object` 75 | - `array` 76 | - `function` 77 | 78 | ```javascript 79 | const foo = [1, 2]; 80 | const bar = foo; 81 | 82 | bar[0] = 9; 83 | 84 | console.log(foo[0], bar[0]); // => 9, 9 85 | ``` 86 | 87 | **[⬆ к оглавлению](#Оглавление)** 88 | 89 | ## Объявление переменных 90 | 91 | 92 | - [2.1](#references--prefer-const) Используйте `const` для объявления переменных; избегайте `var`. eslint: [`prefer-const`](https://eslint.org/docs/rules/prefer-const.html), [`no-const-assign`](https://eslint.org/docs/rules/no-const-assign.html) 93 | 94 | > Почему? Это гарантирует, что вы не сможете переопределять значения, т.к. это может привести к ошибкам и к усложнению понимания кода. 95 | 96 | ```javascript 97 | // плохо 98 | var a = 1; 99 | var b = 2; 100 | 101 | // хорошо 102 | const a = 1; 103 | const b = 2; 104 | ``` 105 | 106 | 107 | - [2.2](#references--disallow-var) Если вам необходимо переопределять значения, то используйте `let` вместо `var`. eslint: [`no-var`](https://eslint.org/docs/rules/no-var.html) 108 | 109 | > Почему? Область видимости `let` — блок, у `var` — функция. 110 | 111 | ```javascript 112 | // плохо 113 | var count = 1; 114 | if (true) { 115 | count += 1; 116 | } 117 | 118 | // хорошо, используйте let. 119 | let count = 1; 120 | if (true) { 121 | count += 1; 122 | } 123 | ``` 124 | 125 | 126 | - [2.3](#references--block-scope) Помните, что у `let` и `const` блочная область видимости. 127 | 128 | ```javascript 129 | // const и let существуют только в том блоке, в котором они определены. 130 | { 131 | let a = 1; 132 | const b = 1; 133 | } 134 | console.log(a); // ReferenceError 135 | console.log(b); // ReferenceError 136 | ``` 137 | 138 | **[⬆ к оглавлению](#Оглавление)** 139 | 140 | ## Объекты 141 | 142 | 143 | - [3.1](#objects--no-new) Для создания объекта используйте литеральную нотацию. eslint: [`no-new-object`](https://eslint.org/docs/rules/no-new-object.html) 144 | 145 | ```javascript 146 | // плохо 147 | const item = new Object(); 148 | 149 | // хорошо 150 | const item = {}; 151 | ``` 152 | 153 | 154 | - [3.2](#es6-computed-properties) Используйте вычисляемые имена свойств, когда создаёте объекты с динамическими именами свойств. 155 | 156 | > Почему? Они позволяют вам определить все свойства объекта в одном месте. 157 | 158 | ```javascript 159 | 160 | function getKey(k) { 161 | return `a key named ${k}`; 162 | } 163 | 164 | // плохо 165 | const obj = { 166 | id: 5, 167 | name: 'San Francisco', 168 | }; 169 | obj[getKey('enabled')] = true; 170 | 171 | // хорошо 172 | const obj = { 173 | id: 5, 174 | name: 'San Francisco', 175 | [getKey('enabled')]: true, 176 | }; 177 | ``` 178 | 179 | 180 | - [3.3](#es6-object-shorthand) Используйте сокращённую запись метода объекта. eslint: [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand.html) 181 | 182 | ```javascript 183 | // плохо 184 | const atom = { 185 | value: 1, 186 | 187 | addValue: function (value) { 188 | return atom.value + value; 189 | }, 190 | }; 191 | 192 | // хорошо 193 | const atom = { 194 | value: 1, 195 | 196 | addValue(value) { 197 | return atom.value + value; 198 | }, 199 | }; 200 | ``` 201 | 202 | 203 | - [3.4](#es6-object-concise) Используйте сокращённую запись свойств объекта. eslint: [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand.html) 204 | 205 | > Почему? Это короче и понятнее. 206 | 207 | ```javascript 208 | const lukeSkywalker = 'Luke Skywalker'; 209 | 210 | // плохо 211 | const obj = { 212 | lukeSkywalker: lukeSkywalker, 213 | }; 214 | 215 | // хорошо 216 | const obj = { 217 | lukeSkywalker, 218 | }; 219 | ``` 220 | 221 | 222 | - [3.5](#objects--grouped-shorthand) Группируйте ваши сокращённые записи свойств в начале объявления объекта. 223 | 224 | > Почему? Так легче сказать, какие свойства используют сокращённую запись. 225 | 226 | ```javascript 227 | const anakinSkywalker = 'Anakin Skywalker'; 228 | const lukeSkywalker = 'Luke Skywalker'; 229 | 230 | // плохо 231 | const obj = { 232 | episodeOne: 1, 233 | twoJediWalkIntoACantina: 2, 234 | lukeSkywalker, 235 | episodeThree: 3, 236 | mayTheFourth: 4, 237 | anakinSkywalker, 238 | }; 239 | 240 | // хорошо 241 | const obj = { 242 | lukeSkywalker, 243 | anakinSkywalker, 244 | episodeOne: 1, 245 | twoJediWalkIntoACantina: 2, 246 | episodeThree: 3, 247 | mayTheFourth: 4, 248 | }; 249 | ``` 250 | 251 | 252 | - [3.6](#objects--quoted-props) Только недопустимые идентификаторы помещаются в кавычки. eslint: [`quote-props`](https://eslint.org/docs/rules/quote-props.html) 253 | 254 | > Почему? На наш взгляд, такой код легче читать. Это улучшает подсветку синтаксиса, а также облегчает оптимизацию для многих JS-движков. 255 | 256 | ```javascript 257 | // плохо 258 | const bad = { 259 | 'foo': 3, 260 | 'bar': 4, 261 | 'data-blah': 5, 262 | }; 263 | 264 | // хорошо 265 | const good = { 266 | foo: 3, 267 | bar: 4, 268 | 'data-blah': 5, 269 | }; 270 | ``` 271 | 272 | 273 | - [3.7](#objects--prototype-builtins) Не вызывайте напрямую методы `Object.prototype`, такие как `hasOwnProperty`, `propertyIsEnumerable`, и `isPrototypeOf`. eslint: [`no-prototype-builtins`](https://eslint.org/docs/rules/no-prototype-builtins) 274 | 275 | > Почему? Эти методы могут быть переопределены в свойствах объекта, который мы проверяем `{ hasOwnProperty: false }`, или этот объект может быть `null` (`Object.create(null)`). 276 | 277 | ```javascript 278 | // плохо 279 | console.log(object.hasOwnProperty(key)); 280 | 281 | // хорошо 282 | console.log(Object.prototype.hasOwnProperty.call(object, key)); 283 | 284 | // отлично 285 | const has = Object.prototype.hasOwnProperty; // Кэшируем запрос в рамках модуля. 286 | /* или */ 287 | import has from 'has'; // https://www.npmjs.com/package/has 288 | // ... 289 | console.log(has.call(object, key)); 290 | ``` 291 | 292 | 293 | - [3.8](#objects--rest-spread) Используйте оператор расширения вместо [`Object.assign`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) для поверхностного копирования объектов. Используйте синтаксис оставшихся свойств, чтобы получить новый объект с некоторыми опущенными свойствами. 294 | 295 | ```javascript 296 | // очень плохо 297 | const original = { a: 1, b: 2 }; 298 | const copy = Object.assign(original, { c: 3 }); // эта переменная изменяет `original` ಠ_ಠ 299 | delete copy.a; // если сделать так 300 | 301 | // плохо 302 | const original = { a: 1, b: 2 }; 303 | const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } 304 | 305 | // хорошо 306 | const original = { a: 1, b: 2 }; 307 | const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } 308 | 309 | const { a, ...noA } = copy; // noA => { b: 2, c: 3 } 310 | ``` 311 | 312 | **[⬆ к оглавлению](#Оглавление)** 313 | 314 | ## Массивы 315 | 316 | 317 | - [4.1](#arrays--literals) Для создания массива используйте литеральную нотацию. eslint: [`no-array-constructor`](https://eslint.org/docs/rules/no-array-constructor.html) 318 | 319 | ```javascript 320 | // плохо 321 | const items = new Array(); 322 | 323 | // хорошо 324 | const items = []; 325 | ``` 326 | 327 | 328 | - [4.2](#arrays--push) Для добавления элемента в массив используйте [Array#push](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/push) вместо прямого присваивания. 329 | 330 | ```javascript 331 | const someStack = []; 332 | 333 | // плохо 334 | someStack[someStack.length] = 'abracadabra'; 335 | 336 | // хорошо 337 | someStack.push('abracadabra'); 338 | ``` 339 | 340 | 341 | - [4.3](#es6-array-spreads) Для копирования массивов используйте оператор расширения `...`. 342 | 343 | ```javascript 344 | // плохо 345 | const len = items.length; 346 | const itemsCopy = []; 347 | let i; 348 | 349 | for (i = 0; i < len; i += 1) { 350 | itemsCopy[i] = items[i]; 351 | } 352 | 353 | // хорошо 354 | const itemsCopy = [...items]; 355 | ``` 356 | 357 | 358 | 359 | - [4.4](#arrays--from-iterable) Для преобразования итерируемого объекта в массив используйте оператор расширения `...` вместо [`Array.from`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/from). 360 | 361 | ```javascript 362 | const foo = document.querySelectorAll('.foo'); 363 | 364 | // хорошо 365 | const nodes = Array.from(foo); 366 | 367 | // отлично 368 | const nodes = [...foo]; 369 | ``` 370 | 371 | 372 | - [4.5](#arrays--from-array-like) Используйте [`Array.from`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/from) для преобразования массивоподобного объекта в массив. 373 | 374 | ```javascript 375 | const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; 376 | 377 | // плохо 378 | const arr = Array.prototype.slice.call(arrLike); 379 | 380 | // хорошо 381 | const arr = Array.from(arrLike); 382 | ``` 383 | 384 | 385 | - [4.6](#arrays--mapping) Используйте [`Array.from`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/from) вместо оператора расширения `...` для маппинга итерируемых объектов, это позволяет избежать создания промежуточного массива. 386 | 387 | ```javascript 388 | // плохо 389 | const baz = [...foo].map(bar); 390 | 391 | // хорошо 392 | const baz = Array.from(foo, bar); 393 | ``` 394 | 395 | 396 | - [4.7](#arrays--callback-return) Используйте операторы `return` внутри функций обратного вызова в методах массива. Можно опустить `return`, когда тело функции состоит из одной инструкции, возвращающей выражение без побочных эффектов. [8.2](#arrows--implicit-return). eslint: [`array-callback-return`](https://eslint.org/docs/rules/array-callback-return) 397 | 398 | ```javascript 399 | // хорошо 400 | [1, 2, 3].map((x) => { 401 | const y = x + 1; 402 | return x * y; 403 | }); 404 | 405 | // хорошо 406 | [1, 2, 3].map(x => x + 1); 407 | 408 | // плохо - нет возвращаемого значения, следовательно, `acc` становится `undefined` после первой итерации 409 | [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { 410 | const flatten = acc.concat(item); 411 | acc[index] = flatten; 412 | }); 413 | 414 | // хорошо 415 | [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { 416 | const flatten = acc.concat(item); 417 | acc[index] = flatten; 418 | return flatten; 419 | }); 420 | 421 | // плохо 422 | inbox.filter((msg) => { 423 | const { subject, author } = msg; 424 | if (subject === 'Mockingbird') { 425 | return author === 'Harper Lee'; 426 | } else { 427 | return false; 428 | } 429 | }); 430 | 431 | // хорошо 432 | inbox.filter((msg) => { 433 | const { subject, author } = msg; 434 | if (subject === 'Mockingbird') { 435 | return author === 'Harper Lee'; 436 | } 437 | 438 | return false; 439 | }); 440 | ``` 441 | 442 | 443 | - [4.8](#arrays--bracket-newline) Если массив располагается на нескольких строках, то используйте разрывы строк после открытия и перед закрытием скобок. 444 | 445 | ```javascript 446 | // плохо 447 | const arr = [ 448 | [0, 1], [2, 3], [4, 5], 449 | ]; 450 | 451 | const objectInArray = [{ 452 | id: 1, 453 | }, { 454 | id: 2, 455 | }]; 456 | 457 | const numberInArray = [ 458 | 1, 2, 459 | ]; 460 | 461 | // хорошо 462 | const arr = [[0, 1], [2, 3], [4, 5]]; 463 | 464 | const objectInArray = [ 465 | { 466 | id: 1, 467 | }, 468 | { 469 | id: 2, 470 | }, 471 | ]; 472 | 473 | const numberInArray = [ 474 | 1, 475 | 2, 476 | ]; 477 | ``` 478 | 479 | **[⬆ к оглавлению](#Оглавление)** 480 | 481 | ## Деструктуризация 482 | 483 | 484 | - [5.1](#destructuring--object) При обращении к нескольким свойствам объекта используйте деструктуризацию объекта. eslint: [`prefer-destructuring`](https://eslint.org/docs/rules/prefer-destructuring) 485 | 486 | > Почему? Деструктуризация избавляет вас от создания временных переменных для этих свойств. 487 | 488 | ```javascript 489 | // плохо 490 | function getFullName(user) { 491 | const firstName = user.firstName; 492 | const lastName = user.lastName; 493 | 494 | return `${firstName} ${lastName}`; 495 | } 496 | 497 | // хорошо 498 | function getFullName(user) { 499 | const { firstName, lastName } = user; 500 | return `${firstName} ${lastName}`; 501 | } 502 | 503 | // отлично 504 | function getFullName({ firstName, lastName }) { 505 | return `${firstName} ${lastName}`; 506 | } 507 | ``` 508 | 509 | 510 | - [5.2](#destructuring--array) Используйте деструктуризацию массивов. eslint: [`prefer-destructuring`](https://eslint.org/docs/rules/prefer-destructuring) 511 | 512 | ```javascript 513 | const arr = [1, 2, 3, 4]; 514 | 515 | // плохо 516 | const first = arr[0]; 517 | const second = arr[1]; 518 | 519 | // хорошо 520 | const [first, second] = arr; 521 | ``` 522 | 523 | 524 | - [5.3](#destructuring--object-over-array) Используйте деструктуризацию объекта для множества возвращаемых значений, но не делайте тоже самое с массивами. 525 | 526 | > Почему? Вы сможете добавить новые свойства через некоторое время или изменить порядок без последствий. 527 | 528 | ```javascript 529 | // плохо 530 | function processInput(input) { 531 | // затем происходит чудо 532 | return [left, right, top, bottom]; 533 | } 534 | 535 | // при вызове нужно подумать о порядке возвращаемых данных 536 | const [left, __, top] = processInput(input); 537 | 538 | // хорошо 539 | function processInput(input) { 540 | // затем происходит чудо 541 | return { left, right, top, bottom }; 542 | } 543 | 544 | // при вызове выбираем только необходимые данные 545 | const { left, top } = processInput(input); 546 | ``` 547 | 548 | **[⬆ к оглавлению](#Оглавление)** 549 | 550 | ## Строки 551 | 552 | 553 | - [6.1](#strings--quotes) Используйте одинарные кавычки `''` для строк. eslint: [`quotes`](https://eslint.org/docs/rules/quotes.html) 554 | 555 | ```javascript 556 | // плохо 557 | const name = "Capt. Janeway"; 558 | 559 | // плохо - литерал шаблонной строки должен содержать интерполяцию или переводы строк 560 | const name = `Capt. Janeway`; 561 | 562 | // хорошо 563 | const name = 'Capt. Janeway'; 564 | ``` 565 | 566 | 567 | - [6.2](#strings--line-length) Строки, у которых в строчке содержится более 100 символов, не пишутся на нескольких строчках с использованием конкатенации. 568 | 569 | > Почему? Работать с разбитыми строками неудобно и это затрудняет поиск по коду. 570 | 571 | ```javascript 572 | // плохо 573 | const errorMessage = 'This is a super long error that was thrown because \ 574 | of Batman. When you stop to think about how Batman had anything to do \ 575 | with this, you would get nowhere \ 576 | fast.'; 577 | 578 | // плохо 579 | const errorMessage = 'This is a super long error that was thrown because ' + 580 | 'of Batman. When you stop to think about how Batman had anything to do ' + 581 | 'with this, you would get nowhere fast.'; 582 | 583 | // хорошо 584 | const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; 585 | ``` 586 | 587 | 588 | - [6.3](#es6-template-literals) При создании строки программным путём используйте шаблонные строки вместо конкатенации. eslint: [`prefer-template`](https://eslint.org/docs/rules/prefer-template.html) [`template-curly-spacing`](https://eslint.org/docs/rules/template-curly-spacing) 589 | 590 | > Почему? Шаблонные строки дают вам читабельность, лаконичный синтаксис с правильными символами перевода строк и функции интерполяции строки. 591 | 592 | ```javascript 593 | // плохо 594 | function sayHi(name) { 595 | return 'How are you, ' + name + '?'; 596 | } 597 | 598 | // плохо 599 | function sayHi(name) { 600 | return ['How are you, ', name, '?'].join(); 601 | } 602 | 603 | // плохо 604 | function sayHi(name) { 605 | return `How are you, ${ name }?`; 606 | } 607 | 608 | // хорошо 609 | function sayHi(name) { 610 | return `How are you, ${name}?`; 611 | } 612 | ``` 613 | 614 | 615 | - [6.4](#strings--eval) Никогда не используйте `eval()`, т.к. это открывает множество уязвимостей. eslint: [`no-eval`](https://eslint.org/docs/rules/no-eval) 616 | 617 | 618 | - [6.5](#strings--escaping) Не используйте в строках необязательные экранирующие символы. eslint: [`no-useless-escape`](https://eslint.org/docs/rules/no-useless-escape) 619 | 620 | > Почему? Обратные слеши ухудшают читабельность, поэтому они должны быть только при необходимости. 621 | 622 | ```javascript 623 | // плохо 624 | const foo = '\'this\' \i\s \"quoted\"'; 625 | 626 | // хорошо 627 | const foo = '\'this\' is "quoted"'; 628 | const foo = `my name is '${name}'`; 629 | ``` 630 | 631 | **[⬆ к оглавлению](#Оглавление)** 632 | 633 | ## Функции 634 | 635 | 636 | - [7.1](#functions--declarations) Используйте функциональные выражения вместо объявлений функций. eslint: [`func-style`](https://eslint.org/docs/rules/func-style) 637 | 638 | > Почему? У объявлений функций есть подъём. Это означает, что можно использовать функцию до того, как она определена в файле, но это вредит читабельности и поддержке. Если вы обнаружили, что определение функции настолько большое или сложное, что мешает понимать остальную часть файла, то, возможно, пришло время извлечь его в отдельный модуль. Не забудьте явно назвать функциональное выражение, независимо от того, подразумевается ли имя из содержащейся переменной (такое часто бывает в современных браузерах или при использовании компиляторов, таких как Babel). Это помогает точнее определять место ошибки по стеку вызовов. ([Обсуждение](https://github.com/airbnb/javascript/issues/794)) 639 | 640 | ```javascript 641 | // плохо 642 | function foo() { 643 | // ... 644 | } 645 | 646 | // плохо 647 | const foo = function () { 648 | // ... 649 | }; 650 | 651 | // хорошо 652 | // лексическое имя, отличное от вызываемой(-ых) переменной(-ых) 653 | const foo = function uniqueMoreDescriptiveLexicalFoo() { 654 | // ... 655 | }; 656 | ``` 657 | 658 | 659 | - [7.2](#functions--iife) Оборачивайте в скобки немедленно вызываемые функции. eslint: [`wrap-iife`](https://eslint.org/docs/rules/wrap-iife.html) 660 | 661 | > Почему? Немедленно вызываемая функция представляет собой единый блок. Чтобы чётко показать это — оберните функцию и вызывающие скобки в ещё одни скобки. Обратите внимание, что в мире с модулями вам больше не нужны немедленно вызываемые функции. 662 | 663 | ```javascript 664 | // Немедленно вызываемая функция 665 | (function () { 666 | console.log('Welcome to the Internet. Please follow me.'); 667 | }()); 668 | ``` 669 | 670 | 671 | - [7.3](#functions--in-blocks) Никогда не объявляйте функции в нефункциональном блоке (`if`, `while`, и т.д.). Вместо этого присвойте функцию переменной. Браузеры позволяют выполнить ваш код, но все они интерпретируют его по-разному. eslint: [`no-loop-func`](https://eslint.org/docs/rules/no-loop-func.html) 672 | 673 | 674 | - [7.4](#functions--note-on-blocks) **Примечание:** ECMA-262 определяет `блок` как список инструкций. Объявление функции не является инструкцией. 675 | 676 | ```javascript 677 | // плохо 678 | if (currentUser) { 679 | function test() { 680 | console.log('Nope.'); 681 | } 682 | } 683 | 684 | // хорошо 685 | let test; 686 | if (currentUser) { 687 | test = () => { 688 | console.log('Yup.'); 689 | }; 690 | } 691 | ``` 692 | 693 | 694 | - [7.5](#functions--arguments-shadow) Никогда не называйте параметр `arguments`. Он будет иметь приоритет над объектом `arguments`, который доступен для каждой функции. 695 | 696 | ```javascript 697 | // плохо 698 | function foo(name, options, arguments) { 699 | // ... 700 | } 701 | 702 | // хорошо 703 | function foo(name, options, args) { 704 | // ... 705 | } 706 | ``` 707 | 708 | 709 | - [7.6](#es6-rest) Никогда не используйте `arguments`, вместо этого используйте синтаксис оставшихся параметров `...`. eslint: [`prefer-rest-params`](https://eslint.org/docs/rules/prefer-rest-params) 710 | 711 | > Почему? `...` явно говорит о том, какие именно аргументы вы хотите извлечь. Кроме того, такой синтаксис создаёт настоящий массив, а не массивоподобный объект как `arguments`. 712 | 713 | ```javascript 714 | // плохо 715 | function concatenateAll() { 716 | const args = Array.prototype.slice.call(arguments); 717 | return args.join(''); 718 | } 719 | 720 | // хорошо 721 | function concatenateAll(...args) { 722 | return args.join(''); 723 | } 724 | ``` 725 | 726 | 727 | - [7.7](#es6-default-parameters) Используйте синтаксис записи аргументов по умолчанию, а не изменяйте аргументы функции. 728 | 729 | ```javascript 730 | // очень плохо 731 | function handleThings(opts) { 732 | // Нет! Мы не должны изменять аргументы функции. 733 | // Плохо вдвойне: если переменная opts будет ложной, 734 | // то ей присвоится пустой объект, а не то что вы хотели. 735 | // Это приведёт к коварным ошибкам. 736 | opts = opts || {}; 737 | // ... 738 | } 739 | 740 | // всё ещё плохо 741 | function handleThings(opts) { 742 | if (opts === void 0) { 743 | opts = {}; 744 | } 745 | // ... 746 | } 747 | 748 | // хорошо 749 | function handleThings(opts = {}) { 750 | // ... 751 | } 752 | ``` 753 | 754 | 755 | - [7.8](#functions--default-side-effects) Избегайте побочных эффектов с параметрами по умолчанию. 756 | 757 | > Почему? И так всё понятно. 758 | 759 | ```javascript 760 | var b = 1; 761 | // плохо 762 | function count(a = b++) { 763 | console.log(a); 764 | } 765 | count(); // 1 766 | count(); // 2 767 | count(3); // 3 768 | count(); // 3 769 | ``` 770 | 771 | 772 | - [7.9](#functions--defaults-last) Всегда вставляйте последними параметры по умолчанию. 773 | 774 | ```javascript 775 | // плохо 776 | function handleThings(opts = {}, name) { 777 | // ... 778 | } 779 | 780 | // хорошо 781 | function handleThings(name, opts = {}) { 782 | // ... 783 | } 784 | ``` 785 | 786 | 787 | - [7.10](#functions--constructor) Никогда не используйте конструктор функций для создания новых функий. eslint: [`no-new-func`](https://eslint.org/docs/rules/no-new-func) 788 | 789 | > Почему? Создание функции в таком духе вычисляет строку подобно `eval()`, из-за чего открываются уязвимости. 790 | 791 | ```javascript 792 | // плохо 793 | var add = new Function('a', 'b', 'return a + b'); 794 | 795 | // всё ещё плохо 796 | var subtract = Function('a', 'b', 'return a - b'); 797 | ``` 798 | 799 | 800 | - [7.11](#functions--signature-spacing) Отступы при определении функции. eslint: [`space-before-function-paren`](https://eslint.org/docs/rules/space-before-function-paren) [`space-before-blocks`](https://eslint.org/docs/rules/space-before-blocks) 801 | 802 | > Почему? Однородность кода — это хорошо. Вам не надо будет добавлять или удалять пробел при манипуляции с именем. 803 | 804 | ```javascript 805 | // плохо 806 | const f = function(){}; 807 | const g = function (){}; 808 | const h = function() {}; 809 | 810 | // хорошо 811 | const x = function () {}; 812 | const y = function a() {}; 813 | ``` 814 | 815 | 816 | - [7.12](#functions--mutate-params) Никогда не изменяйте параметры. eslint: [`no-param-reassign`](https://eslint.org/docs/rules/no-param-reassign.html) 817 | 818 | > Почему? Манипуляция объектами, приходящими в качестве параметров, может вызывать нежелательные побочные эффекты в вызывающей функции. 819 | 820 | ```javascript 821 | // плохо 822 | function f1(obj) { 823 | obj.key = 1; 824 | } 825 | 826 | // хорошо 827 | function f2(obj) { 828 | const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; 829 | } 830 | ``` 831 | 832 | 833 | - [7.13](#functions--reassign-params) Никогда не переназначайте параметры. eslint: [`no-param-reassign`](https://eslint.org/docs/rules/no-param-reassign.html) 834 | 835 | > Почему? Переназначенные параметры могут привести к неожиданному поведению, особенно при обращении к `arguments`. Это также может вызывать проблемы оптимизации, особенно в V8. 836 | 837 | ```javascript 838 | // плохо 839 | function f1(a) { 840 | a = 1; 841 | // ... 842 | } 843 | 844 | function f2(a) { 845 | if (!a) { a = 1; } 846 | // ... 847 | } 848 | 849 | // хорошо 850 | function f3(a) { 851 | const b = a || 1; 852 | // ... 853 | } 854 | 855 | function f4(a = 1) { 856 | // ... 857 | } 858 | ``` 859 | 860 | 861 | - [7.14](#functions--spread-vs-apply) Отдавайте предпочтение использованию оператора расширения `...` при вызове вариативной функции. eslint: [`prefer-spread`](https://eslint.org/docs/rules/prefer-spread) 862 | 863 | > Почему? Это чище, вам не нужно предоставлять контекст, и не так просто составить `new` с `apply`. 864 | 865 | ```javascript 866 | // плохо 867 | const x = [1, 2, 3, 4, 5]; 868 | console.log.apply(console, x); 869 | 870 | // хорошо 871 | const x = [1, 2, 3, 4, 5]; 872 | console.log(...x); 873 | 874 | // плохо 875 | new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); 876 | 877 | // хорошо 878 | new Date(...[2016, 8, 5]); 879 | ``` 880 | 881 | 882 | - [7.15](#functions--signature-invocation-indentation) Функции с многострочным определением или запуском должны содержать такие же отступы, как и другие многострочные списки в этом руководстве: с каждым элементом на отдельной строке, с запятой в конце элемента. eslint: [`function-paren-newline`](https://eslint.org/docs/rules/function-paren-newline) 883 | 884 | ```javascript 885 | // плохо 886 | function foo(bar, 887 | baz, 888 | quux) { 889 | // ... 890 | } 891 | 892 | // хорошо 893 | function foo( 894 | bar, 895 | baz, 896 | quux, 897 | ) { 898 | // ... 899 | } 900 | 901 | // плохо 902 | console.log(foo, 903 | bar, 904 | baz); 905 | 906 | // хорошо 907 | console.log( 908 | foo, 909 | bar, 910 | baz, 911 | ); 912 | ``` 913 | 914 | **[⬆ к оглавлению](#Оглавление)** 915 | 916 | ## Стрелочные функции 917 | 918 | 919 | - [8.1](#arrows--use-them) Когда вам необходимо использовать анонимную функцию (например, при передаче встроенной функции обратного вызова), используйте стрелочную функцию. eslint: [`prefer-arrow-callback`](https://eslint.org/docs/rules/prefer-arrow-callback.html), [`arrow-spacing`](https://eslint.org/docs/rules/arrow-spacing.html) 920 | 921 | > Почему? Таким образом создаётся функция, которая выполняется в контексте `this`, который мы обычно хотим, а также это более короткий синтаксис. 922 | 923 | > Почему бы и нет? Если у вас есть довольно сложная функция, вы можете переместить эту логику внутрь её собственного именованного функционального выражения. 924 | 925 | ```javascript 926 | // плохо 927 | [1, 2, 3].map(function (x) { 928 | const y = x + 1; 929 | return x * y; 930 | }); 931 | 932 | // хорошо 933 | [1, 2, 3].map((x) => { 934 | const y = x + 1; 935 | return x * y; 936 | }); 937 | ``` 938 | 939 | 940 | - [8.2](#arrows--implicit-return) Если тело функции состоит из одного оператора, возвращающего [выражение](https://developer.mozilla.org/ru/docs/Web/JavaScript/Guide/Expressions_and_Operators#Выражения) без побочных эффектов, то опустите фигурные скобки и используйте неявное возвращение. В противном случае, сохраните фигурные скобки и используйте оператор `return`. eslint: [`arrow-parens`](https://eslint.org/docs/rules/arrow-parens.html), [`arrow-body-style`](https://eslint.org/docs/rules/arrow-body-style.html) 941 | 942 | > Почему? Синтаксический сахар. Когда несколько функций соединены вместе, то это лучше читается. 943 | 944 | ```javascript 945 | // плохо 946 | [1, 2, 3].map(number => { 947 | const nextNumber = number + 1; 948 | `A string containing the ${nextNumber}.`; 949 | }); 950 | 951 | // хорошо 952 | [1, 2, 3].map(number => `A string containing the ${number}.`); 953 | 954 | // хорошо 955 | [1, 2, 3].map((number) => { 956 | const nextNumber = number + 1; 957 | return `A string containing the ${nextNumber}.`; 958 | }); 959 | 960 | // хорошо 961 | [1, 2, 3].map((number, index) => ({ 962 | [index]: number, 963 | })); 964 | 965 | // Неявный возврат с побочными эффектами 966 | function foo(callback) { 967 | const val = callback(); 968 | if (val === true) { 969 | // Сделать что-то, если функция обратного вызова вернёт true 970 | } 971 | } 972 | 973 | let bool = false; 974 | 975 | // плохо 976 | foo(() => bool = true); 977 | 978 | // хорошо 979 | foo(() => { 980 | bool = true; 981 | }); 982 | ``` 983 | 984 | 985 | - [8.3](#arrows--paren-wrap) В случае, если выражение располагается на нескольких строках, то необходимо обернуть его в скобки для лучшей читаемости. 986 | 987 | > Почему? Это чётко показывает, где функция начинается и где заканчивается. 988 | 989 | ```javascript 990 | // плохо 991 | ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( 992 | httpMagicObjectWithAVeryLongName, 993 | httpMethod, 994 | ) 995 | ); 996 | 997 | // хорошо 998 | ['get', 'post', 'put'].map(httpMethod => ( 999 | Object.prototype.hasOwnProperty.call( 1000 | httpMagicObjectWithAVeryLongName, 1001 | httpMethod, 1002 | ) 1003 | )); 1004 | ``` 1005 | 1006 | 1007 | - [8.4](#arrows--one-arg-parens) Если ваша функция принимает один аргумент и не использует фигурные скобки, то опустите круглые скобки. В противном случае, всегда оборачивайте аргументы круглыми скобками для ясности и последовательности. Примечание: также допускается всегда использовать круглые скобки, в этом случае используйте [вариант "always"](https://eslint.org/docs/rules/arrow-parens#always) для eslint. eslint: [`arrow-parens`](https://eslint.org/docs/rules/arrow-parens.html) 1008 | > Почему? Меньше визуального беспорядка. 1009 | 1010 | ```javascript 1011 | // плохо 1012 | [1, 2, 3].map((x) => x * x); 1013 | 1014 | // хорошо 1015 | [1, 2, 3].map(x => x * x); 1016 | 1017 | // хорошо 1018 | [1, 2, 3].map(number => ( 1019 | `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` 1020 | )); 1021 | 1022 | // плохо 1023 | [1, 2, 3].map(x => { 1024 | const y = x + 1; 1025 | return x * y; 1026 | }); 1027 | 1028 | // хорошо 1029 | [1, 2, 3].map((x) => { 1030 | const y = x + 1; 1031 | return x * y; 1032 | }); 1033 | ``` 1034 | 1035 | 1036 | - [8.5](#arrows--confusing) Избегайте схожести стрелочной функции (`=>`) с операторами сравнения (`<=`, `>=`). eslint: [`no-confusing-arrow`](https://eslint.org/docs/rules/no-confusing-arrow) 1037 | 1038 | ```javascript 1039 | // плохо 1040 | const itemHeight = item => item.height <= 256 ? item.largeSize : item.smallSize; 1041 | 1042 | // плохо 1043 | const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize; 1044 | 1045 | // хорошо 1046 | const itemHeight = item => (item.height <= 256 ? item.largeSize : item.smallSize); 1047 | 1048 | // хорошо 1049 | const itemHeight = (item) => { 1050 | const { height, largeSize, smallSize } = item; 1051 | return height <= 256 ? largeSize : smallSize; 1052 | }; 1053 | ``` 1054 | 1055 | 1056 | - [8.6](#whitespace--implicit-arrow-linebreak) Зафиксируйте расположение тела стрелочной функции с неявным возвратом. eslint: [`implicit-arrow-linebreak`](https://eslint.org/docs/rules/implicit-arrow-linebreak) 1057 | 1058 | ```javascript 1059 | // плохо 1060 | foo => 1061 | bar; 1062 | foo => 1063 | (bar); 1064 | 1065 | // хорошо 1066 | foo => bar; 1067 | foo => (bar); 1068 | foo => ( 1069 | bar 1070 | ) 1071 | ``` 1072 | 1073 | **[⬆ к оглавлению](#Оглавление)** 1074 | 1075 | ## Классы и конструкторы 1076 | 1077 | 1078 | - [9.1](#constructors--use-class) Всегда используйте `class`. Избегайте прямых манипуляций с `prototype`. 1079 | 1080 | > Почему? Синтаксис `class` является кратким и понятным. 1081 | 1082 | ```javascript 1083 | // плохо 1084 | function Queue(contents = []) { 1085 | this.queue = [...contents]; 1086 | } 1087 | Queue.prototype.pop = function () { 1088 | const value = this.queue[0]; 1089 | this.queue.splice(0, 1); 1090 | return value; 1091 | }; 1092 | 1093 | // хорошо 1094 | class Queue { 1095 | constructor(contents = []) { 1096 | this.queue = [...contents]; 1097 | } 1098 | pop() { 1099 | const value = this.queue[0]; 1100 | this.queue.splice(0, 1); 1101 | return value; 1102 | } 1103 | } 1104 | ``` 1105 | 1106 | 1107 | - [9.2](#constructors--extends) Используйте `extends` для наследования. 1108 | 1109 | > Почему? Это встроенный способ наследовать функциональность прототипа, не нарушая `instanceof`. 1110 | 1111 | ```javascript 1112 | // плохо 1113 | const inherits = require('inherits'); 1114 | function PeekableQueue(contents) { 1115 | Queue.apply(this, contents); 1116 | } 1117 | inherits(PeekableQueue, Queue); 1118 | PeekableQueue.prototype.peek = function () { 1119 | return this.queue[0]; 1120 | }; 1121 | 1122 | // хорошо 1123 | class PeekableQueue extends Queue { 1124 | peek() { 1125 | return this.queue[0]; 1126 | } 1127 | } 1128 | ``` 1129 | 1130 | 1131 | - [9.3](#constructors--chaining) Методы могут возвращать `this`, чтобы делать цепочки вызовов. 1132 | 1133 | ```javascript 1134 | // плохо 1135 | Jedi.prototype.jump = function () { 1136 | this.jumping = true; 1137 | return true; 1138 | }; 1139 | 1140 | Jedi.prototype.setHeight = function (height) { 1141 | this.height = height; 1142 | }; 1143 | 1144 | const luke = new Jedi(); 1145 | luke.jump(); // => true 1146 | luke.setHeight(20); // => undefined 1147 | 1148 | // хорошо 1149 | class Jedi { 1150 | jump() { 1151 | this.jumping = true; 1152 | return this; 1153 | } 1154 | 1155 | setHeight(height) { 1156 | this.height = height; 1157 | return this; 1158 | } 1159 | } 1160 | 1161 | const luke = new Jedi(); 1162 | 1163 | luke.jump() 1164 | .setHeight(20); 1165 | ``` 1166 | 1167 | 1168 | - [9.4](#constructors--tostring) Вы можете определить свой собственный метод `toString()`, просто убедитесь, что он успешно работает и не создаёт никаких побочных эффектов. 1169 | 1170 | ```javascript 1171 | class Jedi { 1172 | constructor(options = {}) { 1173 | this.name = options.name || 'no name'; 1174 | } 1175 | 1176 | getName() { 1177 | return this.name; 1178 | } 1179 | 1180 | toString() { 1181 | return `Jedi - ${this.getName()}`; 1182 | } 1183 | } 1184 | ``` 1185 | 1186 | 1187 | - [9.5](#constructors--no-useless) У классов есть конструктор по умолчанию, если он не задан явно. Можно опустить пустой конструктор или конструктор, который только делегирует выполнение родительскому классу. eslint: [`no-useless-constructor`](https://eslint.org/docs/rules/no-useless-constructor) 1188 | 1189 | ```javascript 1190 | // плохо 1191 | class Jedi { 1192 | constructor() {} 1193 | 1194 | getName() { 1195 | return this.name; 1196 | } 1197 | } 1198 | 1199 | // плохо 1200 | class Rey extends Jedi { 1201 | constructor(...args) { 1202 | super(...args); 1203 | } 1204 | } 1205 | 1206 | // хорошо 1207 | class Rey extends Jedi { 1208 | constructor(...args) { 1209 | super(...args); 1210 | this.name = 'Rey'; 1211 | } 1212 | } 1213 | ``` 1214 | 1215 | 1216 | - [9.6](#classes--no-duplicate-members) Избегайте дублирующих членов класса. eslint: [`no-dupe-class-members`](https://eslint.org/docs/rules/no-dupe-class-members) 1217 | 1218 | > Почему? Если объявление члена класса повторяется, без предупреждения будет использовано последнее. Наличие дубликатов скорее всего приведёт к ошибке. 1219 | 1220 | ```javascript 1221 | // плохо 1222 | class Foo { 1223 | bar() { return 1; } 1224 | bar() { return 2; } 1225 | } 1226 | 1227 | // хорошо 1228 | class Foo { 1229 | bar() { return 1; } 1230 | } 1231 | 1232 | // хорошо 1233 | class Foo { 1234 | bar() { return 2; } 1235 | } 1236 | ``` 1237 | 1238 | **[⬆ к оглавлению](#Оглавление)** 1239 | 1240 | ## Модули 1241 | 1242 | 1243 | - [10.1](#modules--use-them) Всегда используйте модули (`import`/`export`) вместо нестандартных модульных систем. Вы всегда сможете транспилировать код в вашу любимую модульную систему. 1244 | 1245 | > Почему? Модули — это будущее. Давайте начнём использовать будущее уже сейчас! 1246 | 1247 | ```javascript 1248 | // плохо 1249 | const AirbnbStyleGuide = require('./AirbnbStyleGuide'); 1250 | module.exports = AirbnbStyleGuide.es6; 1251 | 1252 | // ok 1253 | import AirbnbStyleGuide from './AirbnbStyleGuide'; 1254 | export default AirbnbStyleGuide.es6; 1255 | 1256 | // отлично 1257 | import { es6 } from './AirbnbStyleGuide'; 1258 | export default es6; 1259 | ``` 1260 | 1261 | 1262 | - [10.2](#modules--no-wildcard) Не используйте импорт через `*`. 1263 | 1264 | > Почему? Это гарантирует, что у вас есть единственный экспорт по умолчанию. 1265 | 1266 | ```javascript 1267 | // плохо 1268 | import * as AirbnbStyleGuide from './AirbnbStyleGuide'; 1269 | 1270 | // хорошо 1271 | import AirbnbStyleGuide from './AirbnbStyleGuide'; 1272 | ``` 1273 | 1274 | 1275 | - [10.3](#modules--no-export-from-import) Не экспортируйте прямо из импорта. 1276 | 1277 | > Почему? Несмотря на то, что запись в одну строку является краткой, разделение на отдельные строки делает вещи последовательными. 1278 | 1279 | ```javascript 1280 | // плохо 1281 | // файл es6.js 1282 | export { es6 as default } from './AirbnbStyleGuide'; 1283 | 1284 | // хорошо 1285 | // файл es6.js 1286 | import { es6 } from './AirbnbStyleGuide'; 1287 | export default es6; 1288 | ``` 1289 | 1290 | 1291 | - [10.4](#modules--no-duplicate-imports) Импортируйте из пути только один раз. 1292 | eslint: [`no-duplicate-imports`](https://eslint.org/docs/rules/no-duplicate-imports) 1293 | > Почему? Наличие нескольких строк, которые импортируют из одного и того же файла, может сделать код неподдерживаемым. 1294 | 1295 | ```javascript 1296 | // плохо 1297 | import foo from 'foo'; 1298 | // … какие-то другие импорты … // 1299 | import { named1, named2 } from 'foo'; 1300 | 1301 | // хорошо 1302 | import foo, { named1, named2 } from 'foo'; 1303 | 1304 | // хорошо 1305 | import foo, { 1306 | named1, 1307 | named2, 1308 | } from 'foo'; 1309 | ``` 1310 | 1311 | 1312 | - [10.5](#modules--no-mutable-exports) Не экспортируйте изменяемые переменные. 1313 | eslint: [`import/no-mutable-exports`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-mutable-exports.md) 1314 | > Почему? Вообще, следует избегать мутации, в особенности при экспорте изменяемых переменных. Несмотря на то, что эта техника может быть необходима в редких случаях, в основном только константа должна быть экспортирована. 1315 | 1316 | ```javascript 1317 | // плохо 1318 | let foo = 3; 1319 | export { foo }; 1320 | 1321 | // хорошо 1322 | const foo = 3; 1323 | export { foo }; 1324 | ``` 1325 | 1326 | 1327 | - [10.6](#modules--prefer-default-export) В модулях с единственным экспортом предпочтительнее использовать экспорт по умолчанию, а не экспорт по имени. 1328 | eslint: [`import/prefer-default-export`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/prefer-default-export.md) 1329 | > Почему? Для того чтобы поощрять создание множества файлов, которые бы экспортировали одну сущность, т.к. это лучше для читабельности и поддержки кода. 1330 | 1331 | ```javascript 1332 | // плохо 1333 | export function foo() {} 1334 | 1335 | // хорошо 1336 | export default function foo() {} 1337 | ``` 1338 | 1339 | 1340 | - [10.7](#modules--imports-first) Поместите все импорты выше остальных инструкций. 1341 | eslint: [`import/first`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md) 1342 | > Почему? Так как `import` обладает подъёмом, то хранение их всех в начале файла предотвращает от неожиданного поведения. 1343 | 1344 | ```javascript 1345 | // плохо 1346 | import foo from 'foo'; 1347 | foo.init(); 1348 | 1349 | import bar from 'bar'; 1350 | 1351 | // хорошо 1352 | import foo from 'foo'; 1353 | import bar from 'bar'; 1354 | 1355 | foo.init(); 1356 | ``` 1357 | 1358 | 1359 | - [10.8](#modules--multiline-imports-over-newlines) Импорты на нескольких строках должны быть с отступами как у многострочных литералов массива и объекта. 1360 | 1361 | > Почему? Фигурные скобки следуют тем же правилам отступа как и любая другая фигурная скобка блока в этом руководстве, тоже самое касается висячих запятых. 1362 | 1363 | ```javascript 1364 | // плохо 1365 | import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; 1366 | 1367 | // хорошо 1368 | import { 1369 | longNameA, 1370 | longNameB, 1371 | longNameC, 1372 | longNameD, 1373 | longNameE, 1374 | } from 'path'; 1375 | ``` 1376 | 1377 | 1378 | - [10.9](#modules--no-webpack-loader-syntax) Запретите синтаксис загрузчика Webpack в импорте. 1379 | eslint: [`import/no-webpack-loader-syntax`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md) 1380 | > Почему? Использование Webpack синтаксиса связывает код с упаковщиком модулей. Предпочтительно использовать синтаксис загрузчика в `webpack.config.js`. 1381 | 1382 | ```javascript 1383 | // плохо 1384 | import fooSass from 'css!sass!foo.scss'; 1385 | import barCss from 'style!css!bar.css'; 1386 | 1387 | // хорошо 1388 | import fooSass from 'foo.scss'; 1389 | import barCss from 'bar.css'; 1390 | ``` 1391 | 1392 | **[⬆ к оглавлению](#Оглавление)** 1393 | 1394 | ## Итераторы и генераторы 1395 | 1396 | 1397 | - [11.1](#iterators--nope) Не используйте итераторы. Применяйте функции высшего порядка вместо таких циклов как `for-in` или `for-of`. eslint: [`no-iterator`](https://eslint.org/docs/rules/no-iterator.html) [`no-restricted-syntax`](https://eslint.org/docs/rules/no-restricted-syntax) 1398 | 1399 | > Почему? Это обеспечивает соблюдение нашего правила о неизменности переменных. Работать с чистыми функциями, которые возвращают значение, проще, чем с функциями с побочными эффектами. 1400 | 1401 | > Используйте `map()` / `every()` / `filter()` / `find()` / `findIndex()` / `reduce()` / `some()` / ... для итерации по массивам, а `Object.keys()` / `Object.values()` / `Object.entries()` для создания массивов, с помощью которых можно итерироваться по объектам. 1402 | 1403 | ```javascript 1404 | const numbers = [1, 2, 3, 4, 5]; 1405 | 1406 | // плохо 1407 | let sum = 0; 1408 | for (let num of numbers) { 1409 | sum += num; 1410 | } 1411 | sum === 15; 1412 | 1413 | // хорошо 1414 | let sum = 0; 1415 | numbers.forEach((num) => { 1416 | sum += num; 1417 | }); 1418 | sum === 15; 1419 | 1420 | // отлично (используйте силу функций) 1421 | const sum = numbers.reduce((total, num) => total + num, 0); 1422 | sum === 15; 1423 | 1424 | // плохо 1425 | const increasedByOne = []; 1426 | for (let i = 0; i < numbers.length; i++) { 1427 | increasedByOne.push(numbers[i] + 1); 1428 | } 1429 | 1430 | // хорошо 1431 | const increasedByOne = []; 1432 | numbers.forEach((num) => { 1433 | increasedByOne.push(num + 1); 1434 | }); 1435 | 1436 | // отлично (продолжайте в том же духе) 1437 | const increasedByOne = numbers.map(num => num + 1); 1438 | ``` 1439 | 1440 | 1441 | - [11.2](#generators--nope) Не используйте пока генераторы. 1442 | 1443 | > Почему? Они не очень хорошо транспилируются в ES5. 1444 | 1445 | 1446 | - [11.3](#generators--spacing) Если всё-таки необходимо использовать генераторы, или вы не обратили внимания на [наш совет](#generators--nope), убедитесь, что `*` у функции генератора расположена должным образом. eslint: [`generator-star-spacing`](https://eslint.org/docs/rules/generator-star-spacing) 1447 | 1448 | > Почему? `function` и `*` являются частью одного и того же ключевого слова. `*` не является модификатором для `function`, `function*` является уникальной конструкцией, отличной от `function`. 1449 | 1450 | ```javascript 1451 | // плохо 1452 | function * foo() { 1453 | // ... 1454 | } 1455 | 1456 | const bar = function * () { 1457 | // ... 1458 | }; 1459 | 1460 | const baz = function *() { 1461 | // ... 1462 | }; 1463 | 1464 | const quux = function*() { 1465 | // ... 1466 | }; 1467 | 1468 | function*foo() { 1469 | // ... 1470 | } 1471 | 1472 | function *foo() { 1473 | // ... 1474 | } 1475 | 1476 | // очень плохо 1477 | function 1478 | * 1479 | foo() { 1480 | // ... 1481 | } 1482 | 1483 | const wat = function 1484 | * 1485 | () { 1486 | // ... 1487 | }; 1488 | 1489 | // хорошо 1490 | function* foo() { 1491 | // ... 1492 | } 1493 | 1494 | const foo = function* () { 1495 | // ... 1496 | }; 1497 | ``` 1498 | 1499 | **[⬆ к оглавлению](#Оглавление)** 1500 | 1501 | ## Свойства 1502 | 1503 | 1504 | - [12.1](#properties--dot) Используйте точечную нотацию для доступа к свойствам. eslint: [`dot-notation`](https://eslint.org/docs/rules/dot-notation.html) 1505 | 1506 | ```javascript 1507 | const luke = { 1508 | jedi: true, 1509 | age: 28, 1510 | }; 1511 | 1512 | // плохо 1513 | const isJedi = luke['jedi']; 1514 | 1515 | // хорошо 1516 | const isJedi = luke.jedi; 1517 | ``` 1518 | 1519 | 1520 | - [12.2](#properties--bracket) Используйте скобочную нотацию `[]`, когда название свойства хранится в переменной. 1521 | 1522 | ```javascript 1523 | const luke = { 1524 | jedi: true, 1525 | age: 28, 1526 | }; 1527 | 1528 | function getProp(prop) { 1529 | return luke[prop]; 1530 | } 1531 | 1532 | const isJedi = getProp('jedi'); 1533 | ``` 1534 | 1535 | 1536 | - [12.3](#es2016-properties--exponentiation-operator) Используйте оператор `**` для возведения в степень. eslint: [`no-restricted-properties`](https://eslint.org/docs/rules/no-restricted-properties). 1537 | 1538 | ```javascript 1539 | // плохо 1540 | const binary = Math.pow(2, 10); 1541 | 1542 | // хорошо 1543 | const binary = 2 ** 10; 1544 | ``` 1545 | 1546 | **[⬆ к оглавлению](#Оглавление)** 1547 | 1548 | ## Переменные 1549 | 1550 | 1551 | - [13.1](#variables--const) Всегда используйте `const` или `let` для объявления переменных. Невыполнение этого требования приведёт к появлению глобальных переменных. Необходимо избегать загрязнения глобального пространства имён. eslint: [`no-undef`](https://eslint.org/docs/rules/no-undef) [`prefer-const`](https://eslint.org/docs/rules/prefer-const) 1552 | 1553 | ```javascript 1554 | // плохо 1555 | superPower = new SuperPower(); 1556 | 1557 | // хорошо 1558 | const superPower = new SuperPower(); 1559 | ``` 1560 | 1561 | 1562 | - [13.2](#variables--one-const) Используйте объявление `const` или `let` для каждой переменной или присвоения. eslint: [`one-var`](https://eslint.org/docs/rules/one-var.html) 1563 | 1564 | > Почему? Таким образом проще добавить новые переменные. Также вы никогда не будете беспокоиться о перемещении `;` и `,` и об отображении изменений в пунктуации. Вы также можете пройтись по каждому объявлению с помощью отладчика, вместо того, чтобы прыгать через все сразу. 1565 | 1566 | ```javascript 1567 | // плохо 1568 | const items = getItems(), 1569 | goSportsTeam = true, 1570 | dragonball = 'z'; 1571 | 1572 | // плохо 1573 | // (сравните с кодом выше и попытайтесь найти ошибку) 1574 | const items = getItems(), 1575 | goSportsTeam = true; 1576 | dragonball = 'z'; 1577 | 1578 | // хорошо 1579 | const items = getItems(); 1580 | const goSportsTeam = true; 1581 | const dragonball = 'z'; 1582 | ``` 1583 | 1584 | 1585 | - [13.3](#variables--const-let-group) В первую очередь группируйте `const`, а затем `let`. 1586 | 1587 | > Почему? Это полезно, когда в будущем вам понадобится создать переменную, зависимую от предыдущих. 1588 | 1589 | ```javascript 1590 | // плохо 1591 | let i, len, dragonball, 1592 | items = getItems(), 1593 | goSportsTeam = true; 1594 | 1595 | // плохо 1596 | let i; 1597 | const items = getItems(); 1598 | let dragonball; 1599 | const goSportsTeam = true; 1600 | let len; 1601 | 1602 | // хорошо 1603 | const goSportsTeam = true; 1604 | const items = getItems(); 1605 | let dragonball; 1606 | let i; 1607 | let length; 1608 | ``` 1609 | 1610 | 1611 | - [13.4](#variables--define-where-used) Создавайте переменные там, где они вам необходимы, но помещайте их в подходящее место. 1612 | 1613 | > Почему? `let` и `const` имеют блочную область видимости, а не функциональную. 1614 | 1615 | ```javascript 1616 | // плохо - вызов ненужной функции 1617 | function checkName(hasName) { 1618 | const name = getName(); 1619 | 1620 | if (hasName === 'test') { 1621 | return false; 1622 | } 1623 | 1624 | if (name === 'test') { 1625 | this.setName(''); 1626 | return false; 1627 | } 1628 | 1629 | return name; 1630 | } 1631 | 1632 | // хорошо 1633 | function checkName(hasName) { 1634 | if (hasName === 'test') { 1635 | return false; 1636 | } 1637 | 1638 | const name = getName(); 1639 | 1640 | if (name === 'test') { 1641 | this.setName(''); 1642 | return false; 1643 | } 1644 | 1645 | return name; 1646 | } 1647 | ``` 1648 | 1649 | - [13.5](#variables--no-chain-assignment) Не создавайте цепочки присваивания переменных. eslint: [`no-multi-assign`](https://eslint.org/docs/rules/no-multi-assign) 1650 | 1651 | > Почему? Такие цепочки создают неявные глобальные переменные. 1652 | 1653 | ```javascript 1654 | // плохо 1655 | (function example() { 1656 | // JavaScript интерпретирует это, как 1657 | // let a = ( b = ( c = 1 ) ); 1658 | // Ключевое слово let применится только к переменной a; 1659 | // переменные b и c станут глобальными. 1660 | let a = b = c = 1; 1661 | }()); 1662 | 1663 | console.log(a); // throws ReferenceError 1664 | console.log(b); // 1 1665 | console.log(c); // 1 1666 | 1667 | // хорошо 1668 | (function example() { 1669 | let a = 1; 1670 | let b = a; 1671 | let c = a; 1672 | }()); 1673 | 1674 | console.log(a); // throws ReferenceError 1675 | console.log(b); // throws ReferenceError 1676 | console.log(c); // throws ReferenceError 1677 | 1678 | // тоже самое и для `const` 1679 | ``` 1680 | 1681 | 1682 | - [13.6](#variables--unary-increment-decrement) Избегайте использования унарных инкрементов и декрементов (`++`, `--`). eslint [`no-plusplus`](https://eslint.org/docs/rules/no-plusplus) 1683 | 1684 | > Почему? Согласно документации eslint, унарные инкремент и декремент автоматически вставляют точку с запятой, что может стать причиной трудноуловимых ошибок при инкрементировании и декрементировании значений. Также нагляднее изменять ваши значения таким образом `num += 1` вместо `num++` или `num ++`. Запрет на унарные инкремент и декремент ограждает вас от непреднамеренных преждевременных инкрементаций/декрементаций значений, которые могут привести к непредсказуемому поведению вашей программы. 1685 | 1686 | ```javascript 1687 | // плохо 1688 | 1689 | const array = [1, 2, 3]; 1690 | let num = 1; 1691 | num++; 1692 | --num; 1693 | 1694 | let sum = 0; 1695 | let truthyCount = 0; 1696 | for (let i = 0; i < array.length; i++) { 1697 | let value = array[i]; 1698 | sum += value; 1699 | if (value) { 1700 | truthyCount++; 1701 | } 1702 | } 1703 | 1704 | // хорошо 1705 | 1706 | const array = [1, 2, 3]; 1707 | let num = 1; 1708 | num += 1; 1709 | num -= 1; 1710 | 1711 | const sum = array.reduce((a, b) => a + b, 0); 1712 | const truthyCount = array.filter(Boolean).length; 1713 | ``` 1714 | 1715 | 1716 | - [13.7](#variables--linebreak) В присвоении избегайте разрывов строк до и после `=`. Если ваше присвоение нарушает правило [`max-len`](https://eslint.org/docs/rules/max-len.html), оберните значение в круглые скобки. eslint [`operator-linebreak`](https://eslint.org/docs/rules/operator-linebreak.html). 1717 | 1718 | > Почему? Разрывы строк до и после `=` могут приводить к путанице в понимании значения. 1719 | 1720 | ```javascript 1721 | // плохо 1722 | const foo = 1723 | superLongLongLongLongLongLongLongLongFunctionName(); 1724 | 1725 | // плохо 1726 | const foo 1727 | = 'superLongLongLongLongLongLongLongLongString'; 1728 | 1729 | // хорошо 1730 | const foo = ( 1731 | superLongLongLongLongLongLongLongLongFunctionName() 1732 | ); 1733 | 1734 | // хорошо 1735 | const foo = 'superLongLongLongLongLongLongLongLongString'; 1736 | ``` 1737 | 1738 | 1739 | - [13.8](#variables--no-unused-vars) Запретить неиспользуемые переменные. eslint: [`no-unused-vars`](https://eslint.org/docs/rules/no-unused-vars) 1740 | 1741 | > Почему? Переменные, которые объявлены и не используются в коде, скорее всего, являются ошибкой из-за незавершённого рефакторинга. Такие переменные занимают место в коде и могут привести к путанице при чтении. 1742 | 1743 | ```javascript 1744 | // плохо 1745 | 1746 | var some_unused_var = 42; 1747 | 1748 | // Переменные, которые используются только для записи, не считаются используемыми. 1749 | var y = 10; 1750 | y = 5; 1751 | 1752 | // Чтение для собственной модификации. 1753 | var z = 0; 1754 | z = z + 1; 1755 | 1756 | // Неиспользуемые аргументы функции. 1757 | function getX(x, y) { 1758 | return x; 1759 | } 1760 | 1761 | // хорошо 1762 | 1763 | function getXPlusY(x, y) { 1764 | return x + y; 1765 | } 1766 | 1767 | var x = 1; 1768 | var y = a + 2; 1769 | 1770 | alert(getXPlusY(x, y)); 1771 | 1772 | // Переменная 'type' игнорируется, даже если она не испольуется, потому что рядом есть rest-свойство. 1773 | // Эта форма извлечения объекта, которая опускает указанные ключи. 1774 | var { type, ...coords } = data; 1775 | // 'coords' теперь 'data' объект без свойства 'type'. 1776 | ``` 1777 | 1778 | **[⬆ к оглавлению](#Оглавление)** 1779 | 1780 | ## Подъём 1781 | 1782 | 1783 | - [14.1](#hoisting--about) Объявления `var` поднимаются в начало области видимости ближайшей закрывающей их функции, а их присвоение нет. Объявления `const` и `let` работают по новой концепции называемой [Временные Мёртвые Зоны (Temporal Dead Zone)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/let#Временные_мертвые_зоны_и_ошибки_при_использовании_let). Важно знать, почему использовать [typeof больше не безопасно](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15). 1784 | 1785 | ```javascript 1786 | // мы знаем, что это не будет работать 1787 | // (если нет глобальной переменной notDefined) 1788 | function example() { 1789 | console.log(notDefined); // => выбросит ошибку ReferenceError 1790 | } 1791 | 1792 | // обращение к переменной до её создания 1793 | // будет работать из-за подъёма. 1794 | // Примечание: значение true не поднимается. 1795 | function example() { 1796 | console.log(declaredButNotAssigned); // => undefined 1797 | var declaredButNotAssigned = true; 1798 | } 1799 | 1800 | // интерпретатор понимает объявление 1801 | // переменной в начало области видимости. 1802 | // это означает, что наш пример 1803 | // можно переписать таким образом: 1804 | function example() { 1805 | let declaredButNotAssigned; 1806 | console.log(declaredButNotAssigned); // => undefined 1807 | declaredButNotAssigned = true; 1808 | } 1809 | 1810 | // использование const и let 1811 | function example() { 1812 | console.log(declaredButNotAssigned); // => выбросит ошибку ReferenceError 1813 | console.log(typeof declaredButNotAssigned); // => выбросит ошибку ReferenceError 1814 | const declaredButNotAssigned = true; 1815 | } 1816 | ``` 1817 | 1818 | 1819 | - [14.2](#hoisting--anon-expressions) Для анонимных функциональных выражений наверх области видимости поднимается название переменной, но не её значение. 1820 | 1821 | ```javascript 1822 | function example() { 1823 | console.log(anonymous); // => undefined 1824 | 1825 | anonymous(); // => TypeError anonymous не является функцией 1826 | 1827 | var anonymous = function () { 1828 | console.log('anonymous function expression'); 1829 | }; 1830 | } 1831 | ``` 1832 | 1833 | 1834 | - [14.3](#hoisting--named-expressions) Для именованных функциональных выражений наверх области видимости поднимается название переменной, но не имя или тело функции. 1835 | 1836 | ```javascript 1837 | function example() { 1838 | console.log(named); // => undefined 1839 | 1840 | named(); // => TypeError named не является функцией 1841 | 1842 | superPower(); // => ReferenceError superPower не определена 1843 | 1844 | var named = function superPower() { 1845 | console.log('Flying'); 1846 | }; 1847 | } 1848 | 1849 | // тоже самое справедливо, когда имя функции 1850 | // совпадает с именем переменной. 1851 | function example() { 1852 | console.log(named); // => undefined 1853 | 1854 | named(); // => TypeError named не является функцией 1855 | 1856 | var named = function named() { 1857 | console.log('named'); 1858 | }; 1859 | } 1860 | ``` 1861 | 1862 | 1863 | - [14.4](#hoisting--declarations) При объявлении функции её имя и тело поднимаются наверх области видимости. 1864 | 1865 | ```javascript 1866 | function example() { 1867 | superPower(); // => Flying 1868 | 1869 | function superPower() { 1870 | console.log('Flying'); 1871 | } 1872 | } 1873 | ``` 1874 | 1875 | - Более подробно можно прочитать в статье [JavaScript Scoping & Hoisting](http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting/) от [Ben Cherry](http://www.adequatelygood.com/). 1876 | 1877 | **[⬆ к оглавлению](#Оглавление)** 1878 | 1879 | ## Операторы сравнения и равенства 1880 | 1881 | 1882 | - [15.1](#comparison--eqeqeq) Используйте `===` и `!==` вместо `==` и `!=`. eslint: [`eqeqeq`](https://eslint.org/docs/rules/eqeqeq.html) 1883 | 1884 | 1885 | - [15.2](#comparison--if) Условные операторы, такие как `if`, вычисляются путём приведения к логическому типу `Boolean` через абстрактный метод `ToBoolean` и всегда следуют следующим правилам: 1886 | - **Object** соответствует **true** 1887 | - **Undefined** соответствует **false** 1888 | - **Null** соответствует **false** 1889 | - **Boolean** соответствует **значению булева типа** 1890 | - **Number** соответствует **false**, если **+0, -0, or NaN**, в остальных случаях **true** 1891 | - **String** соответствует **false**, если строка пустая `''`, в остальных случаях **true** 1892 | 1893 | ```javascript 1894 | if ([0] && []) { 1895 | // true 1896 | // Массив (даже пустой) является объектом, а объекты возвращают true 1897 | } 1898 | ``` 1899 | 1900 | 1901 | - [15.3](#comparison--shortcuts) Используйте сокращения для булевских типов, а для строк и чисел применяйте явное сравнение. 1902 | 1903 | ```javascript 1904 | // плохо 1905 | if (isValid === true) { 1906 | // ... 1907 | } 1908 | 1909 | // хорошо 1910 | if (isValid) { 1911 | // ... 1912 | } 1913 | 1914 | // плохо 1915 | if (name) { 1916 | // ... 1917 | } 1918 | 1919 | // хорошо 1920 | if (name !== '') { 1921 | // ... 1922 | } 1923 | 1924 | // плохо 1925 | if (collection.length) { 1926 | // ... 1927 | } 1928 | 1929 | // хорошо 1930 | if (collection.length > 0) { 1931 | // ... 1932 | } 1933 | ``` 1934 | 1935 | 1936 | - [15.4](#comparison--moreinfo) Более подробную информацию можно узнать в статье [Truth Equality and JavaScript](https://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108) от Angus Croll. 1937 | 1938 | 1939 | - [15.5](#comparison--switch-blocks) Используйте фигурные скобки для `case` и `default`, если они содержат лексические декларации (например, `let`, `const`, `function`, и `class`). eslint: [`no-case-declarations`](https://eslint.org/docs/rules/no-case-declarations.html). 1940 | 1941 | > Почему? Лексические декларации видны во всем `switch` блоке, но инициализируются только при присваивании, которое происходит при входе в блок `case`. Возникают проблемы, когда множество `case` пытаются определить одно и то же. 1942 | 1943 | ```javascript 1944 | // плохо 1945 | switch (foo) { 1946 | case 1: 1947 | let x = 1; 1948 | break; 1949 | case 2: 1950 | const y = 2; 1951 | break; 1952 | case 3: 1953 | function f() { 1954 | // ... 1955 | } 1956 | break; 1957 | default: 1958 | class C {} 1959 | } 1960 | 1961 | // хорошо 1962 | switch (foo) { 1963 | case 1: { 1964 | let x = 1; 1965 | break; 1966 | } 1967 | case 2: { 1968 | const y = 2; 1969 | break; 1970 | } 1971 | case 3: { 1972 | function f() { 1973 | // ... 1974 | } 1975 | break; 1976 | } 1977 | case 4: 1978 | bar(); 1979 | break; 1980 | default: { 1981 | class C {} 1982 | } 1983 | } 1984 | ``` 1985 | 1986 | 1987 | - [15.6](#comparison--nested-ternaries) Тернарные операторы не должны быть вложены и в большинстве случаев должны быть расположены на одной строке. eslint: [`no-nested-ternary`](https://eslint.org/docs/rules/no-nested-ternary.html). 1988 | 1989 | ```javascript 1990 | // плохо 1991 | const foo = maybe1 > maybe2 1992 | ? "bar" 1993 | : value1 > value2 ? "baz" : null; 1994 | 1995 | // разбит на два отдельных тернарных выражения 1996 | const maybeNull = value1 > value2 ? 'baz' : null; 1997 | 1998 | const foo = maybe1 > maybe2 1999 | ? 'bar' 2000 | : maybeNull; 2001 | 2002 | // отлично 2003 | const foo = maybe1 > maybe2 ? 'bar' : maybeNull; 2004 | ``` 2005 | 2006 | 2007 | - [15.7](#comparison--unneeded-ternary) Избегайте ненужных тернарных операторов. eslint: [`no-unneeded-ternary`](https://eslint.org/docs/rules/no-unneeded-ternary.html). 2008 | 2009 | ```javascript 2010 | // плохо 2011 | const foo = a ? a : b; 2012 | const bar = c ? true : false; 2013 | const baz = c ? false : true; 2014 | 2015 | // хорошо 2016 | const foo = a || b; 2017 | const bar = !!c; 2018 | const baz = !c; 2019 | ``` 2020 | 2021 | 2022 | - [15.8](#comparison--no-mixed-operators) При смешивании операторов, помещайте их в круглые скобки. Единственное исключение — это стандартные арифметические операторы (`+`, `-`, `*` и `/`), так как их приоритет широко известен. eslint: [`no-mixed-operators`](https://eslint.org/docs/rules/no-mixed-operators.html) 2023 | 2024 | > Почему? Это улучшает читаемость и уточняет намерения разработчика. 2025 | 2026 | ```javascript 2027 | // плохо 2028 | const foo = a && b < 0 || c > 0 || d + 1 === 0; 2029 | 2030 | // плохо 2031 | const bar = a ** b - 5 % d; 2032 | 2033 | // плохо 2034 | // можно ошибиться, думая что это (a || b) && c 2035 | if (a || b && c) { 2036 | return d; 2037 | } 2038 | 2039 | // хорошо 2040 | const foo = (a && b < 0) || c > 0 || (d + 1 === 0); 2041 | 2042 | // хорошо 2043 | const bar = (a ** b) - (5 % d); 2044 | 2045 | // хорошо 2046 | if (a || (b && c)) { 2047 | return d; 2048 | } 2049 | 2050 | // хорошо 2051 | const bar = a + b / c * d; 2052 | ``` 2053 | 2054 | **[⬆ к оглавлению](#Оглавление)** 2055 | 2056 | ## Блоки 2057 | 2058 | 2059 | - [16.1](#blocks--braces) Используйте фигурные скобки, когда блок кода занимает несколько строк. eslint: [`nonblock-statement-body-position`](https://eslint.org/docs/rules/nonblock-statement-body-position) 2060 | 2061 | ```javascript 2062 | // плохо 2063 | if (test) 2064 | return false; 2065 | 2066 | // хорошо 2067 | if (test) return false; 2068 | 2069 | // хорошо 2070 | if (test) { 2071 | return false; 2072 | } 2073 | 2074 | // плохо 2075 | function foo() { return false; } 2076 | 2077 | // хорошо 2078 | function bar() { 2079 | return false; 2080 | } 2081 | ``` 2082 | 2083 | 2084 | - [16.2](#blocks--cuddled-elses) Если блоки кода в условии `if` и `else` занимают несколько строк, расположите оператор `else` на той же строчке, где находится закрывающая фигурная скобка блока `if`. eslint: [`brace-style`](https://eslint.org/docs/rules/brace-style.html) 2085 | 2086 | ```javascript 2087 | // плохо 2088 | if (test) { 2089 | thing1(); 2090 | thing2(); 2091 | } 2092 | else { 2093 | thing3(); 2094 | } 2095 | 2096 | // хорошо 2097 | if (test) { 2098 | thing1(); 2099 | thing2(); 2100 | } else { 2101 | thing3(); 2102 | } 2103 | ``` 2104 | 2105 | 2106 | - [16.3](#blocks--no-else-return) Если в блоке `if` всегда выполняется оператор `return`, последующий блок `else` не нужен. `return` внутри блока `else if`, следующем за блоком `if`, который содержит `return`, может быть разделён на несколько блоков `if`. eslint: [`no-else-return`](https://eslint.org/docs/rules/no-else-return) 2107 | 2108 | ```javascript 2109 | // плохо 2110 | function foo() { 2111 | if (x) { 2112 | return x; 2113 | } else { 2114 | return y; 2115 | } 2116 | } 2117 | 2118 | // плохо 2119 | function cats() { 2120 | if (x) { 2121 | return x; 2122 | } else if (y) { 2123 | return y; 2124 | } 2125 | } 2126 | 2127 | // плохо 2128 | function dogs() { 2129 | if (x) { 2130 | return x; 2131 | } else { 2132 | if (y) { 2133 | return y; 2134 | } 2135 | } 2136 | } 2137 | 2138 | // хорошо 2139 | function foo() { 2140 | if (x) { 2141 | return x; 2142 | } 2143 | 2144 | return y; 2145 | } 2146 | 2147 | // хорошо 2148 | function cats() { 2149 | if (x) { 2150 | return x; 2151 | } 2152 | 2153 | if (y) { 2154 | return y; 2155 | } 2156 | } 2157 | 2158 | // хорошо 2159 | function dogs(x) { 2160 | if (x) { 2161 | if (z) { 2162 | return y; 2163 | } 2164 | } else { 2165 | return z; 2166 | } 2167 | } 2168 | ``` 2169 | 2170 | **[⬆ к оглавлению](#Оглавление)** 2171 | 2172 | ## Управляющие операторы 2173 | 2174 | 2175 | - [17.1](#control-statements) Если ваш управляющий оператор (`if`, `while` и т.д.) слишком длинный или превышает максимальную длину строки, то каждое (сгруппированное) условие можно поместить на новую строку. Логический оператор должен располагаться в начале строки. 2176 | 2177 | > Почему? Наличие операторов в начале строки приводит к выравниванию операторов и напоминает цепочку методов. Это также улучшает читаемость, упрощая визуальное отслеживание сложной логики. 2178 | 2179 | ```javascript 2180 | // плохо 2181 | if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { 2182 | thing1(); 2183 | } 2184 | 2185 | // плохо 2186 | if (foo === 123 && 2187 | bar === 'abc') { 2188 | thing1(); 2189 | } 2190 | 2191 | // плохо 2192 | if (foo === 123 2193 | && bar === 'abc') { 2194 | thing1(); 2195 | } 2196 | 2197 | // плохо 2198 | if ( 2199 | foo === 123 && 2200 | bar === 'abc' 2201 | ) { 2202 | thing1(); 2203 | } 2204 | 2205 | // хорошо 2206 | if ( 2207 | foo === 123 2208 | && bar === 'abc' 2209 | ) { 2210 | thing1(); 2211 | } 2212 | 2213 | // хорошо 2214 | if ( 2215 | (foo === 123 || bar === 'abc') 2216 | && doesItLookGoodWhenItBecomesThatLong() 2217 | && isThisReallyHappening() 2218 | ) { 2219 | thing1(); 2220 | } 2221 | 2222 | // хорошо 2223 | if (foo === 123 && bar === 'abc') { 2224 | thing1(); 2225 | } 2226 | ``` 2227 | 2228 | 2229 | - [17.2](#control-statements--value-selection) Не используйте операторы выбора вместо управляющих операторов. 2230 | 2231 | ```javascript 2232 | // плохо 2233 | !isRunning && startRunning(); 2234 | 2235 | // хорошо 2236 | if (!isRunning) { 2237 | startRunning(); 2238 | } 2239 | ``` 2240 | 2241 | **[⬆ к оглавлению](#Оглавление)** 2242 | 2243 | ## Комментарии 2244 | 2245 | 2246 | - [18.1](#comments--multiline) Используйте конструкцию `/** ... */` для многострочных комментариев. 2247 | 2248 | ```javascript 2249 | // плохо 2250 | // make() возвращает новый элемент 2251 | // соответствующий переданному названию тега 2252 | // 2253 | // @param {String} tag 2254 | // @return {Element} element 2255 | function make(tag) { 2256 | 2257 | // ... 2258 | 2259 | return element; 2260 | } 2261 | 2262 | // хорошо 2263 | /** 2264 | * make() возвращает новый элемент 2265 | * соответствующий переданному названию тега 2266 | */ 2267 | function make(tag) { 2268 | 2269 | // ... 2270 | 2271 | return element; 2272 | } 2273 | ``` 2274 | 2275 | 2276 | - [18.2](#comments--singleline) Используйте двойной слеш `//` для однострочных комментариев. Располагайте такие комментарии отдельной строкой над кодом, который хотите пояснить. Если комментарий не является первой строкой блока, добавьте сверху пустую строку. 2277 | 2278 | ```javascript 2279 | // плохо 2280 | const active = true; // это текущая вкладка 2281 | 2282 | // хорошо 2283 | // это текущая вкладка 2284 | const active = true; 2285 | 2286 | // плохо 2287 | function getType() { 2288 | console.log('fetching type...'); 2289 | // установить по умолчанию тип 'no type' 2290 | const type = this.type || 'no type'; 2291 | 2292 | return type; 2293 | } 2294 | 2295 | // хорошо 2296 | function getType() { 2297 | console.log('fetching type...'); 2298 | 2299 | // установить по умолчанию тип 'no type' 2300 | const type = this.type || 'no type'; 2301 | 2302 | return type; 2303 | } 2304 | 2305 | // тоже хорошо 2306 | function getType() { 2307 | // установить по умолчанию тип 'no type' 2308 | const type = this.type || 'no type'; 2309 | 2310 | return type; 2311 | } 2312 | ``` 2313 | 2314 | 2315 | - [18.3](#comments--spaces) Начинайте все комментарии с пробела, так их проще читать. eslint: [`spaced-comment`](https://eslint.org/docs/rules/spaced-comment) 2316 | 2317 | ```javascript 2318 | // плохо 2319 | //это текущая вкладка 2320 | const active = true; 2321 | 2322 | // хорошо 2323 | // это текущая вкладка 2324 | const active = true; 2325 | 2326 | // плохо 2327 | /** 2328 | *make() возвращает новый элемент 2329 | *соответствующий переданному названию тега 2330 | */ 2331 | function make(tag) { 2332 | 2333 | // ... 2334 | 2335 | return element; 2336 | } 2337 | 2338 | // хорошо 2339 | /** 2340 | * make() возвращает новый элемент 2341 | * соответствующий переданному названию тега 2342 | */ 2343 | function make(tag) { 2344 | 2345 | // ... 2346 | 2347 | return element; 2348 | } 2349 | ``` 2350 | 2351 | 2352 | - [18.4](#comments--actionitems) Если комментарий начинается со слов `FIXME` или `TODO`, то это помогает другим разработчикам быстро понять, когда вы хотите указать на проблему, которую надо решить, или когда вы предлагаете решение проблемы, которое надо реализовать. Такие комментарии, в отличие от обычных, побуждают к действию: `FIXME: -- нужно разобраться с этим` или `TODO: -- нужно реализовать`. 2353 | 2354 | 2355 | - [18.5](#comments--fixme) Используйте `// FIXME:`, чтобы описать проблему. 2356 | 2357 | ```javascript 2358 | class Calculator extends Abacus { 2359 | constructor() { 2360 | super(); 2361 | 2362 | // FIXME: здесь не должна использоваться глобальная переменная 2363 | total = 0; 2364 | } 2365 | } 2366 | ``` 2367 | 2368 | 2369 | - [18.6](#comments--todo) Используйте `// TODO:`, чтобы описать решение проблемы. 2370 | 2371 | ```javascript 2372 | class Calculator extends Abacus { 2373 | constructor() { 2374 | super(); 2375 | 2376 | // TODO: нужна возможность задать total через параметры 2377 | this.total = 0; 2378 | } 2379 | } 2380 | ``` 2381 | 2382 | **[⬆ к оглавлению](#Оглавление)** 2383 | 2384 | ## Пробелы 2385 | 2386 | 2387 | - [19.1](#whitespace--spaces) Используйте мягкую табуляцию (символ пробела) шириной в 2 пробела. eslint: [`indent`](https://eslint.org/docs/rules/indent.html) 2388 | 2389 | ```javascript 2390 | // плохо 2391 | function foo() { 2392 | ∙∙∙∙let name; 2393 | } 2394 | 2395 | // плохо 2396 | function bar() { 2397 | ∙let name; 2398 | } 2399 | 2400 | // хорошо 2401 | function baz() { 2402 | ∙∙let name; 2403 | } 2404 | ``` 2405 | 2406 | 2407 | - [19.2](#whitespace--before-blocks) Ставьте 1 пробел перед открывающей фигурной скобкой у блока. eslint: [`space-before-blocks`](https://eslint.org/docs/rules/space-before-blocks.html) 2408 | 2409 | ```javascript 2410 | // плохо 2411 | function test(){ 2412 | console.log('test'); 2413 | } 2414 | 2415 | // хорошо 2416 | function test() { 2417 | console.log('test'); 2418 | } 2419 | 2420 | // плохо 2421 | dog.set('attr',{ 2422 | age: '1 year', 2423 | breed: 'Bernese Mountain Dog', 2424 | }); 2425 | 2426 | // хорошо 2427 | dog.set('attr', { 2428 | age: '1 year', 2429 | breed: 'Bernese Mountain Dog', 2430 | }); 2431 | ``` 2432 | 2433 | 2434 | - [19.3](#whitespace--around-keywords) Ставьте 1 пробел перед открывающей круглой скобкой в операторах управления (`if`, `while` и т.п.). Не оставляйте пробелов между списком аргументов и названием в объявлениях и вызовах функций. eslint: [`keyword-spacing`](https://eslint.org/docs/rules/keyword-spacing.html) 2435 | 2436 | ```javascript 2437 | // плохо 2438 | if(isJedi) { 2439 | fight (); 2440 | } 2441 | 2442 | // хорошо 2443 | if (isJedi) { 2444 | fight(); 2445 | } 2446 | 2447 | // плохо 2448 | function fight () { 2449 | console.log ('Swooosh!'); 2450 | } 2451 | 2452 | // хорошо 2453 | function fight() { 2454 | console.log('Swooosh!'); 2455 | } 2456 | ``` 2457 | 2458 | 2459 | - [19.4](#whitespace--infix-ops) Разделяйте операторы пробелами. eslint: [`space-infix-ops`](https://eslint.org/docs/rules/space-infix-ops.html) 2460 | 2461 | ```javascript 2462 | // плохо 2463 | const x=y+5; 2464 | 2465 | // хорошо 2466 | const x = y + 5; 2467 | ``` 2468 | 2469 | 2470 | - [19.5](#whitespace--newline-at-end) В конце файла оставляйте одну пустую строку. eslint: [`eol-last`](https://github.com/eslint/eslint/blob/master/docs/rules/eol-last.md) 2471 | 2472 | ```javascript 2473 | // плохо 2474 | import { es6 } from './AirbnbStyleGuide'; 2475 | // ... 2476 | export default es6; 2477 | ``` 2478 | 2479 | ```javascript 2480 | // плохо 2481 | import { es6 } from './AirbnbStyleGuide'; 2482 | // ... 2483 | export default es6;↵ 2484 | ↵ 2485 | ``` 2486 | 2487 | ```javascript 2488 | // хорошо 2489 | import { es6 } from './AirbnbStyleGuide'; 2490 | // ... 2491 | export default es6;↵ 2492 | ``` 2493 | 2494 | 2495 | - [19.6](#whitespace--chains) Используйте переносы строк и отступы, когда делаете длинные цепочки методов (больше 2 методов). Ставьте точку в начале строки, чтобы дать понять, что это не новая инструкция, а продолжение цепочки. eslint: [`newline-per-chained-call`](https://eslint.org/docs/rules/newline-per-chained-call) [`no-whitespace-before-property`](https://eslint.org/docs/rules/no-whitespace-before-property) 2496 | 2497 | ```javascript 2498 | // плохо 2499 | $('#items').find('.selected').highlight().end().find('.open').updateCount(); 2500 | 2501 | // плохо 2502 | $('#items'). 2503 | find('.selected'). 2504 | highlight(). 2505 | end(). 2506 | find('.open'). 2507 | updateCount(); 2508 | 2509 | // хорошо 2510 | $('#items') 2511 | .find('.selected') 2512 | .highlight() 2513 | .end() 2514 | .find('.open') 2515 | .updateCount(); 2516 | 2517 | // плохо 2518 | const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) 2519 | .attr('width', (radius + margin) * 2).append('svg:g') 2520 | .attr('transform', `translate(${radius + margin},${radius + margin})`) 2521 | .call(tron.led); 2522 | 2523 | // хорошо 2524 | const leds = stage.selectAll('.led') 2525 | .data(data) 2526 | .enter().append('svg:svg') 2527 | .classed('led', true) 2528 | .attr('width', (radius + margin) * 2) 2529 | .append('svg:g') 2530 | .attr('transform', `translate(${radius + margin},${radius + margin})`) 2531 | .call(tron.led); 2532 | 2533 | // хорошо 2534 | const leds = stage.selectAll('.led').data(data); 2535 | ``` 2536 | 2537 | 2538 | - [19.7](#whitespace--after-blocks) Оставляйте пустую строку между блоком кода и следующей инструкцией. 2539 | 2540 | ```javascript 2541 | // плохо 2542 | if (foo) { 2543 | return bar; 2544 | } 2545 | return baz; 2546 | 2547 | // хорошо 2548 | if (foo) { 2549 | return bar; 2550 | } 2551 | 2552 | return baz; 2553 | 2554 | // плохо 2555 | const obj = { 2556 | foo() { 2557 | }, 2558 | bar() { 2559 | }, 2560 | }; 2561 | return obj; 2562 | 2563 | // хорошо 2564 | const obj = { 2565 | foo() { 2566 | }, 2567 | 2568 | bar() { 2569 | }, 2570 | }; 2571 | 2572 | return obj; 2573 | 2574 | // плохо 2575 | const arr = [ 2576 | function foo() { 2577 | }, 2578 | function bar() { 2579 | }, 2580 | ]; 2581 | return arr; 2582 | 2583 | // хорошо 2584 | const arr = [ 2585 | function foo() { 2586 | }, 2587 | 2588 | function bar() { 2589 | }, 2590 | ]; 2591 | 2592 | return arr; 2593 | ``` 2594 | 2595 | 2596 | - [19.8](#whitespace--padded-blocks) Не добавляйте отступы до или после кода внутри блока. eslint: [`padded-blocks`](https://eslint.org/docs/rules/padded-blocks.html) 2597 | 2598 | ```javascript 2599 | // плохо 2600 | function bar() { 2601 | 2602 | console.log(foo); 2603 | 2604 | } 2605 | 2606 | // тоже плохо 2607 | if (baz) { 2608 | 2609 | console.log(qux); 2610 | } else { 2611 | console.log(foo); 2612 | 2613 | } 2614 | 2615 | // хорошо 2616 | function bar() { 2617 | console.log(foo); 2618 | } 2619 | 2620 | // хорошо 2621 | if (baz) { 2622 | console.log(qux); 2623 | } else { 2624 | console.log(foo); 2625 | } 2626 | ``` 2627 | 2628 | 2629 | - [19.9](#whitespace--in-parens) Не добавляйте пробелы между круглыми скобками и их содержимым. eslint: [`space-in-parens`](https://eslint.org/docs/rules/space-in-parens.html) 2630 | 2631 | ```javascript 2632 | // плохо 2633 | function bar( foo ) { 2634 | return foo; 2635 | } 2636 | 2637 | // хорошо 2638 | function bar(foo) { 2639 | return foo; 2640 | } 2641 | 2642 | // плохо 2643 | if ( foo ) { 2644 | console.log(foo); 2645 | } 2646 | 2647 | // хорошо 2648 | if (foo) { 2649 | console.log(foo); 2650 | } 2651 | ``` 2652 | 2653 | 2654 | - [19.10](#whitespace--in-brackets) Не добавляйте пробелы между квадратными скобками и их содержимым. eslint: [`array-bracket-spacing`](https://eslint.org/docs/rules/array-bracket-spacing.html) 2655 | 2656 | ```javascript 2657 | // плохо 2658 | const foo = [ 1, 2, 3 ]; 2659 | console.log(foo[ 0 ]); 2660 | 2661 | // хорошо 2662 | const foo = [1, 2, 3]; 2663 | console.log(foo[0]); 2664 | ``` 2665 | 2666 | 2667 | - [19.11](#whitespace--in-braces) Добавляйте пробелы между фигурными скобками и их содержимым. eslint: [`object-curly-spacing`](https://eslint.org/docs/rules/object-curly-spacing.html) 2668 | 2669 | ```javascript 2670 | // плохо 2671 | const foo = {clark: 'kent'}; 2672 | 2673 | // хорошо 2674 | const foo = { clark: 'kent' }; 2675 | ``` 2676 | 2677 | 2678 | - [19.12](#whitespace--max-len) Старайтесь не допускать, чтобы строки были длиннее 100 символов (включая пробелы). Замечание: согласно [пункту выше](#strings--line-length), длинные строки с текстом освобождаются от этого правила и не должны разбиваться на несколько строк. eslint: [`max-len`](https://eslint.org/docs/rules/max-len.html) 2679 | 2680 | > Почему? Это обеспечивает удобство чтения и поддержки кода. 2681 | 2682 | ```javascript 2683 | // плохо 2684 | const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; 2685 | 2686 | // плохо 2687 | $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); 2688 | 2689 | // хорошо 2690 | const foo = jsonData 2691 | && jsonData.foo 2692 | && jsonData.foo.bar 2693 | && jsonData.foo.bar.baz 2694 | && jsonData.foo.bar.baz.quux 2695 | && jsonData.foo.bar.baz.quux.xyzzy; 2696 | 2697 | // хорошо 2698 | $.ajax({ 2699 | method: 'POST', 2700 | url: 'https://airbnb.com/', 2701 | data: { name: 'John' }, 2702 | }) 2703 | .done(() => console.log('Congratulations!')) 2704 | .fail(() => console.log('You have failed this city.')); 2705 | ``` 2706 | 2707 | 2708 | - [19.13](#whitespace--block-spacing) Требуйте согласованного расстояния между открывающим символом блока и следующим символом на одной и той же строке. Тоже самое касается расстояния между закрывающим символом блока и предыдущим символом. eslint: [`block-spacing`](https://eslint.org/docs/rules/block-spacing) 2709 | 2710 | ```javascript 2711 | // плохо 2712 | function foo() {return true;} 2713 | if (foo) { bar = 0;} 2714 | 2715 | // хорошо 2716 | function foo() { return true; } 2717 | if (foo) { bar = 0; } 2718 | ``` 2719 | 2720 | 2721 | - [19.14](#whitespace--comma-spacing) Избегайте пробелов перед запятыми и ставьте его после. eslint: [`comma-spacing`](https://eslint.org/docs/rules/comma-spacing) 2722 | 2723 | ```javascript 2724 | // плохо 2725 | var foo = 1,bar = 2; 2726 | var arr = [1 , 2]; 2727 | 2728 | // хорошо 2729 | var foo = 1, bar = 2; 2730 | var arr = [1, 2]; 2731 | ``` 2732 | 2733 | 2734 | - [19.15](#whitespace--computed-property-spacing) Избегайте пробелов внутри скобок вычисляемого свойства. eslint: [`computed-property-spacing`](https://eslint.org/docs/rules/computed-property-spacing) 2735 | 2736 | ```javascript 2737 | // плохо 2738 | obj[foo ] 2739 | obj[ 'foo'] 2740 | var x = {[ b ]: a} 2741 | obj[foo[ bar ]] 2742 | 2743 | // хорошо 2744 | obj[foo] 2745 | obj['foo'] 2746 | var x = { [b]: a } 2747 | obj[foo[bar]] 2748 | ``` 2749 | 2750 | 2751 | - [19.16](#whitespace--func-call-spacing) Избегайте пробелов между функциями и их вызовами. eslint: [`func-call-spacing`](https://eslint.org/docs/rules/func-call-spacing) 2752 | 2753 | ```javascript 2754 | // плохо 2755 | func (); 2756 | 2757 | func 2758 | (); 2759 | 2760 | // хорошо 2761 | func(); 2762 | ``` 2763 | 2764 | 2765 | - [19.17](#whitespace--key-spacing) Обеспечьте согласованное расстояние между ключами и значениями в свойствах литералов объекта. eslint: [`key-spacing`](https://eslint.org/docs/rules/key-spacing) 2766 | 2767 | ```javascript 2768 | // плохо 2769 | var obj = { "foo" : 42 }; 2770 | var obj2 = { "foo":42 }; 2771 | 2772 | // хорошо 2773 | var obj = { "foo": 42 }; 2774 | ``` 2775 | 2776 | 2777 | - [19.18](#whitespace--no-trailing-spaces) Избегайте пробелов в конце строки. eslint: [`no-trailing-spaces`](https://eslint.org/docs/rules/no-trailing-spaces) 2778 | 2779 | 2780 | - [19.19](#whitespace--no-multiple-empty-lines) Избегайте множества пустых строк и делайте только одну пустую строку в конце файла. eslint: [`no-multiple-empty-lines`](https://eslint.org/docs/rules/no-multiple-empty-lines) 2781 | 2782 | 2783 | ```javascript 2784 | // плохо 2785 | var x = 1; 2786 | 2787 | 2788 | 2789 | var y = 2; 2790 | 2791 | // хорошо 2792 | var x = 1; 2793 | 2794 | var y = 2; 2795 | ``` 2796 | 2797 | 2798 | **[⬆ к оглавлению](#Оглавление)** 2799 | 2800 | ## Запятые 2801 | 2802 | 2803 | - [20.1](#commas--leading-trailing) Не начинайте строку с запятой. eslint: [`comma-style`](https://eslint.org/docs/rules/comma-style.html) 2804 | 2805 | ```javascript 2806 | // плохо 2807 | const story = [ 2808 | once 2809 | , upon 2810 | , aTime 2811 | ]; 2812 | 2813 | // хорошо 2814 | const story = [ 2815 | once, 2816 | upon, 2817 | aTime, 2818 | ]; 2819 | 2820 | // плохо 2821 | const hero = { 2822 | firstName: 'Ada' 2823 | , lastName: 'Lovelace' 2824 | , birthYear: 1815 2825 | , superPower: 'computers' 2826 | }; 2827 | 2828 | // хорошо 2829 | const hero = { 2830 | firstName: 'Ada', 2831 | lastName: 'Lovelace', 2832 | birthYear: 1815, 2833 | superPower: 'computers', 2834 | }; 2835 | ``` 2836 | 2837 | 2838 | - [20.2](#commas--dangling) Добавляйте висячие запятые. eslint: [`comma-dangle`](https://eslint.org/docs/rules/comma-dangle.html) 2839 | 2840 | > Почему? Такой подход даёт понятную разницу при просмотре изменений. Кроме того, транспиляторы типа Babel удалят висячие запятые из собранного кода, поэтому вы можете не беспокоиться о [проблемах](https://github.com/airbnb/javascript/blob/es5-deprecated/es5/README.md#commas) в старых браузерах. 2841 | 2842 | ```diff 2843 | // плохо - git diff без висячей запятой 2844 | const hero = { 2845 | firstName: 'Florence', 2846 | - lastName: 'Nightingale' 2847 | + lastName: 'Nightingale', 2848 | + inventorOf: ['coxcomb chart', 'modern nursing'] 2849 | }; 2850 | 2851 | // хорошо - git diff с висячей запятой 2852 | const hero = { 2853 | firstName: 'Florence', 2854 | lastName: 'Nightingale', 2855 | + inventorOf: ['coxcomb chart', 'modern nursing'], 2856 | }; 2857 | ``` 2858 | 2859 | ```javascript 2860 | // плохо 2861 | const hero = { 2862 | firstName: 'Dana', 2863 | lastName: 'Scully' 2864 | }; 2865 | 2866 | const heroes = [ 2867 | 'Batman', 2868 | 'Superman' 2869 | ]; 2870 | 2871 | // хорошо 2872 | const hero = { 2873 | firstName: 'Dana', 2874 | lastName: 'Scully', 2875 | }; 2876 | 2877 | const heroes = [ 2878 | 'Batman', 2879 | 'Superman', 2880 | ]; 2881 | 2882 | // плохо 2883 | function createHero( 2884 | firstName, 2885 | lastName, 2886 | inventorOf 2887 | ) { 2888 | // ничего не делает 2889 | } 2890 | 2891 | // хорошо 2892 | function createHero( 2893 | firstName, 2894 | lastName, 2895 | inventorOf, 2896 | ) { 2897 | // ничего не делает 2898 | } 2899 | 2900 | // хорошо (обратите внимание, что висячей запятой не должно быть после rest-параметра) 2901 | function createHero( 2902 | firstName, 2903 | lastName, 2904 | inventorOf, 2905 | ...heroArgs 2906 | ) { 2907 | // ничего не делает 2908 | } 2909 | 2910 | // плохо 2911 | createHero( 2912 | firstName, 2913 | lastName, 2914 | inventorOf 2915 | ); 2916 | 2917 | // хорошо 2918 | createHero( 2919 | firstName, 2920 | lastName, 2921 | inventorOf, 2922 | ); 2923 | 2924 | // хорошо (обратите внимание, что висячей запятой не должно быть после rest-аргумента) 2925 | createHero( 2926 | firstName, 2927 | lastName, 2928 | inventorOf, 2929 | ...heroArgs 2930 | ); 2931 | ``` 2932 | 2933 | **[⬆ к оглавлению](#Оглавление)** 2934 | 2935 | ## Точка с запятой 2936 | 2937 | 2938 | - [21.1](#semicolons--required) **Да.** eslint: [`semi`](https://eslint.org/docs/rules/semi.html) 2939 | 2940 | > Почему? Когда JavaScript встречает перенос строки без точки с запятой, он использует правило под названием [Автоматическая Вставка Точки с запятой (Automatic Semicolon Insertion)](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion), чтобы определить, стоит ли считать этот перенос строки как конец выражения и (как следует из названия) поместить точку с запятой в вашем коде до переноса строки. Однако, ASI содержит несколько странных форм поведения, и ваш код может быть сломан, если JavaScript неверно истолкует ваш перенос строки. Эти правила станут сложнее, когда новые возможности станут частью JavaScript. Явное завершение ваших выражений и настройка вашего линтера для улавливания пропущенных точек с запятыми помогут вам предотвратить возникновение проблем. 2941 | 2942 | ```javascript 2943 | // плохо - выбрасывает исключение 2944 | const luke = {} 2945 | const leia = {} 2946 | [luke, leia].forEach(jedi => jedi.father = 'vader') 2947 | 2948 | // плохо - выбрасывает исключение 2949 | const reaction = "No! That’s impossible!" 2950 | (async function meanwhileOnTheFalcon() { 2951 | // переносимся к `leia`, `lando`, `chewie`, `r2`, `c3p0` 2952 | // ... 2953 | }()) 2954 | 2955 | // плохо - возвращает `undefined` вместо значения на следующей строке. Так всегда происходит, когда `return` расположен сам по себе, потому что ASI (Автоматическая Вставка Точки с запятой)! 2956 | function foo() { 2957 | return 2958 | 'search your feelings, you know it to be foo' 2959 | } 2960 | 2961 | // хорошо 2962 | const luke = {}; 2963 | const leia = {}; 2964 | [luke, leia].forEach((jedi) => { 2965 | jedi.father = 'vader'; 2966 | }); 2967 | 2968 | // хорошо 2969 | const reaction = "No! That’s impossible!"; 2970 | (async function meanwhileOnTheFalcon() { 2971 | // переносимся к `leia`, `lando`, `chewie`, `r2`, `c3p0` 2972 | // ... 2973 | }()); 2974 | 2975 | // хорошо 2976 | function foo() { 2977 | return 'search your feelings, you know it to be foo'; 2978 | } 2979 | ``` 2980 | 2981 | [Читать подробнее](https://stackoverflow.com/questions/7365172/semicolon-before-self-invoking-function/7365214#7365214). 2982 | 2983 | **[⬆ к оглавлению](#Оглавление)** 2984 | 2985 | ## Приведение типов 2986 | 2987 | 2988 | - [22.1](#coercion--explicit) Выполняйте приведение типов в начале инструкции. 2989 | 2990 | 2991 | - [22.2](#coercion--strings) Строки: eslint: [`no-new-wrappers`](https://eslint.org/docs/rules/no-new-wrappers) 2992 | 2993 | ```javascript 2994 | // => this.reviewScore = 9; 2995 | 2996 | // плохо 2997 | const totalScore = new String(this.reviewScore); // тип totalScore будет "object", а не "string" 2998 | 2999 | // плохо 3000 | const totalScore = this.reviewScore + ''; // вызывает this.reviewScore.valueOf() 3001 | 3002 | // плохо 3003 | const totalScore = this.reviewScore.toString(); // нет гарантии что вернётся строка 3004 | 3005 | // хорошо 3006 | const totalScore = String(this.reviewScore); 3007 | ``` 3008 | 3009 | 3010 | - [22.3](#coercion--numbers) Числа: Используйте `Number` и `parseInt` с основанием системы счисления. eslint: [`radix`](https://eslint.org/docs/rules/radix) [`no-new-wrappers`](https://eslint.org/docs/rules/no-new-wrappers) 3011 | 3012 | ```javascript 3013 | const inputValue = '4'; 3014 | 3015 | // плохо 3016 | const val = new Number(inputValue); 3017 | 3018 | // плохо 3019 | const val = +inputValue; 3020 | 3021 | // плохо 3022 | const val = inputValue >> 0; 3023 | 3024 | // плохо 3025 | const val = parseInt(inputValue); 3026 | 3027 | // хорошо 3028 | const val = Number(inputValue); 3029 | 3030 | // хорошо 3031 | const val = parseInt(inputValue, 10); 3032 | ``` 3033 | 3034 | 3035 | - [22.4](#coercion--comment-deviations) Если по какой-то причине вы делаете что-то настолько безумное, что `parseInt` является слабым местом и вам нужно использовать побитовый сдвиг из-за [вопросов производительности](https://jsperf.com/coercion-vs-casting/3), оставьте комментарий, объясняющий почему и что вы делаете. 3036 | 3037 | ```javascript 3038 | // хорошо 3039 | /** 3040 | * этот код медленно работал из-за parseInt. 3041 | * побитовый сдвиг строки для приведения её к числу 3042 | * работает значительно быстрее. 3043 | */ 3044 | const val = inputValue >> 0; 3045 | ``` 3046 | 3047 | 3048 | - [22.5](#coercion--bitwise) **Примечание:** Будьте осторожны с побитовыми операциями. Числа в JavaScript являются [64-битными значениями](https://es5.github.io/#x4.3.19), но побитовые операции всегда возвращают 32-битные значения ([источник](https://es5.github.io/#x11.7)). Побитовый сдвиг может привести к неожиданному поведению для значений больше, чем 32 бита. [Обсуждение](https://github.com/airbnb/javascript/issues/109). Верхний предел — 2 147 483 647: 3049 | 3050 | ```javascript 3051 | 2147483647 >> 0; // => 2147483647 3052 | 2147483648 >> 0; // => -2147483648 3053 | 2147483649 >> 0; // => -2147483647 3054 | ``` 3055 | 3056 | 3057 | - [22.6](#coercion--booleans) Логические типы: eslint: [`no-new-wrappers`](https://eslint.org/docs/rules/no-new-wrappers) 3058 | 3059 | ```javascript 3060 | const age = 0; 3061 | 3062 | // плохо 3063 | const hasAge = new Boolean(age); 3064 | 3065 | // хорошо 3066 | const hasAge = Boolean(age); 3067 | 3068 | // отлично 3069 | const hasAge = !!age; 3070 | ``` 3071 | 3072 | **[⬆ к оглавлению](#Оглавление)** 3073 | 3074 | ## Соглашение об именовании 3075 | 3076 | 3077 | - [23.1](#naming--descriptive) Избегайте названий из одной буквы. Имя должно быть наглядным. eslint: [`id-length`](https://eslint.org/docs/rules/id-length) 3078 | 3079 | ```javascript 3080 | // плохо 3081 | function q() { 3082 | // ... 3083 | } 3084 | 3085 | // хорошо 3086 | function query() { 3087 | // ... 3088 | } 3089 | ``` 3090 | 3091 | 3092 | - [23.2](#naming--camelCase) Используйте `camelCase` для именования объектов, функций и экземпляров. eslint: [`camelcase`](https://eslint.org/docs/rules/camelcase.html) 3093 | 3094 | ```javascript 3095 | // плохо 3096 | const OBJEcttsssss = {}; 3097 | const this_is_my_object = {}; 3098 | function c() {} 3099 | 3100 | // хорошо 3101 | const thisIsMyObject = {}; 3102 | function thisIsMyFunction() {} 3103 | ``` 3104 | 3105 | 3106 | - [23.3](#naming--PascalCase) Используйте `PascalCase` только для именования конструкторов и классов. eslint: [`new-cap`](https://eslint.org/docs/rules/new-cap.html) 3107 | 3108 | ```javascript 3109 | // плохо 3110 | function user(options) { 3111 | this.name = options.name; 3112 | } 3113 | 3114 | const bad = new user({ 3115 | name: 'nope', 3116 | }); 3117 | 3118 | // хорошо 3119 | class User { 3120 | constructor(options) { 3121 | this.name = options.name; 3122 | } 3123 | } 3124 | 3125 | const good = new User({ 3126 | name: 'yup', 3127 | }); 3128 | ``` 3129 | 3130 | 3131 | - [23.4](#naming--leading-underscore) Не используйте `_` в начале или в конце названий. eslint: [`no-underscore-dangle`](https://eslint.org/docs/rules/no-underscore-dangle.html) 3132 | 3133 | > Почему? JavaScript не имеет концепции приватности свойств или методов. Хотя подчёркивание в начале имени является распространённым соглашением, которое показывает «приватность», фактически эти свойства являются такими же доступными, как и часть вашего публичного API. Это соглашение может привести к тому, что разработчики будут ошибочно думать, что изменения не приведут к поломке или что тесты не нужны. Итог: если вы хотите, чтобы что-то было «приватным», то оно не должно быть доступно извне. 3134 | 3135 | ```javascript 3136 | // плохо 3137 | this.__firstName__ = 'Panda'; 3138 | this.firstName_ = 'Panda'; 3139 | this._firstName = 'Panda'; 3140 | 3141 | // хорошо 3142 | this.firstName = 'Panda'; 3143 | 3144 | // хорошо, в средах, где поддерживается WeakMaps 3145 | // смотрите https://kangax.github.io/compat-table/es6/#test-WeakMap 3146 | const firstNames = new WeakMap(); 3147 | firstNames.set(this, 'Panda'); 3148 | ``` 3149 | 3150 | 3151 | - [23.5](#naming--self-this) Не сохраняйте ссылку на `this`. Используйте стрелочные функции или [метод bind()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). 3152 | 3153 | ```javascript 3154 | // плохо 3155 | function foo() { 3156 | const self = this; 3157 | return function () { 3158 | console.log(self); 3159 | }; 3160 | } 3161 | 3162 | // плохо 3163 | function foo() { 3164 | const that = this; 3165 | return function () { 3166 | console.log(that); 3167 | }; 3168 | } 3169 | 3170 | // хорошо 3171 | function foo() { 3172 | return () => { 3173 | console.log(this); 3174 | }; 3175 | } 3176 | ``` 3177 | 3178 | 3179 | - [23.6](#naming--filename-matches-export) Название файла точно должно совпадать с именем его экспорта по умолчанию. 3180 | 3181 | ```javascript 3182 | // содержание файла 1 3183 | class CheckBox { 3184 | // ... 3185 | } 3186 | export default CheckBox; 3187 | 3188 | // содержание файла 2 3189 | export default function fortyTwo() { return 42; } 3190 | 3191 | // содержание файла 3 3192 | export default function insideDirectory() {} 3193 | 3194 | // в других файлах 3195 | // плохо 3196 | import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename 3197 | import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export 3198 | import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export 3199 | 3200 | // плохо 3201 | import CheckBox from './check_box'; // PascalCase import/export, snake_case filename 3202 | import forty_two from './forty_two'; // snake_case import/filename, camelCase export 3203 | import inside_directory from './inside_directory'; // snake_case import, camelCase export 3204 | import index from './inside_directory/index'; // requiring the index file explicitly 3205 | import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly 3206 | 3207 | // хорошо 3208 | import CheckBox from './CheckBox'; // PascalCase export/import/filename 3209 | import fortyTwo from './fortyTwo'; // camelCase export/import/filename 3210 | import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" 3211 | // ^ поддерживает оба варианта: insideDirectory.js и insideDirectory/index.js 3212 | ``` 3213 | 3214 | 3215 | - [23.7](#naming--camelCase-default-export) Используйте `camelCase`, когда экспортируете функцию по умолчанию. Ваш файл должен называться так же, как и имя функции. 3216 | ```javascript 3217 | function makeStyleGuide() { 3218 | // ... 3219 | } 3220 | 3221 | export default makeStyleGuide; 3222 | ``` 3223 | 3224 | 3225 | - [23.8](#naming--PascalCase-singleton) Используйте `PascalCase`, когда экспортируете конструктор / класс / синглтон / библиотечную функцию / объект. 3226 | 3227 | ```javascript 3228 | const AirbnbStyleGuide = { 3229 | es6: { 3230 | }, 3231 | }; 3232 | 3233 | export default AirbnbStyleGuide; 3234 | ``` 3235 | 3236 | 3237 | - [23.9](#naming--Acronyms-and-Initialisms) Сокращения или буквенные аббревиатуры всегда должны быть в верхнем или нижнем регистре. 3238 | 3239 | > Почему? Имена предназначены для удобства чтения. 3240 | 3241 | ```javascript 3242 | // плохо 3243 | import SmsContainer from './containers/SmsContainer'; 3244 | 3245 | // плохо 3246 | const HttpRequests = [ 3247 | // ... 3248 | ]; 3249 | 3250 | // хорошо 3251 | import SMSContainer from './containers/SMSContainer'; 3252 | 3253 | // хорошо 3254 | const HTTPRequests = [ 3255 | // ... 3256 | ]; 3257 | 3258 | // также хорошо 3259 | const httpRequests = [ 3260 | // ... 3261 | ]; 3262 | 3263 | // отлично 3264 | import TextMessageContainer from './containers/TextMessageContainer'; 3265 | 3266 | // отлично 3267 | const requests = [ 3268 | // ... 3269 | ]; 3270 | ``` 3271 | 3272 | **[⬆ к оглавлению](#Оглавление)** 3273 | 3274 | ## Аксессоры 3275 | 3276 | 3277 | - [24.1](#accessors--not-required) Функции-аксессоры для свойств объекта больше не нужны. 3278 | 3279 | 3280 | - [24.2](#accessors--no-getters-setters) Не используйте геттеры/сеттеры, т.к. они вызывают неожиданные побочные эффекты, а также их тяжело тестировать, поддерживать и понимать. Вместо этого создавайте методы `getVal()` и `setVal('hello')`. 3281 | 3282 | ```javascript 3283 | // плохо 3284 | class Dragon { 3285 | get age() { 3286 | // ... 3287 | } 3288 | 3289 | set age(value) { 3290 | // ... 3291 | } 3292 | } 3293 | 3294 | // хорошо 3295 | class Dragon { 3296 | getAge() { 3297 | // ... 3298 | } 3299 | 3300 | setAge(value) { 3301 | // ... 3302 | } 3303 | } 3304 | ``` 3305 | 3306 | 3307 | - [24.3](#accessors--boolean-prefix) Если свойство/метод возвращает логический тип, то используйте названия `isVal()` или `hasVal()`. 3308 | 3309 | ```javascript 3310 | // плохо 3311 | if (!dragon.age()) { 3312 | return false; 3313 | } 3314 | 3315 | // хорошо 3316 | if (!dragon.hasAge()) { 3317 | return false; 3318 | } 3319 | ``` 3320 | 3321 | 3322 | - [24.4](#accessors--consistent) Можно создавать функции `get()` и `set()`, но нужно быть последовательным. 3323 | 3324 | ```javascript 3325 | class Jedi { 3326 | constructor(options = {}) { 3327 | const lightsaber = options.lightsaber || 'blue'; 3328 | this.set('lightsaber', lightsaber); 3329 | } 3330 | 3331 | set(key, val) { 3332 | this[key] = val; 3333 | } 3334 | 3335 | get(key) { 3336 | return this[key]; 3337 | } 3338 | } 3339 | ``` 3340 | 3341 | **[⬆ к оглавлению](#Оглавление)** 3342 | 3343 | ## События 3344 | 3345 | 3346 | - [25.1](#events--hash) Когда привязываете данные к событию (например, события `DOM` или какие-то собственные события, как `Backbone` события), передавайте литерал объекта (также известный как «хэш») вместо простого значения. Это позволяет другим разработчикам добавлять больше данных без поиска и изменения каждого обработчика события. К примеру, вместо: 3347 | 3348 | ```javascript 3349 | // плохо 3350 | $(this).trigger('listingUpdated', listing.id); 3351 | 3352 | // ... 3353 | 3354 | $(this).on('listingUpdated', (e, listingID) => { 3355 | // делает что-то с listingID 3356 | }); 3357 | ``` 3358 | 3359 | предпочитайте: 3360 | 3361 | ```javascript 3362 | // хорошо 3363 | $(this).trigger('listingUpdated', { listingID: listing.id }); 3364 | 3365 | // ... 3366 | 3367 | $(this).on('listingUpdated', (e, data) => { 3368 | // делает что-то с data.listingID 3369 | }); 3370 | ``` 3371 | 3372 | **[⬆ к оглавлению](#Оглавление)** 3373 | 3374 | ## jQuery 3375 | 3376 | 3377 | - [26.1](#jquery--dollar-prefix) Начинайте названия переменных, хранящих объект jQuery, со знака `$`. 3378 | 3379 | ```javascript 3380 | // плохо 3381 | const sidebar = $('.sidebar'); 3382 | 3383 | // хорошо 3384 | const $sidebar = $('.sidebar'); 3385 | 3386 | // хорошо 3387 | const $sidebarBtn = $('.sidebar-btn'); 3388 | ``` 3389 | 3390 | 3391 | - [26.2](#jquery--cache) Кэшируйте jQuery-поиски. 3392 | 3393 | ```javascript 3394 | // плохо 3395 | function setSidebar() { 3396 | $('.sidebar').hide(); 3397 | 3398 | // ... 3399 | 3400 | $('.sidebar').css({ 3401 | 'background-color': 'pink', 3402 | }); 3403 | } 3404 | 3405 | // хорошо 3406 | function setSidebar() { 3407 | const $sidebar = $('.sidebar'); 3408 | $sidebar.hide(); 3409 | 3410 | // ... 3411 | 3412 | $sidebar.css({ 3413 | 'background-color': 'pink', 3414 | }); 3415 | } 3416 | ``` 3417 | 3418 | 3419 | - [26.3](#jquery--queries) Для поиска в DOM используйте каскады `$('.sidebar ul')` или селектор родитель > ребёнок `$('.sidebar > ul')`. [jsPerf](http://jsperf.com/jquery-find-vs-context-sel/16) 3420 | 3421 | 3422 | - [26.4](#jquery--find) Используйте функцию `find` для поиска в сохранённых jQuery-объектах. 3423 | 3424 | ```javascript 3425 | // плохо 3426 | $('ul', '.sidebar').hide(); 3427 | 3428 | // плохо 3429 | $('.sidebar').find('ul').hide(); 3430 | 3431 | // хорошо 3432 | $('.sidebar ul').hide(); 3433 | 3434 | // хорошо 3435 | $('.sidebar > ul').hide(); 3436 | 3437 | // хорошо 3438 | $sidebar.find('ul').hide(); 3439 | ``` 3440 | 3441 | **[⬆ к оглавлению](#Оглавление)** 3442 | 3443 | ## Поддержка ECMAScript 5 3444 | 3445 | 3446 | - [27.1](#es5-compat--kangax) Можно посмотреть в [таблице поддержки](https://kangax.github.io/es5-compat-table/) ES5 от пользователя [Kangax](https://twitter.com/kangax/) . 3447 | 3448 | **[⬆ к оглавлению](#Оглавление)** 3449 | 3450 | ## Возможности ECMAScript 6+ (ES 2015+) 3451 | 3452 | 3453 | - [28.1](#es6-styles) Здесь собраны ссылки на различные возможности ES6. 3454 | 3455 | 1. [Стрелочные функции](#arrow-functions) 3456 | 1. [Классы и конструкторы](#classes--constructors) 3457 | 1. [Сокращённая запись методов объекта](#es6-object-shorthand) 3458 | 1. [Сокращённая запись свойств объекта](#es6-object-concise) 3459 | 1. [Вычисляемые имена свойств объекта](#es6-computed-properties) 3460 | 1. [Шаблонные строки](#es6-template-literals) 3461 | 1. [Деструктуризация](#destructuring) 3462 | 1. [Параметры по умолчанию](#es6-default-parameters) 3463 | 1. [Оставшиеся параметры](#es6-rest) 3464 | 1. [Оператор расширения](#es6-array-spreads) 3465 | 1. [Let и Const](#references) 3466 | 1. [Итераторы и генераторы](#iterators-and-generators) 3467 | 1. [Модули](#modules) 3468 | 3469 | 3470 | - [28.2](#tc39-proposals) Не используйте [предложения TC39](https://github.com/tc39/proposals), которые не перешли на 3-ю стадию. 3471 | 3472 | > Почему? [Они ещё не закончены](https://tc39.github.io/process-document/) и могут быть изменены или полностью изъяты. Мы хотим использовать JavaScript, а предложения ещё не стали частью JavaScript. 3473 | 3474 | **[⬆ к оглавлению](#Оглавление)** 3475 | 3476 | ## Стандартная библиотека 3477 | 3478 | [Стандартная библиотека](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects) 3479 | содержит утилиты, функциональность которых сломана, но они остались для поддержки старого кода. 3480 | 3481 | 3482 | - [29.1](#standard-library--isnan) Используйте `Number.isNaN` вместо глобального `isNaN`. 3483 | eslint: [`no-restricted-globals`](https://eslint.org/docs/rules/no-restricted-globals) 3484 | 3485 | > Почему? Глобальная функция `isNaN` приводит не-числа к числам, возвращая `true` для всего, что приводится к `NaN`. 3486 | > Если такое поведение необходимо, сделайте его явным. 3487 | 3488 | ```javascript 3489 | // плохо 3490 | isNaN('1.2'); // false 3491 | isNaN('1.2.3'); // true 3492 | 3493 | // хорошо 3494 | Number.isNaN('1.2.3'); // false 3495 | Number.isNaN(Number('1.2.3')); // true 3496 | ``` 3497 | 3498 | 3499 | - [29.2](#standard-library--isfinite) Используйте `Number.isFinite` вместо глобального `isFinite`. 3500 | eslint: [`no-restricted-globals`](https://eslint.org/docs/rules/no-restricted-globals) 3501 | 3502 | > Почему? Глобальная функция `isFinite` приводит не-числа к числам, возвращая `true` для всего, что приводится к конечному числу. 3503 | > Если такое поведение необходимо, сделайте его явным. 3504 | 3505 | ```javascript 3506 | // плохо 3507 | isFinite('2e3'); // true 3508 | 3509 | // хорошо 3510 | Number.isFinite('2e3'); // false 3511 | Number.isFinite(parseInt('2e3', 10)); // true 3512 | ``` 3513 | 3514 | **[⬆ к оглавлению](#Оглавление)** 3515 | 3516 | ## Тестирование 3517 | 3518 | 3519 | - [30.1](#testing--yup) **Ага.** 3520 | 3521 | ```javascript 3522 | function foo() { 3523 | return true; 3524 | } 3525 | ``` 3526 | 3527 | 3528 | - [30.2](#testing--for-real) **Нет, но серьёзно**: 3529 | - Какой бы фреймворк вы не использовали, вы должны писать тесты! 3530 | - Стремитесь к тому, чтобы написать много маленьких чистых функций, и к тому, чтобы свести к минимуму места, где происходят мутации. 3531 | - Будьте осторожны со стабами (stubs) и моками (mocks) — они могут сделать ваше тестирование хрупким. 3532 | - Мы в первую очередь советуем вам использовать [`mocha`](https://www.npmjs.com/package/mocha) и [`jest`](https://www.npmjs.com/package/jest) от Airbnb. [`tape`](https://www.npmjs.com/package/tape) также иногда используется для небольших, отдельных модулей. 3533 | - 100% покрытие тестами — это хорошая цель, к которой надо стремиться, даже если это не всегда практично. 3534 | - Всякий раз, когда вы исправляете ошибку, _пишите регрессионный тест_. Исправленная ошибка без регрессионного тестирования почти наверняка всплывёт в будущем. 3535 | 3536 | **[⬆ к оглавлению](#Оглавление)** 3537 | 3538 | ## Производительность 3539 | 3540 | - [On Layout & Web Performance](https://www.kellegous.com/j/2013/01/26/layout-performance/) 3541 | - [String vs Array Concat](https://jsperf.com/string-vs-array-concat/2) 3542 | - [Try/Catch Cost In a Loop](https://jsperf.com/try-catch-in-loop-cost) 3543 | - [Bang Function](https://jsperf.com/bang-function) 3544 | - [jQuery Find vs Context, Selector](https://jsperf.com/jquery-find-vs-context-sel/13) 3545 | - [innerHTML vs textContent for script text](https://jsperf.com/innerhtml-vs-textcontent-for-script-text) 3546 | - [Long String Concatenation](https://jsperf.com/ya-string-concat) 3547 | - [Are Javascript functions like `map()`, `reduce()`, and `filter()` optimized for traversing arrays?](https://www.quora.com/JavaScript-programming-language-Are-Javascript-functions-like-map-reduce-and-filter-already-optimized-for-traversing-array/answer/Quildreen-Motta) 3548 | - Загрузка... 3549 | 3550 | **[⬆ к оглавлению](#Оглавление)** 3551 | 3552 | ## Ресурсы 3553 | 3554 | **Изучение ES6+** 3555 | 3556 | - [Последняя спецификация ECMA](https://tc39.github.io/ecma262/) 3557 | - [ExploringJS](http://exploringjs.com/) 3558 | - [ES6 Compatibility Table](https://kangax.github.io/compat-table/es6/) 3559 | - [Comprehensive Overview of ES6 Features](http://es6-features.org/) 3560 | 3561 | **Почитайте это** 3562 | 3563 | - [Standard ECMA-262](http://www.ecma-international.org/ecma-262/6.0/index.html) 3564 | 3565 | **Инструменты** 3566 | 3567 | - Линтеры 3568 | - [ESlint](https://eslint.org/) - [Airbnb Style .eslintrc](https://github.com/airbnb/javascript/blob/master/linters/.eslintrc) 3569 | - [JSHint](http://jshint.com/) - [Airbnb Style .jshintrc](https://github.com/airbnb/javascript/blob/master/linters/.jshintrc) 3570 | - Neutrino Preset - [@neutrinojs/airbnb](https://neutrinojs.org/packages/airbnb/) 3571 | 3572 | **Другие руководства** 3573 | 3574 | - [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml) 3575 | - [jQuery Core Style Guidelines](https://contribute.jquery.org/style-guide/js/) 3576 | - [Principles of Writing Consistent, Idiomatic JavaScript](https://github.com/rwaldron/idiomatic.js) 3577 | - [StandardJS](https://standardjs.com) 3578 | 3579 | **Другие стили** 3580 | 3581 | - [Naming this in nested functions](https://gist.github.com/cjohansen/4135065) - Christian Johansen 3582 | - [Conditional Callbacks](https://github.com/airbnb/javascript/issues/52) - Ross Allen 3583 | - [Popular JavaScript Coding Conventions on GitHub](http://sideeffect.kr/popularconvention/#javascript) - JeongHoon Byun 3584 | - [Multiple var statements in JavaScript, not superfluous](http://benalman.com/news/2012/05/multiple-var-statements-javascript/) - Ben Alman 3585 | 3586 | **Дальнейшее чтение** 3587 | 3588 | - [Understanding JavaScript Closures](https://javascriptweblog.wordpress.com/2010/10/25/understanding-javascript-closures/) - Angus Croll 3589 | - [Basic JavaScript for the impatient programmer](http://www.2ality.com/2013/06/basic-javascript.html) - Dr. Axel Rauschmayer 3590 | - [You Might Not Need jQuery](http://youmightnotneedjquery.com/) - Zack Bloom & Adam Schwartz 3591 | - [ES6 Features](https://github.com/lukehoban/es6features) - Luke Hoban 3592 | - [Frontend Guidelines](https://github.com/bendc/frontend-guidelines) - Benjamin De Cock 3593 | 3594 | **Книги** 3595 | 3596 | - [JavaScript: The Good Parts](https://www.amazon.com/JavaScript-Good-Parts-Douglas-Crockford/dp/0596517742) - Douglas Crockford 3597 | - [JavaScript Patterns](https://www.amazon.com/JavaScript-Patterns-Stoyan-Stefanov/dp/0596806752) - Stoyan Stefanov 3598 | - [Pro JavaScript Design Patterns](https://www.amazon.com/JavaScript-Design-Patterns-Recipes-Problem-Solution/dp/159059908X) - Ross Harmes and Dustin Diaz 3599 | - [High Performance Web Sites: Essential Knowledge for Front-End Engineers](https://www.amazon.com/High-Performance-Web-Sites-Essential/dp/0596529309) - Steve Souders 3600 | - [Maintainable JavaScript](https://www.amazon.com/Maintainable-JavaScript-Nicholas-C-Zakas/dp/1449327680) - Nicholas C. Zakas 3601 | - [JavaScript Web Applications](https://www.amazon.com/JavaScript-Web-Applications-Alex-MacCaw/dp/144930351X) - Alex MacCaw 3602 | - [Pro JavaScript Techniques](https://www.amazon.com/Pro-JavaScript-Techniques-John-Resig/dp/1590597273) - John Resig 3603 | - [Smashing Node.js: JavaScript Everywhere](https://www.amazon.com/Smashing-Node-js-JavaScript-Everywhere-Magazine/dp/1119962595) - Guillermo Rauch 3604 | - [Secrets of the JavaScript Ninja](https://www.amazon.com/Secrets-JavaScript-Ninja-John-Resig/dp/193398869X) - John Resig and Bear Bibeault 3605 | - [Human JavaScript](http://humanjavascript.com/) - Henrik Joreteg 3606 | - [Superhero.js](http://superherojs.com/) - Kim Joar Bekkelund, Mads Mobæk, & Olav Bjorkoy 3607 | - [JSBooks](http://jsbooks.revolunet.com/) - Julien Bouquillon 3608 | - [Third Party JavaScript](https://www.manning.com/books/third-party-javascript) - Ben Vinegar and Anton Kovalyov 3609 | - [Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript](http://amzn.com/0321812182) - David Herman 3610 | - [Eloquent JavaScript](http://eloquentjavascript.net/) - Marijn Haverbeke 3611 | - [You Don’t Know JS: ES6 & Beyond](http://shop.oreilly.com/product/0636920033769.do) - Kyle Simpson 3612 | 3613 | **Блоги** 3614 | 3615 | - [JavaScript Weekly](http://javascriptweekly.com/) 3616 | - [JavaScript, JavaScript...](https://javascriptweblog.wordpress.com/) 3617 | - [Bocoup Weblog](https://bocoup.com/weblog) 3618 | - [Adequately Good](http://www.adequatelygood.com/) 3619 | - [NCZOnline](https://www.nczonline.net/) 3620 | - [Perfection Kills](http://perfectionkills.com/) 3621 | - [Ben Alman](http://benalman.com/) 3622 | - [Dmitry Baranovskiy](http://dmitry.baranovskiy.com/) 3623 | - [nettuts](http://code.tutsplus.com/?s=javascript) 3624 | 3625 | **Подкасты** 3626 | 3627 | - [JavaScript Air](https://javascriptair.com/) 3628 | - [JavaScript Jabber](https://devchat.tv/js-jabber/) 3629 | 3630 | **[⬆ к оглавлению](#Оглавление)** 3631 | 3632 | 3633 | 3634 | **[⬆ к оглавлению](#Оглавление)** 3635 | 3636 | # }; 3637 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Roistat 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 | -------------------------------------------------------------------------------- /PHP.md: -------------------------------------------------------------------------------- 1 | # Содержание 2 | 1. [Введение](#Введение) 3 | 0. [Ценности](#Ценности) 4 | 0. [Принципы](#Принципы) 5 | 0. [Общие правила](#Общие-правила) 6 | 0. [Правила разделения бизнес-логики](#Правила-разделения-бизнес-логики) 7 | 0. [Работа с файлами](#Работа-с-файлами) 8 | 0. [Работа с переменными](#Работа-с-переменными) 9 | 0. [Логические переменные и методы](#Логические-переменные-и-методы) 10 | 0. [Работа с массивами](#Работа-с-массивами) 11 | 0. [Работа со строками](#Работа-со-строками) 12 | 0. [Работа с датами](#Работа-с-датами) 13 | 0. [Работа с пространствами имён](#Работа-с-пространствами-имён) 14 | 0. [Работа с методами](#Работа-с-методами) 15 | 0. [Возврат результата работы метода](#Возврат-результата-работы-метода) 16 | 0. [Работа с классами](#Работа-с-классами) 17 | 0. [Работа с объектами](#Работа-с-объектами) 18 | 0. [Комментирование кода](#Комментирование-кода) 19 | 0. [Работа с исключениями](#Работа-с-исключениями) 20 | 0. [Работа с внешним хранилищем данных](#Работа-с-внешним-хранилищем-данных) 21 | 0. [Особенности Pull Request (PR)](#Особенности-pull-request-pr) 22 | 0. [Работа с шаблонами](#Работа-с-шаблонами) 23 | 0. [Работа с литералами](#Работа-с-литералами) 24 | 0. [Работа с условиями](#Работа-с-условиями) 25 | 0. [Работа с тернарными операторами](#Работа-с-тернарными-операторами) 26 | 0. [Про тесты](#Про-тесты) 27 | 0. [Использование chain-объектов](#Использование-chain-объектов) 28 | 0. [Работа со скриптами](#Работа-со-скриптами) 29 | 0. [Авторы](#Авторы) 30 | 31 | ## **Введение** 32 | 33 | Этот документ содержит правила написания кода (Code Conventions) в [компании Roistat](http://roistat.com/ru/vacancy?roistat=github_codeconv). У нас накопился большой опыт разработки сложных проектов, с которым мы решили поделиться с остальными. Вы можете взять этот документ как есть или использовать его как основу для вашего собственного Code Conv. 34 | 35 | Здесь всегда находится актуальная версия нашего Code Conv, так как мы ссылаемся на него при проведении наших Code Review. 36 | 37 | О нашем опыте использования Code Conv вы можете прочитать в [статье на Хабре](https://habrahabr.ru/company/roistat/blog/352762/). 38 | 39 | Code Conv — это правила, которые нужно соблюдать при написании любого кода. Мы различаем Code Style и Code Conv. Для нас Code Style — это внешний вид кода. То есть расстановка отступов, запятых, скобок и прочего. А Code Conv — это смысловое содержание кода. Правильные алгоритмы действий, правильные по смыслу названия переменных и методов, правильная композиция кода. Соблюдение Code Style легко проверяется автоматикой. А вот проверить соблюдение Code Conv в большинстве случаев может только человек. 40 | 41 | Обратите внимание: Code Style в примерах может отличаться от Code Style вашего проекта. Придерживаться надо тому Code Style, который принят у вас. Code Conv не об этом. 42 | 43 | ## **Ценности** 44 | Главная цель Code Conv — сохранение низкой стоимости разработки и поддержки кода на длинной дистанции. 45 | 46 | Основные ценности, помогающие достичь этой цели: 47 | 48 | **Читаемость** 49 | 50 | Код должен легко читаться, а не легко записываться. Это значит, что такие вещи как синтаксический сахар (если он направлен на ускорение записи, а не дальнейшего чтения кода) вредны. 51 | Обратите внимание, что быстродействие кода не является ценностью, поэтому не самый оптимальный цикл, но удобный для понимания, будет лучше, чем быстрый, но сложный. Не нужно экономить переменные, буквы для их названий, оперативную память и так далее. 52 | 53 | **Вандалоустойчивость** 54 | 55 | Код надо писать так, чтобы у разработчика, который с ним будет работать, было как можно меньше возможности внести ошибку. Например, покрывайте тестами не только краевые условия, но и кейсы, которые могут появиться в результате доработок кода и рефакторинга. 56 | 57 | **Поддержание наименьшей энтропии** 58 | 59 | Энтропия — это количество информации, из которой состоит проект (информационная емкость проекта). Код проекта должен выполнять продуктовые требования с сохранением наименьшей возможной энтропии. 60 | 61 | ## **Принципы** 62 | 63 | Принципы — это способы соблюдения описанных выше ценностей. Они чуть более детальны, содержат основные методологии разработки и подходы, которыми мы руководствуемся. 64 | 65 | Код должен быть: 66 | 67 | - Понятным, явным. Явное лучше, чем неявное. Например, не должны использоваться магические методы. Также нельзя использовать `exit` и любые другие операторы, которые могут завершить или изменить работу процесса. 68 | - Удобным для использования сейчас 69 | - Удобным для использования в будущем 70 | - Должен стремиться к соблюдению принципов [KISS](https://ru.wikipedia.org/wiki/KISS_(%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF)), [SOLID](https://ru.wikipedia.org/wiki/SOLID_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)), [DRY](https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself), [GRASP](https://ru.wikipedia.org/wiki/GRASP) 71 | - Код должен обладать низкой связанностью и высокой связностью (подробно это описано в GRASP). Любая часть системы должна иметь изолированную логику и при надобности внешний интерфейс, который позволяет с этой логикой работать. Любая внутренняя часть должна иметь возможность быть измененной без какого-либо ущерба внешним системам 72 | - Код должен быть таким, чтобы его можно было автоматически отрефакторить в IDE (например, Find usages и Rename в PHPStorm). То есть должен быть слинкован типизацией и PHPDoc'ами 73 | - В БД не должны храниться части кода (даже названия классов, переменных и констант), так как это делает невозможным автоматический рефакторинг 74 | - Последовательным. Код должен читаться сверху вниз. Читающий не должен держать что-то в уме, возвращаться назад и интерпретировать код иначе. Например, надо избегать обратных циклов `do {} while ();` 75 | - Должен иметь минимальную [цикломатическую сложность](https://ru.wikipedia.org/wiki/%D0%A6%D0%B8%D0%BA%D0%BB%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C) 76 | 77 | ## **Общие правила** 78 | 79 | ### 📖 Запрещен неиспользуемый код 80 | Если код можно убрать, и работа системы от этого не изменится, его быть не должно. 81 | 82 | Плохо: 83 | ```php 84 | if (false) { 85 | legacyMethodCall(); 86 | } 87 | // ... 88 | $legacyCondition = true; 89 | if ($legacyCondition) { 90 | finalizeData($data); 91 | } 92 | ``` 93 | 94 | Хорошо: 95 | ```php 96 | // ... 97 | finalizeData($data); 98 | ``` 99 | 100 | ### 📖 Не должны использоваться специфичные функции какой-то версии PHP, если их можно избежать 101 | Это упростит миграцию кода на новую версию языка. Часто в новой версии языка удаляются какие-либо функции или изменяется их работа. Чем меньше идет завязки на язык и его версию, тем лучше. 102 | 103 | Специфичные функции всегда лучше использовать через функции-обёртки внутри проекта. Тогда в случае миграции придется исправлять одно место, а не тысячу. 104 | 105 | Как понять, можно ли использовать встроенную в PHP функцию или нет? 106 | 107 | - Если эта функция уже используется повсеместно в проекте, значит, её можете использовать и вы. Например, это может быть `explode`/`implode`. Если эти функции будут изменены в новой версии PHP, то в любом случае придется переделать много кода и делать это будет автоматика. 108 | 109 | - Если эта функция не используется или используется только через обёртку в специализированном сервисе, то и вы использовать её можете только через обёртку (добавляется при необходимости). 110 | 111 | Плохо: 112 | ```php 113 | if (ctype_digit($number)) { 114 | // ... 115 | } 116 | $distance = levenshtein($text1, $text2); 117 | $urlParts = parse_url($url); 118 | ``` 119 | 120 | Хорошо: 121 | ```php 122 | if ($number >= 0) { 123 | // ... 124 | } 125 | $distance = $textService->levenshtein($text1, $text2); 126 | $urlParts = $urlService->parseUrl($url); 127 | ``` 128 | 129 | ### 📖 Вместо отсутствующего скалярного значения используется null 130 | 0 и пустую строку нельзя использовать в качестве показателя отсутствия значения. 131 | ```php 132 | /** 133 | * @param string $title 134 | * @param string $message 135 | * @param string $date 136 | */ 137 | function sendEmail($title, $message = null, $date = null) { 138 | // ... 139 | } 140 | 141 | // сообщение не было передано 142 | $object->sendEmail('Title', null, '2017-01-01'); 143 | 144 | // было передано пустое сообщение 145 | $object->sendEmail('Title', '', '2017-01-01'); 146 | ``` 147 | 148 | Однако, это правило не относится к массивам. 149 | 150 | Плохо: 151 | ```php 152 | function deleteUsersByIds(array $ids = [], $someOption = false) { 153 | // ... 154 | } 155 | 156 | deleteUsersByIds(null, true); 157 | ``` 158 | 159 | Хорошо: 160 | ```php 161 | deleteUsersByIds([], true); 162 | ``` 163 | 164 | Итого: использование пустой строки почти всегда является ошибкой. 165 | 166 | **[⬆ наверх](#Содержание)** 167 | 168 | ## **Правила разделения бизнес-логики** 169 | 170 | ### 📖 Сервисы 171 | Сервис – это класс без состояния, содержащий бизнес-логику. Данные для обработки сервис получает либо в виде параметров публичных методов, либо других сервисов. 172 | 173 | Сервис не может использовать в качестве источника данных глобальные переменные или окружение: 174 | 175 | Плохо: 176 | ```php 177 | class User { 178 | 179 | public function loadUsers() { 180 | $path = getenv('DATA_PATH'); // так нельзя! 181 | // ... 182 | } 183 | } 184 | ``` 185 | 186 | Хорошо: 187 | ```php 188 | class Env { 189 | 190 | /** 191 | * @return string 192 | */ 193 | public function getDataPath(): string { 194 | return getenv('DATA_PATH'); 195 | } 196 | } 197 | 198 | class User { 199 | 200 | /** 201 | * @var Env 202 | */ 203 | private $env; 204 | 205 | /** 206 | * @param Env $env 207 | */ 208 | public function __construct(Env $env) { 209 | $this->env = $env; 210 | } 211 | 212 | public function loadUsers() { 213 | $path = $this->env->getDataPath(); 214 | // ... 215 | } 216 | } 217 | ``` 218 | Однако, это правило не работает, если получение данных из внешних источников — единственная бизнес-логика сервиса. 219 | 220 | Для работы с хранилищем мы используем репозиторий. Это частный случай сервиса, но он получает данные из БД через адаптеры. 221 | 222 | ### 📖 Контроллеры 223 | Контроллер принимает и обрабатывает запросы. Он получает параметры на вход, запрашивает данные из сервисов и возвращает представление. 224 | 225 | ### 📖 Модели 226 | Модель — простой объект со свойствами, не содержащий никакой другой бизнес-логики, кроме геттеров и сеттеров. Геттер — метод, позволяющий получить какую-то информацию из внутреннего состояния объекта. Это не обязательно поле, как оно есть. Он может брать значения нескольких полей и делать простые манипуляции с ними (не запрашивая внешней продуктовой бизнес-логики). Сеттер — аналогично может изменять внутреннее состояние одного или нескольких полей без запросов «наружу». 227 | 228 | Условный упрощенный пример: 229 | 230 | ```php 231 | class Bill { 232 | public $id; 233 | public $sum; 234 | public $isPaid; 235 | public $paidDate 236 | // ... 237 | 238 | public function markAsPaid(\DateTime $paymentDate) { 239 | $this->isPaid = true; 240 | $this->paidDate = $paymentDate; 241 | } 242 | } 243 | ``` 244 | 245 | Желательно делать модели неизменяемыми, см. [Работа с объектами](#Работа-с-объектами). Хотите больше гибкости — можно использовать [chain-объекты](#Использование-chain-объектов). 246 | 247 | ### 📖 Представления 248 | Представлением в зависимости от требуемого ответа сервера может быть HTML-шаблон, API-объект или что-то иное. Обратите внимание, API-объект и модель данных это разные сущности, даже если у них совпадает название и все поля. Нельзя просто вернуть в JSON-ответе сервера модель из хранилища: 249 | 250 | Плохо: 251 | ```php 252 | public function actionUsers(): Response { 253 | $users = $repository->loadUsers(); 254 | return new Response(['data' => $users]); 255 | } 256 | ``` 257 | 258 | Свойства модели хранилища могут поменяться из-за новых технических требований, но объект API это продукт, вы должны изменять его явно: 259 | 260 | Хорошо: 261 | ```php 262 | // src/Entity/Api/User.php 263 | namespace Entity\Api; 264 | 265 | class User { 266 | public $id; 267 | public $name; 268 | } 269 | 270 | // src/Controller/Api/User.php 271 | public function actionUsers(): Response { 272 | $users = $repository->loadUsers(); 273 | $apiUsers = array_map(function ($user) { 274 | return $this->convertUserToApiObject($user); 275 | }, $users); 276 | return new Response(['data' => $apiUsers); 277 | } 278 | 279 | private function _convertUserToApiObject(Entity\Mapper\User $user): Entity\Api\User { 280 | // ... 281 | } 282 | ``` 283 | 284 | **[⬆ наверх](#Содержание)** 285 | 286 | ## **Работа с файлами** 287 | 288 | ### 📖 Названия файлов пишутся стиле UpperCamelCase, названия папок пишутся в соответствии со стилем названия папок в проекте слитно без разделителей 289 | 290 | ``` 291 | \doctrine\common\IsolatedClassLoader => /path/to/project/lib/vendor/doctrine/common/IsolatedClassLoader.php 292 | \symfony\core\Request => /path/to/project/lib/vendor/symfony/core/Request.php 293 | \zend\acl => /path/to/project/lib/vendor/zend/Acl.php 294 | \zend\commonmail\message => /path/to/project/lib/vendor/zend/commonmail/Message.php 295 | ``` 296 | 297 | ### 📖 Файлы классов должны быть расположены в директориях в соответствии со стандартом PSR-0 298 | 299 | **[⬆ наверх](#Содержание)** 300 | 301 | ## **Работа с переменными** 302 | 303 | ### 📖 Название переменных пишутся через camelCase 304 | 305 | ### 📖 Переменная не может начинаться с символа `_` 306 | 307 | Плохо: 308 | ```php 309 | private $_propertyName 310 | ``` 311 | 312 | Хоршо: 313 | ```php 314 | private $propertyName; 315 | ``` 316 | 317 | ### 📖 Название переменных должно соответствовать содержанию 318 | Нельзя писать короткие названия, например `$c`. Нельзя назвать переменную `$day` и хранить в ней массив статистических данных за этот день. 319 | 320 | ### 📖 Часто упоминаемые объекты именуются одинаково во всем проекте 321 | 322 | Плохо: 323 | ```php 324 | $customer = new User(); 325 | $client = new User(); 326 | $object = new User(); 327 | ``` 328 | 329 | Хорошо: 330 | ```php 331 | $user = new User(); 332 | ``` 333 | 334 | ### 📖 Признак объекта добавляется к названию 335 | Если это отфильтрованный по какому-то признаку проект, то признак добавляется к названию. Например, `$unpaidProject`. 336 | 337 | ### 📖 Переменные, отражающие свойства объекта, должны включать название объекта 338 | 339 | Плохо: 340 | ```php 341 | $project = new Project(); 342 | $name = $project->name; 343 | $project = $project->name; 344 | ``` 345 | 346 | Хорошо: 347 | ```php 348 | $project = new Project(); 349 | $projectName = $project->name; 350 | ``` 351 | 352 | ### 📖 Переменные по возможности должны называться на корректном английском 353 | 354 | Плохо: 355 | ```php 356 | $usersStored = []; 357 | ``` 358 | 359 | Хорошо: 360 | ```php 361 | $storedUsers = []; 362 | ``` 363 | 364 | Исключение: сгруппированные по некому признаку поля или константы. В этом случае можно использовать префикс. 365 | 366 | ```php 367 | class ProjectInfo { 368 | const STATUS_READY = 1; 369 | const STATUS_BLOCKED = 2; 370 | 371 | public $billingIsPaid; 372 | public $billingPaidDate; 373 | public $billingSum; 374 | } 375 | ``` 376 | 377 | ### 📖 К переменной нельзя обращаться по ссылке (через &) 378 | Амперсанды могут использоваться только как логические или битовые операторы. 379 | 380 | Плохо: 381 | ```php 382 | /** 383 | * @param string &$name 384 | */ 385 | function removePrefix(&$name) { 386 | // ... 387 | } 388 | ``` 389 | 390 | Хорошо: 391 | ```php 392 | /** 393 | * @param string $name 394 | * @return string 395 | */ 396 | function removePrefix($name) { 397 | // ... 398 | return $result; 399 | } 400 | ``` 401 | 402 | ### 📖 Переменные и свойства объекта должны являться существительными и называться так, чтобы они правильно читались при использовании, а не при инициализации. 403 | 404 | Плохо: 405 | ```php 406 | $object->expire_at 407 | $object->setExpireAt($date); 408 | $object->getExpireAt(); 409 | ``` 410 | 411 | Хорошо: 412 | ```php 413 | $object->expiration_date; 414 | $object->setExpirationDate($date); 415 | $object->getExpirationDate(); 416 | ``` 417 | 418 | ### 📖 В названии переменной не должно быть указания типа 419 | Нельзя писать `$projectsArray`, надо писать просто `$projects`. Это же касается и форматов (JSON, XML и т.п.), и любой другой не относящейся к предметной области информации. 420 | 421 | Плохо: 422 | ```php 423 | $projectsArray = $repository->loadProjects(); 424 | $projectsArrayIds = $utils->extractField('id', $projectsList); 425 | $titleString = 'Article header'; 426 | ``` 427 | 428 | Хорошо: 429 | ```php 430 | $projects = $repository->loadProjects(); 431 | $projectsIds = $utils->extractField('id', $projects); 432 | $title = 'Article header'; 433 | ``` 434 | 435 | Исключение для списков, в названии используем суфикс `List`. 436 | 437 | Плохо: 438 | ```php 439 | $projects = []; 440 | $usersList = []; 441 | ``` 442 | 443 | Хорошо: 444 | ```php 445 | $pojectList = []; 446 | $userList = []; 447 | ``` 448 | 449 | ### 📖 Нельзя изменять переменные, которые передаются в метод на вход 450 | Исключение — если эта переменная объект. 451 | 452 | Плохо: 453 | ```php 454 | function parseText($text) { 455 | $text = trim($text); 456 | // ... 457 | } 458 | ``` 459 | 460 | Хорошо: 461 | ```php 462 | function parseText($text) { 463 | $trimmedText = trim($text); 464 | // ... 465 | } 466 | ``` 467 | 468 | ### 📖 Каждая переменная должна быть объявлена на новой строке 469 | 470 | Плохо: 471 | ```php 472 | $foo = false; $bar = true; 473 | ``` 474 | 475 | Хорошо: 476 | ```php 477 | $foo = false; 478 | $bar = true; 479 | ``` 480 | 481 | ### 📖 Нельзя нескольким переменным присваивать одно и то же значение 482 | 483 | Плохо: 484 | ```php 485 | function loadUsers(array $ids) { 486 | $usersIds = $ids; 487 | // ... 488 | } 489 | ``` 490 | 491 | ### 📖 Оператор `clone` должен использоваться только в тех случаях, когда без него не обойтись 492 | Также можно использовать `clone`, если без него код серьёзно усложнится, а с ним станет понятным и очевидным. Простой пример — клонирование объектов DateTime. Или использование клонирования для сравнения двух версий объекта: старой и новой. 493 | 494 | Плохо: 495 | ```php 496 | function loadAnalyticsData(\DateTime $intervalStart) { 497 | $intervalEnd = new \DateTime($intervalStart->format('Y-m-d H:i:s')); 498 | $intervalEnd->modify('+1 day'); 499 | } 500 | 501 | function updateUser(User $user) { 502 | $oldUser = new User(); 503 | $oldUser->id = $user->id; 504 | $oldUser->name = $user->name; 505 | // ... 506 | logObjectDiff($user, $oldUser); 507 | } 508 | ``` 509 | 510 | Хорошо: 511 | ```php 512 | function loadAnalyticsData(\DateTime $intervalStart) { 513 | $intervalEnd = clone $intervalStart; 514 | $intervalEnd->modify('+1 day'); 515 | } 516 | 517 | function updateUser(User $user) { 518 | $oldUser = clone $user; 519 | // ... 520 | logObjectDiff($user, $oldUser); 521 | } 522 | ``` 523 | 524 | ### 📖 Запрещено использовать результат операции присваивания 525 | 526 | Плохо: 527 | ```php 528 | $foo = $bar = strlen($someVar); 529 | ``` 530 | 531 | Хорошо: 532 | ```php 533 | $bar = strlen($someVar); 534 | $foo = $bar; 535 | ``` 536 | 537 | 538 | Плохо: 539 | ```php 540 | $this->callSomeFunc($bar = strlen($foo)); 541 | ``` 542 | 543 | Хорошо: 544 | ```php 545 | $bar = strlen($foo); 546 | $this->callSomeFunc($bar); 547 | ``` 548 | 549 | 550 | Плохо: 551 | ```php 552 | if (strlen($foo = json_encode($bar)) > 100) { 553 | // ... 554 | } 555 | ``` 556 | 557 | Хорошо: 558 | ```php 559 | $foo = json_encode($bar); 560 | if (strlen($foo) > 100) { 561 | // ... 562 | } 563 | ``` 564 | 565 | ### 📖 Нельзя использовать константы через метод `constant` 566 | 567 | Плохо: 568 | ```php 569 | /** 570 | * @return string 571 | */ 572 | public function getProjectDir(): string { 573 | $prefix = 'ACME_'; 574 | $name = $prefix . 'PROJECT_DIR'; 575 | return constant($name); 576 | } 577 | 578 | /** 579 | * @return string 580 | */ 581 | public function getProjectDir(): string { 582 | return constant('ACME_PROJECT_DIR'); 583 | } 584 | ``` 585 | 586 | Хорошо: 587 | ```php 588 | /** 589 | * @return string 590 | */ 591 | public function getProjectDir(): string { 592 | return ACME_PROJECT_DIR; 593 | } 594 | ``` 595 | 596 | **[⬆ наверх](#Содержание)** 597 | 598 | ## **Логические переменные и методы** 599 | 600 | ### 📖 Названия boolean методов и переменных должны содержать глагол `is`, `has` или `can` 601 | 602 | Переменные правильно называть, описывая их содержимое, а методы — задавая вопрос. Если переменная содержит свойство объекта, следуем правилу [признак объекта добавляется к названию](#Признак-объекта-добавляется-к-названию). 603 | 604 | Плохо: 605 | ```php 606 | $isUserValid = $user->valid(); 607 | $isProjectAnalytics = $accessManager->getProjectAccess($project, 'analytics'); 608 | ``` 609 | 610 | Хорошо: 611 | ```php 612 | $userIsValid = $user->isValid(); 613 | $projectCanAccessAnalytics = $accessManager->canProjectAccess($project, 'analytics'); 614 | ``` 615 | 616 | Геттеры именуются аналогично переменным: 617 | 618 | ```php 619 | class User { 620 | private $billingIsPaid; 621 | private $isEnabled; 622 | 623 | public function isEnabled() { 624 | return $this->isEnabled; 625 | } 626 | 627 | public function billingIsPaid() { 628 | return $this->billingIsPaid; 629 | } 630 | } 631 | ``` 632 | 633 | Такое именование позволяет легче читать условия: 634 | 635 | ```php 636 | // if user is valid, then do something 637 | if ($userIsValid) { 638 | // do something 639 | } 640 | ``` 641 | 642 | ### 📖 Запрещены отрицательные логические названия 643 | 644 | Плохо: 645 | ```php 646 | if ($project->isInvalid()) { 647 | // ... 648 | } 649 | if ($project->isNotValid()) { 650 | // ... 651 | } 652 | if ($accessManager->isAccessDenied()) { 653 | // ... 654 | } 655 | ``` 656 | 657 | Хорошо: 658 | ```php 659 | if (!$project->isValid()) { 660 | // ... 661 | } 662 | if (!$accessManager->isAccessAllowed()) { 663 | // ... 664 | } 665 | if ($accessManager->canAccess()) { 666 | // ... 667 | } 668 | ``` 669 | 670 | ### 📖 Не используйте boolean переменные (флаги) как параметры функции 671 | Флаг в качестве параметра это признак того, что функция делает больше одной вещи, нарушая [принцип единственной ответственности (Single Responsibility Principle или SRP)](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF_%D0%B5%D0%B4%D0%B8%D0%BD%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8). Избавляйтесь от них, выделяя код внтури логических блоков в отдельные ветви выполнения. 672 | 673 | Плохо: 674 | ```php 675 | function someMethod() { 676 | // ... 677 | $projectNotificationIsEnabled = $notificationManager->isProjectNotificationEnabled($project); 678 | storeUser($user, $projectNotificationIsEnabled); 679 | } 680 | 681 | function storeUser(User $user, $isNotificationEnabled) { 682 | // ... 683 | if ($isNotificationEnabled) { 684 | notify('new user'); 685 | } 686 | } 687 | ``` 688 | 689 | Хорошо: 690 | ```php 691 | function someMethod() { 692 | // ... 693 | storeUser($user); 694 | if ($notificationManager->isProjectNotificationEnabled($project)) { 695 | notify('new user'); 696 | } 697 | } 698 | 699 | function storeUser(User $user) { 700 | // ... 701 | } 702 | ``` 703 | 704 | **[⬆ наверх](#Содержание)** 705 | 706 | ## **Работа с массивами** 707 | 708 | ### 📖 Для конкатенации массивов запрещено использовать оператор `+`. 709 | Обратите внимание, что `array_merge` все числовые ключи приводит к `int`, даже если они записаны строкой. 710 | 711 | Плохо: 712 | ```php 713 | return $initialData + $loadedData; 714 | ``` 715 | 716 | Хорошо: 717 | ```php 718 | namespace Service; 719 | 720 | class ArrayUtils { 721 | 722 | /** 723 | * @param array $array1 724 | * @param array $array2 725 | * @return array 726 | */ 727 | public function mergeArrays(array $array1, array $array2): array { 728 | return array_merge($array1, $array2); 729 | } 730 | } 731 | 732 | public function someMethod() { 733 | return $this->arrayUtils->mergeArrays($initialData, $loadedData); 734 | } 735 | ``` 736 | 737 | ### 📖 Для проверки наличия ключа в ассоциативном массиве используем `array_key_exists`, а не `isset` 738 | `isset` проверяет не ключ на его наличие, а значение этого ключа, если он есть. Это разные методы с разным поведением и назначением. Если вы хотите проверить значение ключа, то делайте это явно. Сначала явно проверьте наличие ключа через `array_key_exists` и обработайте ситуацию его отсутствия, затем приступайте к работе со значением. 739 | 740 | Плохо: 741 | ```php 742 | function getProjectKey(array $requestData) { 743 | return isset($requestData['project_key']) ? $requestData['project_key'] : null; 744 | } 745 | ``` 746 | 747 | Хорошо: 748 | ```php 749 | function getProjectKey(array $requestData) { 750 | if (!array_key_exists('project_key', $requestData)) { 751 | return null; 752 | } 753 | return $requestData['project_key']; 754 | } 755 | ``` 756 | 757 | ### 📖 Ассоциативный массив мы используем как hashmap 758 | То есть не применяем разные встроенные в PHP инструменты. Приведем несколько очевидных примеров (однако, правило ими не исчерпывается): 759 | 760 | #### 📖 Нельзя сортировать ассоциативные массивы 761 | 762 | Плохо: 763 | ```php 764 | $arr = [ 765 | 'project_key' => 'foo', 766 | 'key' => 'bar', 767 | 'user_id' => 300, 768 | ]; 769 | 770 | uasort($arr); 771 | ``` 772 | 773 | #### 📖 Нельзя смешивать в массиве строковые и числовые ключи 774 | 775 | Плохо: 776 | ```php 777 | $arr = [ 778 | 'project_key' => 'foo', 779 | 'key' => 'bar', 780 | 'user_id' => 300, 781 | 1 => 'value1', 782 | 2 => 'value2', 783 | ]; 784 | 785 | $arr[3] = 'value3'; 786 | ``` 787 | 788 | #### 📖 Для проверки наличия значения по индексу в обычных (не ассоциативных) массивах используем `count($array) > N` 789 | 790 | Плохо: 791 | ```php 792 | if (array_key_exists(1, $users)) { 793 | // ... 794 | } 795 | if (isset($users[1])) { 796 | // ... 797 | } 798 | ``` 799 | 800 | Хорошо: 801 | ```php 802 | if (count($users) > 1) { 803 | // ... 804 | } 805 | ``` 806 | 807 | **[⬆ наверх](#Содержание)** 808 | 809 | ## **Работа со строками** 810 | 811 | ### 📖 Строки обрамляются одинарными кавычками 812 | Двойные кавычки используются только, если: 813 | * Внутри строки должны быть одинарные кавычки 814 | * Внутри строки используется подстановка переменных 815 | * Внутри строки используются спец. символы `\n`, `\r`, `\t` и т.д. 816 | 817 | Плохо: 818 | ```php 819 | $string = "Some string"; 820 | $string = 'Some \'string\''; 821 | $string = "\t".'Some string'."\n"; 822 | ``` 823 | 824 | Хорошо: 825 | ```php 826 | $string = 'Some string'; 827 | $string = "Some 'string'"; 828 | $string = "\tSome string\n"; 829 | ``` 830 | 831 | ### 📖 Вместо лишней конкатенации используем подстановку переменных в двойных кавычках 832 | 833 | Плохо: 834 | ```php 835 | $string = 'Object with type "' . $object->type() . '" has been removed'; 836 | ``` 837 | 838 | Хорошо: 839 | ```php 840 | $string = "Object with type \"{$object->type()}\" has been removed"; 841 | ``` 842 | Использовать `sprintf()` очень хорошо: 843 | ```php 844 | $string = sprintf( 845 | 'Object with type "%s" has been removed', // шаблон 846 | $object->type() // подстановка 847 | ); 848 | ``` 849 | 850 | **[⬆ наверх](#Содержание)** 851 | 852 | ## **Работа с датами** 853 | 854 | ### 📖 Дата всегда должна быть представлена DateTime, интервал как DateInterval 855 | 856 | Плохо: 857 | ```php 858 | $date = $request->get('date'); 859 | $interval = 86400*30; 860 | loadSomeData($date, $interval); 861 | ``` 862 | 863 | Хорошо: 864 | ```php 865 | $date = $this->dateService->instance($request->get('date')); 866 | $interval = new \DateInterval('P30D'); 867 | loadSomeData($date, $interval); 868 | ``` 869 | 870 | ### 📖 Запрещено создавать объект даты при помощи `new \DateTime()` 871 | 872 | В проекте для этого должен быть фабричный метод в сервисе для работы с датами. 873 | 874 | Плохо: 875 | ```php 876 | $date = new \DateTime(); 877 | ``` 878 | 879 | Хорошо: 880 | ```php 881 | $date = $this->dateService->instance(); 882 | ``` 883 | 884 | ### 📖 Если дата должна быть представлена скалярным значением, необходимо использовать строку 885 | 886 | * строка с датой и временем должна быть везде в одинаковом формате 887 | * формат не должен включать временную зону, если для этого не особых требований 888 | * при прочих равных в дате без часового пояса всегда подразумевается UTC0 889 | * если строку по какой-то причине невозможно использовать, используем `int` 890 | 891 | Плохо: 892 | ```php 893 | class User { 894 | public $creation_time; 895 | } 896 | 897 | $user->creation_time = time(); 898 | ``` 899 | 900 | Хорошо: 901 | ```php 902 | class User { 903 | /** 904 | * @type string 905 | */ 906 | public $createdAt; 907 | } 908 | 909 | $user->createdAt = '2018-01-18 12:54:11'; 910 | ``` 911 | 912 | ### 📖 При именовании полей и методов с датами используется постфикс AT 913 | 914 | Все поля или методы которые возращают даты должны именоваться глаголом и иметь постфикс AT 915 | 916 | Плохо: 917 | 918 | ```php 919 | class Model 920 | { 921 | private $createDate; 922 | private $updateTime; 923 | } 924 | ``` 925 | 926 | Хорошо: 927 | 928 | ```php 929 | class Model 930 | { 931 | private $createdAt; 932 | private $updatedAt; 933 | } 934 | ``` 935 | 936 | В случае с **YII** и тем, что обращение к свойствам моделей идёт через магические методы, а они в свою очередь подхватывают названия из БД, для разделения используется нижнее подчёркивание: 937 | 938 | Хорошо: 939 | 940 | ```php 941 | /** 942 | * @property string $created_at 943 | * @property string $updated_at 944 | */ 945 | class Model 946 | { 947 | 948 | } 949 | ``` 950 | 951 | **[⬆ наверх](#Содержание)** 952 | 953 | ## **Работа с пространствами имён** 954 | 955 | ### 📖 Все пространства имён должны быть подключены через `use` в начале файла. В самом коде не должно быть обратного слеша перед названием пространства имён 956 | 957 | Плохо: 958 | ```php 959 | $object = new \Some\Object(); 960 | ``` 961 | 962 | Хорошо: 963 | ```php 964 | use Some; 965 | $object = new Some\Object(); 966 | ``` 967 | 968 | ### 📖 В свою очередь обычные классы без пространства имён не должны быть подключены через `use` 969 | 970 | Плохо: 971 | ```php 972 | use TimeZone; 973 | $date = new TimeZone('Europe\Moscow'); 974 | ``` 975 | 976 | Хорошо: 977 | ```php 978 | $date = new \TimeZone('Europe\Moscow'); 979 | ``` 980 | 981 | ### 📖 Следует избегать использования пседонима (alias) 982 | Они запутывают код и его понимание. Если у вас совпадают названия пространств имён, то, скорее всего, вы делаете что-то не так. Допустимо использовать псевдоним, если другое решение будет слишком сложным. 983 | 984 | Плохо: 985 | ```php 986 | use Component\User; 987 | use Entity\User as UserEntity; 988 | 989 | $user = new UserEntity(); 990 | ``` 991 | 992 | Хорошо: 993 | ```php 994 | use Component\User; 995 | use Entity; 996 | 997 | $user = new Entity\User(); 998 | ``` 999 | 1000 | **[⬆ наверх](#Содержание)** 1001 | 1002 | ## **Работа с методами** 1003 | 1004 | ### 📖 Должна быть использована максимально возможная типизация для вашей версии PHP. Все параметры и их типы должны быть описаны в PHPDoc. Возвращаемое значение тоже. 1005 | 1006 | Плохо: 1007 | ```php 1008 | /** 1009 | * @param $id 1010 | * @param $name 1011 | * @param $tags 1012 | * @return mixed 1013 | */ 1014 | function storeUser($id, $name, $tags = []) { 1015 | // ... 1016 | } 1017 | ``` 1018 | 1019 | Хорошо: 1020 | ```php 1021 | // для PHP 7.1 1022 | /** 1023 | * @param int $id 1024 | * @param string $name 1025 | * @param array $tags 1026 | * @return User|null 1027 | */ 1028 | function storeUser(int $id, string $name, array $tags = []): ?User { 1029 | // ... 1030 | } 1031 | 1032 | // для PHP 5.6 1033 | // без строгой типизации возвращаемых типов любой метод 1034 | // может вернуть null, так что можно его не указывать в PHPDoc 1035 | /** 1036 | * @param int $id 1037 | * @param string $name 1038 | * @param array $tags 1039 | * @return User 1040 | */ 1041 | function storeUser($id, $name, array $tags = []) { 1042 | // ... 1043 | } 1044 | ``` 1045 | 1046 | ### 📖 Все возможные типы должны быть определены в PHPDoc 1047 | Наибольшую пользу это приносит при работе с массивами: 1048 | 1049 | Плохо: 1050 | ```php 1051 | /** 1052 | * @param array $users 1053 | * @param mixed $project 1054 | * @param int $timestamp 1055 | * @return mixed 1056 | */ 1057 | public function someMethod($users, $project, $timestmap) { 1058 | foreach ($users as $user) { 1059 | // IDE не сможет определить тип $user 1060 | } 1061 | // ... 1062 | } 1063 | ``` 1064 | 1065 | Хорошо: 1066 | ```php 1067 | /** 1068 | * @param Users[] $users 1069 | * @param Project $project 1070 | * @param int $timestamp 1071 | * @return Foo 1072 | */ 1073 | public function someMethod(array $users, Project $project, int $timestmap): Foo { 1074 | foreach ($users as $user) { 1075 | // подсказки IDE и рефакторинг работают корректно 1076 | } 1077 | // ... 1078 | } 1079 | ``` 1080 | 1081 | ### 📖 В PHPDoc в возвращаемом значении не надо указывать `void` и `null` 1082 | 1083 | Плохо: 1084 | ```php 1085 | /** 1086 | * @param string $controllerName 1087 | * @return void 1088 | */ 1089 | public function runApplication(string $controllerName) { 1090 | // ... 1091 | } 1092 | 1093 | /** 1094 | * @return null 1095 | */ 1096 | public function run() { 1097 | // ... 1098 | } 1099 | ``` 1100 | 1101 | Хорошо: 1102 | ```php 1103 | /** 1104 | * @param string $controllerName 1105 | */ 1106 | public function runApplication(string $controllerName) { 1107 | // ... 1108 | } 1109 | 1110 | public function run() { 1111 | // ... 1112 | } 1113 | ``` 1114 | 1115 | ### 📖 Название метода должно начинаться с глагола и соответствовать правилам именования переменных. 1116 | 1117 | Плохо: 1118 | ```php 1119 | public function items() { 1120 | // ... 1121 | } 1122 | public function convertedDataObject(array $data) { 1123 | // ... 1124 | } 1125 | ``` 1126 | 1127 | Хорошо: 1128 | ```php 1129 | public function loadItems() { 1130 | // ... 1131 | } 1132 | public function convertDataToObject(array $data) { 1133 | // ... 1134 | } 1135 | ``` 1136 | 1137 | ### 📖 Метод не может начинаться с символа `_` 1138 | 1139 | Плохо: 1140 | ```php 1141 | private function _someMethod() 1142 | { 1143 | // ... 1144 | } 1145 | ``` 1146 | 1147 | Хоршо: 1148 | ```php 1149 | private function someMethod() 1150 | { 1151 | // ... 1152 | } 1153 | ``` 1154 | 1155 | ### 📖 Методы названия, которых начинаются c `check` и `validate`, должны выбрасывать исключения и не возвращать значения 1156 | 1157 | Плохо: 1158 | ```php 1159 | public function validateRequestData(array $requestData) { 1160 | if (!array_key_exists('key', $requestData)) { 1161 | return false; 1162 | } 1163 | // ... 1164 | return true; 1165 | } 1166 | ``` 1167 | 1168 | Хорошо: 1169 | ```php 1170 | public function validateRequestData(array $requestData) { 1171 | if (!array_key_exists('key', $requestData)) { 1172 | throw new ValidationError('Field "key" not found'); 1173 | } 1174 | // ... 1175 | } 1176 | ``` 1177 | 1178 | ### 📖 Все методы класса по умолчанию должны быть private 1179 | Если метод используется наследниками класса, то он объявляется `protected`. Если используется сторонними классами, тогда `public`. 1180 | 1181 | ### 📖 Использование рекурсий допускается только в исключительном случае 1182 | Если код без рекурсии будет очень сложен для написания и понимания и при этом рекурсия гарантированно не выйдет за ограничения стека вызовов. 1183 | 1184 | ### 📖 Запрещается кешировать данные в статических переменных метода 1185 | Для кеширование в памяти используем свойство объекта. 1186 | 1187 | Плохо: 1188 | ```php 1189 | public function loadData() { 1190 | static $cachedData; 1191 | if ($cachedData === null) { 1192 | $cachedData = []; 1193 | } 1194 | return $cachedData; 1195 | } 1196 | ``` 1197 | 1198 | Хорошо: 1199 | ```php 1200 | private $cachedData = []; 1201 | 1202 | public function loadData() { 1203 | if ($this->cachedData === null) { 1204 | $this->cachedData = []; 1205 | } 1206 | return $this->cachedData; 1207 | } 1208 | ``` 1209 | 1210 | ### 📖 Параметры в методах должны следовать в следующем порядке: обязательные → часто используемые → редко используемые 1211 | Нужно соблюдать читаемость при написании вызова. 1212 | 1213 | Плохо: 1214 | ```php 1215 | public function method($required, $practicallyUnused = 5, $often = [], $lessOften = null) 1216 | public function filter($value, $name, $operator) // ...$service->filter(15, "id", "=") 1217 | ``` 1218 | 1219 | Хорошо: 1220 | ```php 1221 | public function method($required, $often = [], $lessOften = null, $practicallyUnused = 5) 1222 | public function filter($name, $operator, $value) // ...$service->filter("id", "=", 15) 1223 | ``` 1224 | 1225 | ### 📖 Если переменные, объявленные на вход к методу, могут быть `null`, они должны явно обозначаться как таковые 1226 | 1227 | Хорошо: 1228 | ```php 1229 | /** 1230 | * @param string $projectName 1231 | */ 1232 | public function someMethod($projectName = null) { 1233 | // ... 1234 | } 1235 | ``` 1236 | 1237 | **[⬆ наверх](#Содержание)** 1238 | 1239 | ## **Возврат результата работы метода** 1240 | 1241 | ### 📖 Метод всегда должен возвращать только одну структуру данных (или `null`) или ничего не возвращать 1242 | Метод не может в разных ситуациях возвращать разные типы данных. 1243 | 1244 | Плохо: 1245 | ```php 1246 | function loadUser() { 1247 | if ($someCondition) { 1248 | return ['id' => 1]; 1249 | } 1250 | return new User(); 1251 | } 1252 | ``` 1253 | 1254 | Хорошо: 1255 | ```php 1256 | function loadUser() { 1257 | if ($someCondition) { 1258 | $user = new User(); 1259 | $user->id = 1; 1260 | return $user; 1261 | } 1262 | return new User(); 1263 | } 1264 | ``` 1265 | 1266 | ### 📖 Если метод возвращает один объект (или скалярный тип), то в случае, если объект не найден, возвращается `null` 1267 | Если же метод возвращает список объектов, то в случае, когда список пуст, возвращает пустой массив. Нельзя возвращать вместо пустого списка `null`. 1268 | 1269 | Плохо: 1270 | ```php 1271 | function loadUsers() { 1272 | if ($someCondition) { 1273 | return null; 1274 | } 1275 | return [new User()]; 1276 | } 1277 | ``` 1278 | 1279 | Хорошо: 1280 | ```php 1281 | function loadUsers() { 1282 | if ($someCondition) { 1283 | return []; 1284 | } 1285 | return [new User()]; 1286 | } 1287 | ``` 1288 | 1289 | Однако, бывают ситуации, когда надо явно указать, что данные отсутвуют, а не содержат пустой список. 1290 | 1291 | Пример: значения полей объекта задаются пользователем. Возможны две ситуации: 1292 | - пользователь не знает, каким категориям принадлежит объект — `null` 1293 | - пользователь знает, что объект не принадлежит ни одной категории — пустой массив (`[]`) 1294 | 1295 | Тогда для получения категорий объекта будет правильным такой код: 1296 | 1297 | ```php 1298 | /** 1299 | * для PHP 5.6 1300 | * @return array|null 1301 | */ 1302 | function getObjectCategories($object) { 1303 | if ($object->categories === null) { 1304 | return null; 1305 | } 1306 | return parseCategories($object->categories); 1307 | } 1308 | 1309 | /** 1310 | * для PHP 7.1 1311 | * @return array|null 1312 | */ 1313 | function getObjectCategories($object): ?array { 1314 | if ($object->categories === null) { 1315 | return null; 1316 | } 1317 | return parseCategories($object->categories); 1318 | } 1319 | ``` 1320 | 1321 | ### 📖 Возвращаемая переменная обычно `$result` 1322 | Если у вас метод `loadUsers`, то не надо внутри метода возвращаемую переменную называть `$users`. В любом месте в методе должно быть понятно, где вы оперируете результатом, а где локальными переменными. 1323 | 1324 | Плохо: 1325 | ```php 1326 | function loadUsers() { 1327 | $users = []; 1328 | // ... 1329 | foreach ($data as $item) { 1330 | $users[] = new User(); 1331 | } 1332 | return $users; 1333 | } 1334 | ``` 1335 | 1336 | Хорошо: 1337 | ```php 1338 | function loadUsers() { 1339 | $result = []; 1340 | // ... 1341 | foreach ($data as $item) { 1342 | $result[] = new User(); 1343 | } 1344 | return $result; 1345 | } 1346 | ``` 1347 | 1348 | ### 📖 Метод должен явно отличать нормальные ситуации от исключительных 1349 | Если никакой ошибки не произошло, но отсутствует результат, то это `null` (или пустой массив), однако если все же произошла исключительная ситуация, которая не заложена системой, то должно кидаться исключение. 1350 | 1351 | Плохо: 1352 | ```php 1353 | function loadUsers() { 1354 | if ($connectionError !== null) { 1355 | return []; // потеряли ошибку, никто не узнает о проблемах с подключением 1356 | } 1357 | // ... 1358 | if (count($data) === 0) { 1359 | return []; 1360 | } 1361 | // ... 1362 | return $result; 1363 | } 1364 | ``` 1365 | 1366 | Хорошо: 1367 | ```php 1368 | function loadUsers() { 1369 | if ($connectionError !== null) { 1370 | throw new Exception\ConnectionError(); 1371 | } 1372 | // ... 1373 | if (count($data) === 0) { 1374 | return []; 1375 | } 1376 | // ... 1377 | return $result; 1378 | } 1379 | ``` 1380 | 1381 | ### 📖 Метод должен придерживаться следующей структуры: Проверка параметров → Получение данных → Работа → Результат 1382 | Во время проверки параметров и получения необходимых данных метод должен возвращать соответствующее пустое значение или кидать исключение. После того как метод получил все необходимые данные и приступил к работе выход из метода крайне нежелателен. Возможны редкие исключения, облегчающие понимание и читаемость кода. 1383 | 1384 | Плохо: 1385 | ```php 1386 | /** 1387 | * @return int 1388 | */ 1389 | public function someMethod() { 1390 | $isValid = $this->someCheck(); 1391 | if ($isValid) { 1392 | $tmp = 0; 1393 | $someValue = $this->getSomeValue(); 1394 | if ($someValue > 0) { 1395 | $tmp = $someValue; 1396 | } 1397 | $anotherValue = $this->getAnotherValue(); 1398 | if ($anotherValue > 0) { 1399 | return $tmp + $anotherValue; 1400 | } else { 1401 | return $someValue; 1402 | } 1403 | } else { 1404 | throw new \Exception('Invalid condition'); 1405 | } 1406 | } 1407 | ``` 1408 | 1409 | Хорошо: 1410 | ```php 1411 | /** 1412 | * @return int 1413 | * @throws \Exception 1414 | */ 1415 | public function someMethod() { 1416 | $result = 0; 1417 | 1418 | $isValid = $this->someCheck(); 1419 | if (!$isValid) { 1420 | throw new \Exception('Invalid condition'); 1421 | } 1422 | 1423 | $someValue = $this->getSomeValue(); 1424 | if ($someValue > 0) { 1425 | $result += $someValue; 1426 | } 1427 | $anotherValue = $this->getAnotherValue(); 1428 | if ($anotherValue > 0) { 1429 | $result += $anotherValue; 1430 | } 1431 | return $result; 1432 | } 1433 | ``` 1434 | 1435 | **[⬆ наверх](#Содержание)** 1436 | 1437 | ## **Работа с классами** 1438 | 1439 | ### 📖 Трейты имеют постфикс Trait 1440 | 1441 | Хорошо: 1442 | ```php 1443 | trait AjaxResponseTrait { 1444 | // ... 1445 | } 1446 | ``` 1447 | 1448 | ### 📖 Интерфейсы имеют постфикс Interface 1449 | 1450 | Хорошо: 1451 | ```php 1452 | interface ApplicationInterface { 1453 | // ... 1454 | } 1455 | ``` 1456 | 1457 | ### 📖 Абстрактные классы имеют префикс Abstract 1458 | 1459 | Хорошо: 1460 | ```php 1461 | abstract class AbstractApplication { 1462 | // ... 1463 | } 1464 | ``` 1465 | 1466 | ### 📖 Все свойства класса по умолчанию должны быть private 1467 | Если свойство используется наследниками класса, то оно объявляется `protected`. Если используется сторонними классами, тогда `public`. 1468 | 1469 | Плохо: 1470 | ```php 1471 | abstract class Loader { 1472 | public $data = []; 1473 | 1474 | public function getData() { 1475 | return $this->data; 1476 | } 1477 | 1478 | public function init() { 1479 | $this->data = $this->load(); 1480 | } 1481 | 1482 | abstract public function load(); 1483 | } 1484 | ``` 1485 | 1486 | Хорошо: 1487 | ```php 1488 | abstract class Loader { 1489 | 1490 | /** 1491 | * @type array 1492 | */ 1493 | private $cachedData = []; 1494 | 1495 | /** 1496 | * @return array 1497 | */ 1498 | public function getData(): array { 1499 | return $this->cachedData; 1500 | } 1501 | 1502 | public function init(): void { 1503 | $this->cachedData = $this->load(); 1504 | } 1505 | 1506 | /** 1507 | * @return array 1508 | */ 1509 | abstract protected function load(): array; 1510 | } 1511 | ``` 1512 | 1513 | ### 📖 Свойства и методы должны именоваться по типу camelCase 1514 | 1515 | Плохо: 1516 | ```php 1517 | class SomeClass 1518 | { 1519 | private $property_name; 1520 | 1521 | public function get_property_name() 1522 | { 1523 | // ... 1524 | } 1525 | } 1526 | ``` 1527 | 1528 | Хорошо: 1529 | ```php 1530 | class SomeClass 1531 | { 1532 | private $propertyName; 1533 | 1534 | public function getPropertyName() 1535 | { 1536 | // ... 1537 | } 1538 | } 1539 | 1540 | В Yii при работе с моделями используются названия свойств взятые из БД, а в базе все названия хранятся с нижним подчёркиванием. 1541 | Если модель доплняется каким-то свойством оно должно быть так же проименовано в стиле camelCase 1542 | Так мы будем чётко понимать какие свойства из БД, а какие были прописаны в классе 1543 | 1544 | Хорошо: 1545 | ```php 1546 | /** 1547 | * @property string $created_at 1548 | * @property string $updated_at 1549 | */ 1550 | class Model 1551 | { 1552 | public $someName; 1553 | } 1554 | ``` 1555 | 1556 | ### 📖 Методы и свойства в классе должны быть отсортированы по уровням видимости и по порядку использования сверху вниз 1557 | Уровни видимости: `public` -> `protected` -> `private`. 1558 | 1559 | Плохо: 1560 | ```php 1561 | class SomeClass { 1562 | private $privPropA; 1563 | public $pubPropA; 1564 | protected $protPropA; 1565 | 1566 | protected function protA() { 1567 | } 1568 | 1569 | 1570 | public function pubB() { 1571 | } 1572 | 1573 | 1574 | private function privA() { 1575 | return $this->protA(); 1576 | } 1577 | 1578 | public function pubA() { 1579 | $this->privA(); 1580 | return $this->pubB(); 1581 | } 1582 | } 1583 | ``` 1584 | 1585 | Хорошо: 1586 | ```php 1587 | class SomeClass { 1588 | public $pubPropA; 1589 | protected $protPropA; 1590 | private $privPropA; 1591 | 1592 | public function pubA() { 1593 | $this->privA(); 1594 | return $this->pubB(); 1595 | } 1596 | 1597 | public function pubB() { 1598 | } 1599 | 1600 | protected function protA() { 1601 | } 1602 | 1603 | private function privA() { 1604 | return $this->protA(); 1605 | } 1606 | } 1607 | ``` 1608 | 1609 | **[⬆ наверх](#Содержание)** 1610 | 1611 | ## **Работа с объектами** 1612 | 1613 | ### 📖 Все объекты должны быть неизменяемыми (immutable), если от них не требуется обратного 1614 | 1615 | Плохо: 1616 | ```php 1617 | class SomeObject { 1618 | /** 1619 | * @var int 1620 | */ 1621 | public $id; 1622 | } 1623 | ``` 1624 | 1625 | Хорошо: 1626 | ```php 1627 | class SomeObject { 1628 | /** 1629 | * @var int 1630 | */ 1631 | private $id; 1632 | 1633 | /** 1634 | * @param int $id 1635 | */ 1636 | public function __construct($id) { 1637 | $this->id = $id; 1638 | } 1639 | 1640 | /** 1641 | * @var int 1642 | */ 1643 | public function id() { 1644 | return $this->id; 1645 | } 1646 | } 1647 | ``` 1648 | 1649 | ### 📖 Статические вызовы можно делать только у самого класса. У экземпляра можно обращаться только к его свойствам и методам 1650 | 1651 | Плохо: 1652 | ```php 1653 | $type = $user::TYPE; 1654 | ``` 1655 | 1656 | Хорошо: 1657 | ```php 1658 | $type = User::TYPE; 1659 | ``` 1660 | 1661 | **[⬆ наверх](#Содержание)** 1662 | 1663 | ## **Комментирование кода** 1664 | 1665 | ### Оформление комментариев 1666 | 1667 | Общие рекомендации к оформлению читать на [официальном сайте](http://docs.phpdoc.org/references/phpdoc/index.html) например для `@var`: 1668 | 1669 | ```php 1670 | @var [“Type”] [$element_name] [] 1671 | ``` 1672 | 1673 | Комментарии к свойствам и переменным должны распологаться на одной строке. 1674 | 1675 | Плохо: 1676 | ```php 1677 | /** 1678 | * @var mixed|null 1679 | */ 1680 | public $variable = null; 1681 | ``` 1682 | 1683 | Хорошо: 1684 | ```php 1685 | /** @var mixed|null */ 1686 | public $variable = null; 1687 | ``` 1688 | 1689 | Комментарии к методам и функциям оформляются в несколько строк, как рекомендовано в phpdoc. 1690 | 1691 | Плохо: 1692 | ```php 1693 | /** 1694 | * @param Object $object, array $params 1695 | * @return null|Object 1696 | */ 1697 | public function someMethod(Object $object, $params = []): ?Object 1698 | { 1699 | 1700 | } 1701 | ``` 1702 | 1703 | Хорошо: 1704 | ```php 1705 | /** 1706 | * @param Object $object 1707 | * @param array $params 1708 | * @return null|Object 1709 | */ 1710 | public function someMethod(Object $object, $params = []): ?Object 1711 | { 1712 | 1713 | } 1714 | ``` 1715 | 1716 | ### 📖 В общем случае комментарии запрещены 1717 | 1718 | Желание добавить комментарий — признак плохо читаемого кода. Любой участок кода, который вы хотели бы выделить или прокомментировать, надо выносить в отдельный метод. 1719 | 1720 | Фразу, которую вы хотели написать в комментарии, надо привести в простой вид и сделать ее названием метода. 1721 | 1722 | Плохо: 1723 | ```php 1724 | public function deleteApprovedUsers() { 1725 | // load users filter them by approval 1726 | $users = $repository->loadUsers(); 1727 | array_filter($users, function($user) { 1728 | return $user->is_approved; 1729 | }); 1730 | 1731 | foreach ($users as $user) { 1732 | // ... 1733 | } 1734 | } 1735 | ``` 1736 | 1737 | Хорошо: 1738 | ```php 1739 | public function deleteApprovedUsers() { 1740 | $users = $this->loadApprovedUsers(); 1741 | foreach ($users as $user) { 1742 | // ... 1743 | } 1744 | } 1745 | 1746 | public function loadApprovedUsers() { 1747 | $users = $repository->loadUsers(); 1748 | array_filter($users, function($user) { 1749 | return $user->is_approved; 1750 | }); 1751 | } 1752 | ``` 1753 | 1754 | ### 📖 Вынужденные хаки должны быть помечены комментариями 1755 | Лучше соблюдать одинаковый формат в рамках проекта 1756 | 1757 | Хорошо: 1758 | ```php 1759 | function loadUsers() { 1760 | $result = $repository->loadUsers(); 1761 | // hack: status field was removed from storage 1762 | foreach ($result as $user) { 1763 | $user->status = 'active'; 1764 | } 1765 | // hack end 1766 | return $result; 1767 | } 1768 | ``` 1769 | 1770 | ### 📖 Готовые алгоритмы, взятые из внешнего источника, должны быть помечены ссылкой на источник 1771 | 1772 | Хорошо: 1773 | ```php 1774 | /** 1775 | * https://en.wikipedia.org/wiki/Quicksort 1776 | */ 1777 | function quickSort(array $arr) { 1778 | // ... 1779 | } 1780 | 1781 | /** 1782 | * https://habrahabr.ru/post/320140/ 1783 | */ 1784 | function generateRandomMaze() { 1785 | // ... 1786 | } 1787 | ``` 1788 | 1789 | ### 📖 При разработке прототипа допустимо помечать участки кода @todo 1790 | 1791 | Хорошо: 1792 | ```php 1793 | function loadUsers() { 1794 | $result = $repository->loadUsers(); 1795 | // @todo: delete the hack when field will be restored 1796 | // hack: status field was removed from storage 1797 | foreach ($result as $user) { 1798 | $user->status = 'active'; 1799 | } 1800 | // hack end 1801 | return $result; 1802 | } 1803 | ``` 1804 | 1805 | **[⬆ наверх](#Содержание)** 1806 | 1807 | ## **Работа с исключениями** 1808 | ### 📖 На каждом уровне бизнес-логики (проект, компонент, библиотека) должно быть абстрактное базовое исключение 1809 | 1810 | ### 📖 Исключения сторонних библиотек должны быть перехвачены сразу 1811 | Далее либо обработаны, либо на их основании должно бросаться свое исключение. Новое исключение должно содержать предыдущее. 1812 | 1813 | Хорошо: 1814 | ```php 1815 | namespace Service\Facebook; 1816 | 1817 | use Exception; 1818 | use FacebookAds; 1819 | 1820 | public function function requestData() { 1821 | // ... 1822 | try { 1823 | $objects = $facebookAds->requestData($params); 1824 | } catch (FacebookAds\Exception\Exception $e) { 1825 | throw new Exception\ExternalServiceError("Facebook error: {$e->getMessage()}", 0, $e); 1826 | } 1827 | //.. 1828 | } 1829 | ``` 1830 | 1831 | ### 📖 По умолчанию тексты исключений не должны показываться пользователю 1832 | Они предназначены для логирования и отладки. Текст исключения можно показать пользователю, если оно явно для этого предназначено: например, реализует интерфейс `HumanReadableInterface`. 1833 | 1834 | ```php 1835 | interface HumanReadableInterface { 1836 | 1837 | /** 1838 | * @return string 1839 | */ 1840 | public function getUserMessage(): string; 1841 | } 1842 | 1843 | public function handleException(\Throwable $exception) { 1844 | if ($exception instanceof HumanReadableInterface) { 1845 | echo $exception->getUserMessage(); 1846 | return; 1847 | } 1848 | // ... 1849 | } 1850 | ``` 1851 | 1852 | **[⬆ наверх](#Содержание)** 1853 | 1854 | ## **Работа с внешним хранилищем данных** 1855 | 1856 | ### 📖 Нельзя делать запросы к внешнему хранилищу внутри цикла с заведомо большим кол-вом итераций 1857 | 1858 | Плохо: 1859 | ```php 1860 | $users = loadUsers(); 1861 | foreach ($users as $user) { 1862 | $userProjects = loadUserProjects($user); 1863 | // ... 1864 | } 1865 | ``` 1866 | 1867 | Хорошо: 1868 | ```php 1869 | $users = loadUsers(); 1870 | $projects = loadProjects(); 1871 | $indexedProjects = []; 1872 | foreach ($projects as $project) { 1873 | if (!array_key_exists($project->user_id, $indexedProjects)) { 1874 | $indexedProjects[$project->user_id] = []; 1875 | } 1876 | $indexedProjects[$project->user_id][] = $project; 1877 | } 1878 | foreach ($users as $user) { 1879 | if (!array_key_exists($user->id, $indexedProjects)) { 1880 | continue; 1881 | } 1882 | $userProjects = $indexedProjects[$user->id]; 1883 | } 1884 | ``` 1885 | 1886 | ### 📖 Для каждой записи в хранилище должно быть понятна дата ее создания 1887 | То есть должна быть колонка `date/creation_date`. Или должен быть зависимый объект (связь 1 к 1), у которого есть такая колонка. Редактируемые записи должны иметь и дату редактирования: `update_date` или `modification_date`. 1888 | 1889 | **[⬆ наверх](#Содержание)** 1890 | 1891 | ## **Особенности Pull Request (PR)** 1892 | ### 📖 PR должен содержать как можно меньше строк кода 1893 | Любая атомарная часть кода должна выделяться в отдельную подзадачу и отдельный PR. 1894 | 1895 | ### 📖 Нельзя смешивать перенос методов в другие классы и места и последующий рефакторинг между собой 1896 | Перенос методов в другие классы и места должны быть выделены в отдельный PR. Последующий рефакторинг после переноса тоже должен быть в отдельном PR. 1897 | 1898 | ### 📖 В случае большого PR — ответственность за долгий просмотр несет сам разработчик, сделавший такой PR 1899 | Нормальный объем кода — 0-300 строк в зависимости от его сложности. PR заглушек и архитектуры может содержать много формального кода, который легко быстро проверить. PR же конкретного метода может содержать много сложностей даже в 10 строчках. 1900 | 1901 | ### 📖 Нельзя накапливать изменения в какой-то своей ветке и потом делать большой PR в master 1902 | Все что можно смержить в master без последствий (даже если это еще не готовый результат, а только заглушки или часть, но они скрыты от юзеров и никому не мешают), должен мержиться в master и PR должен создаваться в master. 1903 | 1904 | ### 📖 В Pull Request не должно попадать кода, не относящегося к задаче 1905 | Также не должно быть забытых комментариев, бессмысленных переносов строк и прочего "строительного мусора". Каждое изменение, которое вы предлагаете сделать в master-ветке, должно так или иначе относиться к решению поставленной вам задачи. 1906 | 1907 | **[⬆ наверх](#Содержание)** 1908 | 1909 | ## **Работа с шаблонами** 1910 | 1911 | ### 📖 В шаблонах не должны вызываться методы объектов (геттеры не в счет) 1912 | Все необходимые данные должны быть загружены до рендера и переданы в виде параметров шаблона. 1913 | 1914 | **[⬆ наверх](#Содержание)** 1915 | 1916 | ## **Работа с литералами** 1917 | 1918 | ### 📖 Назначение всех числовых литералов должно быть понятным из контекста 1919 | Они должны быть или вынесены в переменную или константу, или сравниваться с переменной, или передаваться на вход методу с понятной сигнатурой. В коде должен присутствовать в явном виде ответ: `за что отвечает это число и почему оно именно такое?` 1920 | 1921 | Плохо: 1922 | ```php 1923 | $isOnlyDeleted = 1; 1924 | if ($object->is_deleted === $isOnlyDeleted) { 1925 | // ... 1926 | } 1927 | 1928 | for ($i = 0; $i < 5; $i++) { 1929 | // ... 1930 | } 1931 | ``` 1932 | 1933 | Хорошо: 1934 | ```php 1935 | 1936 | if ($object->is_deleted === 1) { 1937 | // ... 1938 | } 1939 | 1940 | $apiMaxRetryLimit = 5; 1941 | for ($i = 0; $i < $apiMaxRetryLimit; $i++) { 1942 | // ... 1943 | } 1944 | ``` 1945 | 1946 | **[⬆ наверх](#Содержание)** 1947 | 1948 | ### 📖 Тело условия не должно быть на той же строке, что и само условие 1949 | 1950 | Нельзя прописывать условия в одну строку, так же тело условия должно быть обёрнуто в фигурные скобки 1951 | 1952 | Плохо: 1953 | ```php 1954 | if (count($userProjects) > 0) return $userProjects; 1955 | if (count($userProjects) > 0) 1956 | return $userProjects; 1957 | ``` 1958 | 1959 | Хорошо: 1960 | ```php 1961 | if (count($userProjects) > 0) { 1962 | return $userProjects; 1963 | } 1964 | ``` 1965 | 1966 | ### 📖 В условном операторе должно проверяться исключительно `boolean` значение 1967 | 1968 | Плохо: 1969 | ```php 1970 | if (count($userProjects)) { 1971 | // ... 1972 | } 1973 | if ($project) { 1974 | // ... 1975 | } 1976 | ``` 1977 | 1978 | Хорошо: 1979 | ```php 1980 | if ($isResponseError) { // $isResponseError = true 1981 | // ... 1982 | } 1983 | if ($response->isError()) { // isError method returns boolean 1984 | // ... 1985 | } 1986 | if (count($userProjects) > 0) { 1987 | // ... 1988 | } 1989 | ``` 1990 | 1991 | ### 📖 В сравнении не boolean переменных используется строгое сравнение с приведением типа (===), автоматическое приведение и нестрогое сравнение не используются 1992 | 1993 | Плохо: 1994 | ```php 1995 | if ($project) { 1996 | // ... 1997 | } 1998 | if ($request->postData('sum') == 100) { 1999 | // ... 2000 | } 2001 | if (!$request->postData('sum')) { 2002 | // ... 2003 | } 2004 | if (!$bill->comment) { 2005 | // ... 2006 | } 2007 | ``` 2008 | 2009 | Хорошо: 2010 | ```php 2011 | if ($project === null) { // $project is an object 2012 | // ... 2013 | } 2014 | if ((int)$request->postData('sum') === 100) { 2015 | // ... 2016 | } 2017 | if ($bill->comment === '') { 2018 | // ... 2019 | } 2020 | ``` 2021 | 2022 | ### 📖 Автоматическое приведение типов разрешено только, когда один из операндов — литерал с зфиксированным типом 2023 | При сравнении двух переменных с неизвестными типами для читающего код человека не очевидно, к чему они будут приведены интерпретатором. Если же тип одного из операндов известен, то всё становится очевидно и ручное приведение типов не требуется. 2024 | 2025 | Если вы хотите проверить значение `boolean` пришедшее извне, то делается это так: 2026 | 2027 | Плохо: 2028 | ```php 2029 | if ((int)$request->get('is_something') > 0) { 2030 | // ... 2031 | } 2032 | if ((int)$request->get('is_something') === 1) { 2033 | // ... 2034 | } 2035 | if ((int)$user->is_registered === 0) { 2036 | // ... 2037 | } 2038 | ``` 2039 | 2040 | Хорошо: 2041 | ```php 2042 | if ($request->get('is_something') > 0) { 2043 | // ... 2044 | } 2045 | if ($user->is_registered) { 2046 | // ... 2047 | } 2048 | if (!$user->is_registered) { 2049 | // ... 2050 | } 2051 | ``` 2052 | 2053 | #### 📖 Не надо сравнивать `boolean` с `true`/`false` 2054 | Это нарушает запрет на бесполезный код. 2055 | 2056 | Плохо: 2057 | ```php 2058 | if ($bill->isPaid() == true) { 2059 | // ... 2060 | } 2061 | if ($bill->isPaid() !== false) { 2062 | // ... 2063 | } 2064 | if (!$bill->isPaid() === true) { 2065 | // ... 2066 | } 2067 | if (!(!$bill->isPaid() === true)) { 2068 | // ... 2069 | } 2070 | if ((bool)$phone->is_external === true) { 2071 | // ... 2072 | } 2073 | ``` 2074 | 2075 | Хорошо: 2076 | ```php 2077 | if ($bill->isPaid()) { 2078 | // ... 2079 | } 2080 | ``` 2081 | 2082 | ### 📖 Проверять переменные надо на наличие позитивного вхождения, а не отсутствие негативного 2083 | Если вам нужна строка, то проверять надо на то, что переменная является строкой. Не надо проверять на то, что она не является числом или чем-то еще. Перечислять все возможные варианты, чем переменная не должна быть, значит повышать риск ошибки и усложнять поддержку кода. 2084 | 2085 | Плохо: 2086 | ```php 2087 | if (!is_numeric($value) && !is_object($value)) { 2088 | // ... 2089 | } 2090 | ``` 2091 | 2092 | Хорошо: 2093 | ```php 2094 | if (is_string($value) && $value !== '') { 2095 | // ... 2096 | } 2097 | ``` 2098 | 2099 | ### 📖 Если вы используете встроенную функцию PHP, которая возвращает `0`, `1` и, возможно, `false`, то при возможности результат ее работы используем в условии как `bool` без дополнительных сравнений 2100 | Это не касается случая, когда вам нужно отделить два разных результата между собой, например отдельно отработать `0` и `false`. 2101 | 2102 | Плохо: 2103 | ```php 2104 | if (preg_match($pattern, $subject) === 1) { 2105 | // ... 2106 | } 2107 | if (!strpos($search, $text)) { 2108 | // ... 2109 | } 2110 | ``` 2111 | 2112 | Хорошо: 2113 | ```php 2114 | if (preg_match($pattern, $subject)) { 2115 | // handle success 2116 | } 2117 | if (!preg_match($pattern, $subject)) { 2118 | // handle not success 2119 | } 2120 | if (preg_match($pattern, $subject) === false) { 2121 | // handle error 2122 | } 2123 | if (strpos($search, $text) === false) { 2124 | // handle not success 2125 | } 2126 | ``` 2127 | 2128 | **[⬆ наверх](#Содержание)** 2129 | 2130 | ## **Работа с тернарными операторами** 2131 | 2132 | ### 📖 При использовании тернарных операторов действуют те же правила, что и при использовании условий 2133 | 2134 | ### 📖 Тернарный оператор следует использовать, если обе ветви условия предназначены для установки одной переменной одним языковым выражением 2135 | При наличии логики в ветках условия следует рассмотреть возможность вынести ее в отдельный метод. 2136 | 2137 | Плохо: 2138 | ```php 2139 | if ($isExternal) { 2140 | $bill = $this->loadExternalBill(); 2141 | } else { 2142 | $bill = $this->loadInternalBill(); 2143 | } 2144 | ``` 2145 | 2146 | Хорошо: 2147 | ```php 2148 | $bill = $isExternal ? $this->loadExternalBill() : $this->loadInternalBill(); 2149 | ``` 2150 | 2151 | ### 📖 Использовать цепочки из тернарных операторов `?:` допустимо только при указании значения по умолчанию 2152 | 2153 | Плохо: 2154 | ```php 2155 | $contact = $this->loadContactByPhone() ?: $this->loadContactByEmail() ?: $this->loadContactByName(); 2156 | ``` 2157 | 2158 | Хорошо: 2159 | ```php 2160 | $lead = $this->loadLeadFromCache() ?: $this->loadLeadFromDB(); 2161 | $contact = $this->loadContactByPhone() ?: $this->loadContactByEmail() ?: $this->loadContactByName() ?: null; 2162 | ``` 2163 | 2164 | **[⬆ наверх](#Содержание)** 2165 | 2166 | ## **Про тесты** 2167 | 2168 | ### 📖 Тесты являются таким же production-кодом, как и любой другой код 2169 | Они должны быть написаны с соблюдением соглашений, описанных в этом документе. 2170 | 2171 | ### 📖 В дата провайдерах для тестов надо писать комментарий к структуре отдаваемого массива значений 2172 | 2173 | Плохо: 2174 | ```php 2175 | public function isEmailAddressData() { 2176 | return [ 2177 | ['test@test.ru', true ], 2178 | // ... 2179 | ] 2180 | } 2181 | ``` 2182 | 2183 | Хорошо: 2184 | ```php 2185 | public function isEmailAddressData() { 2186 | return [ 2187 | // email isValid 2188 | ['test@test.ru', true ], 2189 | ['invalidEmail', false], 2190 | // ... 2191 | ] 2192 | } 2193 | ``` 2194 | 2195 | **[⬆ наверх](#Содержание)** 2196 | 2197 | ## **Использование chain-объектов** 2198 | ### 📖 Метод с большим количеством необязательных параметров (А) может быть заменен chain-объектом 2199 | Метод с большим количеством необязательных параметров (А) может быть заменен chain-объектом. 2200 | В объекте конструктор принимает все обязательные параметры, а все необязательные реализуются сеттерами без глагола set (только существительное), возвращающими текущий объект (chaining методов). Метод-глагол у объекта один без параметров, он завершает использование объекта и выполняет действие, которое должен был выполнить метод А. 2201 | 2202 | **Был метод:** 2203 | ```php 2204 | function send($method, $url, $body = null, $headers = null, $retries = 1, $timeout = 300) {} 2205 | ``` 2206 | 2207 | **Должен замениться на chain-объект:** 2208 | ```php 2209 | public function __construct($method, $url) { 2210 | // ... 2211 | } 2212 | 2213 | public function body($body) { 2214 | return $this; 2215 | } 2216 | // остальные методы с необязательными параметрами 2217 | 2218 | public function send(); 2219 | ``` 2220 | 2221 | **Новый объект используется так:** 2222 | ```php 2223 | new $sender($method, $url)->body($body)->retries(10)->timeout(25)->send(); 2224 | ``` 2225 | 2226 | **[⬆ наверх](#Содержание)** 2227 | 2228 | ## **Работа со скриптами** 2229 | ### 📖 Любой скрипт, который удаляет данные, должен иметь подтверждение перед выполнением действий с данными и `debug` по результатам работы 2230 | 2231 | Плохо: 2232 | ``` php 2233 | // cli/delete_items.php 2234 | $repository->deleteItems(); 2235 | ``` 2236 | 2237 | Исправим, чтобы случайный запуск не удалил элементы: 2238 | 2239 | Хорошо: 2240 | ```php 2241 | // cli/delete_items.php 2242 | $totalItems = $repository->countItems(); 2243 | if (!confirm("Do you want to delete {$totalItems} item(s)?")) { 2244 | echo 'Delete canceled, exit'; 2245 | exit(1); 2246 | } 2247 | 2248 | $repository->deleteItems(); 2249 | 2250 | function confirm(string $question): bool { 2251 | return readline("{$question} [y/n]: ") === 'y' 2252 | } 2253 | ``` 2254 | 2255 | **[⬆ наверх](#Содержание)** 2256 | 2257 | ## **Авторы** 2258 | 2259 | - Удодов Евгений ([flrnull](https://github.com/flrnull)) 2260 | - Рудаченко Сергей ([m1nor](https://github.com/m1nor)) 2261 | - Зюзькевич Юрий ([Farengier](https://github.com/Farengier)) 2262 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Конвенции 2 | 3 | [Php-code-conventions](PHP.md) 4 | 5 | [JS-code-conventions](JS.md) 6 | 7 | [Работа с git](GIT.md) 8 | 9 | [Работа с git commits](https://www.conventionalcommits.org/ru) 10 | --------------------------------------------------------------------------------