update
254
1-js/3-writing-js/1-debugging-chrome/article.md
Normal file
|
@ -0,0 +1,254 @@
|
|||
# Отладка в браузере Chrome
|
||||
|
||||
Перед тем, как двигаться дальше, поговорим об отладке скриптов.
|
||||
|
||||
Все современные браузеры поддерживают для этого "инструменты разработчика". Исправление ошибок с их помощью намного проще и быстрее.
|
||||
|
||||
На текущий момент самые многофункциональные инструменты -- в браузере Chrome. Также очень хорош Firebug (для Firefox).
|
||||
|
||||
[cut]
|
||||
|
||||
## Общий вид панели Sources
|
||||
|
||||
В вашей версии Chrome панель может выглядеть несколько по-иному, но что где находится, должно быть понятно.
|
||||
|
||||
Зайдите на страницу [debugging/pow/index.html](/debugging/pow/index.html) браузером Chrome.
|
||||
|
||||
Откройте инструменты разработчика: [key F12] или в меню `Инструменты > Инструменты Разработчика`.
|
||||
|
||||
Выберите сверху `Sources` (вместо иконок у вас могут быть просто надписи "Elements", "Resources", "Network", "Sources"...)
|
||||
|
||||
<img src="chrome_sources.png">
|
||||
|
||||
Вы видите три зоны:
|
||||
|
||||
<ol>
|
||||
<li>**Зона исходных файлов.** В ней находятся все подключённые к странице файлы, включая JS/CSS. Выберите `pow.js`, если он не выбран.</li>
|
||||
<li>**Зона текста.** В ней находится текст файлов.</li>
|
||||
<li>**Зона информации и контроля.** Мы поговорим о ней позже.</li>
|
||||
</ol>
|
||||
|
||||
Обычно зона исходных файлов при отладке не нужна. Скройте её кнопкой <span class="devtools" style="background-position:-200px -76px"></span>.
|
||||
|
||||
## Общие кнопки управления
|
||||
|
||||
<img src="chrome_sources_buttons.png">
|
||||
|
||||
Три полезные кнопки управления:
|
||||
<dl>
|
||||
<dt>Формат <span class="devtools" style="background-position:-264px 94px"></span></dt>
|
||||
<dd>Нажатие форматирует текст текущего файла, расставляет отступы. Нужна, если вы хотите разобраться в чужом коде, плохо отформатированном или сжатом.</dd>
|
||||
<dt>Консоль <span class="devtools" style="background-position:-70px 94px"></span></dt>
|
||||
<dd>Очень полезная кнопка, открывает тут же консоль для запуска команд. Можно смотреть код и тут же запускайть функции. Её нажатие можно заменить на клавишу <code class="key">Esc</code>.</dd>
|
||||
<dt>Окно <span class="devtools" style="background-position:-6px 70px"></span></dt>
|
||||
<dd>Если код очень большой, то можно вынести инструменты разработки вбок или в отдельное окно, зажав эту кнопку и выбрав соответствующий вариант из списка.</dd>
|
||||
</dl>
|
||||
|
||||
## Точки остановки
|
||||
|
||||
Открыли `pow.js` в зоне текста? Кликните на 6й строке файла `pow.js`, прямо на цифре 6.
|
||||
|
||||
Поздравляю! Вы поставили "точку остановки" или, как чаще говорят, "брейкпойнт".
|
||||
|
||||
<img src="chrome_sources_breakpoint.png">
|
||||
|
||||
Слово *Брейкпойнт* (breakpoint) -- часто используемый английский жаргонизм. Это то место в коде, где отладчик будет *автоматически* останавливать выполнение JavaScript, как только оно до него дойдёт.
|
||||
|
||||
|
||||
**В остановленном коде можно посмотреть текущие значения переменных, выполнять команды и т.п., в общем -- отлаживать его.**
|
||||
|
||||
Вы можете видеть, что информация о точке остановки появилась справа, в подвкладке Breakpoints.
|
||||
|
||||
Вкладка Breakpoints очень удобна, когда код большой, она позволяет:
|
||||
|
||||
<ul>
|
||||
<li>Быстро перейти на место кода, где стоит брейкпойнт -- кликом на текст.</li>
|
||||
<li>Временно выключить брейкпойнт -- кликом на чекбокс.</li>
|
||||
<li>Быстро удалить брейкпойнт -- правым кликом на текст и выбором Remove...</li>
|
||||
</ul>
|
||||
|
||||
[smart header="Дополнительные возможности"]
|
||||
<ul>
|
||||
<li>Остановку можно инициировать и напрямую из кода скрипта, командой `debugger`:
|
||||
|
||||
```js
|
||||
function pow(x, n) {
|
||||
...
|
||||
debugger; // <-- отладчик остановится тут
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>*Правый клик* на номер строки `pow.js` позволит создать условную точку остановки (conditional breakpoint), т.е. задать условие, при котором точка остановки сработает.
|
||||
|
||||
Это удобно, если остановка нужна только при определённом значении переменной или параметра функции.
|
||||
</li>
|
||||
</ul>
|
||||
[/smart]
|
||||
|
||||
## Остановиться и осмотреться
|
||||
|
||||
Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать JavaScript -- перезагрузить её. Итак, нажимаем [key F5] (Windows, Linux) или [key Cmd+R] (Mac).
|
||||
|
||||
Если вы сделали всё, как описано выше, то выполнение прервётся как раз на 6й строке.
|
||||
|
||||
<img src="chrome_sources_break.png">
|
||||
|
||||
Обратите внимание на информационные вкладки справа (отмечены стрелками).
|
||||
|
||||
В них мы можем посмотреть текущее состояние:
|
||||
<ol>
|
||||
<li>**`Watch Expressions` -- показывает текущие значения любых выражений.**
|
||||
|
||||
Можно раскрыть эту вкладку, нажать мышью `+` на ней и ввести любое выражение. Отладчик будет отображать его значение на текущий момент, автоматически перевычисляя его при проходе по коду.</li>
|
||||
<li>**`Call Stack` -- стек вызовов, все вложенные вызовы, которые привели к текущему месту кода.**
|
||||
|
||||
На текущий момент видно, отладчик находится в функции `pow` (pow.js, строка 6), вызванной из анонимного кода (index.html, строка 13).</li>
|
||||
<li>**`Scope Variables` -- переменные.**
|
||||
|
||||
На текущий момент строка 6 ещё не выполнилась, поэтому `result` равен `undefined`.
|
||||
|
||||
В `Local` показываются переменные функции: объявленные через `var` и параметры. Вы также можете там видеть ключевое слово `this`, если вы не знаете, что это такое -- ничего страшного, мы это обсудим позже, в следующих главах учебника.
|
||||
|
||||
В `Global` -- глобальные переменные и функции.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
## Управление выполнением
|
||||
|
||||
Пришло время "погонять" скрипт и "оттрейсить" (от англ. trace, отслеживать) его работу.
|
||||
|
||||
Обратим внимание на панель управления справа-сверху, в ней есть 6 кнопок:
|
||||
|
||||
<dl>
|
||||
<dt><img style="vertical-align:middle" src="manage1.png"> -- продолжить выполнение, горячая клавиша [key F8].</dt>
|
||||
<dd> Если скрипт не встретит новых точек остановки, то на этом работа в отладчике закончится.
|
||||
|
||||
Нажмите на эту кнопку.
|
||||
|
||||
Вы увидите, что отладчик остался на той же строке, но в `Call Stack` появился новый вызов. Это произошло потому, что в 6й строке находится рекурсивный вызов функции `pow`, т.е. управление перешло в неё опять, но с другими аргументами.
|
||||
|
||||
Походите по стеку вверх-вниз -- вы увидите, что действительно аргументы разные.
|
||||
</dd>
|
||||
<dt><img style="vertical-align:middle" src="manage2.png"> -- сделать шаг, не заходя внутрь функции, горячая клавиша [key F10].</dt>
|
||||
<dd>Выполняет одну команду скрипта. Если в ней есть вызов функции -- то отладчик обходит его стороной, т.е. не переходит на код внутри.
|
||||
|
||||
Эта кнопка очень удобна, если в текущей строке вызывается функция JS-фреймворка или какая-то другая, которая нас ну совсем не интересует. Тогда выполнение продолжится дальше, без захода в эту функцию, что нам и нужно.
|
||||
|
||||
Обратим внимание, в данном случае эта кнопка при нажатии всё-таки перейдёт внутрь вложенного вызова `pow`, так как внутри `pow` находится брейкпойнт, а на включённых брейкпойнтах отладчик останавливается всегда.
|
||||
</dd>
|
||||
<dt><img style="vertical-align:middle" src="manage3.png"> -- сделать шаг, горячая клавиша [key F11].</dt>
|
||||
<dd>Выполняет одну команду скрипта и переходит к следующей. Если есть вложенный вызов, то заходит внутрь функции.
|
||||
|
||||
Эта кнопка позволяет подробнейшим образом пройтись по очереди по командам скрипта.
|
||||
</dd>
|
||||
<dt><img style="vertical-align:middle" src="manage4.png"> -- выполнять до выхода из текущей функции, горячая клавиша [key Shift+F11].</dt>
|
||||
<dd>Выполняет команды до завершения текущей функции.
|
||||
|
||||
Эта кнопка очень удобна в случае, если мы нечаянно вошли во вложенный вызов, который нам не интересен -- чтобы быстро из него выйти.
|
||||
</dd>
|
||||
<dt><img style="vertical-align:middle" src="manage5.png"> -- отключить/включить все точки остановки.</dt>
|
||||
<dd>Эта кнопка никак не двигает нас по коду, она позволяет временно отключить все точки остановки в файле.
|
||||
</dd>
|
||||
<dt><img style="vertical-align:middle" src="manage6.png"> -- включить/отключить автоматическую остановку при ошибке.</dt>
|
||||
<dd>Эта кнопка -- одна из самых важных.
|
||||
|
||||
Нажмите её несколько раз. В старых версиях Chrome у неё три режима -- нужен фиолетовый, в новых -- два, тогда достаточно синего.
|
||||
|
||||
Когда она включена, то при ошибке в коде он автоматически остановится и мы сможем посмотреть в отладчике текущие значения переменных, при желании выполнить команды и выяснить, как так получилось.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
**Процесс отладки заключается в том, что мы останавливаем скрипт, смотрим, что с переменными, переходим дальше и ищем, где поведение отклоняется от правильного.**
|
||||
|
||||
[smart header="Дополнительные возможности"]
|
||||
Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here).
|
||||
|
||||
Это очень удобно, если промежуточные строки нас не интересуют.
|
||||
[/smart]
|
||||
|
||||
|
||||
|
||||
## Консоль
|
||||
|
||||
При отладке, кроме просмотра переменных, бывает полезно запускать команды JavaScript. Для этого нужна консоль.
|
||||
|
||||
В неё можно перейти, нажав кнопку "Console" вверху-справа, а можно и открыть в дополнение к отладчику, нажав на кнопку <span class="devtools" style="background-position:-72px -28px"></span> или клавишей [key ESC].
|
||||
|
||||
**Самая любимая команда разработчиков: `console.log(...)`.**
|
||||
|
||||
Она пишет переданные ей аргументы в консоль, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// результат будет виден в консоли
|
||||
for(var i=0; i<5; i++) {
|
||||
console.log("значение", i);
|
||||
}
|
||||
```
|
||||
|
||||
Полную информацию по специальным командам консоли вы можете получить на странице [](https://developers.google.com/chrome-developer-tools/docs/commandline-api?hl=ru). Эти команды также действуют в Firebug (отладчик для браузера Firefox).
|
||||
|
||||
Консоль поддерживают все браузеры, и, хотя IE10- поддерживает далеко не все функции, `console.log` работает везде, пользуйтесь им вместо `alert`.
|
||||
|
||||
## Ошибки
|
||||
|
||||
Ошибки JavaScript выводятся в консоли.
|
||||
|
||||
Например, прервите отладку -- для этого достаточно закрыть инструменты разрабтчика -- и откройте страницу [debugging/pow-error/index.html](/debugging/pow-error/index.html).
|
||||
|
||||
Перейдите во вкладку Console инструментов разработчика ([key Ctrl+Shift+J] / [key Cmd+Shift+J]).
|
||||
|
||||
В консоли вы увидите что-то подобное:
|
||||
<img src="console_error.png">
|
||||
|
||||
Красная строка -- это сообщение об ошибке.
|
||||
|
||||
В чём дело? Если мы хотим понять, что случилось -- перейдём в отладчик.
|
||||
|
||||
Для этого:
|
||||
<ol>
|
||||
<li>Перейдите на вкладку Sources.</li>
|
||||
<li>Включите останов при ошибке, используя кнопку <img style="vertical-align:middle" src="manage6.png">.</li>
|
||||
<li>Перезагрузите страницу.</li>
|
||||
</ol>
|
||||
|
||||
После перезагрузки страницы JavaScript-код запустится снова и отладчик остановит выполнение на строке с ошибкой:
|
||||
|
||||
<img src="chrome_break_error.png">
|
||||
|
||||
Можно посмотреть значения переменных. Открыть консоль и попробовать запустить что-то в ней. Поставить брейкпойнты раньше по коду и посмотреть, что привело к такой печальной картине, и так далее.
|
||||
|
||||
В данном случае-то всё просто: опечатка в имени переменной `y` вместо `x`. Этот тип ошибки называется `ReferenceError`.
|
||||
|
||||
## Итого
|
||||
|
||||
Отладчик позволяет:
|
||||
<ul>
|
||||
<li>Останавливаться на отмеченном месте (breakpoint) или по команде `debugger`.</li>
|
||||
<li>Выполнять код -- по одной строке или до определённого места.</li>
|
||||
<li>Смотреть переменные, выполнять команды в консоли и т.п.</li>
|
||||
</ul>
|
||||
|
||||
В этой главе кратко описаны возможности отладчика Google Chrome, относящиеся именно к работе с кодом.
|
||||
|
||||
Пока что это всё, что нам надо, но, конечно, инструменты разработчика умеют много чего ещё. В частности, вкладка Elements -- позволяет работать со страницей (понадобится позже), Timeline -- смотреть, что именно делает браузер и сколько это у него занимает и т.п.
|
||||
|
||||
Осваивать можно двумя путями:
|
||||
<ol>
|
||||
<li>[Официальная документация](https://developers.google.com/chrome-developer-tools/docs/overview) (на англ.)</li>
|
||||
<li>Кликать в разных местах и смотреть, что получается. Не забывать о клике правой кнопкой мыши.</li>
|
||||
</ol>
|
||||
|
||||
Мы ещё вернёмся к отладчику позже, когда будем работать с HTML.
|
||||
[head]
|
||||
<style>
|
||||
span.devtools {
|
||||
display: inline-block;
|
||||
background-image: url(/article/debugging-chrome/statusbarButtonGlyphs.svg);
|
||||
height:16px;
|
||||
width:16px;
|
||||
}
|
||||
</style>
|
||||
[/head]
|
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_break_error.png
Executable file
After Width: | Height: | Size: 84 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_break_error@2x.png
Executable file
After Width: | Height: | Size: 154 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources.png
Executable file
After Width: | Height: | Size: 81 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources@2x.png
Executable file
After Width: | Height: | Size: 264 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources_break.png
Executable file
After Width: | Height: | Size: 82 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources_break@2x.png
Executable file
After Width: | Height: | Size: 153 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources_breakpoint.png
Executable file
After Width: | Height: | Size: 68 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources_breakpoint@2x.png
Executable file
After Width: | Height: | Size: 225 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources_buttons.png
Executable file
After Width: | Height: | Size: 70 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/chrome_sources_buttons@2x.png
Executable file
After Width: | Height: | Size: 130 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/console_error.png
Executable file
After Width: | Height: | Size: 25 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/console_error@2x.png
Executable file
After Width: | Height: | Size: 50 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/manage1.png
Executable file
After Width: | Height: | Size: 717 B |
BIN
1-js/3-writing-js/1-debugging-chrome/manage1@2x.png
Executable file
After Width: | Height: | Size: 3 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/manage2.png
Executable file
After Width: | Height: | Size: 504 B |
BIN
1-js/3-writing-js/1-debugging-chrome/manage2@2x.png
Executable file
After Width: | Height: | Size: 3.5 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/manage3.png
Executable file
After Width: | Height: | Size: 431 B |
BIN
1-js/3-writing-js/1-debugging-chrome/manage3@2x.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/manage4.png
Executable file
After Width: | Height: | Size: 418 B |
BIN
1-js/3-writing-js/1-debugging-chrome/manage4@2x.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/manage5.png
Executable file
After Width: | Height: | Size: 554 B |
BIN
1-js/3-writing-js/1-debugging-chrome/manage5@2x.png
Executable file
After Width: | Height: | Size: 3.6 KiB |
BIN
1-js/3-writing-js/1-debugging-chrome/manage6.png
Executable file
After Width: | Height: | Size: 463 B |
BIN
1-js/3-writing-js/1-debugging-chrome/manage6@2x.png
Executable file
After Width: | Height: | Size: 3 KiB |
1735
1-js/3-writing-js/1-debugging-chrome/statusbarButtonGlyphs.svg
Executable file
After Width: | Height: | Size: 67 KiB |
48
1-js/3-writing-js/2-coding-style/1-style-errors/solution.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Ответ
|
||||
|
||||
Вы могли заметить следующие недостатки, сверху-вниз:
|
||||
|
||||
```js
|
||||
function pow(x,n) // <- отсутствует пробел между аргументами
|
||||
{ // <- фигурная скобка на отдельной строке
|
||||
var result=1; // <- нет пробелов вокруг знака =
|
||||
for(var i=0;i<n;i++) {result*=x;} // <- нет пробелов
|
||||
// содержимое скобок { ... } лучше вынести на отдельную строку
|
||||
return result;
|
||||
}
|
||||
|
||||
x=prompt("x?",'') // <- не объявлена переменная, нет пробелов, ;
|
||||
n=prompt("n?",'')
|
||||
if (n<0) // <- нет пробелов, стоит добавить вертикальную отбивку
|
||||
{ // <- фигурная скобка на отдельной строке
|
||||
// ниже - слишком длинная строка, нет пробелов
|
||||
alert('Степень '+n+'не поддерживается, введите целую степень, большую 0');
|
||||
}
|
||||
else // <- можно на одной строке } else {
|
||||
{
|
||||
alert(pow(x,n)) // нет точки с запятой
|
||||
}
|
||||
```
|
||||
|
||||
Исправленный вариант:
|
||||
|
||||
```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) );
|
||||
}
|
||||
```
|
||||
|
26
1-js/3-writing-js/2-coding-style/1-style-errors/task.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Ошибки в стиле
|
||||
|
||||
[importance 4]
|
||||
|
||||
Какие недостатки вы видите в стиле этого примера?
|
||||
|
||||
```js
|
||||
function pow(x,n)
|
||||
{
|
||||
var result=1;
|
||||
for(var i=0;i<n;i++) {result*=x;}
|
||||
return result;
|
||||
}
|
||||
|
||||
x=prompt("x?",'')
|
||||
n=prompt("n?",'')
|
||||
if (n<=0)
|
||||
{
|
||||
alert('Степень '+n+'не поддерживается, введите целую степень, большую 0');
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(pow(x,n))
|
||||
}
|
||||
```
|
||||
|
406
1-js/3-writing-js/2-coding-style/article.md
Normal file
|
@ -0,0 +1,406 @@
|
|||
# Советы по стилю кода
|
||||
|
||||
Код должен быть максимально читаемым и понятным. Для этого нужен *хороший стиль* написания кода. В этой главе мы рассмотрим компоненты такого стиля.
|
||||
|
||||
[cut]
|
||||
## Синтаксис
|
||||
|
||||
Шпаргалка с правилами синтаксиса:
|
||||
|
||||
<img src="cheatsheet.png">
|
||||
|
||||
Разберём основные моменты.
|
||||
|
||||
### Фигурные скобки
|
||||
|
||||
Пишутся на той же строке, так называемый "египетский" стиль. Перед скобкой -- пробел.
|
||||
|
||||
<img src="figure.png">
|
||||
|
||||
Если у вас уже есть опыт в разработке и вы привыкли делать скобку на отдельной строке -- это тоже вариант. В конце концов, решать вам. Но в основных JavaScript-фреймворках (jQuery, Dojo, Google Closure Library, Mootools, Ext.JS, YUI...) стиль именно такой.
|
||||
|
||||
Если условие и код достаточно короткие, например `if (cond) return null;`, то запись в одну строку вполне читаема... Но, как правило, отдельная строка всё равно воспринимается лучше.
|
||||
|
||||
### Длина строки
|
||||
|
||||
Максимальную длину строки согласовывают в команде. Как правило, это либо `80`, либо `120` символов, в зависимости от того, какие мониторы у разработчиков.
|
||||
|
||||
Более длинные строки необходимо разбивать. Если этого не сделать, то перевод очень длинной строки сделает редактор, и это может быть менее красиво и читаемо.
|
||||
|
||||
### Отступы
|
||||
|
||||
Отступы нужны двух типов:
|
||||
|
||||
<ul>
|
||||
<li>**Горизонтальный отступ, при вложенности -- два(или четыре) пробела.**
|
||||
|
||||
Как правило, используются именно пробелы, т.к. они позволяют сделать более гибкие "конфигурации отступов", чем символ "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;
|
||||
}
|
||||
```
|
||||
|
||||
Кстати, обратите внимание, переменные в выделенном фрагменте объявлены по вертикали, а не в строку `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);
|
||||
}
|
||||
```
|
||||
|
||||
Вставляйте дополнительный перевод строки туда, где это сделает код более читаемым. Не должно быть более 9 строк кода подряд без вертикального отступа.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### Точка с запятой
|
||||
|
||||
Точки с запятой нужно ставить, даже если их, казалось бы, можно пропустить.
|
||||
|
||||
Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript она тоже не обязательна, но ставить нужно. В чём же разница?
|
||||
|
||||
Она в том, что **в JavaScript без точки с запятой возможны трудноуловимые ошибки.** С некоторыми примерами вы встретитесь дальше в учебнике. Такая вот особенность синтаксиса. И поэтому рекомендуется её всегда ставить.
|
||||
|
||||
## Именование
|
||||
|
||||
Общее правило:
|
||||
<ul>
|
||||
<li>Имя переменной -- существительное.</li>
|
||||
<li>Имя функции -- глагол или начинается с глагола. Бывает, что имена для краткости делают существительными, но глаголы понятнее.</li>
|
||||
</ul>
|
||||
|
||||
Для имён используется английский язык (не транслит) и верблюжья нотация.
|
||||
|
||||
Более подробно -- читайте про [имена функций](#function-naming) и [имена переменных](#variable-naming).
|
||||
|
||||
## Уровни вложенности
|
||||
|
||||
Уровней вложенности должно быть немного.
|
||||
|
||||
Например, [проверки в циклах лучше делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`:
|
||||
|
||||
Вместо:
|
||||
|
||||
```js
|
||||
for (var i=0; i<10; i++) {
|
||||
if (i подходит) {
|
||||
... // <- уровень вложенности 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Используйте:
|
||||
|
||||
```js
|
||||
for (var i=0; i<10; i++) {
|
||||
if (i *!*не*/!* подходит) *!*continue*/!*;
|
||||
... // <- уровень вложенности 1
|
||||
}
|
||||
```
|
||||
|
||||
Аналогичная ситуация -- с `if/else` и `return`. Следующие две конструкции идентичны.
|
||||
|
||||
Первая:
|
||||
|
||||
```js
|
||||
function isEven(n) { // проверка чётности
|
||||
if (n % 2 == 0) {
|
||||
return true;
|
||||
*!*
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
```
|
||||
|
||||
Вторая:
|
||||
|
||||
```js
|
||||
function isEven(n) { // проверка чётности
|
||||
if (n % 2 == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
*!*
|
||||
return false;
|
||||
*/!*
|
||||
}
|
||||
```
|
||||
|
||||
Если в блоке `if` идёт `return`, то `else` за ним не нужен.
|
||||
|
||||
**Лучше быстро обработать простые случаи, вернуть результат, а дальше разбираться со сложным, без дополнительного уровня вложенности.**
|
||||
|
||||
В случае с функцией `isEven` можно было бы поступить и проще:
|
||||
|
||||
```js
|
||||
function isEven(n) { // проверка чётности
|
||||
return n % 2 == 0;
|
||||
}
|
||||
```
|
||||
|
||||
..Казалось бы, можно пойти дальше, есть ещё более короткий вариант:
|
||||
|
||||
```js
|
||||
function isEven(n) { // проверка чётности
|
||||
return !(n % 2);
|
||||
}
|
||||
```
|
||||
|
||||
...Однако, код `!(n % 2)` менее очевиден чем `n % 2 == 0`. Поэтому, на самом деле, последний вариант хуже. **Главное для нас -- не краткость кода, а его простота и читаемость.**
|
||||
|
||||
## Функции = Комментарии
|
||||
|
||||
Функции должны быть небольшими. Если функция большая -- желательно разбить её на несколько.
|
||||
|
||||
Этому правилу бывает сложно следовать, но оно стоит того. При чем же здесь комментарии?
|
||||
|
||||
Вызов отдельной небольшой функции не только легче отлаживать и тестировать -- сам факт его наличия является *отличным комментарием*.
|
||||
|
||||
Сравните, например, две функции `showPrimes(n)` для вывода простых чисел до `n`.
|
||||
|
||||
Первый вариант:
|
||||
|
||||
```js
|
||||
function showPrimes(n) {
|
||||
nextPrime:
|
||||
for (var i=2; i<n; i++) {
|
||||
|
||||
for (var j=2; j<i; j++) {
|
||||
if ( i % j == 0) continue nextPrime;
|
||||
}
|
||||
|
||||
alert(i); // простое
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Второй вариант, вынесена подфункция `isPrime(n)` для проверки на простоту:
|
||||
|
||||
```js
|
||||
function showPrimes(n) {
|
||||
|
||||
for (var i=2; i<n; i++) {
|
||||
*!*if (!isPrime(i)) continue;*/!*
|
||||
|
||||
alert(i); // простое
|
||||
}
|
||||
}
|
||||
|
||||
function isPrime(n) {
|
||||
for (var i=2; i<n; i++) {
|
||||
if ( n % i == 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
Второй вариант проще и понятнее, не правда ли? Вместо участка кода мы видим описание действия, которое там совершается (проверка `isPrime`).
|
||||
|
||||
## Функции -- под кодом
|
||||
|
||||
Есть два способа расположить функции, необходимые для выполнения кода.
|
||||
|
||||
<ol>
|
||||
<li>Функции над кодом, который их использует:
|
||||
|
||||
```js
|
||||
// *!*объявить функции*/!*
|
||||
function createElement() {
|
||||
...
|
||||
}
|
||||
|
||||
function setHandler(elem) {
|
||||
...
|
||||
}
|
||||
|
||||
function walkAround() {
|
||||
...
|
||||
}
|
||||
|
||||
// *!*код, использующий функции*/!*
|
||||
var elem = createElement();
|
||||
setHandler(elem);
|
||||
walkAround();
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Сначала код, а функции внизу:
|
||||
|
||||
```js
|
||||
// *!*код, использующий функции*/!*
|
||||
var elem = createElement();
|
||||
setHandler(elem);
|
||||
walkAround();
|
||||
|
||||
// --- *!*функции*/!* ---
|
||||
|
||||
function createElement() {
|
||||
...
|
||||
}
|
||||
|
||||
function setHandler(elem) {
|
||||
...
|
||||
}
|
||||
|
||||
function walkAround() {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду ;), но это ведь не наш метод, да?
|
||||
|
||||
**Как правило, лучше располагать функции под кодом, который их использует.** То есть, это 2й способ.
|
||||
|
||||
Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно.
|
||||
|
||||
У первого способа, впрочем, есть то преимущество, что на момент чтения мы уже знаем, какие функции существуют.
|
||||
|
||||
Таким образом, если над названиями функций никто не думает -- может быть, это будет лучшим выбором :). Попробуйте оба варианта, но по моей практике предпочтителен всё же второй.
|
||||
|
||||
|
||||
## Комментарии
|
||||
|
||||
В коде нужны комментарии.
|
||||
|
||||
**Как правило, комментарии отвечают на вопрос "что происходит в коде?"**
|
||||
|
||||
Например:
|
||||
<ul>
|
||||
<li>**Архитектурный комментарий -- "как оно, вообще, устроено".**
|
||||
|
||||
Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Эти комментарии особенно нужны, если вы не один.
|
||||
|
||||
Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно.
|
||||
</li>
|
||||
<li>**Справочный комментарий перед функцией -- о том, что именно она делает, какие параметры принимает и что возвращает.**
|
||||
|
||||
Для таких комментариев существует синтаксис [JSDoc](http://en.wikipedia.org/wiki/JSDoc).
|
||||
|
||||
```js
|
||||
/**
|
||||
* Возвращает x в степени n, только для натуральных n
|
||||
*
|
||||
* @param {number} x Число для возведения в степень.
|
||||
* @param {number} n Показатель степени, натуральное число.
|
||||
* @return {number} x в степени n.
|
||||
*/
|
||||
function pow(x, n) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код.
|
||||
|
||||
Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении.
|
||||
</li>
|
||||
<li>**Краткий комментарий, что именно происходит в данном блоке кода.**
|
||||
|
||||
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках кода".
|
||||
|
||||
На самом деле именно эти комментарии, как правило, являются самыми ненужными. Хороший код и так самоочевиден, если не используются особо сложные алгоритмы.
|
||||
|
||||
Об этом замечательно выразился Р. Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код не достаточно прост, и, может, стоит переписать его".
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
**...Но куда более важными могут быть комментарии, которые объясняют не *что*, а *почему* в коде происходит именно это!**
|
||||
|
||||
Как правило, из кода можно понять, что он делает. Бывает, конечно, всякое, но, в конце концов, вы этот код *видите*. Однако гораздо важнее может быть то, чего вы *не видите*!
|
||||
|
||||
*Почему* это сделано именно так? На это сам код ответа не даёт.
|
||||
|
||||
Например:
|
||||
|
||||
<ul>
|
||||
<li>**Есть несколько способов решения задачи. Почему выбран именно этот?**
|
||||
|
||||
Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой.
|
||||
|
||||
Без этого возможна, например, такая ситуация:
|
||||
<ul>
|
||||
<li>Вы открываете код, который был написан какое-то время назад, и видите, что он "неоптимален".</li>
|
||||
<li>Думаете: "Какой я был дурак", и переписываете под "более очевидный и правильный" вариант.</li>
|
||||
<li>...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание.</li>
|
||||
</ul>
|
||||
|
||||
Комментарии, которые объясняют поведение кода, очень важны. Они помогают понять происходящее и принять правильное решение о развитии кода.
|
||||
|
||||
</li>
|
||||
<li>**Какие неочевидные возможности обеспечивает этот код?** Где в другом месте кода они используются?
|
||||
|
||||
В хорошем коде должно быть минимум неочевидного. Но там, где это есть -- пожалуйста, комментируйте.
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
Один из показателей хорошего разработчика -- качество комментариев, которые позволяют эффективно поддерживать код, возвращаться к нему после любой паузы и легко вносить изменения.
|
||||
|
||||
|
||||
## Руководства по стилю
|
||||
|
||||
Когда написанием проекта занимается целая команда, то должен существовать один стандарт кода, описывающий где и когда ставить пробелы, запятые, переносы строк и т.п.
|
||||
|
||||
Сейчас, когда есть столько готовых проектов, нет смысла придумывать целиком своё руководство по стилю. Можно взять уже готовое, и которому, по желанию, всегда можно что-то добавить.
|
||||
|
||||
Большинство есть на английском, сообщите мне, если найдёте хороший перевод:
|
||||
|
||||
<ul>
|
||||
<li>[Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml)</li>
|
||||
<li>[JQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines)</li>
|
||||
<li>[Idiomatic.JS](https://github.com/rwldrn/idiomatic.js) (есть [перевод](https://github.com/rwldrn/idiomatic.js/tree/master/translations/ru_RU))</li>
|
||||
<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>[Closure Linter](https://developers.google.com/closure/utilities/) -- проверка на соответствие [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml).</li>
|
||||
</ul>
|
||||
|
||||
Все они также доступны в виде программ, которые можно скачать.
|
||||
|
||||
## Итого
|
||||
|
||||
Описанные принципы оформления кода уместны в большинстве проектов. Есть и другие полезные соглашения.
|
||||
|
||||
Следуя (или не следуя) им, необходимо помнить, что любые советы по стилю хороши лишь тогда, когда делают код читаемее, понятнее, проще в поддержке.
|
||||
|
BIN
1-js/3-writing-js/2-coding-style/cheatsheet.png
Executable file
After Width: | Height: | Size: 38 KiB |
BIN
1-js/3-writing-js/2-coding-style/figure.png
Executable file
After Width: | Height: | Size: 17 KiB |
332
1-js/3-writing-js/3-write-unmain-code/article.md
Normal file
|
@ -0,0 +1,332 @@
|
|||
# Как писать неподдерживаемый код?
|
||||
|
||||
Предлагаю вашему вниманию советы мастеров древности, следование которым создаст дополнительные рабочие места для JavaScript-разработчиков.
|
||||
|
||||
Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо".
|
||||
|
||||
Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него...
|
||||
|
||||
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
|
||||
|
||||
Статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) с дополнениями, актуальными для JavaScript.
|
||||
|
||||
[cut]
|
||||
|
||||
## Соглашения
|
||||
|
||||
[quote author="Сериал \"Симпсоны\", серия Helter Shelter"]
|
||||
Рабочий-чистильщик осматривает дом:<br>
|
||||
"...Вот только жук у вас необычный...<br>
|
||||
И чтобы с ним справиться, я должен жить как жук, стать жуком, думать как жук."<br>
|
||||
(грызёт стол Симпсонов)
|
||||
[/quote]
|
||||
|
||||
Чтобы помешать другому программисту исправить ваш код, вы должны понять путь его мыслей.
|
||||
|
||||
Представьте, перед ним -- ваш большой скрипт. И ему нужно поправить его. У него нет ни времени ни желания, чтобы читать его целиком, а тем более -- досконально разбирать. Он хотел бы по-быстрому найти нужное место, сделать изменение и убраться восвояси без появления побочных эффектов.
|
||||
|
||||
Он рассматривает ваш код как бы через трубочку из туалетной бумаги. Это не даёт ему общей картины, он ищет тот небольшой фрагмент, который ему необходимо изменить. По крайней мере, он надеется, что этот фрагмент будет небольшим.
|
||||
|
||||
**На что он попытается опереться в этом поиске -- так это на соглашения, принятые в программировании, об именах переменных, названиях функций и методов...**
|
||||
|
||||
Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте?
|
||||
|
||||
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
|
||||
|
||||
Если пример, который я приведу ниже, пока сложноват -- пропустите его, но обязательно вернитесь к нему позже. Поверьте, это стоит того.
|
||||
|
||||
Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого:
|
||||
|
||||
```js
|
||||
var img = $('<img/>'); // создали новые элементы (jQuery-синтаксис)
|
||||
var div = $('<div/>'); // и поместили в переменную
|
||||
|
||||
*!*
|
||||
img.wrap(div); // обернуть img в div
|
||||
*/!*
|
||||
```
|
||||
|
||||
Результат кода выше -- два элемента, один вложен в другой:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<img/>
|
||||
</div>
|
||||
```
|
||||
|
||||
(`div` обернулся вокруг `img`)
|
||||
|
||||
А теперь, когда все расслабились и насладились этим замечательным методом...
|
||||
|
||||
...Самое время ниндзя нанести свой удар!
|
||||
|
||||
**Как вы думаете, что будет, если добавить к коду выше строку:**
|
||||
|
||||
```js
|
||||
//+ lines first-line=5
|
||||
div.append('<span/>');
|
||||
```
|
||||
|
||||
[smart header="jQuery-справка"]
|
||||
Вызов `elemA.append(elemB)` добавляет `elemB` в конец содержимого элемента `elemA`.
|
||||
[/smart]
|
||||
|
||||
**Возможно, вы полагаете, что `<span/>` добавится в конец `div`, сразу после `img`?**
|
||||
|
||||
А вот и нет! А вот и нет!..
|
||||
|
||||
Оказывается, внутри вызова `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` -- клонирует.
|
||||
|
||||
Код его истинный ниндзя писал!
|
||||
|
||||
## Краткость -- сестра таланта!
|
||||
|
||||
Пишите "как короче", а не как понятнее. "Меньше букв" -- уважительная причина для нарушения любых соглашений.
|
||||
|
||||
Ваш верный помощник -- возможности языка, использованные неочевидным образом.
|
||||
|
||||
Обратите внимание на оператор вопросительный знак `'?'`, например:
|
||||
|
||||
```js
|
||||
// код из jQuery
|
||||
i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
|
||||
```
|
||||
|
||||
Разработчик, встретивший эту строку и попытавшийся понять, чему же всё-таки равно `i`, скорее всего придёт к вам за разъяснениями. Смело скажите ему, что короче -- это всегда лучше. Посвятите и его в пути ниндзя. Не забудьте вручить [Дао дэ цзин](http://lib.ru/POECHIN/lao1.txt).
|
||||
|
||||
## Именование
|
||||
|
||||
Существенную часть науки о создании неподдерживаемого кода занимает искусство выбора имён.
|
||||
|
||||
### Однобуквенные переменные
|
||||
|
||||
Называйте переменные коротко: `a`, `b` или `c`.
|
||||
|
||||
В этом случае никто не сможет найти её, используя фунцию "Поиск" текстового редактора.
|
||||
|
||||
Более того, даже найдя -- никто не сможет "расшифровать" её и догадаться, что она означает.
|
||||
|
||||
### Не используйте i для цикла
|
||||
|
||||
В тех местах, где однобуквенные переменные общеприняты, например, в счетчике цикла -- ни в коем случае не используйте стандартные названия `i`, `j`, `k`. Где угодно, только не здесь!
|
||||
|
||||
Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`.
|
||||
|
||||
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы.
|
||||
|
||||
В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно.
|
||||
|
||||
### Русские слова и сокращения
|
||||
|
||||
Если вам *приходится* использовать длинные, понятные имена переменных -- что поделать.. Но и здесь есть простор для творчества!
|
||||
|
||||
**Назовите переменные "калькой" с русского языка или как-то "улучшите" английское слово.**
|
||||
|
||||
В одном месте напишите `var ssilka`, в другом `var ssylka`, в третьем `var link`, в четвёртом -- `var lnk`... Это действительно великолепно работает и очень креативно!
|
||||
|
||||
Количество ошибок при поддержке такого кода увеличивается во много раз.
|
||||
|
||||
### Будьте абстрактны при выборе имени
|
||||
|
||||
[quote author="Лао-цзы"]Лучший кувшин лепят всю жизнь.<br>
|
||||
Высокая музыка неподвластна слуху.<br>
|
||||
Великий образ не имеет формы.[/quote]
|
||||
|
||||
При выборе имени старайтесь применить максимально абстрактное слово, например `obj`, `data`, `value`, `item`, `elem` и т.п.
|
||||
|
||||
<ul>
|
||||
<li>**Идеальное имя для переменной: `data`.** Используйте это имя везде, где можно. В конце концов, каждая переменная содержит *данные*, не правда ли?
|
||||
|
||||
Но что делать, если имя `data` уже занято? Попробуйте `value`, оно не менее универсально. Ведь каждая переменная содержит *значение*.
|
||||
|
||||
Занято и это? Есть и другой вариант.
|
||||
</li>
|
||||
<li>**Называйте переменную по типу данных, которые она хранит: `obj`, `num`, `arr`...**
|
||||
|
||||
Насколько это усложнит разработку? Как ни странно, намного!
|
||||
|
||||
Казалось бы, название переменной содержит информацию, говорит о том, что в переменной -- число, объект или массив... С другой стороны, **когда непосвящённый будет разбирать этот код -- он с удивлением обнаружит, что информации нет!**
|
||||
|
||||
Ведь как раз тип легко понять, запустив отладчик и посмотрев, что внутри. Но в чём смысл этой переменной? Что за массив/объект/число в ней хранится? Без долгой медитации над кодом тут не обойтись!
|
||||
</li>
|
||||
<li>**Что делать, если и эти имена кончились? Просто добавьте цифру:** `item1, item2, elem5, data1`...</li>
|
||||
</ul>
|
||||
|
||||
### Похожие имена
|
||||
|
||||
Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий?
|
||||
|
||||
**Один из способов -- использовать похожие имена переменных, например `data` и `date`.** Бегло прочитать такой код почти невозможно. А уж заметить опечатку и поправить её... Ммммм... Мы здесь надолго, время попить чайку.
|
||||
|
||||
### А.К.Р.О.Н.И.М
|
||||
|
||||
Используйте сокращения, чтобы сделать код короче.
|
||||
|
||||
Например `ie` (Inner Element), `mc` (Money Counter) и другие. Если вы обнаружите, что путаетесь в них сами -- героически страдайте, но не переписывайте код. Вы знали, на что шли.
|
||||
|
||||
### Хитрые синонимы
|
||||
|
||||
[quote author="Конфуций"]Очень трудно найти чёрную кошку в тёмной комнате, особенно когда её там нет.[/quote]
|
||||
|
||||
**Чтобы было не скучно -- используйте *похожие названия* для обозначения *одинаковых действий*.**
|
||||
|
||||
Например, если метод показывает что-то на экране -- начните его название с `display..` (скажем, `displayElement`), а в другом месте объявите аналогичный метод как `show..` (`showFrame`).
|
||||
|
||||
**Как бы намекните этим, что существует тонкое различие между способами показа в этих методах, хотя на самом деле его нет.**
|
||||
|
||||
По возможности, договоритесь с членами своей команды. Если Вася в своих классах использует `display..`, то Валера -- обязательно `render..`, а Петя -- `paint..`.
|
||||
|
||||
**...И напротив, если есть две функции с важными отличиями -- используйте одно и то же слово для их описания!** Например, с `print...` можно начать метод печати на принтере `printPage`, а также -- метод добавления текста на страницу `printText`.
|
||||
|
||||
А теперь, пусть читающий код думает: "Куда же выводит сообщение `printMessage`?". Особый шик -- добавить элемент неожиданности. Пусть `printMessage` выводит не туда, куда все, а в новое окно!
|
||||
|
||||
### Словарь терминов -- это еда!
|
||||
|
||||
Ни в коем случае не поддавайтесь требованиям написать словарь терминов для проекта. Если же он уже есть -- не следуйте ему, а лучше проглотите и скажите, что так и былО!
|
||||
|
||||
Пусть читающий ваш код программист напрасно ищет различия в `helloUser` и `welcomeVisitor` и пытается понять, когда что использовать. Вы-то знаете, что на самом деле различий нет, но искать их можно о-очень долго.
|
||||
|
||||
**Для обозначения посетителя в одном месте используйте `user`, а в другом `visitor`, в третьем -- просто `u`. Выбирайте одно имя или другое, в зависимости от функции и настроения.**
|
||||
|
||||
Это воплотит сразу два ключевых принципа ниндзя-дизайна -- *сокрытие информации* и *подмена понятий*!
|
||||
|
||||
### Повторно используйте имена
|
||||
|
||||
По возможности, повторно используйте имена переменных, функций и свойств. Просто записывайте в них новые значения.
|
||||
|
||||
Добавляйте новое имя только если это абсолютно необходимо.
|
||||
|
||||
В функции старайтесь обойтись только теми переменными, которые были переданы как параметры.
|
||||
|
||||
Это не только затруднит идентификацию того, что *сейчас* находится в переменной, но и сделает почти невозможным поиск места, в котором конкретное значение было присвоено.
|
||||
|
||||
Цель -- максимально усложнить отладку и заставить читающего код программиста построчно анализировать код и конспектировать изменения переменных для каждой ветки исполнения.
|
||||
|
||||
**Продвинутый вариант этого подхода -- незаметно (!) подменить переменную на нечто похожее, например:**
|
||||
|
||||
```js
|
||||
function ninjaFunction(elem) {
|
||||
// 20 строк кода, работающего с elem
|
||||
|
||||
elem = elem.cloneNode(true);
|
||||
|
||||
// еще 20 строк кода, работающего с elem
|
||||
}
|
||||
```
|
||||
|
||||
Программист, пожелавший добавить действия с `elem` во вторую часть функции, будет удивлён. Лишь во время отладки, посмотрев весь код, он с удивлением обнаружит, что оказывается имел дело с клоном!
|
||||
|
||||
Регулярные встречи с этим приемом на практике говорят: защититься невозможно. Эффективно даже против опытного ниндзи.
|
||||
|
||||
### Добавляйте подчеркивания
|
||||
|
||||
Добавляйте подчеркивания `_` и `__` к именам переменных. Желательно, чтобы их смысл был известен только вам, а лучше -- вообще без явной причины.
|
||||
|
||||
Этим вы достигните двух целей. Во-первых, код станет длиннее и менее читаемым, а во-вторых, другой программист будет долго искать смысл в подчёркиваниях. Особенно хорошо сработает и внесет сумятицу в его мысли, если в некоторых частях проекта подчеркивания будут, а в некоторых -- нет.
|
||||
|
||||
В процессе развития кода вы, скорее всего, будете путаться и смешивать стили: добавлять имена с подчеркиваниями там, где обычно подчеркиваний нет, и наоборот. Это нормально и полностью соответствует третьей цели -- увеличить количество ошибок при внесении исправлений.
|
||||
|
||||
### Покажите вашу любовь к разработке
|
||||
|
||||
Пусть все видят, какими замечательными сущностями вы оперируете! Имена `superElement`, `megaFrame` и `niceItem` при благоприятном положении звёзд могут привести к просветлению читающего.
|
||||
|
||||
Действительно, с одной стороны, кое-что написано: `super..`, `mega..`, `nice..` С другой -- это не несёт никакой конкретики. Читающий может решить поискать в этом глубинный смысл и замедитировать на часок-другой оплаченного рабочего времени.
|
||||
|
||||
### Перекрывайте внешние переменные
|
||||
|
||||
[quote author="Гуань Инь-цзы"]
|
||||
Находясь на свету, нельзя ничего увидеть в темноте.<br>
|
||||
Пребывая же в темноте, увидишь все, что находится на свету.
|
||||
[/quote]
|
||||
|
||||
Почему бы не использовать одинаковые переменные внутри и снаружи функции? Это просто и не требует придумывать новых имён.
|
||||
|
||||
```js
|
||||
var *!*user*/!* = authenticateUser();
|
||||
|
||||
function render() {
|
||||
var *!*user*/!* = ...
|
||||
...
|
||||
...многобукв...
|
||||
...
|
||||
... // <-- программист захочет внести исправления сюда, и..
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` "уже не та" и использует её... Ловушка захлопнулась! Здравствуй, отладчик.
|
||||
|
||||
## Мощные функции!
|
||||
|
||||
Не ограничивайте действия функции тем, что написано в её названии. Будьте шире.
|
||||
|
||||
Например, функция `validateEmail(email)` может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail.
|
||||
|
||||
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.** Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.</li>
|
||||
|
||||
**Объединение нескольких смежных действий в одну функцию защитит ваш код от повторного использования.**
|
||||
|
||||
Представьте, что другому разработчику нужно только проверить адрес, а сообщение -- не выводить. Ваша функция `validateEmail(email)`, которая делает и то и другое, ему не подойдёт. Работодатель будет вынужден оплатить создание новой.
|
||||
|
||||
|
||||
## Внимание.. Сюр-при-из!
|
||||
|
||||
Есть функции, название которых говорит о том, что они ничего не меняют. Например, `isReady`, `checkPermission`, `findTags`... Предполагается, что при вызове они произведут некие вычисления, или найдут и возвратят полезные данные, но при этом их не изменят. В трактатах это называется "отсутствие сторонних эффектов".
|
||||
|
||||
**По-настоящему красивый приём -- делать в таких функциях что-нибудь полезное, заодно с процессом проверки. Что именно -- совершенно неважно.**
|
||||
|
||||
Удивление и ошеломление, которое возникнет у вашего коллеги, когда он увидит, что функция с названием на `is..`, `check..` или `find...` что-то меняет -- несомненно, расширит его границы разумного!
|
||||
|
||||
**Ещё одна вариация такого подхода -- возвращать нестандартное значение.**
|
||||
|
||||
Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект -- с результатами проверки! А что, полезно.
|
||||
|
||||
Те разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
|
||||
|
||||
## Заключение
|
||||
|
||||
Все советы выше пришли из реального кода... И в том числе от разработчиков с большим опытом.
|
||||
|
||||
Возможно, даже больше вашего, так что не судите опрометчиво ;)
|
||||
|
||||
<ul>
|
||||
<li>Следуйте нескольким из них -- и ваш код станет полон сюрпризов.</li>
|
||||
<li>Следуйте многим -- и ваш код станет истинно вашим, никто не захочет изменять его.</li>
|
||||
<li>Следуйте всем -- и ваш код станет ценным уроком для молодых разработчиков, ищущих просветления.</li>
|
||||
</ul>
|
|
@ -0,0 +1,10 @@
|
|||
function pow(x, n) {
|
||||
if (n < 0) return NaN;
|
||||
if (Math.round(n) != n) return NaN;
|
||||
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/* исправьте этот код */
|
||||
function pow(x, n) {
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
26
1-js/3-writing-js/4-testing/1-pow-nan-spec/_js.view/test.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
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" );
|
||||
});
|
||||
|
||||
});
|
17
1-js/3-writing-js/4-testing/1-pow-nan-spec/solution.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
```js
|
||||
function pow(x, n) {
|
||||
*!*
|
||||
if (n < 0) return NaN;
|
||||
if (Math.round(n) != n) return NaN;
|
||||
*/!*
|
||||
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
17
1-js/3-writing-js/4-testing/1-pow-nan-spec/task.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Сделать pow по спецификации
|
||||
|
||||
[importance 5]
|
||||
|
||||
Исправьте код функции `pow`, чтобы тесты проходили.
|
||||
|
||||
Для этого ниже в задаче вы найдёте ссылку на песочницу.
|
||||
|
||||
Она содержит HTML с тестами. Обратите внимание, что HTML-страница в ней короче той, что обсуждалась в статье [](/testing). Это потому что библиотеки Chai, Mocha и Sinon объединены в один файл:
|
||||
|
||||
```html
|
||||
<script src="http://js.cx/test/libs.js"></script>
|
||||
```
|
||||
|
||||
Этот файл содержит код библиотек, стили, настройки для них и запуск `mocha.run` по окончании загрузки страницы. Если нет элемента с `id="mocha"`, то результаты выводятся в `<body>`.
|
||||
|
||||
Сборка сделана исключительно для более компактного представления задач, без рекомендаций использовать именно её в проектах.
|
|
@ -0,0 +1,11 @@
|
|||
function pow(x, n) {
|
||||
if (n < 0) return NaN;
|
||||
if (Math.round(n) != n) return NaN;
|
||||
if (n == 0 && x ==0) return NaN;
|
||||
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
10
1-js/3-writing-js/4-testing/2-pow-test-0/_js.view/source.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
function pow(x, n) {
|
||||
if (n < 0) return NaN;
|
||||
if (Math.round(n) != n) return NaN;
|
||||
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
44
1-js/3-writing-js/4-testing/2-pow-test-0/_js.view/test.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
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");
|
||||
});
|
||||
|
||||
});
|
38
1-js/3-writing-js/4-testing/2-pow-test-0/solution.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
Новый тест может быть, к примеру, таким:
|
||||
|
||||
```js
|
||||
it("любое число в степени 0 равно 1", function() {
|
||||
assert.equal( pow(123, 0), 1);
|
||||
});
|
||||
```
|
||||
|
||||
Конечно, желательно проверить на нескольких числах.
|
||||
|
||||
Поэтому лучше будет создать блок `describe`, аналогичный тому, что мы делали для произвольных чисел:
|
||||
|
||||
```js
|
||||
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);
|
||||
}
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
И не забудем добавить отдельный тест для нуля:
|
||||
|
||||
```js
|
||||
...
|
||||
it("ноль в нулевой степени даёт NaN", function() {
|
||||
assert( isNaN(pow(0,0), "0 в степени 0 не NaN");
|
||||
});
|
||||
...
|
||||
```
|
||||
|
7
1-js/3-writing-js/4-testing/2-pow-test-0/task.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Добавьте тест к задаче
|
||||
|
||||
[importance 5]
|
||||
|
||||
Добавьте к [предыдущей задаче](/task/pow-nan-spec) тесты, которые будет проверять, что любое число, кроме нуля, в нулевой степени равно `1`, а ноль в нулевой степени даёт `NaN` (это математически корректно, результат 0<sup>0</sup> не определён).
|
||||
|
||||
При необходимости, исправьте реализацию, чтобы тесты проходили без ошибок.
|
27
1-js/3-writing-js/4-testing/3-pow-test-wrong/solution.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
Этот тест демонстрирует один из соблазнов, которые ожидают начинающего автора тестов.
|
||||
|
||||
Вместо того, чтобы написать три различных теста, он изложил их в виде одного потока вычислений, с несколькими `assert`.
|
||||
|
||||
Иногда так написать легче и проще, однако при ошибке в тесте гораздо менее очевидно, что же пошло не так.
|
||||
|
||||
Если в сложном тесте произошла ошибка где-то посередине потока вычислений, то придётся выяснять, какие конкретно были входные и выходные данные на этот момент, то есть по сути -- отлаживать код самого теста.
|
||||
|
||||
Гораздо лучше будет разбить тест на несколько блоков `it`, с чётко прописанными входными и выходными данными.
|
||||
|
||||
```js
|
||||
describe("Возводит x в степень n", function() {
|
||||
it("5 в степени 1 равно 5", function() {
|
||||
assert.equal( pow(5, 1), 5 );
|
||||
});
|
||||
|
||||
it("5 в степени 2 равно 25", function() {
|
||||
assert.equal( pow(5, 2), 25 );
|
||||
});
|
||||
|
||||
it("5 в степени 3 равно 125", function() {
|
||||
assert.equal( pow(5, 3), 25 );
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Можно использовать цикл для генерации блоков `it`, в этом случае важно, чтобы сам код такого цикла был достаточно простым. Иногда проще записать несколько блоков `it` вручную, как сделано выше, чем "городить огород" из синтаксических конструкций.
|
22
1-js/3-writing-js/4-testing/3-pow-test-wrong/task.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Что не так в тесте?
|
||||
|
||||
[importance 5]
|
||||
|
||||
Что не так в этом тесте функции `pow`?
|
||||
|
||||
```js
|
||||
it("Возводит x в степень n", function() {
|
||||
var x = 5;
|
||||
|
||||
var result = x;
|
||||
assert.equal( pow(x, 1), result );
|
||||
|
||||
var result *= x;
|
||||
assert.equal( pow(x, 2), result );
|
||||
|
||||
var result *= x;
|
||||
assert.equal( pow(x, 3), result );
|
||||
});
|
||||
```
|
||||
|
||||
P.S. Синтаксически он верен и работает, но спроектирован неправильно.
|
447
1-js/3-writing-js/4-testing/article.md
Normal file
|
@ -0,0 +1,447 @@
|
|||
# Автоматические тесты при помощи chai и mocha
|
||||
|
||||
В этой главе мы разберём основы автоматического тестирования. Оно будет применяться далее в задачах, и вообще, входит в "образовательный минимум" программиста.
|
||||
|
||||
[cut]
|
||||
|
||||
## Зачем нужны тесты?
|
||||
|
||||
При написании функции мы обычно представляем, что она должна делать, какое значение -- на каких аргументах выдавать.
|
||||
|
||||
В процессе разработки мы, время от времени, проверяем функцию. Самый простой способ проверки -- это запустить функцию и посмотреть результат.
|
||||
|
||||
Потом написать ещё код, попробовать запустить -- опять посмотреть результат.
|
||||
|
||||
И так -- "до победного конца".
|
||||
|
||||
К сожалению, такие ручные запуски -- очень несовершенное средство проверки.
|
||||
|
||||
**Когда проверяешь работу кода вручную -- легко его "недотестировать".**
|
||||
|
||||
Например, пишем функцию `f`. Написали, тестируем с разными аргументами. Вызов функции `f(a)` -- работает, а вот `f(b)` -- не работает. Поправили код -- стало работать `f(b)`, вроде закончили. Но при этом забыли заново протестировать `f(a)` -- упс, вот и возможная ошибка в коде.
|
||||
|
||||
**Автоматизированное тестирование -- это когда тесты написаны отдельно от кода, и можно в любой момент запустить их и проверить все важные случаи использования.**
|
||||
|
||||
## BDD -- поведенческие тесты кода
|
||||
|
||||
Мы рассмотрим методику тестирования, которая входит в [BDD](http://en.wikipedia.org/wiki/Behavior-driven_development) -- Behavior Driven Development. Подход BDD давно и с успехом используется во многих проектах.
|
||||
|
||||
BDD -- это не просто тесты. Это гораздо больше.
|
||||
|
||||
**Тесты BDD -- это три в одном: это И тесты И документация И примеры использования одновременно.**
|
||||
|
||||
Впрочем, хватит слов. Рассмотрим примеры.
|
||||
|
||||
## Разработка pow
|
||||
|
||||
Допустим, мы хотим разработать функцию `pow(x, n)`, которая возводит `x` в целую степень `n`, для простоты `n≥0`.
|
||||
|
||||
### Спецификация
|
||||
|
||||
Ещё до разработки мы можем представить себе, что эта функция будет делать и описать это по методике BDD.
|
||||
|
||||
Это описание называется *спецификация* (или, как говорят в обиходе, "спека") и выглядит так:
|
||||
|
||||
```js
|
||||
describe("pow", function() {
|
||||
|
||||
it("возводит в n-ю степень", function() {
|
||||
assert.equal( pow(2, 3), 8);
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
У спецификации есть три основных строительных блока, которые вы видите в примере выше:
|
||||
<dl>
|
||||
<dt>`describe(название, function() { ... })`</dt>
|
||||
<dd>Задаёт, что именно мы описываем, используется для группировки "рабочих лошадок" -- блоков `it`. В данном случае мы описываем функцию `pow`.</dd>
|
||||
<dt>`it(название, function() { ... })`</dt>
|
||||
<dd>В названии блока `it` *человеческим языком* описывается, что должна делать функция, далее следует *тест*, который проверяет это.</dd>
|
||||
<dt>`assert.equal(value1, value2)`</dt>
|
||||
<dd>Код внутри `it`, если реализация верна, должен выполняться без ошибок.
|
||||
|
||||
Для того, чтобы проверить, делает ли `pow` то, что задумано, используются функции вида `assert.*`. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. Есть и другие виды сравнений и проверок, которые мы увидим далее.</dd>
|
||||
</dl>
|
||||
|
||||
Как правило, поток разработки таков:
|
||||
<ol>
|
||||
<li>Пишется спецификация, которая описывает самый базовый функционал.</li>
|
||||
<li>Делается начальная реализация.</li>
|
||||
<li>Для проверки соответствия спецификации мы задействуем одновременно фреймворк, в нашем случае [Mocha](http://visionmedia.github.io/mocha/) вместе со спецификацией и реализацией. Фреймворк запускает все тесты `it` и выводит ошибки, если они возникнут. При ошибках вносятся исправления.</li>
|
||||
<li>Спецификация расширяется, в неё добавляются возможности, которые пока, возможно, не поддерживаются реализацией.</li>
|
||||
<li>Идём на пункт 3, делаем реализацию, и так далее, до победного конца.</li>
|
||||
</ol>
|
||||
|
||||
Разработка ведётся *итеративно*, один проход за другим, пока спецификация и реализация не будут завершены.
|
||||
|
||||
В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают.
|
||||
|
||||
### Проверка спецификации
|
||||
|
||||
Для запуска тестов нужны соответствующие JavaScript-библиотеки.
|
||||
|
||||
Мы будем использовать:
|
||||
<ul>
|
||||
<li>[Mocha](http://visionmedia.github.io/mocha/) -- эта библиотека содержит общие функции для тестирования, включая `describe` и `it`.</li>
|
||||
<li>[Chai](http://chaijs.com) -- библиотека поддерживает разнообразные функции для проверок. Есть разные "стили" проверки результатов, с которыми мы познакомимся позже, на текущий момент мы будем использовать лишь `assert.equal`.</li>
|
||||
<li>[Sinon](http://sinonjs.org/) -- для эмуляции и хитрой подмены функций "заглушками", понадобится позднее.</li>
|
||||
</ul>
|
||||
|
||||
Эти библиотеки позволяют тестировать JS не только в браузере, но и на сервере Node.JS. Здесь мы рассмотрим браузерный вариант, серверный использует те же функции.
|
||||
|
||||
Пример HTML-страницы для тестов:
|
||||
|
||||
```html
|
||||
<!--+ src="index.html" -->
|
||||
```
|
||||
|
||||
Эту страницу можно условно разделить на три части:
|
||||
<ol>
|
||||
<li>В `<head>` подключаем библиотеки и стили.</li>
|
||||
<li>Подключаем `<script>` с реализацией, в нашем случае -- с кодом для `pow`. Пока что функции нет, мы лишь готовимся её написать.</li>
|
||||
<li>Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше. Их вызов добавляет тесты, для запуска которых используется команда `mocha.run()`. Она выведет результат тестов в элемент с `id="mocha"`.</li>
|
||||
</ol>
|
||||
|
||||
Результат срабатывания:
|
||||
|
||||
[iframe height=250 src="pow-1" border=1 edit]
|
||||
|
||||
Пока что у нас одна функция и одна спецификация, но на будущее заметим, что если различных функций и тестов много -- это не проблема, можно их все подключить на одной странице. Конфликта не будет, так как каждый функционал имеет свой блок `describe("что тестируем"...)`. Сами же тесты обычно пишутся так, чтобы не влиять друг на друга, не делать лишних глобальных переменных.
|
||||
|
||||
Посмотрели, попробовали запустить у себя что-то подобное? Если да -- идём дальше.
|
||||
|
||||
### Начальная реализация
|
||||
|
||||
Пока что, как видно, тесты не проходят, ошибка сразу же. Давайте сделаем минимальную реализацию `pow`, которая бы работала нормально:
|
||||
|
||||
```js
|
||||
function pow() {
|
||||
return 8; // :) мы - мошенники!
|
||||
}
|
||||
```
|
||||
|
||||
О, вот теперь работает:
|
||||
|
||||
[iframe height=250 src="pow-min" border=1 edit]
|
||||
|
||||
### Расширение спецификации
|
||||
|
||||
Функция, конечно, ещё не готова, но тесты проходят. Это ненадолго :)
|
||||
|
||||
Здесь мы видим ситуацию, которая (и не обязательно при ленивом программисте!) бывает на практике -- да, есть тесты, они проходят, но увы, функция работает неправильно.
|
||||
|
||||
**С точки зрения BDD, ошибка при проходящих тестах -- вина спецификации.**
|
||||
|
||||
В первую очередь не реализация исправляется, а уточняется спецификация, пишется (падающий) тест.
|
||||
|
||||
Сейчас мы расширим спецификацию, добавив проверку на `pow(3, 4) = 81`.
|
||||
|
||||
Здесь есть два варианта организации кода:
|
||||
|
||||
<ol>
|
||||
<li>Первый вариант -- добавить `assert` в тот же `it`:
|
||||
|
||||
```js
|
||||
describe("pow", function() {
|
||||
|
||||
it("возводит в n-ю степень", function() {
|
||||
assert.equal( pow(2, 3), 8);
|
||||
*!*
|
||||
assert.equal( pow(3, 4), 81);
|
||||
*/!*
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Второй вариант -- сделать два теста:
|
||||
|
||||
```js
|
||||
describe("pow", function() {
|
||||
|
||||
it("при возведении 2 в 3ю степень результат 8", function() {
|
||||
assert.equal( pow(2, 3), 8);
|
||||
});
|
||||
|
||||
it("при возведении 3 в 4ю степень равен 81", function() {
|
||||
assert.equal( pow(3, 4), 81);
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Их принципиальное различие в том, что если `assert` обнаруживает ошибку, то он тут же прекращает выполнение блоки `it`. Поэтому в первом варианте, если вдруг первый `assert` "провалился", то про результат второго мы никогда не узнаем.
|
||||
|
||||
**Таким образом, разделить эти тесты может быть полезно, чтобы мы получили больше информации о происходящем.**
|
||||
|
||||
Кроме того, есть ещё одно правило, которое желательно соблюдать.
|
||||
|
||||
**Один тест тестирует ровно одну вещь.**
|
||||
|
||||
Если мы явно видим, что тест включает в себя совершенно независимые проверки -- лучше разбить его на два более простых и наглядных.
|
||||
|
||||
По этим причинам второй вариант здесь предпочтительнее.
|
||||
|
||||
Результат:
|
||||
[iframe height=250 src="pow-2" edit border="1"]
|
||||
|
||||
Как и следовало ожидать, второй тест не проходит. Ещё бы, ведь функция всё время возвращает `8`.
|
||||
|
||||
### Уточнение реализации
|
||||
|
||||
Придётся написать нечто более реальное:
|
||||
|
||||
```js
|
||||
function pow(x, n) {
|
||||
var result = 1;
|
||||
|
||||
for(var i = 0; i < n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
Чтобы быть уверенными, что функция работает верно, желательно протестировать её на большем количестве значений. Вместо того, чтобы писать блоки `it` вручную, мы можем сгенерировать тесты в цикле `for`:
|
||||
|
||||
```js
|
||||
describe("pow", 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);
|
||||
}
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Результат:
|
||||
[iframe height=250 src="pow-3" edit border="1"]
|
||||
|
||||
|
||||
### Вложенный describe
|
||||
|
||||
Функция `makeTest` и цикл `for`, очевидно, нужны друг другу, но не нужны для других тестов, которые мы добавим в дальнейшем. Они образуют единую группу, задача которой -- проверить возведение в `n`-ю степень.
|
||||
|
||||
Будет правильно выделить их, при помощи вложенного блока `describe`:
|
||||
|
||||
```js
|
||||
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 и подблоки describe ...
|
||||
});
|
||||
```
|
||||
|
||||
Вложенный `describe` объявит новую "подгруппу" тестов, блоки `it` которой запускаются так же, как и обычно, но выводятся с подзаголовком, вот так:
|
||||
|
||||
[iframe height=300 src="pow-4" edit border="1"]
|
||||
|
||||
В будущем мы сможем в добавить другие тесты `it` и блоки `describe` со своими вспомогательными функциями.
|
||||
|
||||
[smart header="before/after и beforeEach/afterEach"]
|
||||
В каждом блоке `describe` можно также задать функции `before/after`, которые будут выполнены до/после запуска тестов, а также `beforeEach/afterEach`, которые выполняются до/после каждого `it`.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
describe("Тест", function() {
|
||||
|
||||
before(function() { alert("Начало тестов"); });
|
||||
after(function() { alert("Конец тестов"); });
|
||||
|
||||
beforeEach(function() { alert("Вход в тест"); });
|
||||
afterEach(function() { alert("Выход из теста"); });
|
||||
|
||||
it('тест 1', function() { alert('1'); });
|
||||
it('тест 2', function() { alert('2'); });
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Последовательность будет такой:
|
||||
|
||||
```
|
||||
Начало тестов
|
||||
Вход в тест
|
||||
1
|
||||
Выход из теста
|
||||
Вход в тест
|
||||
2
|
||||
Выход из теста
|
||||
Конец тестов
|
||||
```
|
||||
|
||||
[edit src="beforeafter"]Открыть пример с тестами в песочнице[/edit]
|
||||
|
||||
Как правило, `beforeEach/afterEach` (`before/each`) используют, если необходимо произвести инициализацию, обнулить счётчики или сделать что-то ещё в таком духе между тестами (или их группами).
|
||||
[/smart]
|
||||
|
||||
### Расширение спецификации
|
||||
|
||||
Базовый функционал описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его.
|
||||
|
||||
Как говорилось ранее, функция `pow(x, n)` предназначена для работы с целыми неотрицательными `n`.
|
||||
|
||||
В JavaScript для ошибки вычислений служит специальное значение `NaN`, которое функция будет возвращать при некорректных `n`.
|
||||
|
||||
Добавим это поведение в спецификацию:
|
||||
|
||||
```js
|
||||
describe("pow", function() {
|
||||
|
||||
// ...
|
||||
|
||||
it("при возведении в отрицательную степень результат NaN", function() {
|
||||
*!*
|
||||
assert( isNaN( pow(2, -1) ) );
|
||||
*/!*
|
||||
});
|
||||
|
||||
it("при возведении в дробную степень результат NaN", function() {
|
||||
*!*
|
||||
assert( isNaN( pow(2, 1.5) ) );
|
||||
*/!*
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Результат с новыми тестами:
|
||||
[iframe height=450 src="pow-nan" edit border="1"]
|
||||
|
||||
Конечно, новые тесты не проходят, так как наша реализация ещё не поддерживает их. Так и задумано: сначала написали заведомо не работающие тесты, а затем пишем реализацию под них.
|
||||
|
||||
### Другие assert
|
||||
|
||||
Обратим внимание, в спецификации выше использована проверка не `assert.equal`, как раньше, а `assert(выражение)`. Такая проверка выдаёт ошибку, если значение выражения при приведении к логическому типу не `true`.
|
||||
|
||||
Она потребовалась, потому что сравнивать с `NaN` обычным способом нельзя: `NaN` не равно никакому значению, даже самому себе, поэтому `assert.equal(NaN, x)` не подойдёт.
|
||||
|
||||
Кстати, мы и ранее могли бы использовать `assert(value1 == value2)` вместо `assert.equal(value1, value2)`. Оба этих `assert` проверяют одно и тоже.
|
||||
|
||||
Однако, между этими вызовами есть отличие в деталях сообщения об ошибке.
|
||||
|
||||
При `assert` мы видим `Unspecified AssertionError`, то есть просто "что-то пошло не так", а при `assert.equal(value1, value2)` -- будут дополнительные подробности: `expected 8 to equal 81`.
|
||||
|
||||
**Поэтому рекомендуется использовать именно ту проверку, которая максимально соответствует задаче.**
|
||||
|
||||
Вот самые востребованные `assert`-проверки, встроенные в Chai:
|
||||
|
||||
<ul>
|
||||
<li>`assert(value)` -- проверяет что `value` является `true` в логическом контексте.</li>
|
||||
<li>`assert.equal(value1, value2)` -- проверяет равенство `value1 == value2`.</li>
|
||||
<li>`assert.strictEqual(value1, value2)` -- проверяет строгое равенство `value1 === value2`.</li>
|
||||
<li>`assert.notEqual`, `assert.notStrictEqual` -- проверки, обратные двум предыдущим.</li>
|
||||
<li>`assert.isTrue(value)` -- проверяет, что `value === true`</li>
|
||||
<li>`assert.isFalse(value)` -- проверяет, что `value === false`</li>
|
||||
<li>...более полный список -- в [документации](http://chaijs.com/api/assert/)</li>
|
||||
</ul>
|
||||
|
||||
В нашем случае хорошо бы иметь проверку `assert.isNaN`, но, увы, такого метода нет, поэтому приходится использовать самый общий `assert(...)`. В этом случае для того, чтобы сделать сообщение об ошибке понятнее, желательно добавить к `assert` описание.
|
||||
|
||||
**Все вызовы `assert` позволяют дополнительным последним аргументом указать строку с описанием ошибки, которое выводится, если `assert` не проходит.**
|
||||
|
||||
Добавим описание ошибки в конец наших `assert'ов`:
|
||||
|
||||
```js
|
||||
describe("pow", function() {
|
||||
|
||||
// ...
|
||||
|
||||
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" );
|
||||
*/!*
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Теперь результат теста гораздо яснее говорит о том, что не так:
|
||||
|
||||
[iframe height=450 src="pow-nan-assert" edit border="1"]
|
||||
|
||||
В коде тестов выше можно было бы добавить описание и к `assert.equal`, указав в конце: `assert.equal(value1, value2, "описание")`, но с равенством обычно и так всё понятно, поэтому мы так делать не будем.
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
Итак, разработка завершена, мы получили полноценную спецификацию и код, который её реализует.
|
||||
|
||||
Задачи выше позволяют дополнить её, и в результате может получиться что-то в таком духе:
|
||||
|
||||
```js
|
||||
//+ src="pow-full/test.js"
|
||||
```
|
||||
|
||||
[edit src="pow-full"]Открыть полный пример с реализацией в песочнице[/edit]
|
||||
|
||||
Эту спецификацию можно использовать как:
|
||||
<ol>
|
||||
<li>**Тесты**, которые гарантируют правильность работы кода.</li>
|
||||
<li>**Документацию** по функции, что она конкретно делает.</li>
|
||||
<li>**Примеры** использования функции, которые демонстрируют её работу внутри `it`.</li>
|
||||
</ol>
|
||||
|
||||
Имея спецификацию, мы можем улучшать, менять, переписывать функцию и легко контролировать её работу, просматривая тесты.
|
||||
|
||||
Особенно важно это в больших проектах.
|
||||
|
||||
Бывает так, что изменение в одной части кода может повлечь за собой "падение" другой части, которая её использует. Так как всё-всё в большом проекте руками не перепроверишь, то такие ошибки имеют большой шанс остаться в продукте и вылезти позже, когда проект увидит посетитель или заказчик.
|
||||
|
||||
Чтобы избежать таких проблем, бывает, что вообще стараются не трогать код, от которого много что зависит, даже если его ну очень нужно переписать. Жизнь пробивается тонкими росточками там, где должен цвести и пахнуть новый функционал.
|
||||
|
||||
**Код, покрытый автотестами, являет собой полную противоположность этому!**
|
||||
|
||||
Даже если какое-то изменение потенциально может порушить всё -- его совершенно не страшно сделать. Ведь есть масса тестов, которые быстро и в автоматическом режиме проверят работу кода и, если что-то падает -- это можно будет легко локализовать и поправить.
|
||||
|
||||
**Кроме того, код, покрытый тестами, имеет лучшую архитектуру.**
|
||||
|
||||
Конечно, это естественное следствие того, что его легче менять и улучшать. Но не только.
|
||||
|
||||
Чтобы написать тесты, нужно разбить код на функции так, чтобы для каждой функции было чётко понятно, что она получает на вход, что делает с этим и что возвращает. Это означает ясную и понятную структуру с самого начала.
|
||||
|
||||
Конечно, в реальной жизни всё не так просто. Зачастую написать тест сложно. Или сложно поддерживать тесты, поскольку код активно меняется. Сами тесты тоже пишутся по-разному, при помощи разных инструментов.
|
||||
|
||||
## Что дальше?
|
||||
|
||||
В дальнейшем условия ряда задач будут уже содержать в себе тесты. На них вы познакомитесь с дополнительными примерами.
|
||||
|
||||
Как правило, они будут вполне понятны, даже если немного выходят за пределы этой главы.
|
24
1-js/3-writing-js/4-testing/beforeafter.view/index.html
Executable file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
12
1-js/3-writing-js/4-testing/beforeafter.view/test.js
Executable file
|
@ -0,0 +1,12 @@
|
|||
describe("Тест", function() {
|
||||
|
||||
before(function() { alert("Начало тестов"); });
|
||||
after(function() { alert("Конец тестов"); });
|
||||
|
||||
beforeEach(function() { alert("Вход в тест"); });
|
||||
afterEach(function() { alert("Выход из теста"); });
|
||||
|
||||
it('тест 1', function() { alert('1'); });
|
||||
it('тест 2', function() { alert('2'); });
|
||||
|
||||
});
|
36
1-js/3-writing-js/4-testing/index.html
Executable file
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
/* код функции, пока что пусто */
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
36
1-js/3-writing-js/4-testing/pow-1.view/index.html
Executable file
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
/* код функции, пока что пусто */
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
7
1-js/3-writing-js/4-testing/pow-1.view/test.js
Executable file
|
@ -0,0 +1,7 @@
|
|||
describe("pow", function() {
|
||||
|
||||
it("возводит в n-ю степень", function() {
|
||||
assert.equal( pow(2, 3), 8);
|
||||
});
|
||||
|
||||
});
|
36
1-js/3-writing-js/4-testing/pow-2.view/index.html
Executable file
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
return 8;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
11
1-js/3-writing-js/4-testing/pow-2.view/test.js
Executable file
|
@ -0,0 +1,11 @@
|
|||
describe("pow", function() {
|
||||
|
||||
it("при возведении 2 в 3ю степень результат 8", function() {
|
||||
assert.equal( pow(2, 3), 8);
|
||||
});
|
||||
|
||||
it("при возведении 3 в 4ю степень равен 81", function() {
|
||||
assert.equal( pow(3, 4), 81);
|
||||
});
|
||||
|
||||
});
|
43
1-js/3-writing-js/4-testing/pow-3.view/index.html
Executable file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
var result = 1;
|
||||
|
||||
for(var i = 0; i < n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
14
1-js/3-writing-js/4-testing/pow-3.view/test.js
Executable file
|
@ -0,0 +1,14 @@
|
|||
describe("pow", 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);
|
||||
}
|
||||
|
||||
});
|
43
1-js/3-writing-js/4-testing/pow-4.view/index.html
Executable file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
var result = 1;
|
||||
|
||||
for(var i = 0; i < n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
20
1-js/3-writing-js/4-testing/pow-4.view/test.js
Executable file
|
@ -0,0 +1,20 @@
|
|||
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);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ...
|
||||
|
||||
});
|
36
1-js/3-writing-js/4-testing/pow-min.view/index.html
Executable file
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
return 8;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
7
1-js/3-writing-js/4-testing/pow-min.view/test.js
Executable file
|
@ -0,0 +1,7 @@
|
|||
describe("pow", function() {
|
||||
|
||||
it("возводит в n-ю степень", function() {
|
||||
assert.equal( pow(2, 3), 8);
|
||||
});
|
||||
|
||||
});
|
40
1-js/3-writing-js/4-testing/pow-nan-assert.view/index.html
Executable file
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
26
1-js/3-writing-js/4-testing/pow-nan-assert.view/test.js
Executable file
|
@ -0,0 +1,26 @@
|
|||
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" );
|
||||
});
|
||||
|
||||
});
|
40
1-js/3-writing-js/4-testing/pow-nan.view/index.html
Executable file
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
26
1-js/3-writing-js/4-testing/pow-nan.view/test.js
Executable file
|
@ -0,0 +1,26 @@
|
|||
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) ) );
|
||||
});
|
||||
|
||||
it("при возведении в дробную степень результат NaN", function() {
|
||||
assert( isNaN( pow(2, 1.5) ) );
|
||||
});
|
||||
|
||||
});
|
44
1-js/3-writing-js/4-testingpow-full/pow-full.view/index.html
Executable file
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- подключаем стили Mocha, для отображения результатов -->
|
||||
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.css">
|
||||
<!-- подключаем библиотеку Mocha -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/1.13.0/mocha.js"></script>
|
||||
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
|
||||
<script> mocha.setup('bdd'); </script>
|
||||
|
||||
<!-- подключаем chai -->
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/chai/1.9.0/chai.js"></script>
|
||||
<!-- в chai есть много всего, выносим assert в глобальную область -->
|
||||
<script> var assert = chai.assert; </script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function pow(x, n) {
|
||||
if (n < 0) return NaN;
|
||||
if (Math.round(n) != n) return NaN;
|
||||
if (n == 0 && x ==0) return NaN;
|
||||
|
||||
var result = 1;
|
||||
for(var i=0; i<n; i++) {
|
||||
result *= x;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- в этом скрипте находятся спеки -->
|
||||
<script src="test.js"></script>
|
||||
|
||||
<!-- в элементе с id="mocha" будут результаты тестов -->
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!-- запустить тесты! -->
|
||||
<script> mocha.run(); </script>
|
||||
</body>
|
||||
</html>
|
44
1-js/3-writing-js/4-testingpow-full/pow-full.view/test.js
Executable file
|
@ -0,0 +1,44 @@
|
|||
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");
|
||||
});
|
||||
|
||||
});
|
44
1-js/3-writing-js/4-testingpow-full/test.js
Executable file
|
@ -0,0 +1,44 @@
|
|||
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");
|
||||
});
|
||||
|
||||
});
|
8
1-js/3-writing-js/index.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Качество кода
|
||||
|
||||
Для того, чтобы код был качественным, необходимы как минимум:
|
||||
<ol>
|
||||
<li>Умение отладить код и поправить ошибки.</li>
|
||||
<li>Хороший стиль кода.</li>
|
||||
<li>Тестировать код, желательно -- в автоматическом режиме.</li>
|
||||
</ol>
|