Стилистические требования к написанию программного кода

пишите поддерживаемый код

| Категории: Javascript, FAQ, Coffee-script
Дмитрий Горбунов

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

На помощь приходят правила написания кода. У каждой компании они отличаются, но обязательно существуют, если они хотя бы чего-то стоят.
В Makeomatic мы верим в то, что открытые знания позволят сделать мир лучше, клиентов доверчивее, ну и, конечно же, привлекут еще
больше сотрудников в наш дружный коллектив.

Общие положения

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

Разбивка по модулям

Каждый логический объект кода должен находиться в отдельном файле, далее называемом модулем. Модуль должен содержать не более одного логического объекта. Это может быть класс, функция или даже константа. Всё зависит от назначения объекта. Примерами модулей являются:

  • Контроллер Express.js
  • Модуль Node.js
  • Плагин yb-processor
  • Middleware yb-server
  • Контроллер yb-client

Оформление модуля

Каждый модуль должен начинаться с комментария, в котором должны быть указаны:

  1. Имя первоначального автора модуля
  2. Дата создания модуля
  3. Назначение и краткое описание модуля
  4. Место модуля в иерархии
  5. Лицензия, под которой распространяется модуль (если отличается от лицензии вышестоящего в иерархии объекта)

Примером правильно оформленного начала модуля является такой заголовок модуля CoffeeScript:

1
2
3
4
5
6
7
###
Dmitry Gorbunov <atskiisotona@gmail.com>
8/11/2013 8:21:35 PM
This plugin fetches user profiles from ark.com and populates message with them.
This plugin belongs to yb-processor project.
###

Общие правила оформления кода

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

Причины:

  • Легче форматировать
  • Легче редактировать на удалённом сервере
  • Можно настроить ширину табуляции по своему вкусу

JavaScript

Общие правила

Комментарии записываются перед объектом, который нуждается в комментировании. Многострочные комментарии используются в заголовке файла и в тех случаях, когда оформляется JSDoc, в остальных случаях используется пара символов «/» для комментирования, даже в тех случаях, когда комментарий многострочный.

Текст комментария должен быть отделён одним пробелом от символа комментария.

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
I'm file header, see above for more info
*/
// This is some code that does some work
// And this commentary is multi-line
var a = someFunc()
/**
* JSDoc commentary
*/
function someFunc() {
}

Использование пробела

Пробел ставится:

  • В однострочных массивах и объектах после открывающей и перед закрывающей скобками
  • Между именем функции и списком параметров (если функция анонимна, то между словом function и списком параметров)
  • Всегда после запятой, кроме случаев, когда следующий элемент списка записывается с новой строки
  • Вокруг бинарных операторов (арифметические, логические, оператор присваивания)
  • После двоеточия в описаниях объектов
1
2
3
4
5
6
var a = { test: 1 }
var b = [ 1, 2, 3 ]
var c = 2 + 3
var d = function (a, b) {
return a + b
}

Пробел не ставится

  • В списке параметров функции после открывающей и перед закрывающей скобками
  • Для выравнивания переменных в столбцах
  • При вызове функций
1
2
3
4
5
6
7
// плохо
function ( a, b ) { return a + b }
// ужасно
var a = 5
var bcf = 7
var b = someFunc (a, bcf)

Перевод строки

Символ перевода строки ставится:

  • После открывающей фигурной или квадратной скобки в описаниях массивов, объектов, при ограничении области видимости или группировке операторов за исключением однострочных объектов и массивов
  • Между операторами и выражениями (т.о. каждая строка должна содержать не более одного оператора/выражения)
  • Между элементами длинных списков при записи массивов, параметров функций, объектов

Пустая строка ставится между:

  • Двумя определениями функций
  • Логически раздельными участками кода
  • По усмотрению автора

Использование символа «;»

Использование символа «точка с запятой» в JS допустимо лишь в единственном случае: разделение операторов или выражений, записанных в одну строку.

Использование точки с запятой в конце строк в модулях JS запрещено.

