Правила написания JavaScript кода

Dither edited this page Feb 1, 2012 · 6 revisions

Правила написания JavaScript кода

Данный обзор предназначен только для личного использования. Это не сборник абсолютных/идеальных правил, если существующий код уже написан в определённом стиле, то именно ему стоит отдать предпочтение.

Подразумевается возможность развития и обновления этого документа если какие-то идеи окажутся неудачными.

TL:DR;

  • FullCamel именование пространств и конструкторов.
  • camelCase или mixed_underscore для названий переменных и функций.
  • UPPERCASE_UNDERSCORED для констант.
  • Предпочтительны понятные названия функций.
  • Отступ в 4 пробела.
  • Пробелы после двоеточий и запятых.
  • Использование паттерна "конструктор".
  • В случаях если параметры функции не ясны из её названия стоит использовать именованные параметры.
  • В замыканиях стоит использовать 'self' для хранения ссылки на конструируемый объект.
  • Стоит использовать исключения, а не тихое отключение.

Определение перемененных

Переменные должны определяться в верху функции, даже если они определяются ниже по коду. Если переменной присваивается значение, эта запись должна идти отдельной строкой кода; если переменные только объявляются можно писать их одной строкой, но только в самом начале определения. Перед знаком = и после него должны быть пробелы. Переменные должны быть выровнены по левой кромке.

var yeah, this_is, a, way,
    foo = 1,
    bar = 2;

Предполагается использование camelCase стиля именования методов и mixed_underscore для атрибутов и сервисных функций. Подумайте как лучше выразить назначение функции или переменной, скажем массивы лучше именовать во множественном числе, а перед булевыми перемененными использовать is.

Отступы и пробелы

Для отступа используются четыре пробела. Стоит убирать пробелы перед переносом строки. После двоеточий и запятых пробелы необходимы всегда.

Строки

Для определения строк используются одинарные кавычки.

Блоки

Из-за того, что используется явное указание точек-с-запятой всегда начинайте фигурную скобку на той строке, где идёт открывающее выражение. Например:

if (foo) {
    bar();
}

if (foo) {
    foo = 1;
    return false;
}

Комментирование

В целом рекомендуется комментировать сложные моменты и игнорировать очевидный код. Не стоит добавлять бессмысленные, личные или расплывчатые комментарии.

Если оказывается что необходимо постоянно что-то комментировать, подумайте над тем не стоит ли разбить код на именованные функции, описывающие его работу.

При работе с откомментированным кодом не забывайте проверять сохраняется ли актуальность данного комментария и исправлять/удалять его.

Для длинных строк нужно использовать /* ... */.

Длинные одно-строчные комментарии должны быть отдельной строчкой, предпочтительно над комментируемым кодом. Так же перед ними стоит оставлять пустую строку.

var some = "stuff";

// Делаем цикл
for (var i = 0; i < 10; i++ ) {
    doIt();
}

Комментарии внутри строки можно использовать только для описания специфических аргументов в списке формальных параметров:

function foo(types, selector, data, fn, /*ВНУТРЕННИЙ*/ one ) {
    // do stuff.
}

Массивы и инициализация объектов

Пустые объекты и массивы не должны содержать пробелов. Однострочные массивы и инициализаторы объектов допустимы только если они умещаются в строку-две.

var arr = [1, 2, 3];    // Без пробелов после [ или до ].
var obj = {a: 1, b: 2, c: 3};    // Без пробелов после { или до }.

Отступы многострочных инициализаторов аналогичны таковым у блоков.

var inset = {
    top: 10,
    right: 20,
    bottom: 15,
    left: 12
};

Обратите внимание на то, что перед двоеточием пробела нет.

this.rows = [
    '"Чёртпобериэтоттекст" ',
    '"Marvin the Paranoid Android" ',
    'the.mice@magrathea.com'
];

Функции

По возможности избегайте анонимных функций. По определению если функции не присвоено имя, она считается анонимной: неудачно:

this.do_the_magic = function () {
    //...
}

Хотя она и назначена осмысленному атрибуту, функция была задана анонимной.

удачно:

this.do_the_magic = function do_the_magic() {
    //...
}

Приложение сделанное из множества анонимных функций почти невозможно профилировать, а трасса вызовов функции оказывается бессмысленной.

Незначительные по размеру блоки, вроде функций обратного вызова или обработчиков событий могут и не иметь подходящего имени, в таких случаях анонимность оправданна.

Об именовании

Правильное именование функций очень важно. Внятные имена функций могут помочь прочесть код. Не забывайте о ясности. Длинные имена не представляют никаких проблем.

Вызов функций

Вызов функции должен содержать пробелы между параметрами, но не должен содержать пробелов около открывающей и закрывающей скобок:

foo('bar', 1);

Простые вызовы должны быть в одну строку:

activate(minigun, '10000+rounds');

Если функция вызывается со множеством параметров или длинными аргументами, её вызов должен разделяться на название и список аргументов, по одному на строку:

activate(
    the_super_multi_laser_array,
    'Piiiiiuuuuuuuuuuuuuuuuuuuuuuuuuuu'
);

К слову, если аргументы содержат объекты с длинными атрибутами, их стоит тоже записать в несколько строк:

activate(
    the_super_multi_laser_array,
    settings: {
        voltage: Piiiiiuuuuuuuuuuuuuuuuuuuuuuuuuuu,
        target:    "WTF are you targeting our satellites XD"
    }
);

Конструкторы

Существует несколько способов создания объектов. В целом рекомендуется использовать паттерн "конструктор":

function Laser() {
    this.fire = function fire() {
        //...
    }
}

window.the_laser = new Laser();

Вложенные конструкторы допустимы, но перед этим стоит подумать "а стоит ли".

function Laser() {
    var power_supply = new PowerSupply();

    this.fire = function fire() {
        power_supply.turn_on();
        //...
    }

    function PowerSupply() {
        //...
    }
}

Self

Часто требуется сохранить ссылку на конструируемый объект для использования во внутренних функциях. Для этого стоит задать переменную self:

function Laser() {
    var self = this;

    function fire() {
        set_voltage(self.voltage);
    }
}

Стоит отметить что эта строка всегда идёт в конструкторе первой.

Использование именованных параметров

Использование именованных параметров уменьшает повторения(?) и делает код читабельнее. Их использование особенно полезно если из названия функции не ясно что в неё передавать.

Неудачно:

function do_the_thing(tempo, start_at_step, repeats) {
    //...
}

do_the_thing(120, 5, 3);

Чтобы в таком коде понять что делает вызов функции, придётся возвращаться к её определению.

Удачно:

function do_the_thing(params) {
    //params: tempo, start_at_step, repeats
}

do_the_thing({
    tempo: 120,
    start_at_step: 5,
    repeats: 3
});

или же:

function do_the_thing(params) {
    var tempo =         params.tempo;
    var start_at_step = params.start_at_step;
    var repeats =       params.repeats;
}

do_the_thing({
    tempo: 120,
    start_at_step: 5,
    repeats: 3
});

В данном примере становится очевидно что представляет собой каждый параметр.

Что нужно для начала работы функции должно быть очевидно в самом её начале. При её определении либо заносите в var все используемые перемененные, либо комментируйте что из параметров будет использоваться и для чего.

Вложенные функции

Вложенные функции неплохо подходят для разбиения функционала и могут использоваться свободно. Если функция используется только другой функцией её стоит определить внутри последней.

Неудачно:

function foo_plus_bar() {
    return get_foo_for_foo_plus_bar() + 'bar';
};
function get_foo_for_foo_plus_bar() {
    return 'foo';
};

Удачно:

function foo_plus_bar() {
    function get_foo() {
        return 'foo';
    };
    return get_foo() + 'bar';
};

Как исключение иногда стоит определять дочернюю функцию вне основной с целью избегания перекрёстных ссылок (см. раздел о замыканиях).

Модульная структура

Общее поведение может быть сгруппировано в отдельные модули:

function PoweredThing() {
    this.set_voltage = function set_voltage() {
        //...
    }
}

function Laser() {
    PoweredThing.apply(this);
}

var laser = new Laser();
laser.set_voltage(100000000);

Файлы

В целом рекомендуется создавать по файлу на каждый конструктор или логический блок. Названия должны быть строчными и описывать назначение кода.

Код в представлении

Допустимо использование встроенного JavaScript-кода, но только если инициализаторы связаны с содержимым страницы. В целом код должен быть отделён от представления.

Допустимо встраивать в представление установку констант.

Использование JavaScript в атрибутах HTML противопоказано:

<a onclick="do_something();">Запускаемся</a>

Применяйте ненавязчивый javascript с целью разделить функциональность (слой обработки) от представлений и кода страничек.

Языковые правила

Var

Всегда используйте var. Никогда не применяйте неявного приравнивания. Скажем, если хотите создать объект внутри window, так и пишите:

Неверно:

foo = 'bar';

Верно:

window.foo = 'bar';

Константы

Константы должны определяться аналогично обычным переменным, но при этом следовать своему стилю именования. Ключевое слово 'const' плохо поддерживается и его следует избегать.

