This commit is contained in:
Ilya Kantor 2016-11-28 21:35:42 +03:00
parent 83b93e5992
commit 9ad9063d00
742 changed files with 884 additions and 779 deletions

View file

@ -0,0 +1,228 @@
# TODO: Отладка в браузере Chrome
Перед тем, как двигаться дальше, поговорим об отладке скриптов.
Все современные браузеры поддерживают для этого "инструменты разработчика". Исправление ошибок с их помощью намного проще и быстрее.
На текущий момент самые многофункциональные инструменты -- в браузере Chrome. Также очень хорош Firebug (для Firefox).
[cut]
## Общий вид панели Sources
В вашей версии Chrome панель может выглядеть несколько по-иному, но что где находится, должно быть понятно.
Зайдите на [страницу с примером](debugging/index.html) браузером Chrome.
Откройте инструменты разработчика: `key:F12` или в меню `Инструменты > Инструменты Разработчика`.
Выберите сверху `Sources`.
![](chrome_sources.png)
Вы видите три зоны:
1. **Зона исходных файлов.** В ней находятся все подключённые к странице файлы, включая JS/CSS. Выберите `pow.js`, если он не выбран.
2. **Зона текста.** В ней находится текст файлов.
3. **Зона информации и контроля.** Мы поговорим о ней позже.
Обычно зона исходных файлов при отладке не нужна. Скройте её кнопкой <span class="devtools" style="background-position:-200px -76px"></span>.
## Общие кнопки управления
![](chrome_sources_buttons.png)
Три наиболее часто используемые кнопки управления:
Формат <span class="devtools" style="background-position:-264px 94px"></span>
: Нажатие форматирует текст текущего файла, расставляет отступы. Нужна, если вы хотите разобраться в чужом коде, плохо отформатированном или сжатом.
Консоль <span class="devtools" style="background-position:-70px 94px"></span>
: Очень полезная кнопка, открывает тут же консоль для запуска команд. Можно смотреть код и тут же запускайть функции. Её нажатие можно заменить на клавишу <code class="key">Esc</code>.
Окно <span class="devtools" style="background-position:-6px 70px"></span>
: Если код очень большой, то можно вынести инструменты разработки вбок или в отдельное окно, зажав эту кнопку и выбрав соответствующий вариант из списка.
## Точки остановки
Открыли файл `pow.js` во вкладке Sources? Кликните на 6й строке файла `pow.js`, прямо на цифре 6.
Поздравляю! Вы поставили "точку остановки" или, как чаще говорят, "брейкпойнт".
Выглядет это должно примерно так:
![](chrome_sources_breakpoint.png)
Слово *Брейкпойнт* (breakpoint) -- часто используемый английский жаргонизм. Это то место в коде, где отладчик будет *автоматически* останавливать выполнение JavaScript, как только оно до него дойдёт.
**В остановленном коде можно посмотреть текущие значения переменных, выполнять команды и т.п., в общем -- отлаживать его.**
Вы можете видеть, что информация о точке остановки появилась справа, в подвкладке Breakpoints.
Вкладка Breakpoints очень удобна, когда код большой, она позволяет:
- Быстро перейти на место кода, где стоит брейкпойнт кликом на текст.
- Временно выключить брейкпойнт кликом на чекбокс.
- Быстро удалить брейкпойнт правым кликом на текст и выбором Remove, и так далее.
````smart header="Дополнительные возможности"
- Остановку можно инициировать и напрямую из кода скрипта, командой `debugger`:
```js
function pow(x, n) {
...
debugger; // <-- отладчик остановится тут
...
}
```
- *Правый клик* на номер строки `pow.js` позволит создать условную точку остановки (conditional breakpoint), т.е. задать условие, при котором точка остановки сработает.
Это удобно, если остановка нужна только при определённом значении переменной или параметра функции.
````
## Остановиться и осмотреться
Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать отладчик JavaScript -- перезагрузить её. Итак, нажимаем `key:F5` (Windows, Linux) или `key:Cmd+R` (Mac).
Если вы сделали всё, как описано выше, то выполнение прервётся как раз на 6й строке.
![](chrome_sources_break.png)
Обратите внимание на информационные вкладки справа (отмечены стрелками).
В них мы можем посмотреть текущее состояние:
1. **`Watch Expressions` -- показывает текущие значения любых выражений.**
Можно раскрыть эту вкладку, нажать мышью `+` на ней и ввести любое выражение. Отладчик будет отображать его значение на текущий момент, автоматически перевычисляя его при проходе по коду.
2. **`Call Stack` -- стек вызовов, все вложенные вызовы, которые привели к текущему месту кода.**
На текущий момент видно, отладчик находится в функции `pow` (pow.js, строка 6), вызванной из анонимного кода (index.html, строка 13).
3. **`Scope Variables` -- переменные.**
На текущий момент строка 6 ещё не выполнилась, поэтому `result` равен `undefined`.
В `Local` показываются переменные функции: объявленные через `var` и параметры. Вы также можете там видеть ключевое слово `this`, если вы не знаете, что это такое -- ничего страшного, мы это обсудим позже, в следующих главах учебника.
В `Global` -- глобальные переменные и функции.
## Управление выполнением
Пришло время, как говорят, "погонять" скрипт и "оттрейсить" (от англ. trace -- отслеживать) его работу.
Обратим внимание на панель управления справа-сверху, в ней есть 6 кнопок:
![|style="vertical-align:middle"](manage1.png) -- продолжить выполнение, горячая клавиша `key:F8`.
: Продолжает выполнения скрипта с текущего момента в обычном режиме. Если скрипт не встретит новых точек остановки, то в отладчик управление больше не вернётся.
Нажмите на эту кнопку.
Скрипт продолжится, далее, в 6й строке находится рекурсивный вызов функции `pow`, т.е. управление перейдёт в неё опять (с другими аргументами) и сработает точка остановки, вновь включая отладчик.
При этом вы увидите, что выполнение стоит на той же строке, но в `Call Stack` появился новый вызов.
Походите по стеку вверх-вниз -- вы увидите, что действительно аргументы разные.
![|style="vertical-align:middle"](manage2.png) -- сделать шаг, не заходя внутрь функции, горячая клавиша `key:F10`.
: Выполняет одну команду скрипта. Если в ней есть вызов функции -- то отладчик обходит его стороной, т.е. не переходит на код внутри.
Эта кнопка очень удобна, если в текущей строке вызывается функция JS-фреймворка или какая-то другая, которая нас ну совсем не интересует. Тогда выполнение продолжится дальше, без захода в эту функцию, что нам и нужно.
Обратим внимание, в данном случае эта кнопка при нажатии всё-таки перейдёт внутрь вложенного вызова `pow`, так как внутри `pow` находится брейкпойнт, а на включённых брейкпойнтах отладчик останавливается всегда.
![|style="vertical-align:middle"](manage3.png) -- сделать шаг, горячая клавиша `key:F11`.
: Выполняет одну команду скрипта и переходит к следующей. Если есть вложенный вызов, то заходит внутрь функции.
Эта кнопка позволяет подробнейшим образом пройтись по очереди по командам скрипта.
![|style="vertical-align:middle"](manage4.png) -- выполнять до выхода из текущей функции, горячая клавиша `key:Shift+F11`.
: Выполняет команды до завершения текущей функции.
Эта кнопка очень удобна в случае, если мы нечаянно вошли во вложенный вызов, который нам не интересен -- чтобы быстро из него выйти.
![|style="vertical-align:middle"](manage5.png) -- отключить/включить все точки остановки.
: Эта кнопка никак не двигает нас по коду, она позволяет временно отключить все точки остановки в файле.
![|style="vertical-align:middle"](manage6.png) -- включить/отключить автоматическую остановку при ошибке.
: Эта кнопка -- одна из самых важных.
Нажмите её несколько раз. В старых версиях Chrome у неё три режима -- нужен фиолетовый, в новых -- два, тогда достаточно синего.
Когда она включена, то при ошибке в коде он автоматически остановится и мы сможем посмотреть в отладчике текущие значения переменных, при желании выполнить команды и выяснить, как так получилось.
**Процесс отладки заключается в том, что мы останавливаем скрипт, смотрим, что с переменными, переходим дальше и ищем, где поведение отклоняется от правильного.**
```smart header="Continue to here"
Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here). Это удобно, когда хочется сразу прыгнуть вперёд и breakpoint неохота ставить.
```
## Консоль
При отладке, кроме просмотра переменных и передвижения по скрипту, бывает полезно запускать команды 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);
}
```
Полную информацию по специальным командам консоли вы можете получить на странице [Chrome Console API](https://developer.chrome.com/devtools/docs/console-api) и [Chrome CommandLine API](https://developer.chrome.com/devtools/docs/commandline-api). Почти все команды также действуют в Firebug (отладчик для браузера Firefox).
Консоль поддерживают все браузеры, и, хотя IE10- поддерживает далеко не все функции, но `console.log` работает везде. Используйте его для вывода отладочной информации по ходу работы скрипта.
## Ошибки
Ошибки JavaScript выводятся в консоли.
Например, прервите отладку -- для этого достаточно закрыть инструменты разрабтчика -- и откройте [страницу с ошибкой](error/index.html).
Перейдите во вкладку Console инструментов разработчика (`key:Ctrl+Shift+J` / `key:Cmd+Shift+J`).
В консоли вы увидите что-то подобное:
![](console_error.png)
Красная строка -- это сообщение об ошибке.
Если кликнуть на ссылке `pow.js` в консоли, справа в строке с ошибкой, то мы перейдём непосредственно к месту в скрипте, где возникла ошибка.
Однако почему она возникла?
Более подробно прояснить произошедшее нам поможет отладчик. Он может "заморозить" выполнение скрипта на момент ошибки и дать нам возможность посмотреть значения переменных и стека на тот момент.
Для этого:
1. Перейдите на вкладку Sources.
2. Включите остановку при ошибке, кликнув на кнопку ![|style="vertical-align:middle"](manage6.png)
3. Перезагрузите страницу.
После перезагрузки страницы JavaScript-код запустится снова и отладчик остановит выполнение на строке с ошибкой:
![](chrome_break_error.png)
Можно посмотреть значения переменных. Открыть консоль и попробовать запустить что-то в ней. Поставить брейкпойнты раньше по коду и посмотреть, что привело к такой печальной картине, и так далее.
## Итого
Отладчик позволяет:
- Останавливаться на отмеченном месте (breakpoint) или по команде `debugger`.
- Выполнять код -- по одной строке или до определённого места.
- Смотреть переменные, выполнять команды в консоли и т.п.
В этой главе кратко описаны возможности отладчика Google Chrome, относящиеся именно к работе с кодом.
Пока что это всё, что нам надо, но, конечно, инструменты разработчика умеют много чего ещё. В частности, вкладка Elements -- позволяет работать со страницей (понадобится позже), Timeline -- смотреть, что именно делает браузер и сколько это у него занимает и т.п.
Осваивать можно двумя путями:
1. [Официальная документация](https://developer.chrome.com/devtools) (на англ.)
2. Кликать в разных местах и смотреть, что получается. Не забывать о клике правой кнопкой мыши.
Мы ещё вернёмся к отладчику позже, когда будем работать с HTML.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,8 @@
<style>
span.devtools {
display: inline-block;
background-image: url(/article/debugging-chrome/statusbarButtonGlyphs.svg);
height:16px;
width:16px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,50 @@
# Ответ
Вы могли заметить следующие недостатки, сверху-вниз:
```js no-beautify
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) );
}
```

View file

@ -0,0 +1,28 @@
importance: 4
---
# Ошибки в стиле
Какие недостатки вы видите в стиле этого примера?
```js no-beautify
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))
}
```

View file

@ -0,0 +1,433 @@
# TODO: Coding style
Our code must be as clean and easy to read as possible.
That is actually an art of programming -- to take a complex task and describe it using a programming language in a way that is both correct and human-readable.
One thing to help is a good code style. In this chapter we cover it's components.
[cut]
## Syntax
A cheatsheet with the rules (more details below):
![](code-style.png)
<!--
```js
function pow(x, n) {
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
let x = prompt("x?", "");
let n = prompt("n?", "");
if (n < 0) {
alert(`Power ${n} is not supported,
please enter an integer number, greater than 0`);
} else {
alert( pow(x, n) );
}
```
-->
Nothing is "carved in stone" here, so let's discuss the rules in detail.
### Figure brackets
In most JavaScript projects figure brackets are written on the same line. A so-called "egyptian" style. There's also a space before an opening bracket.
A corner-case if a single-line `if/for`. Should we use brackets at all? If yes, then where?
Here are the annotated variants, so you can judge about their readability on your own:
<!--
```js no-beautify
if (n < 0) {alert(`Power ${n} is not supported`);}
if (n < 0) alert(`Power ${n} is not supported`);
if (n < 0)
alert(`Power ${n} is not supported`);
if (n < 0) {
alert(`Power ${n} is not supported`);
}
```
-->
![](figure-bracket-style.png)
As a summary, for a really short code one line is acceptable: like `if (cond) return null`.
But a separate line for each statement in brackets is usually better.
### Line length
The maximal line length should be limited. No one likes to eye-follow a long horizontal line. Doesn't matter if it's a text or an OR'ed list of `if` conditions. It's better to split it.
The maximal line length is agreed on the team-level. It's usually 80 or 120 characters.
### Indents
There are two types of indents:
- **A horizontal indent: 2(4) spaces.**
A horizantal identation is made using either spaces or the "Tab" symbol (displayed as 2 or 4 or even 8 spaces, but 8 is too much).
Which one to choose is a kind of an old holy war. Spaces are a little more common nowadays.
One of advantages of spaces over tabs is that they allow more flexible configurations of indents than the "Tab" symbol.
For instance, we can align the arguments with the opening bracket, like this:
```js no-beautify
show(parameters,
aligned,
one,
after,
another
) {
// ...
}
```
- **A vertical indent, line breaks for splitting the code in logical blocks.**
Even a single function can often be divided in logical blocks. In the example below, the initialization of variables, the main loop and returning the result are split vertically:
```js
function pow(x, n) {
let result = 1;
// <--
for (let i = 0; i < n; i++) {
result *= x;
}
// <--
return result;
}
```
Insert an additional line break where it helps to make the code more readable. There should not be more than 9 lines of code without a vertical indentation.
### A semicolon
A semicolons should be after each statement. Even if could possibly be skipped.
There are languages where a semicolon is truly optional. It's rarely used there.
But in JavaScript a line break is sometimes interpreted as a semicolon and sometimes not. That leaves a place for programming errors, so semicolons should be at place.
### Nesting levels
There should not be too many nesting levels.
Sometimes it's a good idea to use the ["continue"](info:while-for#continue) directive in the loop to evade extra nesting in `if(..) { ... }`:
Instead of:
```js
for (let i = 0; i < 10; i++) {
if (cond) {
... // <- one more nesting level
}
}
```
We can write:
```js
for (let i = 0; i < 10; i++) {
if (!cond) *!*continue*/!*;
... // <- no extra nesting level
}
```
The similar thing can be done with `if/else` and `return`.
For example, two constructs below are identical.
The first one:
```js
function isEven(n) { // returns whether the number is even
if (n % 2 == 0) {
return true;
*!*
} else {
return false;
}
*/!*
}
```
The second one:
```js
function isEven(n) {
if (n % 2 == 0) {
return true;
}
*!*
return false;
*/!*
}
```
If there's a `return` inside the `if` block, then we need no `else` after it.
By the way, the very this function we can be implemented in a shorter way:
```js
function isEven(n) {
return !(n % 2);
}
```
But the code became less obvious.
...Of course we can write even shorter here:
**The most important not shortness, but simplicity and readability of the code.**
A shorter code is not always simpler to understand and maintain.
## The Naming
As a general rule:
- A variable name should be a noun.
- A function name should be a verb, or start with a verbal prefix. There can be exceptions if covered by another rule.
The camel-case notation is used for long words.
We discussed that before -- see [function basics](info:function-basics#function-naming) and [variables](info:variables#variable-naming).
A name should be descriptive and long enough, with the exception of:
- loop counter variables, `i` is a well-recognized name for a loop counter.
- functions/libraries that are well known and used very often.
- other cases when the code readability doesn't suffer.
## Functions = Comments
Functions should be short and do exactly one thing. If that thing is big, maybe it's worth to split the function into parts.
Sometimes following this rule may be not easy, but it's a definitely good thing.
...So what's here about comments?
A separate function is not only easier to test and debug -- it's very existance is a great comment!
For instance, compare the two functions `showPrimes(n)` below. Each one outputs [prime numbers](https://en.wikipedia.org/wiki/Prime_number) up to `n`.
The first variant uses a label:
```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 ); // a prime
}
}
```
The second variant uses an additional function `isPrime(n)` to test primality:
```js
function showPrimes(n) {
for (let i = 2; i < n; i++) {
*!*if (!isPrime(i)) continue;*/!*
alert(i); // a prime
}
}
function isPrime(n) {
for (let i = 2; i < n; i++) {
if ( n % i == 0) return false;
}
return true;
}
```
The second variant is easier to understand isn't it? Instead of the code piece we see a name of the action (`isPrime`). Sometimes people refer to such code as *self-describing*.
## Functions below the code
There are three way to place the "helper" functions used in the code.
1. Above the code that uses them:
```js
// *!*function declarations*/!*
function createElement() {
...
}
function setHandler(elem) {
...
}
function walkAround() {
...
}
// *!*the code which uses them*/!*
var elem = createElement();
setHandler(elem);
walkAround();
```
2. Code first, then functions
```js
// *!*the code which uses the functions*/!*
var elem = createElement();
setHandler(elem);
walkAround();
// --- *!*helper functions*/!* ---
function createElement() {
...
}
function setHandler(elem) {
...
}
function walkAround() {
...
}
```
3. Mixed, a function is described when it's first used.
Most of time, the second variant is preferred.
That's because when reading a code, we first want to know "what it does". If the code goes first, then it provides that information. And then maybe we won't need to read functions at all, especially if their names are adequate to what they're doing.
## Bad comments
В коде нужны комментарии.
Сразу начну с того, каких комментариев быть почти не должно.
**Должен быть минимум комментариев, которые отвечают на вопрос "что происходит в коде?"**
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках".
Серьёзно, хороший код и так понятен.
Об этом замечательно выразился Р.Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код недостаточно прост, и, может, стоит переписать его".
Если у вас образовалась длинная "простыня", то, возможно, стоит разбить её на отдельные функции, и тогда из их названий будет понятно, что делает тот или иной фрагмент.
Да, конечно, бывают сложные алгоритмы, хитрые решения для оптимизации, поэтому нельзя такие комментарии просто запретить. Но перед тем, как писать подобное -- подумайте: "Нельзя ли сделать код понятным и без них?"
## Хорошие комментарии
А какие комментарии полезны и приветствуются?
- **Архитектурный комментарий -- "как оно, вообще, устроено".**
Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Взгляд с высоты птичьего полёта. Эти комментарии особенно нужны, если вы не один, а проект большой.
Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно.
- **Справочный комментарий перед функцией -- о том, что именно она делает, какие параметры принимает и что возвращает.**
Для таких комментариев существует синтаксис [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/), которые учитывают их при автодополнении, а также выводят их в автоподсказках при наборе кода.
Кроме того, есть инструменты, например [JSDoc 3](https://github.com/jsdoc3/jsdoc), которые умеют генерировать по таким комментариям документацию в формате HTML. Более подробную информацию об этом можно также найти на сайте <http://usejsdoc.org/>.
**...Но куда более важными могут быть комментарии, которые объясняют не *что*, а *почему* в коде происходит именно это!**
Как правило, из кода можно понять, что он делает. Бывает, конечно, всякое, но, в конце концов, вы этот код *видите*. Однако гораздо важнее может быть то, чего вы *не видите*!
*Почему* это сделано именно так? На это сам код ответа не даёт.
Например:
Есть несколько способов решения задачи. Почему выбран именно этот?
: Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой.
Без этого возможна, например, такая ситуация:
- Вы открываете код, который был написан какое-то время назад, и видите, что он "неоптимален".
- Думаете: "Какой я был дурак", и переписываете под "более очевидный и правильный" вариант.
- ...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание.
Комментарии, которые объясняют выбор решения, очень важны. Они помогают понять происходящее и предпринять правильные шаги при развитии кода.
Какие неочевидные возможности обеспечивает этот код? Где ещё они используются?
: В хорошем коде должно быть минимум неочевидного. Но там, где это есть -- пожалуйста, комментируйте.
```smart header="Комментарии -- это важно"
Один из показателей хорошего разработчика -- качество комментариев, которые позволяют эффективно поддерживать код, возвращаться к нему после любой паузы и легко вносить изменения.
```
## Руководства по стилю
Когда написанием проекта занимается целая команда, то должен существовать один стандарт кода, описывающий где и когда ставить пробелы, запятые, переносы строк и т.п.
Сейчас, когда есть столько готовых проектов, нет смысла придумывать целиком своё руководство по стилю. Можно взять уже готовое, и которому, по желанию, всегда можно что-то добавить.
Большинство есть на английском, сообщите мне, если найдёте хороший перевод:
- [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml)
- [JQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines)
- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript)
- [Idiomatic.JS](https://github.com/rwldrn/idiomatic.js) (есть [перевод](https://github.com/rwldrn/idiomatic.js/tree/master/translations/ru_RU))
- [Dojo Style Guide](http://dojotoolkit.org/community/styleGuide)
Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрев эти руководства, вы можете выработать и свой стиль, но лучше не делать его особенно "уникальным и неповторимым", себе дороже потом будет с людьми сотрудничать.
## Автоматизированные средства проверки
Существуют средства, проверяющие стиль кода.
Самые известные -- это:
- [JSLint](http://www.jslint.com/) -- проверяет код на соответствие [стилю JSLint](http://www.jslint.com/lint.html), в онлайн-интерфейсе вверху можно ввести код, а внизу различные настройки проверки, чтобы сделать её более мягкой.
- [JSHint](http://www.jshint.com/) -- вариант JSLint с большим количеством настроек.
- [Closure Linter](https://developers.google.com/closure/utilities/) -- проверка на соответствие [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml).
В частности, JSLint и JSHint интегрированы с большинством редакторов, они гибко настраиваются под нужный стиль и совершенно незаметно улучшают разработку, подсказывая, где и что поправить.
Побочный эффект -- они видят некоторые ошибки, например необъявленные переменные. У меня это обычно результат опечатки, которые таким образом сразу отлавливаются. Очень рекомендую поставить что-то из этого. Я использую [JSHint](http://www.jshint.com/).
## Итого
Описанные принципы оформления кода уместны в большинстве проектов. Есть и другие полезные соглашения.
Следуя (или не следуя) им, необходимо помнить, что любые советы по стилю хороши лишь тогда, когда делают код читаемее, понятнее, проще в поддержке.

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View file

@ -0,0 +1,297 @@
# TODO: Как писать неподдерживаемый код?
```warn header="Познай свой код"
Эта статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) ("как писать неподдерживаемый код") с дополнениями, актуальными для JavaScript.
Возможно, в каких-то из этих советов вам даже удастся узнать "этого парня в зеркале".
```
Предлагаю вашему вниманию советы мастеров древности, следование которым создаст дополнительные рабочие места для JavaScript-разработчиков.
Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо".
Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него...
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым.
Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
[cut]
## Соглашения -- по настроению
```quote author="Сериал \"Симпсоны\", серия Helter Shelter"
Рабочий-чистильщик осматривает дом:<br>
"...Вот только жук у вас необычный...<br>
И чтобы с ним справиться, я должен жить как жук, стать жуком, думать как жук."<br>
(грызёт стол Симпсонов)
```
Чтобы помешать другому программисту исправить ваш код, вы должны понять путь его мыслей.
Представьте, перед ним -- ваш большой скрипт. И ему нужно поправить его. У него нет ни времени ни желания, чтобы читать его целиком, а тем более -- досконально разбирать. Он хотел бы по-быстрому найти нужное место, сделать изменение и убраться восвояси без появления побочных эффектов.
Он рассматривает ваш код как бы через трубочку из туалетной бумаги. Это не даёт ему общей картины, он ищет тот небольшой фрагмент, который ему необходимо изменить. По крайней мере, он надеется, что этот фрагмент будет небольшим.
**На что он попытается опереться в этом поиске -- так это на соглашения, принятые в программировании, об именах переменных, названиях функций и методов...**
Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте?
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.**
Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
### Пример из jQuery
```warn header="jQuery / DOM"
Этот пример требует знаний jQuery/DOM, если пока их у вас нет -- пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки.
```
Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого:
```js
var img = $('<img/>'); // создали новые элементы (jQuery-синтаксис)
var div = $('<div/>'); // и поместили в переменную
img.wrap(div); // обернуть img в div
div.append('<span/>');
```
Результат кода после операции `wrap` -- два элемента, один вложен в другой:
```html
<div>
<img/>
</div>
```
А что же после `append`?
Можно предположить, что `<span/>` добавится в конец `div`, сразу после `img`... Но ничего подобного!
Искусный ниндзя уже нанёс свой удар и поведение кода стало неправильным, хотя разработчик об этом даже не подозревает.
Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь!
Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался.
В итоге, после вызова получается два независимых `div'а`: первый содержит `img` (этот неявный клон никуда не присвоен), а второй -- наш `span`.
Объяснения не очень понятны? Написано что-то странное? Это просто разум, привыкший, что соглашения уважаются, не допускает мысли, что вызов `wrap` -- неявно клонирует элемент. Ведь другие jQuery-методы, кроме `clone` этого не делают.
Как говорил [Учитель](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%84%D1%83%D1%86%D0%B8%D0%B9): "В древности люди учились для того, чтобы совершенствовать себя. Нынче учатся для того, чтобы удивить других".
## Краткость -- сестра таланта!
Пишите "как короче", а не как понятнее. "Меньше букв" -- уважительная причина для нарушения любых соглашений.
Ваш верный помощник -- возможности языка, использованные неочевидным образом.
Обратите внимание на оператор вопросительный знак `'?'`, например:
```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>
Великий образ не имеет формы.
```
При выборе имени старайтесь применить максимально абстрактное слово, например `obj`, `data`, `value`, `item`, `elem` и т.п.
- **Идеальное имя для переменной: `data`.** Используйте это имя везде, где можно. В конце концов, каждая переменная содержит *данные*, не правда ли?
Но что делать, если имя `data` уже занято? Попробуйте `value`, оно не менее универсально. Ведь каждая переменная содержит *значение*.
Занято и это? Есть и другой вариант.
- **Называйте переменную по типу данных, которые она хранит: `obj`, `num`, `arr`...**
Насколько это усложнит разработку? Как ни странно, намного!
Казалось бы, название переменной содержит информацию, говорит о том, что в переменной -- число, объект или массив... С другой стороны, **когда непосвящённый будет разбирать этот код -- он с удивлением обнаружит, что информации нет!**
Ведь как раз тип легко понять, запустив отладчик и посмотрев, что внутри. Но в чём смысл этой переменной? Что за массив/объект/число в ней хранится? Без долгой медитации над кодом тут не обойтись!
- **Что делать, если и эти имена кончились? Просто добавьте цифру:** `item1, item2, elem5, data1`...
### Похожие имена
Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий?
**Один из способов -- использовать похожие имена переменных, например `data` и `date`.** Бегло прочитать такой код почти невозможно. А уж заметить опечатку и поправить её... Ммммм... Мы здесь надолго, время попить чайку.
### А.К.Р.О.Н.И.М
Используйте сокращения, чтобы сделать код короче.
Например `ie` (Inner Element), `mc` (Money Counter) и другие. Если вы обнаружите, что путаетесь в них сами -- героически страдайте, но не переписывайте код. Вы знали, на что шли.
### Хитрые синонимы
```quote author="Конфуций"
Очень трудно найти чёрную кошку в тёмной комнате, особенно когда её там нет.
```
**Чтобы было не скучно -- используйте *похожие названия* для обозначения *одинаковых действий*.**
Например, если метод показывает что-то на экране -- начните его название с `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>
Пребывая же в темноте, увидишь все, что находится на свету.
```
Почему бы не использовать одинаковые переменные внутри и снаружи функции? Это просто и не требует придумывать новых имён.
```js
var *!*user*/!* = authenticateUser();
function render() {
var *!*user*/!* = anotherValue();
...
...многобукв...
...
... // <-- программист захочет внести исправления сюда, и..
...
}
```
Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` локально перекрыта и попытается работать с ней, полагая, что это результат `authenticateUser()`... Ловушка захлопнулась! Здравствуй, отладчик.
## Мощные функции!
Не ограничивайте действия функции тем, что написано в её названии. Будьте шире.
Например, функция `validateEmail(email)` может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail.
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.**
Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.</li>
**Объединение нескольких смежных действий в одну функцию защитит ваш код от повторного использования.**
Представьте, что другому разработчику нужно только проверить адрес, а сообщение -- не выводить. Ваша функция `validateEmail(email)`, которая делает и то и другое, ему не подойдёт. Работодатель будет вынужден оплатить создание новой.
## Внимание.. Сюр-при-из!
Есть функции, название которых говорит о том, что они ничего не меняют. Например, `isReady`, `checkPermission`, `findTags`... Предполагается, что при вызове они произведут некие вычисления, или найдут и возвратят полезные данные, но при этом их не изменят. В трактатах это называется "отсутствие сторонних эффектов".
**По-настоящему красивый приём -- делать в таких функциях что-нибудь полезное, заодно с процессом проверки. Что именно -- совершенно неважно.**
Удивление и ошеломление, которое возникнет у вашего коллеги, когда он увидит, что функция с названием на `is..`, `check..` или `find...` что-то меняет -- несомненно, расширит его границы разумного!
**Ещё одна вариация такого подхода -- возвращать нестандартное значение.**
Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект с результатами проверки! А чего, полезно.
Те же разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
## Заключение
Все советы выше пришли из реального кода... И в том числе от разработчиков с большим опытом.
Возможно, даже больше вашего, так что не судите опрометчиво ;)
- Следуйте нескольким из них -- и ваш код станет полон сюрпризов.
- Следуйте многим -- и ваш код станет истинно вашим, никто не захочет изменять его.
- Следуйте всем -- и ваш код станет ценным уроком для молодых разработчиков, ищущих просветления.

View 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;
}

View file

@ -0,0 +1,8 @@
/* исправьте этот код */
function pow(x, n) {
var result = 1;
for (var i = 0; i < n; i++) {
result *= x;
}
return result;
}

View 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");
});
});

View 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;
}
```

