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

@ -12,11 +12,11 @@
В вашей версии Chrome панель может выглядеть несколько по-иному, но что где находится, должно быть понятно.
Зайдите на страницу [debugging/pow/index.html](/debugging/pow/index.html) браузером Chrome.
Зайдите на [страницу с примером](debugging/index.html) браузером Chrome.
Откройте инструменты разработчика: [key F12] или в меню `Инструменты > Инструменты Разработчика`.
Выберите сверху `Sources` (вместо иконок у вас могут быть просто надписи "Elements", "Resources", "Network", "Sources"...)
Выберите сверху `Sources`.
<img src="chrome_sources.png">
@ -34,7 +34,7 @@
<img src="chrome_sources_buttons.png">
Три полезные кнопки управления:
Три наиболее часто используемые кнопки управления:
<dl>
<dt>Формат <span class="devtools" style="background-position:-264px 94px"></span></dt>
<dd>Нажатие форматирует текст текущего файла, расставляет отступы. Нужна, если вы хотите разобраться в чужом коде, плохо отформатированном или сжатом.</dd>
@ -46,10 +46,12 @@
## Точки остановки
Открыли `pow.js` в зоне текста? Кликните на 6й строке файла `pow.js`, прямо на цифре 6.
Открыли файл `pow.js` во вкладке Sources? Кликните на 6й строке файла `pow.js`, прямо на цифре 6.
Поздравляю! Вы поставили "точку остановки" или, как чаще говорят, "брейкпойнт".
Выглядет это должно примерно так:
<img src="chrome_sources_breakpoint.png">
Слово *Брейкпойнт* (breakpoint) -- часто используемый английский жаргонизм. Это то место в коде, где отладчик будет *автоматически* останавливать выполнение JavaScript, как только оно до него дойдёт.
@ -62,9 +64,9 @@
Вкладка Breakpoints очень удобна, когда код большой, она позволяет:
<ul>
<li>Быстро перейти на место кода, где стоит брейкпойнт -- кликом на текст.</li>
<li>Временно выключить брейкпойнт -- кликом на чекбокс.</li>
<li>Быстро удалить брейкпойнт -- правым кликом на текст и выбором Remove...</li>
<li>Быстро перейти на место кода, где стоит брейкпойнт кликом на текст.</li>
<li>Временно выключить брейкпойнт кликом на чекбокс.</li>
<li>Быстро удалить брейкпойнт правым кликом на текст и выбором Remove, и так далее.</li>
</ul>
[smart header="Дополнительные возможности"]
@ -89,7 +91,7 @@ function pow(x, n) {
## Остановиться и осмотреться
Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать JavaScript -- перезагрузить её. Итак, нажимаем [key F5] (Windows, Linux) или [key Cmd+R] (Mac).
Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать отладчик JavaScript -- перезагрузить её. Итак, нажимаем [key F5] (Windows, Linux) или [key Cmd+R] (Mac).
Если вы сделали всё, как описано выше, то выполнение прервётся как раз на 6й строке.
@ -117,17 +119,19 @@ function pow(x, n) {
## Управление выполнением
Пришло время "погонять" скрипт и "оттрейсить" (от англ. trace, отслеживать) его работу.
Пришло время, как говорят, "погонять" скрипт и "оттрейсить" (от англ. trace -- отслеживать) его работу.
Обратим внимание на панель управления справа-сверху, в ней есть 6 кнопок:
<dl>
<dt><img style="vertical-align:middle" src="manage1.png"> -- продолжить выполнение, горячая клавиша [key F8].</dt>
<dd> Если скрипт не встретит новых точек остановки, то на этом работа в отладчике закончится.
<dd>Продолжает выполнения скрипта с текущего момента в обычном режиме. Если скрипт не встретит новых точек остановки, то в отладчик управление больше не вернётся.
Нажмите на эту кнопку.
Вы увидите, что отладчик остался на той же строке, но в `Call Stack` появился новый вызов. Это произошло потому, что в 6й строке находится рекурсивный вызов функции `pow`, т.е. управление перешло в неё опять, но с другими аргументами.
Скрипт продолжится, далее, в 6й строке находится рекурсивный вызов функции `pow`, т.е. управление перейдёт в неё опять (с другими аргументами) и сработает точка остановки, вновь включая отладчик.
При этом вы увидите, что выполнение стоит на той же строке, но в `Call Stack` появился новый вызов.
Походите по стеку вверх-вниз -- вы увидите, что действительно аргументы разные.
</dd>
@ -162,17 +166,15 @@ function pow(x, n) {
**Процесс отладки заключается в том, что мы останавливаем скрипт, смотрим, что с переменными, переходим дальше и ищем, где поведение отклоняется от правильного.**
[smart header="Дополнительные возможности"]
Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here).
Это очень удобно, если промежуточные строки нас не интересуют.
[smart header="Continue to here"]
Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here). Это удобно, когда хочется сразу прыгнуть вперёд и breakpoint неохота ставить.
[/smart]
## Консоль
При отладке, кроме просмотра переменных, бывает полезно запускать команды JavaScript. Для этого нужна консоль.
При отладке, кроме просмотра переменных и передвижения по скрипту, бывает полезно запускать команды JavaScript. Для этого нужна консоль.
В неё можно перейти, нажав кнопку "Console" вверху-справа, а можно и открыть в дополнение к отладчику, нажав на кнопку <span class="devtools" style="background-position:-72px -28px"></span> или клавишей [key ESC].
@ -190,13 +192,13 @@ for(var i=0; i<5; i++) {
Полную информацию по специальным командам консоли вы можете получить на странице [](https://developers.google.com/chrome-developer-tools/docs/commandline-api?hl=ru). Эти команды также действуют в Firebug (отладчик для браузера Firefox).
Консоль поддерживают все браузеры, и, хотя IE10- поддерживает далеко не все функции, `console.log` работает везде, пользуйтесь им вместо `alert`.
Консоль поддерживают все браузеры, и, хотя IE10- поддерживает далеко не все функции, но `console.log` работает везде. Используйте его для вывода отладочной информации по ходу работы скрипта.
## Ошибки
Ошибки JavaScript выводятся в консоли.
Например, прервите отладку -- для этого достаточно закрыть инструменты разрабтчика -- и откройте страницу [debugging/pow-error/index.html](/debugging/pow-error/index.html).
Например, прервите отладку -- для этого достаточно закрыть инструменты разрабтчика -- и откройте [страницу с ошибкой](error/index.html).
Перейдите во вкладку Console инструментов разработчика ([key Ctrl+Shift+J] / [key Cmd+Shift+J]).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="pow.js"></script>
Пример для отладчика.
<script>
var fiveInCube = pow(5, 3);
alert( fiveInCube );
</script>
</body>
</html>

View file

@ -0,0 +1,8 @@
function pow(x, n) {
if (n == 1) {
return x;
}
var result = x * pow(x, n-1);
return result;
}

View file

@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="pow.js"></script>
Пример для отладчика.
<script>
var fiveInCube = pow(5, 3);
alert( fiveInCube );
</script>
</body>
</html>

View file

@ -0,0 +1,8 @@
function pow(x, n) {
if (n == 1) {
return y;
}
var result = x * pow(x, n-1);
return result;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

View file

@ -27,11 +27,13 @@ else // <- можно на одной строке } else {
Исправленный вариант:
```js
function pow(x,n) {
var result = 1;
function pow(x, n) {
var result = 1;
for(var i = 0; i < n; i++) {
result *=x;
}
}
return result;
}

View file

@ -5,27 +5,70 @@
[cut]
## Синтаксис
Шпаргалка с правилами синтаксиса:
Шпаргалка с правилами синтаксиса (детально они их варианты разобраны далее):
<img src="cheatsheet.png">
Разберём основные моменты.
<img src="code-style.svg">
<!--
```js
function pow(x, n) {
var result = 1;
for (var i = 0; i < n; i++) {
result *=x;
}
return result;
}
var x = prompt("x?", "");
var n = prompt("n?", "");
if (n < 0) {
alert('Степень ' + n +
'не поддерживается, введите целую степень, большую 0');
} else {
alert( pow(x, n) );
}
```
-->
Не всё здесь однозначно, так что разберём эти правила подробнее.
### Фигурные скобки
Пишутся на той же строке, так называемый "египетский" стиль. Перед скобкой -- пробел.
<img src="figure.png">
<!--
```js
if (n < 0) {alert('Степень ' + n + ' не поддерживается');}
Если у вас уже есть опыт в разработке и вы привыкли делать скобку на отдельной строке -- это тоже вариант. В конце концов, решать вам. Но в основных JavaScript-фреймворках (jQuery, Dojo, Google Closure Library, Mootools, Ext.JS, YUI...) стиль именно такой.
Если условие и код достаточно короткие, например `if (cond) return null;`, то запись в одну строку вполне читаема... Но, как правило, отдельная строка всё равно воспринимается лучше.
if (n < 0) alert('Степень ' + n + ' не поддерживается');
if (n < 0) {
alert('Степень ' + n + ' не поддерживается');
}
```
-->
<img src="figure-bracket-style.svg">
Если у вас уже есть опыт в разработке и вы привыкли делать скобку на отдельной строке -- это тоже вариант. В конце концов, решать вам. Но в большинстве JavaScript-фреймворков стиль именно такой.
Если условие и код достаточно короткие, например `if (cond) return null`, то запись в одну строку вполне читаема... Но, как правило, отдельная строка всё равно воспринимается лучше.
### Длина строки
Максимальную длину строки согласовывают в команде. Как правило, это либо `80`, либо `120` символов, в зависимости от того, какие мониторы у разработчиков.
Более длинные строки необходимо разбивать. Если этого не сделать, то перевод очень длинной строки сделает редактор, и это может быть менее красиво и читаемо.
Более длинные строки необходимо разбивать для улучшения читаемости.
### Отступы
@ -36,42 +79,29 @@
Как правило, используются именно пробелы, т.к. они позволяют сделать более гибкие "конфигурации отступов", чем символ "Tab".
Например:
Например, выровнять аргументы относительно открывающей скобки:
```js
function fib(n) {
*!*
var a = 1;
var b = 1;
*/!*
for (var i = 3; i <= n; i++) {
var c = a + b;
a = b;
b = c;
}
return b;
}
show("Строки" +
" выровнены" +
" строго" +
" одна под другой");
```
Кстати, обратите внимание, переменные в выделенном фрагменте объявлены по вертикали, а не в строку `var a=1, b=1`. Так более наглядно, человеческий глаз лучше воспринимает ("сканирует") вертикально выравненную информацию, нежели по горизонтали. Это известный факт среди дизайнеров и нам, программистам, он тоже будет полезен для лучшей организации кода.
</li>
<li>**Вертикальный отступ, для лучшей разбивки кода -- перевод строки.**
Используется, чтобы разделить логические блоки внутри одной функции. В примере ниже разделены функция `pow`, получение данных `x,n` и их обработка `if`.
Используется, чтобы разделить логические блоки внутри одной функции. В примере разделены инициализация переменных, главный цикл и возвращение результата:
```js
function pow(x, n) {
return (n != 1) ? pow(x, n-1) : x;
}
// <--
x = prompt(...);
n = prompt(...);
// <--
if (n >= 1) {
var result = pow(x, n);
alert(result);
var result = 1;
// <--
for (var i = 0; i < n; i++) {
result *=x;
}
// <--
return result;
}
```
Вставляйте дополнительный перевод строки туда, где это сделает код более читаемым. Не должно быть более 9 строк кода подряд без вертикального отступа.
@ -82,9 +112,7 @@ if (n >= 1) {
Точки с запятой нужно ставить, даже если их, казалось бы, можно пропустить.
Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript она тоже не обязательна, но ставить нужно. В чём же разница?
Она в том, что **в JavaScript без точки с запятой возможны трудноуловимые ошибки.** С некоторыми примерами вы встретитесь дальше в учебнике. Такая вот особенность синтаксиса. И поэтому рекомендуется её всегда ставить.
Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript перевод строки её заменяет, но лишь частично, поэтому лучше её ставить, как обсуждалось [ранее](#semicolon).
## Именование
@ -102,7 +130,7 @@ if (n >= 1) {
Уровней вложенности должно быть немного.
Например, [проверки в циклах лучше делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`:
Например, [проверки в циклах можно делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`:
Вместо:
@ -159,21 +187,15 @@ function isEven(n) { // проверка чётности
В случае с функцией `isEven` можно было бы поступить и проще:
```js
function isEven(n) { // проверка чётности
return n % 2 == 0;
}
```
..Казалось бы, можно пойти дальше, есть ещё более короткий вариант:
```js
function isEven(n) { // проверка чётности
return !(n % 2);
}
```
...Однако, код `!(n % 2)` менее очевиден чем `n % 2 == 0`. Поэтому, на самом деле, последний вариант хуже. **Главное для нас -- не краткость кода, а его простота и читаемость.**
...Однако, если код `!(n % 2)` для вас менее очевиден чем предыдущий вариант, то стоит использовать предыдущий.
Главное для нас -- не краткость кода, а его простота и читаемость. Совсем не всегда более короткий код проще для понимания, чем более развёрнутый.
## Функции = Комментарии
@ -185,7 +207,7 @@ function isEven(n) { // проверка чётности
Сравните, например, две функции `showPrimes(n)` для вывода простых чисел до `n`.
Первый вариант:
Первый вариант использует метку:
```js
function showPrimes(n) {
@ -201,7 +223,7 @@ function showPrimes(n) {
}
```
Второй вариант, вынесена подфункция `isPrime(n)` для проверки на простоту:
Второй вариант -- дополнительную функцию `isPrime(n)` для проверки на простоту:
```js
function showPrimes(n) {
@ -277,28 +299,41 @@ function walkAround() {
</li>
</ol>
...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду ;), но это ведь не наш метод, да?
...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду, но это ведь не наш метод, да?
**Как правило, лучше располагать функции под кодом, который их использует.** То есть, это 2й способ.
**Как правило, лучше располагать функции под кодом, который их использует.**
Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно.
То есть, предпочтителен 2й способ.
У первого способа, впрочем, есть то преимущество, что на момент чтения мы уже знаем, какие функции существуют.
Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно из названия.
Таким образом, если над названиями функций никто не думает -- может быть, это будет лучшим выбором :). Попробуйте оба варианта, но по моей практике предпочтителен всё же второй.
## Комментарии
## Плохие комментарии
В коде нужны комментарии.
**Как правило, комментарии отвечают на вопрос "что происходит в коде?"**
Сразу начну с того, каких комментариев быть почти не должно.
**Должен быть минимум комментариев, которые отвечают на вопрос "что происходит в коде?"**
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках".
Серьёзно, хороший код и так понятен.
Об этом замечательно выразился Р.Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код недостаточно прост, и, может, стоит переписать его".
Если у вас образовалась длинная "простыня", то, возможно, стоит разбить её на отдельные функции, и тогда из их названий будет понятно, что делает тот или иной фрагмент.
Да, конечно, бывают сложные алгоритмы, хитрые решения для оптимизации, поэтому нельзя такие комментарии просто запретить. Но перед тем, как писать подобное -- подумайте: "Нельзя ли сделать код понятным и без них?"
## Хорошие комментарии
А какие комментарии полезны и приветствуются?
Например:
<ul>
<li>**Архитектурный комментарий -- "как оно, вообще, устроено".**
Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Эти комментарии особенно нужны, если вы не один.
Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Взгляд с высоты птичьего полёта. Эти комментарии особенно нужны, если вы не один, а проект большой.
Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно.
</li>
@ -319,18 +354,11 @@ function pow(x, n) {
}
```
Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код.
Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код.
Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении.
</li>
<li>**Краткий комментарий, что именно происходит в данном блоке кода.**
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках кода".
На самом деле именно эти комментарии, как правило, являются самыми ненужными. Хороший код и так самоочевиден, если не используются особо сложные алгоритмы.
Об этом замечательно выразился Р. Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код не достаточно прост, и, может, стоит переписать его".
Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении, а также выводят их в автоподсказках при наборе кода.
Кроме того, есть инструменты, например [JSDoc 3](https://github.com/jsdoc3/jsdoc), которые умеют генерировать по таким комментариям документацию в формате HTML. Более подробную информацию об этом можно также найти на сайте [](http://usejsdoc.org/).
</li>
</ul>
@ -342,9 +370,9 @@ function pow(x, n) {
Например:
<ul>
<li>**Есть несколько способов решения задачи. Почему выбран именно этот?**
<dl>
<dt>Есть несколько способов решения задачи. Почему выбран именно этот?</dt>
<dd>
Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой.
Без этого возможна, например, такая ситуация:
@ -354,18 +382,18 @@ function pow(x, n) {
<li>...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание.</li>
</ul>
Комментарии, которые объясняют поведение кода, очень важны. Они помогают понять происходящее и принять правильное решение о развитии кода.
</li>
<li>**Какие неочевидные возможности обеспечивает этот код?** Где в другом месте кода они используются?
Комментарии, которые объясняют выбор решения, очень важны. Они помогают понять происходящее и предпринять правильные шаги при развитии кода.
</dd>
<dt>Какие неочевидные возможности обеспечивает этот код? Где ещё они используются?</dt>
<dd>
В хорошем коде должно быть минимум неочевидного. Но там, где это есть -- пожалуйста, комментируйте.
</li>
</dd>
</dl>
</ul>
[smart header="Комментарии -- это важно"]
Один из показателей хорошего разработчика -- качество комментариев, которые позволяют эффективно поддерживать код, возвращаться к нему после любой паузы и легко вносить изменения.
[/smart]
## Руководства по стилю
@ -382,21 +410,23 @@ function pow(x, n) {
<li>[Dojo Style Guide](http://dojotoolkit.org/community/styleGuide)</li>
</ul>
Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрите на эти руководства, найдите "свой" стиль ;)
Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрев эти руководства, вы можете выработать и свой стиль, но лучше не делать его особенно "уникальным и неповторимым", себе дороже потом будет с людьми сотрудничать.
### Автоматизированные средства проверки
Существуют онлайн-сервисы, проверяющие стиль кода.
Существуют средства, проверяющие стиль кода.
Самые известные -- это:
<ul>
<li>[JSLint](http://www.jslint.com/) -- проверяет код на соответствие [стилю JSLint](http://www.jslint.com/lint.html), в онлайн-интерфейсе вверху можно ввести код, а внизу различные настройки проверки, чтобы сделать её более мягкой. </li>
<li>[JSHint](http://www.jshint.com/) -- ещё один вариант JSLint, ослабляющий требования в ряде мест.</li>
<li>[JSHint](http://www.jshint.com/) -- вариант JSLint с большим количеством настроек.</li>
<li>[Closure Linter](https://developers.google.com/closure/utilities/) -- проверка на соответствие [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml).</li>
</ul>
Все они также доступны в виде программ, которые можно скачать.
В частности, JSLint и JSHint интегрированы с большинством редакторов, они гибко настраиваются под нужный стиль и совершенно незаметно улучшают разработку, подсказывая, где и что поправить.
Побочный эффект -- они видят некоторые ошибки, например необъявленные переменные. У меня это обычно результат опечатки, которые таким образом сразу отлавливаются. Очень рекомендую поставить что-то из этого. Я использую [JSHint](http://www.jshint.com/).
## Итого

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 125 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

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(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
## Заключение

View file

@ -8,13 +8,11 @@
При написании функции мы обычно представляем, что она должна делать, какое значение -- на каких аргументах выдавать.
В процессе разработки мы, время от времени, проверяем функцию. Самый простой способ проверки -- это запустить функцию и посмотреть результат.
В процессе разработки мы, время от времени, проверяем, правильно ли работает функция. Самый простой способ проверить -- это запустить её, например, в консоли, и посмотреть результат.
Потом написать ещё код, попробовать запустить -- опять посмотреть результат.
Если что-то не так -- поправить, опять запустить -- посмотреть результат... И так -- "до победного конца".
И так -- "до победного конца".
К сожалению, такие ручные запуски -- очень несовершенное средство проверки.
Но такие ручные запуски -- очень несовершенное средство проверки.
**Когда проверяешь работу кода вручную -- легко его "недотестировать".**
@ -28,15 +26,14 @@
BDD -- это не просто тесты. Это гораздо больше.
**Тесты BDD -- это три в одном: это И тесты И документация И примеры использования одновременно.**
**Тесты BDD -- это три в одном: И тесты И документация И примеры использования одновременно.**
Впрочем, хватит слов. Рассмотрим примеры.
## Разработка pow
## Разработка pow: спецификация
Допустим, мы хотим разработать функцию `pow(x, n)`, которая возводит `x` в целую степень `n`, для простоты `n≥0`.
### Спецификация
Ещё до разработки мы можем представить себе, что эта функция будет делать и описать это по методике BDD.
@ -61,9 +58,15 @@ describe("pow", function() {
<dt>`assert.equal(value1, value2)`</dt>
<dd>Код внутри `it`, если реализация верна, должен выполняться без ошибок.
Для того, чтобы проверить, делает ли `pow` то, что задумано, используются функции вида `assert.*`. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. Есть и другие виды сравнений и проверок, которые мы увидим далее.</dd>
Различные функции вида `assert.*` используются, чтобы проверить, делает ли `pow` то, что задумано. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат `pow(2, 3)` равен `8`.
Есть и другие виды сравнений и проверок, которые мы увидим далее.</dd>
</dl>
## Поток разработки
Как правило, поток разработки таков:
<ol>
<li>Пишется спецификация, которая описывает самый базовый функционал.</li>
@ -77,7 +80,7 @@ describe("pow", function() {
В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают.
### Проверка спецификации
## Пример в действии
Для запуска тестов нужны соответствующие JavaScript-библиотеки.
@ -96,22 +99,23 @@ describe("pow", function() {
<!--+ src="index.html" -->
```
Эту страницу можно условно разделить на три части:
Эту страницу можно условно разделить на четыре части:
<ol>
<li>В `<head>` подключаем библиотеки и стили.</li>
<li>Подключаем `<script>` с реализацией, в нашем случае -- с кодом для `pow`. Пока что функции нет, мы лишь готовимся её написать.</li>
<li>Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше. Их вызов добавляет тесты, для запуска которых используется команда `mocha.run()`. Она выведет результат тестов в элемент с `id="mocha"`.</li>
<li>Блок `<head>` -- в нём мы подключаем библиотеки и стили для тестирования, нашего кода там нет.</li>
<li>Блок `<script>` с реализацией спецификации, в нашем случае -- с кодом для `pow`.</li>
<li>Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше.</li>
<li>Элемент `<div id="mocha">` будет использоваться библиотекой Mocha для вывода результатов. Запуск тестов инициируется командой `mocha.run()`.</li>
</ol>
Результат срабатывания:
[iframe height=250 src="pow-1" border=1 edit]
Пока что тесты не проходят, но это логично -- вместо функции стоит "заглушка", пустой код.
Пока что у нас одна функция и одна спецификация, но на будущее заметим, что если различных функций и тестов много -- это не проблема, можно их все подключить на одной странице. Конфликта не будет, так как каждый функционал имеет свой блок `describe("что тестируем"...)`. Сами же тесты обычно пишутся так, чтобы не влиять друг на друга, не делать лишних глобальных переменных.
Посмотрели, попробовали запустить у себя что-то подобное? Если да -- идём дальше.
### Начальная реализация
## Начальная реализация
Пока что, как видно, тесты не проходят, ошибка сразу же. Давайте сделаем минимальную реализацию `pow`, которая бы работала нормально:
@ -125,7 +129,7 @@ function pow() {
[iframe height=250 src="pow-min" border=1 edit]
### Расширение спецификации
## Исправление спецификации
Функция, конечно, ещё не готова, но тесты проходят. Это ненадолго :)
@ -192,9 +196,9 @@ describe("pow", function() {
Как и следовало ожидать, второй тест не проходит. Ещё бы, ведь функция всё время возвращает `8`.
### Уточнение реализации
## Уточнение реализации
Придётся написать нечто более реальное:
Придётся написать нечто более реальное, чтобы тесты проходили:
```js
function pow(x, n) {
@ -231,7 +235,7 @@ describe("pow", function() {
[iframe height=250 src="pow-3" edit border="1"]
### Вложенный describe
## Вложенный describe
Функция `makeTest` и цикл `for`, очевидно, нужны друг другу, но не нужны для других тестов, которые мы добавим в дальнейшем. Они образуют единую группу, задача которой -- проверить возведение в `n`-ю степень.
@ -307,9 +311,9 @@ describe("Тест", function() {
Как правило, `beforeEach/afterEach` (`before/each`) используют, если необходимо произвести инициализацию, обнулить счётчики или сделать что-то ещё в таком духе между тестами (или их группами).
[/smart]
### Расширение спецификации
## Расширение спецификации
Базовый функционал описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его.
Базовый функционал `pow` описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его.
Как говорилось ранее, функция `pow(x, n)` предназначена для работы с целыми неотрицательными `n`.
@ -342,7 +346,7 @@ describe("pow", function() {
Конечно, новые тесты не проходят, так как наша реализация ещё не поддерживает их. Так и задумано: сначала написали заведомо не работающие тесты, а затем пишем реализацию под них.
### Другие assert
## Другие assert
Обратим внимание, в спецификации выше использована проверка не `assert.equal`, как раньше, а `assert(выражение)`. Такая проверка выдаёт ошибку, если значение выражения при приведении к логическому типу не `true`.
@ -352,7 +356,7 @@ describe("pow", function() {
Однако, между этими вызовами есть отличие в деталях сообщения об ошибке.
При `assert` мы видим `Unspecified AssertionError`, то есть просто "что-то пошло не так", а при `assert.equal(value1, value2)` -- будут дополнительные подробности: `expected 8 to equal 81`.
При "упавшем" `assert` в примере выше мы видим `"Unspecified AssertionError"`, то есть просто "что-то пошло не так", а при `assert.equal(value1, value2)` -- будут дополнительные подробности: `expected 8 to equal 81`.
**Поэтому рекомендуется использовать именно ту проверку, которая максимально соответствует задаче.**

View file

@ -1,7 +1,7 @@
describe("Тест", function() {
before(function() { alert("Начало тестов"); });
after(function() { alert("Конец тестов"); });
before(function() { alert("Начало всех тестов"); });
after(function() { alert("Окончание всех тестов"); });
beforeEach(function() { alert("Вход в тест"); });
afterEach(function() { alert("Выход из теста"); });

View file

@ -1,44 +0,0 @@
describe("pow", function() {
describe("возводит x в степень n", function() {
function makeTest(x) {
var expected = x*x*x;
it("при возведении "+x+" в степень 3 результат: " + expected, function() {
assert.equal( pow(x, 3), expected);
});
}
for(var x = 1; x <= 5; x++) {
makeTest(x);
}
});
it("при возведении в отрицательную степень результат NaN", function() {
assert( isNaN( pow(2, -1) ), "pow(2, -1) не NaN" );
});
it("при возведении в дробную степень результат NaN", function() {
assert( isNaN( pow(2, 1.5) ), "pow(2, -1.5) не NaN" );
});
describe("любое число, кроме нуля, в степени 0 равно 1", function() {
function makeTest(x) {
it("при возведении " + x + " в степень 0 результат: 1", function() {
assert.equal( pow(x, 0), 1);
});
}
for(var x = -5; x <= 5; x+=2) {
makeTest(x);
}
});
it("ноль в нулевой степени даёт NaN", function() {
assert( isNaN( pow(0,0) ), "0 в степени 0 не NaN");
});
});