Точки-с-запятой

Никогда не надейтесь на встроенное добавление точек-с-запятой. Они необходимы после каждого приравнивания или вызова функции. После блоков они не нужны.

Так же рекомендуют использовать точки-с-запятой после определений функций, хотя это и не обязательно.

После блоков for / while / switch / if / else точки-с-запятой не используются в целях повышения удобочитаемости кода.

var foo = 'bar';
// приравнивание: ';' нужна

function foo() {
    alert("don't you foo me");
    // вызов функции: ';' нужна
};
// ';' не нужна, но рекомендуется

this.foo = function foo() {
    alert("а ну не фукай");
};
// ';' нужна, т.к. приравнивание

if (foo) {
    foo();
}
// ';' здесь не нужна

Тернарный оператор

Тернарный оператор полезен при назначении переменных. Однако вложенные тернарные операторы недопустимы из-за их низкой читаемости.

Неудачно:

var a = b ? c : d ? e : f;

Проверка на равенство

В большинстве случаев необходимо использование строгой проверки на (не)равенство (=== and !==). В частности желательно приведение перемененных перед сравнением.

Проверки типов

Проверка типов по возможности должна делаться с помощью именованных методов: arr.isArray, str.isString, func.isFunction, и obj.isObject.

Определение внутри блоков

Как общую практику избегайте объявлений внутри вложенных блоков. Особенно старайтесь избегать определения внутри блоков функций, т.к. это нестандартно для ECMAScript. ECMAScript допускает только определения функций в корневом блоке скрипта или функции(?).

Ужасно:

if (foo_is_wibble) {
    function foo() { return 'wibble'; }
}
else {
    function foo() { return 'bar'; }
}
foo();

Нормально:

function wibble() {
    return 'wibble';
}
function bar() {
    return 'bar';
}

var foo;
if (foo_is_wibble) {
    foo = wibble;
}
else {
    foo = bar;
}
foo();

Как общее правило переменные стоит выносить из блоков.

Неудачно:

for (var i=0; i<5 i++) {
    var wibble = new Wibble(i);
    wibbles.push(bar);
}

Удачно:

var wibble;
for (var i=0; i<5 i++) {
    wibble = new Wibble(i);
    wibbles.push(wibble);
}

Исключения

Используйте исключения. Если нужно обработать ошибки генерируйте throw. Единственно, не стоит злоупотреблять блоками try / catch, т.к. они не слишком быстро обрабатываются.

Не бойтесь создать своё исключение, т.к. если что-то сломалось - оно сломалось и нам нужно это знать, чтобы сэкономить время при отладке. Никогда не делайте "тихих" сбоев.

Создание примитивов

Не используйте обёртки примитивов, а задавайте их явно:

var str = 'foo';
var bool = false;
var arr = [];
var num = 10;
var obj = {};

В частности не нужно использовать new с конструкторами примитивов, вот почему:

typeof(Boolean()) // ==> 'boolean'
typeof(new Boolean()) // ==> 'object'

В JavaScript есть пять типов примитивов: undefined, null, boolean, number, и string.

Замыкания

Замыкания великолепны. Порой они захватывают дух, однако используя их стоит быть внимательным, особенно если код может работать длительное время.

Рассмотрим пример:

function foo(element, a, b) {
    element.onclick = function handle_click() { /* использует a и b */ };
}

В данном примере создана перекрёстная ссылка, вызывающая утечку памяти. Функция handle_click содержит element в своей области видимости и element содержит handle_click, что означает, что ни одна не может быть удалена.

function foo(element, a, b) {
    element.onclick = bar(a, b);
}
function bar(a, b) {
    return function() { /* использует a и b */ }
}

В данном случае создано второе замыкание не содержащее element и исключающее перекрёстную ссылку.

Eval

Основное назначение eval для десериализации. Если вы используете его для чего-то ещё, вы увлеклись.

With

В целом - не стоит. Использование with легко маскирует область видимости и даёт нечитаемый код.

This

Использование this может сбивать с толку. В целом не стоит использовать его кроме как в конструкторах или совместно с call или apply.

Использование конечных элементов DOM в вызовах событий говорит о непродуманном коде и как бы намекает на необходимость другого подхода.

Циклы for … in …

Не стоит использовать этот метод при обработке массивов.

Модификация прототипов встроенных объектов

К примеру:

Array.prototype.find = function () {....} 

Встроенные конструкторы не должны модифицироваться! Если нужно расширить функционал встроенных классов создайте новый конструктор, который создаёт объект и добавляет к нему свой функционал.