View file

@ -0,0 +1,19 @@
importance: 5
---
# Сделать pow по спецификации
Исправьте код функции `pow`, чтобы тесты проходили.
Для этого ниже в задаче вы найдёте ссылку на песочницу.
Она содержит HTML с тестами. Обратите внимание, что HTML-страница в ней короче той, что обсуждалась в статье <info:testing>. Это потому что библиотеки Chai, Mocha и Sinon объединены в один файл:
```html
<script src="https://js.cx/test/libs.js"></script>
```
Этот файл содержит код библиотек, стили, настройки для них и запуск `mocha.run` по окончании загрузки страницы. Если нет элемента с `id="mocha"`, то результаты выводятся в `<body>`.
Сборка сделана исключительно для более компактного представления задач, без рекомендаций использовать именно её в проектах.

View 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 no-beautify
...
it("ноль в нулевой степени даёт NaN", function() {
assert( isNaN(pow(0, 0)), "0 в степени 0 не NaN");
});
...
```

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://js.cx/test/libs.js"></script>
<script src="test.js"></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>
</body>
</html>

View 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");
});
});

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://js.cx/test/libs.js"></script>
<script src="test.js"></script>
</head>
<body>
<script>
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;
}
</script>
</body>
</html>

View 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");
});
});

View file

@ -0,0 +1,9 @@
importance: 5
---
# Добавьте тест к задаче
Добавьте к [предыдущей задаче](/task/pow-nan-spec) тесты, которые будут проверять, что любое число, кроме нуля, в нулевой степени равно `1`, а ноль в нулевой степени даёт `NaN` (это математически корректно, результат 0<sup>0</sup> не определён).
При необходимости, исправьте реализацию, чтобы тесты проходили без ошибок.

View 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), 125);
});
});
```
Можно использовать цикл для генерации блоков `it`, в этом случае важно, чтобы сам код такого цикла был достаточно простым. Иногда проще записать несколько блоков `it` вручную, как сделано выше, чем "городить огород" из синтаксических конструкций.