1
2
3
4
5
6
7
8
9
10
11
// запрещено
var a = 1;
var b = function () {
return 5;
}
// разрешено
var a = 1
var b = function () {
return 5
}

Использование символа «,»

Данное правило касается оформления списков, в которых элементы разделяются запятыми. В случае если такой список оформляется с переносом каждого элемента на новую строку строго запрещается записывать символ «,» в начале строки перед следующим элементом списка.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// запрещено
var a = [
1
,2
,3
]
// запрещено под страхом смерти из-за использования пробелов вместо табуляции
var a = [
1
,2
,3
]
// разрешено
var a = [
1,
2,
3
]

Использование фигурных скобок

Фигурные скобки играют в JS три роли: ограничение области видимости, группировка операторов и описание объектов. Во всех случаях используется одинаковое оформление.

Открывающая фигурная скобка не должна быть «одинокой».
Закрывающая фигурная скобка должна быть «одинокой», если объект не однострочный или если после неё не идёт круглая или квадратная скобка.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// запрещено
var a =
{
}
// запрещено
var a = {
test: 1,
test2: 2}
// разрешено!
var a = { test: 1 }
// разрешено
var a = {
test: 1
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// плохо
var a ={}
function (){}
var b = [ {test:1}, {test:2
} ]
// хорошо
var a = {}
function () {}
var b = [{
test: 1
}, {
test: 2
}]
Однострочные объекты

Использование однострочных объектов допустимо, когда такие объекты содержат не более одного поля. В остальных случаях нежелательно.

1
2
3
4
5
// допустимо, но нежелательно
var a = { test: 1, test2: 2 }
// разрешено
var a = { test: 1 }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// допустимо
var a = someFunc({ test: 1, test2: 2, test3: 3}, 1)
// тоже хороший вариант
var a = someFunc({
test: 1,
test2: 2,
test3: 3
}, 1)
// идеальный вариант
var b = {
test: 1,
test2: 2,
test3: 3
}
var a = someFunc(b, 1)
Группировка операторов

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

1
2
3
4
5
6
7
8
// плохо
if (a === true)
return "TRUE"
// хорошо
if (a === true) {
return "TRUE"
}

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

Имена переменных, функций, объектов

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

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

CoffeeScript

Общие правила

Общие правила оформления совпадают с правилами для JavaScript, однако есть некоторые дополнения, связанные с особенностями синтаксиса.

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

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

1
2
3
4
5
# плохо
result = someFunc({ param1: 1, param2: "test" })
# хорошо
result = someFunc param1: 1, param2: "test"

В случае, если параметров несколько, они также не обрамляются в круглые скобки. При этом объекты-параметры всегда обрамляются фигурными скобками.

1
2
3
4
5
# плохо
result = someFunc 1, param1: 2, param2: "test"
# хорошо
result = someFunc 1, { param1: 2, param2: "test" }

В случае, если функция в качестве одного из параметров принимает callback, используется следующая запись:

1
2
3
4
5
result = someFunc a, b, (error, result) ->
if error
doSomething()
else
doAnotherThing(result)

Круглые скобки в вызове someFunc также не ставятся. В случае, если callback не является последним параметром, или если нужно добавить ещё один callback, используется следующая запись:

1
2
3
4
5
6
7
8
9
10
result = someFunc a, b, (result) ->
...
, d
result = someFunc a, b,
(result) ->
...
(error) ->
...
, d

Это единственный допустимый случай, когда запятая ставится в начале новой строки в списке параметров.

Вызов функций в выражениях

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

1
2
3
# здесь параметр нужно заключить в скобки, чтобы избежать неоднозначности
if someFunc(param) is true
...

Несмотря на то, что компилятор верно скомпилирует такое выражение:

1
if someFunc param is true

человек — не компилятор, поэтому лучше избегать неоднозначностей, даже если они не приводят к логическим ошибкам на деле.

Условные операторы

Запрещается использование краткой формы условного оператора:

1
2
3
4
5
6
# плохо
if a then b
# хорошо
if someVariable is true
doSomething()

Запрещается использование if not:

1
2
3
4
5
6
7
# плохо
if not a
b
# хорошо
unless someVariable
doSomething()

Запрещается использование is undefined и is null. Следует использовать оператор существования: if someVariable?.

Запрещается использовать обратные условия:

1
2
3
4
5
6
# плохо
return 5 if someVar > 0
# хорошо
if someVar > 0
return 5

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# callback определён выше в области видимости
# первый вариант
asyncOperation (error, result) ->
if error
return callback message: "An error occured", info: error
# some long code
...
# второй вариант
asyncOperation (error, result) ->
if error
callback message: "An error occured", info: error
else
# not so long code
...

return

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# желательно использовать return для ясности
(param) ->
result = []
for key, value of param
if key.indexOf("tag") >= 0
result.push(param)
return result
# здесь return не обязателен
->
...
doSomething()
# и здесь
->
5
# не забывайте, что результатом функции будет последний выполненный оператор
(a) ->
if a
doSomething()
else
doAnotherThing()

Короткие функции

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

1
2
3
4
result = list.forEach (element, index) -> list[index] = element / 2
# или определяем константу по-дурацки
constant = -> 5

Хотя такая запись и не приветствуется.

AngularJS

Общие правила

Код следует разделять либо по назначению, либо по функции. В небольших проектах допускается следующая структура кода:

1
2
3
4
-application
--controllers
--directives
--resources

Где controllers содержит все контроллеры, directives все директивы, а resources все сервисы. Однако для больших проектов такой способ малопригоден и выгоднее разделять модули по группе выполняемых функций.

1
2
3
4
5
6
7
8
9
10
-application
--authentication
---controllers
---directives
---resources
--news
---controllers
---directives
---resources
...

При таком разделении очень легко найти нужные модули, а группы не мешают друг другу.

Dependency Injection

Следует использовать нотацию «массив». Запрещено указывать внедряемые модули только в качестве параметров функции.

1
2
3
4
5
6
7
8
9
application.controller "authController", [
"$scope"
"$http"
($scope, $http) ->
...
]

Также выше указана рекомендуемая форма записи во всех подобных ситуациях.

Использование манипуляций с DOM

Любые манипуляции с DOM за пределами директив строго запрещены. Забыть про jQuery и иже с ними.

Использование $scope

Не следует рассматривать $scope как модели. Это связка между настоящими моделями (Backbone.js, Restangular) и видом. Не больше, но и не меньше. Модели не обязательно выделять в отдельные сервисы, чаще всего достаточно просто использовать Restangular напрямую.

Рекомендуемые модули

Angular UI Router, Restangular.

HTML

Общие правила

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

1
2
3
4
5
6
7
8
9
10
11
<!-- Плохо -->
<p class="article"
title="Some article"
rel="Some relation">
Article
</p>
<!-- Хорошо -->
<p class="article" title="Some article" rel="Some relation">
Article
</p>

Отбивка

Как и во всех остальных случаях осуществляется только символами табуляции. Отбивка осуществляется всегда, когда возникает новая ступень иерархии DOM. Следует помнить, что текст внутри тега создаёт новую ступень иерархии (см. пример выше).

Перевод строк

Рекомендуется переводить строку перед переходом на новую ступень иерархии, однако в некоторых случаях это не обязательно. Обязательно переводить строку, если производится возврат на предыдущую ступень иерархии.

1
2
3
4
5
6
7
8
9
<section>
<h1>This is header</h1>
<div class="content">
... <!-- a lot of html -->
</div>
</section>
<!-- Перевод строки перед </p> не осуществляется -->
<p class="info">This is some information</p>

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

1
2
3
4
5
6
7
<!-- Не очень хорошо -->
<div><p><span>Text</span></p></div>
<!-- Лучше -->
<div>
<p><span>Text</span></p>
</div>

CSS, LESS, SASS

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

При записи правил CSS/LESS/SASS следует придерживаться того же стиля записи фигурных скобок, что и в JavaScript:

1
2
3
4
5
6
7
p {
font-family: Verdana, sans-serif;
a {
text-decoration: underlined;
}
}

Читайте так же статьи по теме: