Код-стайл

База

За основу берём распространнённые в JS-мире правила от Airbnb:

tslint

Чтобы минимизировать количество обсуждений код-стайла в процессе код-ревью проверка части правил автоматизирована с помощью tslint. Конфигурационный файл tslint.json находится в корне проекта. Со временем можно будет автоматитизировать больше правил, написав свои кастомные правила для tslint.

Отключение правил tslint для особых случаев

В некоторых случаях tslint неправильно отрабатывает или просто необходимо сделать исключение и пренебречь правилом. Для обхода проверок линтера можно вставлять в код специальные комментарии, которые описаны в документации: http://palantir.github.io/tslint/usage/rule-flags/. Они позволяют отключать проверку польностью или конкретное правило, для текущей строки, следующей строки, блока или файла целиком.

Полное отключение проверки всего файла допускается только в случаях, когда это небольшая внешняя библиотека (не наш код), которая по каким-либо причинам используется не через npm, а прямым копированием в utils/.

Для своего кода допускается только отключение определённых правил для строки или для небольшого блока.

Использование tslint в WebStorm

git pre-commit hook

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

Файл

  • Отступы: 2 пробела, никаких табов.
  • Никаких пробелов в конце строк.
  • В конце файла ровно одна пустая строка.
  • В начале файле нет пустых строк.
  • Максимальная длина строки — 120 символов.

Импорты

  • Все импорты должны быть расположены в начале файла до всего остального кода.
  • Раздел импортов отделяется от последующего кода двумя пробельными строками.
  • Испорты группируются по следующим признакам и в следующем порядке:
    1. Базовые библиотеки (типа фреймворк): всё, что начинается на react и redux, исключая сторонние библиотеки с компонентами, типа react-bootstrap, react-native-datepicker, которые к ядру системы никак не отнесёшь.
    2. Остальные внешние библиотеки.
    3. Внутренние библиотеки и утилиты: всё, что начинается на lib/, util/.
    4. “Далёкие” импорты из других бандлов, т.е. остальные импорты с абсолютными путями: common/*, web/common/*, mobile/* и т.п.
    5. Локальные импорты: всё, что начинается с ./, ../, ../../. Не следует злоупотреблять относительными путями, если нужно “подняться” больше, чем на 2 уровня вверх, то лучше использовать абсолютный путь.
  • Группы отделяются друг от друга одной пробельной строкой.
  • Внутри группы импорты желательно сортировать в алфавитном порядке по названию модуля. Модули с относительными путями тем выше, чем “дальше” они от текущего файла (сколько уровней вверх).
  • При импорте имён в фигурных скобках:
    • Фигурные скобки не отделяются пробелами (внутри).
    • Имена внутри фигурных скобок сортируются в алфавитном порядке.
Пример правильного форматирования импортов.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import * as React from 'react'
import {IndexRedirect, Route} from 'react-router'

import {inject} from 'lib/ts-guice'

import {viewerQueries} from 'common/master/relayQueries'

import NoMatchPage from '../components/NoMatchPage'
import crmRoutes from './crm/routes'
import PrivateLayout from './components/PrivateLayout'
import inboxRoutes from './inbox/routes'
import profileRoutes from './profile/routes'
import scheduleRoutes from './schedule/routes'


export default {

}

Примечание

Стоит написать скрипт для автоматического форматирования импортов и использовать его из WebStorm как External tool.

Классы

Декораторы

  • Свои декораторы называем с маленькой буквы camelCase.
  • Применение декоратора форматируется так же, как вызов функции.
  • Если все декораторы класса — однострочные, то между ними не ставится пробельных строк.
  • Если у класса есть многострочные декораторы, то между декораторами можно ставить одинарные пробельные строки для лучшей читаемости по усмотрение автора.
  • Последний декоратор (однострочный или многострочный) должен примыкать к объявлению своего класса (пробельной строки быть не должно).

Пробелы и пробельные строки

Использование пробелов и пробельных строк для логического украшения кода приветствуется, но без злоупотреблений. Учитывайте следующие правила:

  • Не используются пробелы рядом со скобками внутри (...) и {...} нигде, в том числе во вставках кода в JSX, в свойствах объекта, и в импортах.
  • Внутри тела функции — не более одной пробельной строки к ряду.
  • Максимальное количество пробельных строк подряд — 2. 3 и более — запрещено.
  • Перед закрывающей фигурной скобкой блока (конец if-блока, тела функции, объявления класса) пробельных строк быть не должно.
  • Не следует исползовать пробельные строки в однострочных блоках.
  • После заголовка класса ставится одна пробельная строка. Исключение можно делать для маленьких классов, помещающихся в несколько строк (до 20).
  • Объявления функций (как внутри класса, так и вне) отделяются друг от друга пробельной строкой. Можно использовать двойной пробел, чтобы отделить какую-либо группу функций от другой.
  • Объявления полей класса можно не разделять, тут группировка на усмотрение разработчика, так же как и внутри тела функции.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Demo {

  private field = "1234"

  /**
   * Some description
   */
  method(arg: number): string {
    return field + number
  }                                     // тут пробел обязателен

  private anotherMethod = () => {
    // ...
  }                                     // конец класса, пробела нет
}

Объекты

Сокращённая запись

Для ключей объектов, которые совпадают с названием переменной значения, предпочтительной является сокращённая запись:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// not so good
const x = {kind: kind}

// much better
const x = {kind}

// this is ok too
const x = {
  kind,
  someValue: "foo",
  zIndex,
}

Комментарии

Предпочтительный язык для комментариев — английский. Если выразить мысль на английском уж очень трудно, можно на русском. Действует принцип — лучше нормальный понятный комент на русском, чем никакой.

Док-блоки

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

Док-блоки следует обязательно писать ко всем классам и функциям, предназначение которых не очевидно по их названию и/или расположению в проекте. Верно и обратное — не следует писать док-блоки к классам и функциям, если в них содержится только чуть более развёрнутое название.

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Provides authentication routines using JST tokens
 */
class AuthService {

  // bad - тривиально, отвечает на вопрос "что делать?", а не "что делает функция?"
  /**
   * Validate input token
   */
  validateToken(token: string): boolean {
    // ...
  }

  // good - отвечает на вопрос "что делает функция?"
  /**
   * Generates encryption key using special internal algorithm and salt provided by configuration.
   */
  private h256Encrypt(input: string): string {
    // ...
  }
}

Inline-комментарии

Для пояснения произвольных кусков кода в теле функции или класса следует использовать формат однострочных комментариев с использованием //, даже если комментарий растягивается на несколько строк. Формат /** ... */ должен использоваться только для док-блоков. Следует придерживаться следующих правил:

  • Предпочтительным является добавление однострочных комментариев на отдельной строке.
  • Добавление комментариев справа от кода допустимо в отдельных случаях, когда это улучшает читаемость, например, при комментировании } else { или короткой подсказке к полю объекта/интерфейса. При этом комментарий должен быть коротким и не может растягиваться на несколько строк.
  • Перед инлайн-комментарием следует обязательно оставлять пустую строку, за исключением случаев, когда комментарий — первая строка блока.
  • Между // и началом комментария должен стоять один пробел.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SomeClass {

  // internal logic state
  private st: State = {
    opened: true,
    wealth: 155,     // USD
    timeout: 10000,  // ms
  }

  serve() {
    if (x == 1 or y == 2) {
      // all right, putting it all together
      z = x + y

      // and here is great algorithm
      // see super-duper book paper to understand it
      return z * z
    } else {            // if invalid state

      // ok - начало блока, пустая строка допустима, если это нужно для лучшей читаемости
      // need to throw error here
      throw new Error('Ups!')
    }
  }
}

Временное коммертирование (выключение) кода

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

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

1
2
3
4
// todo: need to be disabled for some staging tests
/* if (true) {
  return 123
}*/

Компоненты

Формат файла компонента