View file

@ -0,0 +1,24 @@
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. Синтаксически он верен и работает, но спроектирован неправильно.

View file

@ -0,0 +1,434 @@
# TODO: Автоматические тесты при помощи 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);
});
});
```
У спецификации есть три основных строительных блока, которые вы видите в примере выше:
`describe(название, function() { ... })`
: Задаёт, что именно мы описываем, используется для группировки "рабочих лошадок" -- блоков `it`. В данном случае мы описываем функцию `pow`.
`it(название, function() { ... })`
: В названии блока `it` *человеческим языком* описывается, что должна делать функция, далее следует *тест*, который проверяет это.
`assert.equal(value1, value2)`
: Код внутри `it`, если реализация верна, должен выполняться без ошибок.
Различные функции вида `assert.*` используются, чтобы проверить, делает ли `pow` то, что задумано. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат `pow(2, 3)` равен `8`.
Есть и другие виды сравнений и проверок, которые мы увидим далее.
## Поток разработки
Как правило, поток разработки таков:
1. Пишется спецификация, которая описывает самый базовый функционал.
2. Делается начальная реализация.
3. Для проверки соответствия спецификации мы задействуем одновременно фреймворк, в нашем случае [Mocha](http://mochajs.org/) вместе со спецификацией и реализацией. Фреймворк запускает все тесты `it` и выводит ошибки, если они возникнут. При ошибках вносятся исправления.
4. Спецификация расширяется, в неё добавляются возможности, которые пока, возможно, не поддерживаются реализацией.
5. Идём на пункт 3, делаем реализацию, и так далее, до победного конца.
Разработка ведётся *итеративно*, один проход за другим, пока спецификация и реализация не будут завершены.
В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают.
## Пример в действии
Для запуска тестов нужны соответствующие JavaScript-библиотеки.
Мы будем использовать:
- [Mocha](http://mochajs.org/) -- эта библиотека содержит общие функции для тестирования, включая `describe` и `it`.
- [Chai](http://chaijs.com) -- библиотека поддерживает разнообразные функции для проверок. Есть разные "стили" проверки результатов, с которыми мы познакомимся позже, на текущий момент мы будем использовать лишь `assert.equal`.
- [Sinon](http://sinonjs.org/) -- для эмуляции и хитрой подмены функций "заглушками", понадобится позднее.
Эти библиотеки позволяют тестировать JS не только в браузере, но и на сервере Node.JS. Здесь мы рассмотрим браузерный вариант, серверный использует те же функции.
Пример HTML-страницы для тестов:
[html src="index.html"]
Эту страницу можно условно разделить на четыре части:
1. Блок `<head>` -- в нём мы подключаем библиотеки и стили для тестирования, нашего кода там нет.
2. Блок `<script>` с реализацией спецификации, в нашем случае -- с кодом для `pow`.
3. Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше.
4. Элемент `<div id="mocha">` будет использоваться библиотекой Mocha для вывода результатов. Запуск тестов инициируется командой `mocha.run()`.
Результат срабатывания:
[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`.
Здесь есть два варианта организации кода:
1. Первый вариант -- добавить `assert` в тот же `it`:
```js
describe("pow", function() {
it("возводит в n-ю степень", function() {
assert.equal(pow(2, 3), 8);
*!*
assert.equal(pow(3, 4), 81);
*/!*
});
});
```
2. Второй вариант -- сделать два теста:
```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);
});
});
```
Их принципиальное различие в том, что если `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 no-beautify
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" title="Открыть пример с тестами в песочнице"]
Как правило, `beforeEach/afterEach` (`before/each`) используют, если необходимо произвести инициализацию, обнулить счётчики или сделать что-то ещё в таком духе между тестами (или их группами).
````
## Расширение спецификации
Базовый функционал `pow` описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его.
Как говорилось ранее, функция `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:
- `assert(value)` -- проверяет что `value` является `true` в логическом контексте.
- `assert.equal(value1, value2)` -- проверяет равенство `value1 == value2`.
- `assert.strictEqual(value1, value2)` -- проверяет строгое равенство `value1 === value2`.
- `assert.notEqual`, `assert.notStrictEqual` -- проверки, обратные двум предыдущим.
- `assert.isTrue(value)` -- проверяет, что `value === true`
- `assert.isFalse(value)` -- проверяет, что `value === false`
- ...более полный список -- в [документации](http://chaijs.com/api/assert/)
В нашем случае хорошо бы иметь проверку `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" title="Открыть полный пример с реализацией в песочнице"]
Эту спецификацию можно использовать как:
1. **Тесты**, которые гарантируют правильность работы кода.
2. **Документацию** по функции, что она конкретно делает.
3. **Примеры** использования функции, которые демонстрируют её работу внутри `it`.
Имея спецификацию, мы можем улучшать, менять, переписывать функцию и легко контролировать её работу, просматривая тесты.
Особенно важно это в больших проектах.
Бывает так, что изменение в одной части кода может повлечь за собой "падение" другой части, которая её использует. Так как всё-всё в большом проекте руками не перепроверишь, то такие ошибки имеют большой шанс остаться в продукте и вылезти позже, когда проект увидит посетитель или заказчик.
Чтобы избежать таких проблем, бывает, что вообще стараются не трогать код, от которого много что зависит, даже если его ну очень нужно переписать. Жизнь пробивается тонкими росточками там, где должен цвести и пахнуть новый функционал.
**Код, покрытый автотестами, являет собой полную противоположность этому!**
Даже если какое-то изменение потенциально может порушить всё -- его совершенно не страшно сделать. Ведь есть масса тестов, которые быстро и в автоматическом режиме проверят работу кода и, если что-то падает -- это можно будет легко локализовать и поправить.
**Кроме того, код, покрытый тестами, имеет лучшую архитектуру.**
Конечно, это естественное следствие того, что его легче менять и улучшать. Но не только.
Чтобы написать тесты, нужно разбить код на функции так, чтобы для каждой функции было чётко понятно, что она получает на вход, что делает с этим и что возвращает. Это означает ясную и понятную структуру с самого начала.
Конечно, в реальной жизни всё не так просто. Зачастую написать тест сложно. Или сложно поддерживать тесты, поскольку код активно меняется. Сами тесты тоже пишутся по-разному, при помощи разных инструментов.
## Что дальше?
В дальнейшем условия ряда задач будут уже содержать в себе тесты. На них вы познакомитесь с дополнительными примерами.
Как правило, они будут вполне понятны, даже если немного выходят за пределы этой главы.

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View file

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

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View file

@ -0,0 +1,7 @@
describe("pow", function() {
it("возводит в n-ю степень", function() {
assert.equal(pow(2, 3), 8);
});
});

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View 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);
});
});

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View 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);
}
});

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View 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);
}
});
// ...
});

View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View 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");
});
});

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View file

@ -0,0 +1,7 @@
describe("pow", function() {
it("возводит в n-ю степень", function() {
assert.equal(pow(2, 3), 8);
});
});

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View 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");
});
});

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- подключаем стили Mocha, для отображения результатов -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.css">
<!-- подключаем библиотеку Mocha -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.1.0/mocha.js"></script>
<!-- настраиваем Mocha: предстоит BDD-тестирование -->
<script>
mocha.setup('bdd');
</script>
<!-- подключаем chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/2.0.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>

View 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)));
});
});

View file

@ -0,0 +1,57 @@
# Polyfills
The JavaScript language steadily evolves. The new proposals get analyzed and, if they look worthy, are appended to the list at <https://tc39.github.io/ecma262/> and then progress to the [specification](http://www.ecma-international.org/publications/standards/Ecma-262.htm).
Each JS engine has its own idea about what to implement first. It may implement proposals that are not approved yet and fail to implement things that are already in the spec, because they are less interesting or just harder to do.
So it's quite common for an engine to implement only the part of the standard.
A good page to see the current state of support for language features is <https://kangax.github.io/compat-table/es6/> (remember the link to use in the future when you know the language).
## Babel.JS
When we use all the modern features of the language, some engines may fail to support such code. Just as it was said, not all features are implemented everywhere.
Here Babel.JS comes to the rescue.
[Babel.JS](https://babeljs.io) is a [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler). It rewrites the modern JavaScript code into the previous standard.
Actually, there are two parts in Babel:
1. The transpiler program, which rewrites the code.
The transpiler runs on a developer's computer. It rewrites the code, which is then bundled by a project build system (like [webpack](http://webpack.github.io/) or [brunch](http://brunch.io/)). Most build systems can support Babel easily.
2. The polyfill.
For some functions we also need add a special script that should run before our scripts and introduce modern functions that the engine may not support by itself. There's a term "polyfill" for such scripts.
The two interesting variants are [babel polyfill](https://babeljs.io/docs/usage/polyfill/) that supports a lot, but is big and the [polyfill.io](http://polyfill.io) service that allows to load/construct polyfills on-demand, depending on the features we need.
The transpiler and/or polyfill may be not needed if we orient towards more-or-less modern engines and don't use rarely supported features.
## Examples in the tutorial
```warn header="Browser support is required"
Examples that use modern JS will work only if your browser supports it.
```
````online
Most examples are runnable at-place, like here:
```js run
alert('Press the "Play" button in the upper-right corner to run');
```
...But if it uses a feature that your browser does not support, an error is shown.
That doesn't mean that the example is wrong! It's just the browser lacking the support for certain features yet.
````
[Chrome Canary](https://www.google.com/chrome/browser/canary.html) is good for more examples.
Note that on production we can use Babel to translate the code into suitable for less recent browsers, so there will be no such limitation, the code will run everywhere.
Now we can go coding, so let's choose a good code editor.

View file

@ -0,0 +1,5 @@
# Code quality
This chapter goes early to explain coding practices that we'll use further in the development.