This commit is contained in:
Ilya Kantor 2016-03-04 19:06:22 +03:00
parent e78e527866
commit 05a93ced80
212 changed files with 3213 additions and 3968 deletions

View file

@ -1,35 +1,33 @@
# Как писать неподдерживаемый код?
[warn header="Познай свой код"]
```warn header="Познай свой код"
Эта статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) ("как писать неподдерживаемый код") с дополнениями, актуальными для JavaScript.
Возможно, в каких-то из этих советов вам даже удастся узнать "этого парня в зеркале".
[/warn]
```
Предлагаю вашему вниманию советы мастеров древности, следование которым создаст дополнительные рабочие места для JavaScript-разработчиков.
Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо".
Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него...
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым.
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым.
Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
[cut]
## Соглашения -- по настроению
[quote author="Сериал \"Симпсоны\", серия Helter Shelter"]
```quote author="Сериал \"Симпсоны\", серия Helter Shelter"
Рабочий-чистильщик осматривает дом:<br>
"...Вот только жук у вас необычный...<br>
И чтобы с ним справиться, я должен жить как жук, стать жуком, думать как жук."<br>
(грызёт стол Симпсонов)
[/quote]
```
Чтобы помешать другому программисту исправить ваш код, вы должны понять путь его мыслей.
Чтобы помешать другому программисту исправить ваш код, вы должны понять путь его мыслей.
Представьте, перед ним -- ваш большой скрипт. И ему нужно поправить его. У него нет ни времени ни желания, чтобы читать его целиком, а тем более -- досконально разбирать. Он хотел бы по-быстрому найти нужное место, сделать изменение и убраться восвояси без появления побочных эффектов.
@ -39,15 +37,16 @@
Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте?
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.**
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.**
Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
### Пример из jQuery
[warn header="jQuery / DOM"]
```warn header="jQuery / DOM"
Этот пример требует знаний jQuery/DOM, если пока их у вас нет -- пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки.
[/warn]
```
Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого:
```js
@ -74,11 +73,11 @@ div.append('<span/>');
Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь!
Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался.
Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался.
В итоге, после вызова получается два независимых `div'а`: первый содержит `img` (этот неявный клон никуда не присвоен), а второй -- наш `span`.
Объяснения не очень понятны? Написано что-то странное? Это просто разум, привыкший, что соглашения уважаются, не допускает мысли, что вызов `wrap` -- неявно клонирует элемент. Ведь другие jQuery-методы, кроме `clone` этого не делают.
Объяснения не очень понятны? Написано что-то странное? Это просто разум, привыкший, что соглашения уважаются, не допускает мысли, что вызов `wrap` -- неявно клонирует элемент. Ведь другие jQuery-методы, кроме `clone` этого не делают.
Как говорил [Учитель](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%84%D1%83%D1%86%D0%B8%D0%B9): "В древности люди учились для того, чтобы совершенствовать себя. Нынче учатся для того, чтобы удивить других".
@ -99,13 +98,13 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
## Именование
Существенную часть науки о создании неподдерживаемого кода занимает искусство выбора имён.
Существенную часть науки о создании неподдерживаемого кода занимает искусство выбора имён.
### Однобуквенные переменные
Называйте переменные коротко: `a`, `b` или `c`.
В этом случае никто не сможет найти её, используя фунцию "Поиск" текстового редактора.
В этом случае никто не сможет найти её, используя фунцию "Поиск" текстового редактора.
Более того, даже найдя -- никто не сможет "расшифровать" её и догадаться, что она означает.
@ -113,73 +112,73 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
В тех местах, где однобуквенные переменные общеприняты, например, в счетчике цикла -- ни в коем случае не используйте стандартные названия `i`, `j`, `k`. Где угодно, только не здесь!
Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`.
Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`.
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше).
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше).
В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно.
В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно.
### Русские слова и сокращения
Если вам *приходится* использовать длинные, понятные имена переменных -- что поделать.. Но и здесь есть простор для творчества!
**Назовите переменные "калькой" с русского языка или как-то "улучшите" английское слово.**
**Назовите переменные "калькой" с русского языка или как-то "улучшите" английское слово.**
В одном месте напишите `var ssilka`, в другом `var ssylka`, в третьем `var link`, в четвёртом -- `var lnk`... Это действительно великолепно работает и очень креативно!
В одном месте напишите `var ssilka`, в другом `var ssylka`, в третьем `var link`, в четвёртом -- `var lnk`... Это действительно великолепно работает и очень креативно!
Количество ошибок при поддержке такого кода увеличивается во много раз.
### Будьте абстрактны при выборе имени
[quote author="Лао-цзы"]Лучший кувшин лепят всю жизнь.<br>
```quote author="Лао-цзы"
Лучший кувшин лепят всю жизнь.<br>
Высокая музыка неподвластна слуху.<br>
Великий образ не имеет формы.[/quote]
Великий образ не имеет формы.
```
При выборе имени старайтесь применить максимально абстрактное слово, например `obj`, `data`, `value`, `item`, `elem` и т.п.
При выборе имени старайтесь применить максимально абстрактное слово, например `obj`, `data`, `value`, `item`, `elem` и т.п.
<ul>
<li>**Идеальное имя для переменной: `data`.** Используйте это имя везде, где можно. В конце концов, каждая переменная содержит *данные*, не правда ли?
- **Идеальное имя для переменной: `data`.** Используйте это имя везде, где можно. В конце концов, каждая переменная содержит *данные*, не правда ли?
Но что делать, если имя `data` уже занято? Попробуйте `value`, оно не менее универсально. Ведь каждая переменная содержит *значение*.
Но что делать, если имя `data` уже занято? Попробуйте `value`, оно не менее универсально. Ведь каждая переменная содержит *значение*.
Занято и это? Есть и другой вариант.
</li>
<li>**Называйте переменную по типу данных, которые она хранит: `obj`, `num`, `arr`...**
Занято и это? Есть и другой вариант.
- **Называйте переменную по типу данных, которые она хранит: `obj`, `num`, `arr`...**
Насколько это усложнит разработку? Как ни странно, намного!
Насколько это усложнит разработку? Как ни странно, намного!
Казалось бы, название переменной содержит информацию, говорит о том, что в переменной -- число, объект или массив... С другой стороны, **когда непосвящённый будет разбирать этот код -- он с удивлением обнаружит, что информации нет!**
Казалось бы, название переменной содержит информацию, говорит о том, что в переменной -- число, объект или массив... С другой стороны, **когда непосвящённый будет разбирать этот код -- он с удивлением обнаружит, что информации нет!**
Ведь как раз тип легко понять, запустив отладчик и посмотрев, что внутри. Но в чём смысл этой переменной? Что за массив/объект/число в ней хранится? Без долгой медитации над кодом тут не обойтись!
</li>
<li>**Что делать, если и эти имена кончились? Просто добавьте цифру:** `item1, item2, elem5, data1`...</li>
</ul>
Ведь как раз тип легко понять, запустив отладчик и посмотрев, что внутри. Но в чём смысл этой переменной? Что за массив/объект/число в ней хранится? Без долгой медитации над кодом тут не обойтись!
- **Что делать, если и эти имена кончились? Просто добавьте цифру:** `item1, item2, elem5, data1`...
### Похожие имена
Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий?
Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий?
**Один из способов -- использовать похожие имена переменных, например `data` и `date`.** Бегло прочитать такой код почти невозможно. А уж заметить опечатку и поправить её... Ммммм... Мы здесь надолго, время попить чайку.
### А.К.Р.О.Н.И.М
Используйте сокращения, чтобы сделать код короче.
Используйте сокращения, чтобы сделать код короче.
Например `ie` (Inner Element), `mc` (Money Counter) и другие. Если вы обнаружите, что путаетесь в них сами -- героически страдайте, но не переписывайте код. Вы знали, на что шли.
### Хитрые синонимы
[quote author="Конфуций"]Очень трудно найти чёрную кошку в тёмной комнате, особенно когда её там нет.[/quote]
```quote author="Конфуций"
Очень трудно найти чёрную кошку в тёмной комнате, особенно когда её там нет.
```
**Чтобы было не скучно -- используйте *похожие названия* для обозначения *одинаковых действий*.**
**Чтобы было не скучно -- используйте *похожие названия* для обозначения *одинаковых действий*.**
Например, если метод показывает что-то на экране -- начните его название с `display..` (скажем, `displayElement`), а в другом месте объявите аналогичный метод как `show..` (`showFrame`).
Например, если метод показывает что-то на экране -- начните его название с `display..` (скажем, `displayElement`), а в другом месте объявите аналогичный метод как `show..` (`showFrame`).
**Как бы намекните этим, что существует тонкое различие между способами показа в этих методах, хотя на самом деле его нет.**
**Как бы намекните этим, что существует тонкое различие между способами показа в этих методах, хотя на самом деле его нет.**
По возможности, договоритесь с членами своей команды. Если Вася в своих классах использует `display..`, то Валера -- обязательно `render..`, а Петя -- `paint..`.
По возможности, договоритесь с членами своей команды. Если Вася в своих классах использует `display..`, то Валера -- обязательно `render..`, а Петя -- `paint..`.
**...И напротив, если есть две функции с важными отличиями -- используйте одно и то же слово для их описания!** Например, с `print...` можно начать метод печати на принтере `printPage`, а также -- метод добавления текста на страницу `printText`.
**...И напротив, если есть две функции с важными отличиями -- используйте одно и то же слово для их описания!** Например, с `print...` можно начать метод печати на принтере `printPage`, а также -- метод добавления текста на страницу `printText`.
А теперь, пусть читающий код думает: "Куда же выводит сообщение `printMessage`?". Особый шик -- добавить элемент неожиданности. Пусть `printMessage` выводит не туда, куда все, а в новое окно!
@ -187,15 +186,15 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
Ни в коем случае не поддавайтесь требованиям написать словарь терминов для проекта. Если же он уже есть -- не следуйте ему, а лучше проглотите и скажите, что так и былО!
Пусть читающий ваш код программист напрасно ищет различия в `helloUser` и `welcomeVisitor` и пытается понять, когда что использовать. Вы-то знаете, что на самом деле различий нет, но искать их можно о-очень долго.
Пусть читающий ваш код программист напрасно ищет различия в `helloUser` и `welcomeVisitor` и пытается понять, когда что использовать. Вы-то знаете, что на самом деле различий нет, но искать их можно о-очень долго.
**Для обозначения посетителя в одном месте используйте `user`, а в другом `visitor`, в третьем -- просто `u`. Выбирайте одно имя или другое, в зависимости от функции и настроения.**
Это воплотит сразу два ключевых принципа ниндзя-дизайна -- *сокрытие информации* и *подмена понятий*!
Это воплотит сразу два ключевых принципа ниндзя-дизайна -- *сокрытие информации* и *подмена понятий*!
### Повторно используйте имена
По возможности, повторно используйте имена переменных, функций и свойств. Просто записывайте в них новые значения.
По возможности, повторно используйте имена переменных, функций и свойств. Просто записывайте в них новые значения.
Добавляйте новое имя только если это абсолютно необходимо.
@ -217,7 +216,7 @@ function ninjaFunction(elem) {
}
```
Программист, пожелавший добавить действия с `elem` во вторую часть функции, будет удивлён. Лишь во время отладки, посмотрев весь код, он с удивлением обнаружит, что оказывается имел дело с клоном!
Программист, пожелавший добавить действия с `elem` во вторую часть функции, будет удивлён. Лишь во время отладки, посмотрев весь код, он с удивлением обнаружит, что оказывается имел дело с клоном!
Регулярные встречи с этим приемом на практике говорят: защититься невозможно. Эффективно даже против опытного ниндзи.
@ -231,16 +230,16 @@ function ninjaFunction(elem) {
### Покажите вашу любовь к разработке
Пусть все видят, какими замечательными сущностями вы оперируете! Имена `superElement`, `megaFrame` и `niceItem` при благоприятном положении звёзд могут привести к просветлению читающего.
Пусть все видят, какими замечательными сущностями вы оперируете! Имена `superElement`, `megaFrame` и `niceItem` при благоприятном положении звёзд могут привести к просветлению читающего.
Действительно, с одной стороны, кое-что написано: `super..`, `mega..`, `nice..` С другой -- это не несёт никакой конкретики. Читающий может решить поискать в этом глубинный смысл и замедитировать на часок-другой оплаченного рабочего времени.
### Перекрывайте внешние переменные
[quote author="Гуань Инь-цзы"]
```quote author="Гуань Инь-цзы"
Находясь на свету, нельзя ничего увидеть в темноте.<br>
Пребывая же в темноте, увидишь все, что находится на свету.
[/quote]
```
Почему бы не использовать одинаковые переменные внутри и снаружи функции? Это просто и не требует придумывать новых имён.
@ -261,11 +260,11 @@ function render() {
## Мощные функции!
Не ограничивайте действия функции тем, что написано в её названии. Будьте шире.
Не ограничивайте действия функции тем, что написано в её названии. Будьте шире.
Например, функция `validateEmail(email)` может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail.
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.**
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.**
Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.</li>
@ -273,12 +272,11 @@ function render() {
Представьте, что другому разработчику нужно только проверить адрес, а сообщение -- не выводить. Ваша функция `validateEmail(email)`, которая делает и то и другое, ему не подойдёт. Работодатель будет вынужден оплатить создание новой.
## Внимание.. Сюр-при-из!
Есть функции, название которых говорит о том, что они ничего не меняют. Например, `isReady`, `checkPermission`, `findTags`... Предполагается, что при вызове они произведут некие вычисления, или найдут и возвратят полезные данные, но при этом их не изменят. В трактатах это называется "отсутствие сторонних эффектов".
Есть функции, название которых говорит о том, что они ничего не меняют. Например, `isReady`, `checkPermission`, `findTags`... Предполагается, что при вызове они произведут некие вычисления, или найдут и возвратят полезные данные, но при этом их не изменят. В трактатах это называется "отсутствие сторонних эффектов".
**По-настоящему красивый приём -- делать в таких функциях что-нибудь полезное, заодно с процессом проверки. Что именно -- совершенно неважно.**
**По-настоящему красивый приём -- делать в таких функциях что-нибудь полезное, заодно с процессом проверки. Что именно -- совершенно неважно.**
Удивление и ошеломление, которое возникнет у вашего коллеги, когда он увидит, что функция с названием на `is..`, `check..` или `find...` что-то меняет -- несомненно, расширит его границы разумного!
@ -290,12 +288,10 @@ function render() {
## Заключение
Все советы выше пришли из реального кода... И в том числе от разработчиков с большим опытом.
Все советы выше пришли из реального кода... И в том числе от разработчиков с большим опытом.
Возможно, даже больше вашего, так что не судите опрометчиво ;)
<ul>
<li>Следуйте нескольким из них -- и ваш код станет полон сюрпризов.</li>
<li>Следуйте многим -- и ваш код станет истинно вашим, никто не захочет изменять его.</li>
<li>Следуйте всем -- и ваш код станет ценным уроком для молодых разработчиков, ищущих просветления.</li>
</ul>
- Следуйте нескольким из них -- и ваш код станет полон сюрпризов.
- Следуйте многим -- и ваш код станет истинно вашим, никто не захочет изменять его.
- Следуйте всем -- и ваш код станет ценным уроком для молодых разработчиков, ищущих просветления.