Код компонента пишется так, чтобы читая сверху вниз последовательно понимать, что он из себя представляет. Блоки в компоненте располагаются в следующем порядке:

  1. Импорты в правильном порядке (см. секцию про импорты).
  2. Интерфейс Props, описывающий свойства компонента.Правила оформления описаны ниже
  3. Интерфейс State, описывающий параметры состояния компонента.
  4. Контейнеры компонента: relay, redux, container и т.п.
  5. Класс компонента.
    1. Начальные значения для Props и State.
    2. Внутренние свойства компонента.
    3. Lifecycle-методы вроде componentWillMount (если есть).
    4. Методы рендера с минимумом подготовительной работы и максимумом JSX-кода. Методы рендера нужно располагать в порядке от внешнего контейнера к внутренним частям (если рендер разбит на несколько функций).
    5. Остальные методы класса.
  6. Вспомогательные функции, код которых не зависит от внутреннего состояния компонента (не используется this). Их следует выносить из кода класса и располагать после него в виде обычных функций. В том числе такие методы, как mapStateToProps и mapDispatchToProps.

Формат описания интерфейса Props

Интерфейс Props необходимо описывать в несколько блоков отдельными интерфейсами. Каждый блок необходимо начинать с обязательных свойств. Свойства-функции должны идти после свойств-скаляров.

Порядок определения блоков:

  1. OwnProps: собственные свойства компонента, которыми планируется управлять извне.
  2. ContainerProps (только для контейнеров): свойства контейнера, которые будут переданы непосредственно в компонент
  3. ReduxProps: свойства, прокинутые через redux-actions (mapDispatchToProps()) или взятые из глобального стейта redux (mapStateToProps())
  4. ReduxFormProps: свойства, прокинутые через reduxForm()
  5. Props: собственные свойства компонента, которые используются для собственных нужд и подключаются остальными декораторами

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

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

1
2
3
class SomeComponent extends React.Component<ContainerProps & ReduxProps, void> {
  // component's implementation
}

Форматирование JSX

Многострочная запись компонента/тега

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

  1. Первое свойство пишется на одной строке с названием компонента/тега. Переносимые свойства выравниваются по первому свойству. Закрывающая угловая скобка пишется на одной строке с последним свойством:

    1
    2
    3
    4
    5
    6
    7
    <TestComponent prop1="1234" prop2="1234"
                   prop3={test}/>
    
    <TestComponent prop1="1234" prop2="1234"
                   prop3={test}>
      <div></div>
    </TestComponent>
    
  2. Все свойства перечисляются начиная со следующей строки после названия компонента/тега с одинарным отступом. Закрывающая угловая скобка пишется на отдельной строке на уровне открывающей:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <TestComponent
      prop1="1234" prop2="1234"
      prop3={test}
    />
    
    <TestComponent
      prop1="1234" prop2="1234"
      prop3={test}
    >
      <div></div>
    </TestComponent>
    

В обоих случаях допускается расположение нескольких свойств на строке, но это должно подчинятся какой-то очевидной логике.

Другие вариации форматирования недопустимы.

Управляющие конструкции

  • Управляющая конструкция пишется сразу за открывающей фигурной скобкой (без пробелов), вложенный JSX — начиная с новой строки с одинарным отступом, закрывающая фигурная скобка — на отдельной строке на уровне открывающей.
  • Сложные трудночитаемые выражения должны выноситься за пределы JSX-шаблона.
  • Для условий используем {condition &&.
  • Для циков — {array.map(x => ... )}.
  • Для циклов с условием — {array.filter(x => condition(x)).map(x =>.
  • Тернарный оператор ? : в сочетании с вложенным JSX-кодом не применяется. Можно только с вызовом дополнительных render-методов. Вместо этого можно использовать {condition && и {!condition &&.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
render() {
  const isOk = this.props.prop1 && !this.props.prop2 || this.isItemsOk(items)
  return (
    <div>
      {isOk &&
        <ul className="ttt">
          {items.filter(x => x.count > 0).map(item =>
            <li>
              <Item info={item} />
              {isLast ? this.renderLastBorder() : false}
            </li>
          )}
        </ul>
      }
      {!isOk &&
        <div className="error">Ошибка!</div>
      }
    </div>
  )
}