renovations

This commit is contained in:
Ilya Kantor 2015-01-10 00:54:38 +03:00
parent 223dd884ae
commit 4b8b168fd2
42 changed files with 562 additions and 490 deletions

View file

@ -1,18 +1,26 @@
# Как писать неподдерживаемый код?
[warn header="Познай свой код"]
Эта статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) ("как писать неподдерживаемый код") с дополнениями, актуальными для JavaScript.
Возможно, в каких-то из этих советов вам даже удастся узнать "этого парня в зеркале".
[/warn]
Предлагаю вашему вниманию советы мастеров древности, следование которым создаст дополнительные рабочие места для JavaScript-разработчиков.
Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо".
Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него...
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым.
Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
Статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) с дополнениями, актуальными для JavaScript.
[cut]
## Соглашения
## Соглашения -- по настроению
[quote author="Сериал \"Симпсоны\", серия Helter Shelter"]
Рабочий-чистильщик осматривает дом:<br>
@ -31,22 +39,26 @@
Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте?
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.**
Если пример, который я приведу ниже, пока сложноват -- пропустите его, но обязательно вернитесь к нему позже. Поверьте, это стоит того.
Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
### Пример из jQuery
[warn header="jQuery / DOM"]
Этот пример требует знаний jQuery/DOM, если пока их у вас нет -- пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки.
[/warn]
Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого:
```js
var img = $('<img/>'); // создали новые элементы (jQuery-синтаксис)
var div = $('<div/>'); // и поместили в переменную
*!*
img.wrap(div); // обернуть img в div
*/!*
div.append('<span/>');
```
Результат кода выше -- два элемента, один вложен в другой:
Результат кода после операции `wrap` -- два элемента, один вложен в другой:
```html
<div>
@ -54,66 +66,24 @@ img.wrap(div); // обернуть img в div
</div>
```
(`div` обернулся вокруг `img`)
А что же после `append`?
А теперь, когда все расслабились и насладились этим замечательным методом...
Можно предположить, что `<span/>` добавится в конец `div`, сразу после `img`... Но ничего подобного!
...Самое время ниндзя нанести свой удар!
Искусный ниндзя уже нанёс свой удар и поведение кода стало неправильным, хотя разработчик об этом даже не подозревает.
**Как вы думаете, что будет, если добавить к коду выше строку:**
Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь!
```js
//+ lines first-line=5
div.append('<span/>');
```
Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался.
[smart header="jQuery-справка"]
Вызов `elemA.append(elemB)` добавляет `elemB` в конец содержимого элемента `elemA`.
[/smart]
В итоге, после вызова получается два независимых `div'а`: первый содержит `img` (этот неявный клон никуда не присвоен), а второй -- наш `span`.
**Возможно, вы полагаете, что `<span/>` добавится в конец `div`, сразу после `img`?**
Злая магия? Плохой феншуй?
А вот и нет! А вот и нет!..
Ничего подобного, просто избирательное следование соглашениям. Вызов `wrap` -- неявно клонирует элемент.
Оказывается, внутри вызова `img.wrap(div)` происходит *клонирование* `div`. И вокруг `img` оборачивается не сам `div`, а его <strike>злой</strike> клон.
Такой сюрприз, бесспорно, стоит многих часов отладки.
При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался. В итоге, после применения к нему `append` получается два `div'а`: один обёрнут вокруг `span`, а в другом -- только `img`.
<table>
<tr>
<th>Переменная `div`</th>
<th>Клон `div`, созданный `wrap`
(не присвоен никакой переменной)</th>
</tr>
<tr>
<td>
```html
<div>
<span/>
</div>
```
</td>
<td>
```html
<div>
<img/>
</div>
```
</td>
</tr>
</table>
Странно? Неочевидно? Да, и не только вам :)
Соглашение в данном случае -- в том, что большинство методов jQuery не клонируют элементы. А вызов `wrap` -- клонирует.
Код его истинный ниндзя писал!
## Краткость -- сестра таланта!
@ -148,7 +118,7 @@ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`.
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы.
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше).
В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно.
@ -281,7 +251,7 @@ function ninjaFunction(elem) {
var *!*user*/!* = authenticateUser();
function render() {
var *!*user*/!* = ...
var *!*user*/!* = anotherValue();
...
...многобукв...
...
@ -290,7 +260,7 @@ function render() {
}
```
Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` "уже не та" и использует её... Ловушка захлопнулась! Здравствуй, отладчик.
Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` локально перекрыта и попытается работать с ней, полагая, что это результат `authenticateUser()`... Ловушка захлопнулась! Здравствуй, отладчик.
## Мощные функции!
@ -298,7 +268,9 @@ function render() {
Например, функция `validateEmail(email)` может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail.
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.** Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.</li>
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.**
Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.</li>
**Объединение нескольких смежных действий в одну функцию защитит ваш код от повторного использования.**
@ -315,9 +287,9 @@ function render() {
**Ещё одна вариация такого подхода -- возвращать нестандартное значение.**
Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект -- с результатами проверки! А что, полезно.
Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект с результатами проверки! А чего, полезно.
Те разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
Те же разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
## Заключение