Google JavaScript руководство по стилю

О переводе

Оригинальное руководство по стилю Вы можете найти по ссылке — Google JavaScript Style Guide.
Репозиторий с кодом данного перевода находится здесь — Репозиторий перевода JS Style Guide

Если Вы нашли несоответствие, ошибку или неточность в переводе — все читатели данного перевода будут очень благодарны Вам за pull request с исправлением :).

1 Введение

Этот документ содержит полное описание стандартов Google для исходного кода на языке JavaScript. JavaScript файл считается написанным в стиле Google, если и только если он придерживается правил, описанных в этом документе.

Как и другие руководства по стилю программирования, данный документ охватывает не только вопрос оформления и форматирования кода, но также включает соглашения и стандарты написания кода. Хотя документ фокусируется на основных быстро применимых правилах, которым в компании Google следуют повсеместно — здесь также описываются правила и советы, которым не следует следовать (как при ручном, так и при автоматическом написании кода).

1.1 Терминологические примечания

В данном документе, если не указано другое:

  1. Термин комментарий всегда относится к комментарию реализации. Мы не используем фразу документирующий комментарий. Для этого мы используем общий термин “JSDoc”, включая текст, читаемый человеком, и аннотации, обрабатывающиеся компьютером при помощи /** … */.

  2. Данное руководство по стилю ссылается на терминологию RFC 2119, когда использует фразы "нужно", "не нужно", "не стоит" и "вероятно". Термины "отдавайте предпочтение" и "избегайте" используются в смысле "стоит" и "не стоит" делать соответственно, но не являются обязательными правилами.

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

1.2 Примечания по руководству

Примеры кода в этом документе не являются нормативными (т.е. обязательными). Это значит, что примеры в стиле Google могут и не иллюстрировать единственно правильный вариант стилизации кода. Форматирование кода в данном файле не должно приниматься в качестве правила.

2 Основы исходных файлов

2.1 Имя файла

Имена файлов должны быть в нижнем регистре и могут включать нижнее подчеркивание (_) или "тире" (-), но без любой другой пунктуации. Придерживайтесь договоренностей, которые используются в вашем проекте. Расширения файлов должны быть .js.

2.2 Кодировка файлов UTF-8

Кодировка файлов должна быть в UTF-8.

2.3 Специальные символы

2.3.1 Пробельные символы

Помимо последовательности символов перевода строки, символ горизонтального пробела (0х20) из таблицы символов ASCII — единственный допустимый символ пробела, который может появляться где-либо в исходном коде. Это подразумевает, что:

  1. Все другие пробельные символы экранируются.

  2. Отступ с помощью Tab не используется для отступа.

2.3.2 Специальные экранирующие последовательности

Любые символы, которые требуется экранировать специальными символьными последовательностями (\', \", \\, \b, \f, \n, \r, \t, \v) — используют данные последовательности вместо числового экранирования (например, \x0a, \u000a или \u{a}). Устаревшие восьмеричные символы экранирования не используются.

2.3.3 Не-ASCII символы

Для оставшихся не-ASCII символов используется или подходящий символ Unicode (например, ), или эквивалентная hex или Unicode последовательность символов (например, \u221e) в зависимости от того, что делает код более легко читаемым и более понятным.

Подсказка: при использовании Unicode-символов (или их последовательностей), лучше использовать пояснительный комментарий.

        /* В идеале: код хорошо понятен даже без комментария. */
const units = 'μs';

/* Разрешено: но необязательно, так как μ — это печатаемый символ. */
const units = '\u03bcs'; // 'μs'

/* Хорошо: используется экранирование для непечатаемых символов с комментарием для большей ясности. */
return '\ufeff' + content;  // Добавляется знак "порядка байтов".
/* Плохо: читатель не понимает, что это за символ. */
const units = '\u03bcs';
      

Подсказка: никогда не делайте Ваш код менее читабельным просто из-за страха, что некоторые программы могут некорректно обрабатывать не-ASCII символы. В случае, если это произойдет — программа будет "сломана" и это потребуется исправить (т.е. вы не упустите данный момент).

3 Структура исходных файлов

Все новые файлы должны быть или файлом goog.module (файл содержащий вызов goog.module), или модулем ECMAScript (использует операторы import и export). Файлы состоят из следующего (по порядку):

  1. Информация о лицензии или авторских правах, если такие имеются
  2. @fileoverview JSDoc, если имеется
  3. Оператор goog.module, если это goog.module файл
  4. ES оператор import, если это ES модуль
  5. Операторы goog.require и goog.requireType
  6. Реализация файла

Только одна линия отделяет каждую секцию кода, за исключением отделения реализации файла, которая должна отделяться 1 или 2 пустыми линиями.

Если информация о лицензии или авторском праве находится в файле, она находится здесь.

3.2 @fileoverview JSDoc, если имеется

Смотрите ?? о правилах форматирования.

3.3 Оператор goog.module

Все goog.module файлы должны объявлять только одно имя для goog.module в одном файле: линии, содержащие объявление goog.module не должны разрываться и, следовательно, они являются исключением из правила про лимит в 80 символов.

Аргумент goog.module определяет пространство имен. Это имя пакета (идентификатор, который отражает фрагмент структуры каталогов, где находится исходный код) и, опционально, главный класс\перечисление\интерфейс, который определяет связь со всем модулем.

Пример:

        goog.module('search.urlHistory.UrlHistoryService');
      

3.3.1 Иерархия

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

Не разрешается:

        goog.module('foo.bar');   // 'foo.bar.qux' было бы неплохо
goog.module('foo.bar.baz');
      

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

3.3.2 goog.module.declareLegacyNamespace

Оператор goog.module может опционально следовать до вызова goog.module.declareLegacyNamespace();. Не используйте goog.module.declareLegacyNamespace(), когда это возможно.

Пример:

        goog.module('my.test.helpers');
goog.module.declareLegacyNamespace();
goog.setTestOnly();
      

goog.module.declareLegacyNamespace существует для облегчения перехода от традиционных пространств имен на основе иерархии объектов, но имеет некоторые ограничения в плане именования. Поскольку имя дочернего модуля должно быть создано после родительского пространства имен, это имя не должно быть дочерним или родительским по отношению к goog.module (например, goog.module('parent'); и goog.module('parent.child'); не могут существовать одновременно, так же как и не может goog.module('parent'); и goog.module('parent.child.grandchild');).

3.3.3 goog.module Экспорт

Классы, перечисления, функции, константы и другие символы экспортируются, используя объект exports. Экспортируемые символы могут быть определены прямо в объекте exports или определяться локально, а потом экспортироваться раздельно. Символы экспортируются только тогда, когда они должны использоваться за пределами модуля. Не экспортируемые модульно-локальные символы не декларируются как @private и не заканчиваются нижней чертой. Определенного порядка для экспортируемых модульно-локальных символов — нет.

Пример:

        const /** !Array<number> */ exportedArray = [1, 2, 3];

const /** !Array<number> */ moduleLocalArray = [4, 5, 6];

/** @return {number} */
function moduleLocalFunction() {
  return moduleLocalArray.length;
}

/** @return {number} */
function exportedFunction() {  
  return moduleLocalFunction() * 2;
}

exports = {exportedArray, exportedFunction};
/** @const {number} */
exports.CONSTANT_ONE = 1;

/** @const {string} */
exports.CONSTANT_TWO = 'Другая константа';
      

Не аннотируйте exports с помощью @const, так как они уже рассматриваются компилятором в качестве констант.

        /** @const */
exports = {exportedFunction};
      

3.4 ES модули

3.4.1 Импорт

Операторы импорта не должны разделяться переносом строки и, следовательно, являются исключением из ограничения в 80 символов.

3.4.1.1 Импорт путей

Файлы ES модуля должны использовать оператор import для импорта файлов из ES модуля. Не используйте goog.require для импорта другого ES модуля.

        import './sideeffects.js';
          
import * as goog from '../closure/goog/goog.js';
import * as parent from '../parent.js';

import {name} from './sibling.js';
      

3.4.1.1.1 Расширения файлов в для импортируемых путей

Расширение .js не является опциональным в импортируемых путях и всегда должно присутствовать.

import '../directory/file';
import '../directory/file.js';
3.4.1.2 Импорт одного файла несколько раз

Не импортируйте один и тот же файл несколько раз. Это может затруднить определение совокупности всех импортов в файле.

        // Импорт имеет один и тот же путь, но, поскольку он не выравнивается, может быть трудно его заметить.
import {short} from './long/path/to/a/file.js';
import {aLongNameThatBreaksAlignment} from './long/path/to/a/file.js';
      

3.4.1.3 Именование импортов
3.4.1.3.1 Именование модульных импортов

Имена импорта модуля (import * as name) — это имена в нижнемВерблюжьемСтиле, которые получены из имени импортируемого файла.

        import * as fileOne from '../file-one.js';
import * as fileTwo from '../file_two.js';
import * as fileThree from '../filethree.js';
      
        import * as libString from './lib/string.js';
import * as math from './math/math.js';
import * as vectorMath from './vector/math.js';
      
3.4.1.3.2 Именование импортов по умолчанию

Имена импорта по умолчанию получаются из имени импортируемого файла и следуют правилам из ??.

        import MyClass from '../my-class.js';
import myFunction from '../my_function.js';
import SOME_CONSTANT from '../someconstant.js';
      

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

3.4.1.3.3 Именование именованного импорта

В целом, символы, импортируемые через именованный импорт (import {name}), должны иметь одинаковое имя. Избегайте импорта псевдонимов (import {SomeThing as SomeOtherThing}). Отдавайте предпочтение исправлению конфликтов имен при помощи импорта модуля (import *) или переименовывая сами экспорты.

        import * as bigAnimals from './biganimals.js';
import * as domesticatedAnimals from './domesticatedanimals.js';

new bigAnimals.Cat();
new domesticatedAnimals.Cat();
      

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

        import {Cat as BigCat} from './biganimals.js';
import {Cat as DomesticatedCat} from './domesticatedanimals.js';

new BigCat();
new DomesticatedCat();
      

3.4.2 Экспорт

Символы экспортируются только в том случае, если они предназначены для использования вне модуля. Неэкспортированные локальные символы не объявляются как @private, и их имена не заканчиваются подчеркиванием. Не существует предписанного порядка для экспортируемых и локальных символов.

3.4.2.1 Именованный экспорт vs экспорт по умолчанию

Используйте именованный экспорт во всем коде. Вы можете применить ключевое слово export в объявлении или использовать синтаксис export {name};.

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

        // Не используйте экспорт по умолчанию:
export default class Foo { ... } // Плохо!
      
        // Используйте именованный экспорт:
export class Foo { ... }
      
        // Альтернативный стиль с именем экспорта:
class Foo { ... }

export {Foo};
      
3.4.2.2 Экспорт статических контейнерных классов и объектов

Не экспортируйте классы контейнеров или объекты со статическими методами или свойствами для пространства имен.

        // container.js
// Плохо: контейнер — это экспортируемый класс, который имеет только статические методы и поля.
export class Container {
  /** @return {number} */
  static bar() {
    return 1;
  }
}

/** @const {number} */
Container.FOO = 1;
      

Вместо этого экспортируйте отдельные константы и функции:

        /** @return {number} */
export function bar() {
  return 1;
}

export const /** number */ FOO = 1;
      

3.4.2.3 Мутабельность экспорта

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

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

// Плохо: и foо, и mutateFoo экспортируются и могут быть изменены.
export let /** number */ foo = 0;

/**
 * Изменяется foo. 
 */
 export function mutateFoo() {
  ++foo;
}

/**
 * @param {function(number): number} newMutateFoo 
 */
 export function setMutateFoo(newMutateFoo) {
  // Экспортируемые классы и функции могут быть видоизменены!
  mutateFoo = () => {
    foo = newMutateFoo(foo);  
  };
}
      
        // Хорошо: вместо того, чтобы экспортировать изменяемые переменные foo и mutateFoo напрямую,
// вместо этого сделайте их модульными и экспортируйте getter для foo и оболочку для
// mutateFooFunc.
let /** number */ foo = 0;
let /** function(number): number */ mutateFooFunc = foo => foo + 1;

/** @return {number} */
export function getFoo() {
  return foo;
}

export function mutateFoo() {
  foo = mutateFooFunc(foo);
}

/** @param {function(number): number} mutateFoo */
export function setMutateFoo(mutateFoo) {
  mutateFooFunc = mutateFoo;
}
      

3.4.2.4 export from

Операторы export from не должны быть перенесены на другую строку и, следовательно, являются исключением из ограничения в 80 символом. Это относится ко всем export from операторам.

        export {specificName} from './other.js';
export * from './another.js';
      

3.4.3 Циклические зависимости в ES модулях

Не создавайте циклы между модулями ES, даже если спецификация ECMAScript позволяет это. Обратите внимание, что можно создавать циклы как с помощью операторов import, так и export.

        // a.js
import './b.js';
      
        // b.js
import './a.js';

// `export from` тоже может вызвать циклические зависимости!
export {x} from './c.js';
      
        // c.js
import './b.js';

export let x;
      

3.4.4 Взаимодействие с Closure

3.4.4.1 Ссылка на goog

Для того, чтобы сослаться на пространство имён Closure goog, импортируйте goog.js из Closure.

        import * as goog from '../closure/goog/goog.js';

const name = goog.require('a.name');

export const CONSTANT = name.compute();
      

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

3.4.4.2 goog.require в ES модулях

goog.require в ES модулях работает так же, как и в goog.module файлах. Вы можете потребовать (require) любой символ пространства имён из Closure (т.е. символы, созданные с помощью goog.provide или goog.module), и goog.require вернёт значение.

        import * as goog from '../closure/goog/goog.js';
import * as anEsModule from './anEsModule.js';

const GoogPromise = goog.require('goog.Promise');
const myNamespace = goog.require('my.namespace');
      

3.4.4.3 Декларирование Closure ID модулей в ES модулях

goog.declareModuleId может использоваться в ES модулях, чтобы декларировать ID модуля в стиле goog.module. Это значит, что ID модуля может быть потребован с помощью goog.require, goog.module.get, goog.forwardDeclare и т.п., как будто это goog.module, который не вызывает goog.module.declareLegacyNamespace. Это не делает ID модуля глобально доступным JavaScript символом.

goog.require (или goog.module.get) примененный для ID модуля из goog.declareModuleId — будет всегда возвращать объект модуля (как будто используется import *). Как результат, аргумент для goog.declareModuleId должен всегда заканчиваться именемНижнегоРегистра.

Обратите внимание: ошибка — вызывать goog.module.declareLegacyNamespace в ES модуле, он может быть вызван только из файлов goog.module. Не существует прямых путей, чтобы ассоциировать устаревшее пространство имён с ES модулем.

goog.declareModuleId должен использоваться только для того, чтобы обновить Closure файлы в месте, где используется именованный экспорт.

        import * as goog from '../closure/goog.js';
goog.declareModuleId('my.esm');
export class Class {};
      

3.5 goog.setTestOnly

В файле модуля goog.module оператор goog.module может опционально сопровождаться вызовом goog.setTestOnly().

В ES модуле оператор import может опционально сопровождаться вызовом goog.setTestOnly().

3.6 Операторы goog.require и goog.requireType

Импорты, реализованные с помощью операторов goog.require и goog.requireType. Имена, импортированные с помощью оператора goog.require, могут быть использованы и в коде, и в аннотациях, в то время как импортированные с помощью goog.requireType могут быть использованы в аннотациях типа.

Операторы goog.require и goog.requireType формируют единый блок без пустых линий внутри. Этот блок следует за декларацией goog.module, разделённой с помощью одной пустой линии. Единственный аргумент для goog.require или goog.requireType — это пространство имен, определенное с помощью goog.module в отдельном файле. Операторы goog.require и goog.requireType не должны появляться в других местах данного файла.

Каждый goog.require или goog.requireType определяется в виде отдельного константного псевдонима или деструктурируется в несколько константных псевдонимов. Эти псевдонимы - единственный допустимый способ сослаться на зависимости в коде или аннотациях типа. Полные имена пространства имен не должны использоваться нигде, кроме как в качестве аргумента для goog.require или goog.requireType.

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

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

Исключение: В некоторых случаях, дополнительные компоненты пространства имен могут использоваться для формирования более длинных псевдонимов. Полученный псевдоним должен сохранять регистр исходного идентификатора, чтобы он по-прежнему правильно идентифицировал свой тип. Более длинные псевдонимы могут использоваться для устранения неоднозначности идентичных псевдонимов, или если это значительно улучшает читабельность. Кроме того, для предотвращения маскирования нативных типов, таких как Element, Event, Error, Map и Promise (см. более полный список в Стандартные встроенные объекты и MDN Web APIs). При переименовании деструктурированных псевдонимов пробел должен следовать за двоеточием, как требуется в ??.

Файл не должен содержать оба оператора goog.require и goog.requireType для одного и того же пространства имен. Если импортированное имя используется как в коде, так и в аннотациях типа, оно должно быть импортировано одним оператором goog.require.

Если модуль импортируется только для его побочных эффектов, вызов должен быть сделан при помощи goog.require (а не goog.requireType), а назначение может быть опущено. Также необходим комментарий, чтобы объяснить, почему это необходимо, и подавить предупреждение компилятора.

Строки сортируются в соответствии со следующими правилами: Все require с именем слева идут на первом месте, отсортированными по алфавиту. Затем require с деструктуризацией, снова отсортированные по названиям слева. Наконец, любые вызовы require, которые являются автономными (обычно модули, импортированные только для побочных эффектов).

Совет: Нет необходимости запоминать этот порядок и применять его вручную. Вы можете положиться на свою среду IDE, чтобы сообщить о require, которые отсортированы неправильно.

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

Пример:

        // Стандартный стиль псевдонимов.
const MyClass = goog.require('some.package.MyClass');
const MyType = goog.requireType('some.package.MyType');
// Псевдоним на основе пространства имен, используемый для устранения неоднозначности.
const NsMyClass = goog.require('other.ns.MyClass');
// Псевдоним на основе пространства имен, используемый для предотвращения маскирования собственного типа.
const RendererElement = goog.require('web.renderer.Element');
// Непоследовательные псевдонимы на основе пространства имен используются для улучшения читабельности.
// Кроме того, строки, содержащие более 80 столбцов, не должны переноситься.
const SomeDataStructureModel = goog.requireType('identical.package.identifiers.models.SomeDataStructure');
const SomeDataStructureProto = goog.require('proto.identical.package.identifiers.SomeDataStructure');
// Стандартный стиль псевдонима.
const asserts = goog.require('goog.asserts');
// Псевдоним на основе пространства имен, используемый для устранения неоднозначности.
const testingAsserts = goog.require('goog.testing.asserts');
// Стандартная деструктуризация в псевдонимы.
const {clear, clone} = goog.require('goog.array');
const {Rgb} = goog.require('goog.color');
// Деструктуризация на основе пространства имен в псевдонимы для устранения неоднозначности.
const {SomeType: FooSomeType} = goog.requireType('foo.types');
const {clear: objectClear, clone: objectClone} = goog.require('goog.object');
// goog.require без псевдонима, чтобы вызвать побочные эффекты.
/** @suppress {extraRequire} Инициализируется MyFramework. */
goog.require('my.framework.initialization');
      

Неоднозначно:

// Если необходимо устранить неоднозначность, предпочтите PackageClass, а не SomeClass,
// так как он ближе к формату имени модуля.
const SomeClass = goog.require('some.package.Class');
      

Запрещено:

        // Дополнительные термины должны исходить из пространства имен.
const MyClassForBizzing = goog.require('some.package.MyClass');
// Псевдоним должен включать все конечные компоненты пространства имен.
const MyClass = goog.require('some.package.MyClassForBizzing');
// Псевдоним не должен маскировать собственный тип (здесь должно быть `const JspbMap`).
const Map = goog.require('jspb.Map');
// Не переносите goog.require строки более 80 символов.
const SomeDataStructure =
    goog.require('proto.identical.package.identifiers.SomeDataStructure');
// Псевдоним должен быть основан на пространстве имен.
const randomName = goog.require('something.else');
// Отсутствует пробел после двоеточия.
const {Foo:FooProto} = goog.require('some.package.proto.Foo');
// goog.requireType без псевдонима.
goog.requireType('some.package.with.a.Type');

/** 
 * @param {!some.unimported.Dependency} param Все внешние типы, использующиеся в JSDoc 
 *    аннотациях, должны быть потребованы с помощью goog.require, если не объявлено в externs. 
 */
 function someFunction(param) {
  // goog.require строки должны быть на верхнем уровне перед любым другим кодом.
  const alias = goog.require('my.long.name.alias');
  // ...
}
      

3.7 Реализация файла

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

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

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

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

Подсказка: Используйте clang-format. Сообщество JavaScript приложило усилия, чтобы убедиться, что clang-format правильно работает в JavaScript файлах. clang-format имеет интеграцию с несколькими популярными редакторами.

4.1 Скобки

4.1.1 Скобки используются для всех управляющих структур

Скобки требуются для всех управляющих структур (например, if, else, for, do, while и т.п.), даже если тело содержит один оператор. Первый оператор непустого блока должен начинаться с отдельной строки.

Не разрешено:

if (someVeryLongCondition()) doSomething();
for (let i = 0; i < foo.length; i++) bar(foo[i]);

Исключение: Простой оператор if, который может поместиться целиком в одну строку без переноса (и в котором нет другого), может быть помещен в одной строке без фигурных скобок, когда он улучшает читабельность. Это единственный случай, когда управляющая структура может пропускать скобки и переводы строк.

if (shortCondition()) foo();

4.1.2 Непустые блоки: стиль K&R

Скобки следуют стилю Кернигана и Ричи (египетские скобки) для непустых блоков и блочных конструкций, если:

Пример:

class InnerClass {
  constructor() {}

  /** @param {number} foo */
  method(foo) {
    if (condition(foo)) {
      try {
        // Внимание: это может не сработать.
        something();
      } catch (err) {
        recover();
      }
    }
  }
}

4.1.3 Пустые блоки: могут быть краткими

Пустой блок или блочно-подобная конструкция может быть закрыта сразу после открытия, но без символов, пробелов или разрыва строки между ними (например, {}), Если только это не является частью многоблочного оператора (который содержит непосредственно несколько блоков: if/else или try/catch/finally).

Пример:

function doNothing() {}

Запрещено:

if (condition) {
  // …
} else if (otherCondition) {} else {
  // …
}

try {
  // …
} catch (e) {}

4.2 Блочный отступ: +2 пробела

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

4.2.1 Литералы массива: опционально блочные

Любой литерал массива может быть необязательно отформатирован так, как если бы он был «блочной конструкцией». Например, допустимые варианты ниже (не исчерпывающий список):

const a = [
  0,
  1,
  2,
];

const b =
    [0, 1, 2];

const c = [0, 1, 2];

someMethod(foo, [
  0, 1, 2,
], bar);

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

4.2.2 Объектные литералы: опционально блочные

Любой объектный литерал может быть отформатирован так, как если бы он был «блочной конструкцией». Применяются те же примеры, что и ??. Например, следующие примеры допустимы (не исчерпывающий список):

const a = {
  a: 0,
  b: 1,
};

const b =
    {a: 0, b: 1};
const c = {a: 0, b: 1};

someMethod(foo, {
  a: 0, b: 1,
}, bar);

4.2.3 Литералы класса

Литералы класса (будь то объявления или выражения) имеют отступ в виде блоков. Не добавляйте точку с запятой после методов или после закрывающей скобки объявления класса (операторы, такие как присваивания, которые содержат выражения класса, по-прежнему заканчиваются точкой с запятой). Используйте ключевое слово extends, но не аннотацию @extends JSDoc, если только класс не расширяет шаблонизированный тип.

Пример:

class Foo {
  constructor() {
    /** @type {number} */
    this.x = 42;
  }

  /** @return {number} */
  method() {
    return this.x;
  }
}
Foo.Empty = class {};
/** @extends {Foo<string>} */
foo.Bar = class extends Foo {
  /** @override */
  method() {
    return super.method() / 2;
  }
};

/** @interface */
class Frobnicator {
  /** @param {string} message */
  frobnicate(message) {}
}

4.2.4 Функциональные выражения

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

Пример:

prefix.something.reallyLongFunctionName('whatever', (a1, a2) => {
  // Отступ тела функции +2 относительно глубины отступа
  // оператора на одну строку выше.
  if (a1.equals(a2)) {
    someOtherLongFunctionName(a1);
  } else {
    andNowForSomethingCompletelyDifferent(a2.parrot);
  }
});

some.reallyLongFunctionCall(arg1, arg2, arg3)
    .thatsWrapped()
    .then((result) => {
      // Отступ тела функции +2 относительно глубины отступа
      // вызова '.then()'.
      if (result) {
        result.use();
      }
    });

4.2.5 Switch оператор

Как и в любом другом блоке, содержимое блока switch имеет отступ +2.

После оператора switch добавляется новая строка и уровень отступа увеличивается на +2, точно так же, как если бы блок открывался. Явный блок может использоваться, если этого требует лексическая область видимости. После завершения оператора отступ возвращается на предыдущий уровень.

Пустая строка необязательна между break и следующим случаем.

Пример:

switch (animal) {
  case Animal.BANDERSNATCH:
    handleBandersnatch();
    break;

  case Animal.JABBERWOCK:
    handleJabberwock();
    break;

  default:
    throw new Error('Неизвестное животное');
}

4.3 Операторы

4.3.1 Один оператор на строку

После каждого оператора делается перевод строки.

4.3.2 Необходима точка с запятой.

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

4.4 Лимит строки: 80 символов

Код JavaScript имеет ограничение столбца в 80 символов. За исключением случаев, указанных ниже, любая строка, которая превысила бы этот предел, должна быть перенесена, как описано в ??.

Исключения:

  1. Операторы goog.module, goog.require и goog.requireType (см. ?? и ??).
  2. ES модули import и операторы export from (см. ?? и ??).
  3. Линии, в которых соблюдение предела столбцов невозможно или может препятствовать обнаружению. Примеры включают в себя:
    • Длинный URL, который должен быть кликабельным в источнике.
    • Команда консоли, предназначенная для копирования и вставки
    • Длинный строковый литерал, который может потребоваться полностью скопировать или найти (например, длинный путь к файлу).

4.5 Перенос строк

Терминологическое примечание: Перенос строки разбивает кусок кода на несколько строк в соответствии с ограничением по количеству символов, где фрагмент в противном случае мог бы поместиться в одну строку в соответствии с правилами данного руководства.

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

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

Совет: извлечение метода или локальной переменной может решить проблему без необходимости переноса строки.

4.5.1 Когда переносить

Основная директива переноса строк такова: предпочитайте переносить на более высоком синтаксическом уровне.

Предпочтительно:

currentEstimate =
    calc(currentEstimate + x * currentEstimate) /
        2.0;

Нежелательно:

currentEstimate = calc(currentEstimate + x *
    currentEstimate) / 2.0;

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

Операторы переносятся следующим образом:

  1. Когда у оператора разрывается строка, разрыв следует за оператором. (Обратите внимание, что это не та же практика, что и в стиле Google для Java.)
    1. Это не относится к точке (.), которая на самом деле не является оператором.
  2. Имя метода или конструктора остается присоединенным к открытой круглой скобке ((), которая следует за ней.
  3. Запятая (,) остается прикрепленной к символу, который предшествует ей.

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

4.5.2 Отступ строк с протяженностью не менее +4 пробелов

При переносе строк каждая строка после первой (каждая строка продолжения ) имеет отступ не менее +4 от исходной строки, если только она не подпадает под правила отступа блока.

Когда имеется несколько строк продолжения, отступ может выходить за пределы +4 в зависимости от ситуации. Как правило, строки продолжения на более глубоком синтаксическом уровне имеют отступ с большим числом, кратным 4. Две строки используют один и тот же уровень отступа, если и только если они начинаются с синтаксически равных элементов.

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

4.6 Пробелы

4.6.1 Вертикальный пробел

Одна пустая строка ставится:

  1. Между последовательными методами в литерале класса или объекта
    1. Исключение: пустая строка между двумя последовательными определениями свойств в литерале объекта (без другого кода между ними) является необязательной. Такие пустые строки используются по мере необходимости для создания логических группировок полей.
  2. Внутри тел методов имеет смысл создавать логические группировки операторов. Пустые строки в начале или конце тела функции не допускаются.
  3. Необязательно перед первым или после последнего метода в литерале класса или объекта (не рекомендуется и не поощряется).
  4. Как того требуют другие разделы этого документа (например, ??).

Несколько последовательных пустых строк разрешены, но никогда не требуются (и не поощряются).

4.6.2 Горизонтальный пробел

Использование горизонтального пробела зависит от местоположения и подразделяется на три широкие категории: ведущий (в начале строки), конечный (в конце строки) и внутренний. Ведущий пробел (то есть отступ) рассматривается в другом месте. Конечный пробел запрещен.

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

  1. Отделение любого зарезервированного слова (например, if, for или catch), за исключением function и super начиная с открытой скобки ((), которая следует за зарезервированным словом в этой же строке.
  2. Отделение любого зарезервированного слова (такого как else или catch) от закрывающей фигурной скобки (}), которая предшествует ему в этой строке.
  3. Перед любой открытой фигурной скобкой ({), с двумя исключениями:
    1. Перед литералом объекта, который является первым аргументом функции или первым элементом в литерале массива (например, foo ({a: [{c: d}]}) ).
    2. В расширении шаблона, так как это запрещено языком (например, валидно: `ab${1 + 2}cd`, не валидно: `xy$ {3}z`).
  4. С обеих сторон любой бинарного или тернарного оператор.
  5. После запятой (,) или точки с запятой (;). Обратите внимание, что пробелы никогда не разрешены перед этими символами.
  6. После двоеточия (:) в литерале объекта.
  7. По обе стороны от двойной косой черты (//), начинающей комментарий в конце строки. Здесь разрешено использование нескольких пробелов, но это не обязательно.
  8. После символа блочного комментария и с обеих сторон закрывающих символов (например, для кратких объявлений типов, приведений и комментариев имени параметра: this.foo = /** @type {number} */ (bar); или function(/** string */ foo) {; или baz(/* buzz= */ true)).

4.6.3 Горизонтальное выравнивание: не рекомендуется

Терминологическое примечание: Горизонтальное выравнивание — это практика добавления в ваш код переменного числа дополнительных пробелов с целью отображения определенных символов непосредственно под некоторыми другими символами в предыдущих строках.

Эта практика разрешена, но, как правило, не рекомендуется в Google Style. Даже не требуется поддерживать горизонтальное выравнивание в местах, где оно уже использовалось.

Вот пример без выравнивания, за которым следует пример с выравниванием. Оба разрешены, но последний не рекомендуется:

{
  tiny: 42, // хорошо 
  longer: 435, // тоже хорошо
};

{
  tiny:   42,  // разрешено, но требует будущего редактирования 
  longer: 435, // может остаться без выравнивания
};

Совет: Выравнивание может улучшить читаемость, но создает проблемы для дальнейшего обслуживания. Допустим, есть изменение, которое должно касаться только одной строки. Это изменение может оставить искаженное ранее приятное форматирование. Чаще всего это побуждает разработчика (возможно, вас) также корректировать пробелы на близлежащих линиях. Это изменение в одну строку потребовало изменения нескольких строк. В лучшем случае это может привести к бессмысленной занятой работе, но в худшем это еще и исказит информацию об истории версий, замедлит работу рецензентов и усугубит конфликты слияний.

4.6.4 Аргументы функций

Предпочитайте помещать все аргументы функции в одну строку с именем функции. Если это превысит ограничение в 80 столбцов, аргументы должны быть перенесены для удобочитаемости. Чтобы сэкономить место, вы можете сделать перенос строки как можно ближе к 80 символам или поместить каждый аргумент в отдельной строке, чтобы улучшить читаемость. Отступы должны быть в размере 4-х пробелов. Выравнивание в скобках разрешено, но не рекомендуется. Ниже приведены наиболее распространенные шаблоны для переноса аргументов:

// Аргументы начинаются с новой строки, с отступом в четыре пробела. Предпочтительнее,
// когда аргументы помещаются не в одну строку с именем функции (или ключевым словом
// "function"), а помещаются полностью на второй строке. Такой подход работает с длинными
// именами функций, позволяет делать переименование без изменения отступов.
doSomething(    
    descriptiveArgumentOne, descriptiveArgumentTwo, descriptiveArgumentThree) {  
  // …
}

// Если список аргументов длинный (больше 80 символов), сделайте перенос. 
// Такой подход использует меньше вертикального пространства, но нарушает
// правило прямоугольника и поэтому не рекомендуется.
doSomething(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
  // …
}

// Четыре пробела, один аргумент в строке. Работает с длинными именами
// функций, позволяет переименование, и выделяет каждый аргумент.
doSomething(  
    veryDescriptiveArgumentNumberOne, 
    veryDescriptiveArgumentTwo,    
    tableModelEventHandlerProxy,    
    artichokeDescriptorAdapterIterator) {
  // …
}

4.7 Группировка скобок: рекомендуется

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

Не используйте лишние скобки вокруг всего выражения после delete, typeof, void, return, throw, case, in, of или yield.

Круглые скобки необходимы для приведения типов: /** @тип {!Foo}. */(foo).

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

В этом разделе рассматриваются комментарии реализации. JSDoc рассматривается отдельно в ??.

4.8.1 Стиль блочных комментариев

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

/*
 * Это выглядит
 * хорошо.
 */

// Тоже
// хорошо.

/* Аналогично */

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

Не используйте JSDoc (/** … */) для комментариев, описывающих реализацию.

4.8.2 Комментарии для имен параметров

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

someFunction(obviousParam, /* shouldRender= */ true, /* name= */ 'hello');

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

someFunction(obviousParam, true /* shouldRender */, 'hello' /* name */);

5 Языковые особенности

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

5.1 Объявление локальных переменных

5.1.1 Используйте const и let

Объявляйте все локальные переменные либо с помощью const, либо с помощью let. Используйте const по умолчанию, если только переменная не нуждается в переназначении. Ключевое слово var не должно использоваться.

5.1.2 Одна переменная для одного определения

Каждая декларация локальной переменной объявляет только одну переменную: декларации типа let a = 1, b = 2; не используются.

5.1.3 Определяйте, когда требуется, но инициализируйте — как можно быстрее

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

5.1.4 Объявлять типы по мере необходимости

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

Пример:

const /** !Array<number> */ data = [];

/**
 * Какое-то описание.
 * @type {!Array<number>}
 */
const data = [];

Смешивание строчных (inline) и JSDoc стилей не допускается: компилятор будет обрабатывать только первые JSDoc комментарии, и строчные будут потеряны.

/** Какое-то описание. */
const /** !Array<number> */ data = [];

Подсказка: Существует множество случаев, когда компилятор может сделать вывод о шаблонизированном типе, но не о его параметрах. В частности, это происходит, когда вызывающий литерал или конструктор не включает в себя никаких значений с типом параметра (например, пустые массивы, объекты, Map или Set), или если переменная изменяется в рамках замыкания. Особенно полезны в этих случаях аннотации к типам локальных переменных, так как в противном случае компилятор выдаст параметр шаблона как неизвестный (unknown).

5.2 Литералы массива

5.2.1 Используйте закрывающие запятые

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

Пример:

const values = [
  'first value',
  'second value',
];

5.2.2 Не используйте вариационный конструктор массива

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

Запрещено:

const a1 = new Array(x1, x2, x3);
const a2 = new Array(x1, x2);
const a3 = new Array(x1);
const a4 = new Array();

Это работает, как и ожидалось, за исключением третьего случая: если x1 целое число, то a3 — массив размером x1, где все элементы неопределены. Если x1 — любое другое число, то будет брошено исключение, а если это что-то другое, то это будет одноэлементный массив.

Вместо этого используйте

const a1 = [x1, x2, x3];
const a2 = [x1, x2];
const a3 = [x1];
const a4 = [];

Явное определение массива заданной длины с помощью new Array(length) разрешено, когда это необходимо.

5.2.3 Нецифровые свойства

Не определяйте и не используйте нецифровые свойства вместе с массивом (кроме length). Вместо этого используйте Map (или Object).

5.2.4 Деструктуризация

Для выполнения деструктуризации (например, при распаковке нескольких значений из одного массива или iterable), можно использовать литералы массива слева от определения . Заключительный rest элемент может быть включен в конец (без пробела между ... и именем переменной). Значения должны быть опущены, если они не используются.

const [a, b, c, ...rest] = generateResults();
let [, b,, d] = someArray;

Деструктуризация также может быть использована для параметров функции (обратите внимание, что имя параметра требуется, но игнорируется). Всегда указывайте [] в качестве значения по умолчанию, если параметр деструктурированного массива является необязательным, а также указывайте значения по умолчанию с левой стороны:

/** @param {!Array<number>=} param1 */
function optionalDestructuring([a = 4, b = 2] = []) { … };

Запрещено:

function badDestructuring([a, b] = [4, 2]) { … };

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

5.2.5 Оператор расширения

Литералы массива могут включать оператор расширения (...) для извлечения элементов из одного или нескольких iterables. Оператор расширения следует использовать вместо более неудобных конструкций с Array.prototype. Пробел после ... отсутствует.

Пример:

[...foo]   // предпочтительнее, чем Array.prototype.slice.call(foo)
[...foo, ...bar]   // предпочтительнее, чем foo.concat(bar)

5.3 Литералы объекта

5.3.1 Использование закрывающих запятых

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

5.3.2 Не используйте конструктор Object

Хотя Object не имеет тех же проблем, что и Array, для согласованности он все равно запрещен. Вместо этого используйте ({} или {a: 0, b: 1, c: 2}).

5.3.3 Не смешивайте ключи с кавычками и без

Объектные литералы могут представлять собой либо structs (с ключами и/или символами без кавычек), либо dicts (с и/или вычисляемыми ключами в кавычках). Не смешивайте эти типы ключей в одном объектном литерале.

Запрещено:

{
  width: 42, // ключ без кавычек в стиле struct
  'maxWidth': 43, // ключ с кавычками в стиле dict
}

Это также распространяется на передачу имени свойства функциям, таким как hasOwnProperty. Это может нарушить компиляцию кода, так как компилятор не может переименовать/обфусцировать строковый литерал.

Запрещено:

/** @type {{width: number, maxWidth: (number|undefined)}} */
const o = {width: 42};
if (o.hasOwnProperty('maxWidth')) {
  ...
}

Лучше всего это реализовать как:

/** @type {{width: number, maxWidth: (number|undefined)}} */
const o = {width: 42};
if (o.maxWidth != null) {
  ...
}

5.3.4 Вычисляемые имена свойств

Вычисляемые имена свойств (например, {['key' + foo()]: 42}) разрешены, заключаются в кавычки и считаются ключами в dict-стиле, (т.е. не должны смешиваться с ключами в кавычках), если только вычисленное свойство не является символом (например, [Symbol.iterator]). Значения перечислений также могут быть использованы для вычисляемых ключей, но не должны смешиваться с незнаковыми ключами в одном и том же литерале.

5.3.5 Сокращенное объявление метода

Методы могут быть определены в объектных литералах с помощью сокращенного варианта ({method() {… }}) вместо двоеточия, сразу за которым следует function или литерал стрелочной функции.

Пример:

return {
  stuff: 'candy',
  method() {
    return this.stuff;  // Вернет 'candy'
  },
};

Обратите внимание, что this в сокращенном объявлении метода или функции ссылается на сам литерал объекта, в то время как this в функции со стрелочном объявлением ссылается на область видимости, выходящую за рамки литерала объекта.

Пример:

class {
  getObjectLiteral() {
    this.stuff = 'fruit';
    return {
      stuff: 'candy',
      method: () => this.stuff,  // Вернет 'fruit'
    };
  }
}

5.3.6 Сокращенные свойства

В литералах объекта допустимы сокращенные свойства.

Пример:

const foo = 1;
const bar = 2;
const obj = {
  foo,
  bar,
  method() { return this.foo + this.bar; },
};
assertEquals(3, obj.method());

5.3.7 Деструктуризация

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

Деструктурируемые объекты также могут использоваться в качестве параметров функции, но должны быть максимально простыми: может быть только один уровень свойств без кавычек. Более глубокие уровни вложенности и вычисляемые свойства не могут использоваться при деструктуризации параметров. Указывайте значения по умолчанию в левой части деструктурируемого параметра ({str = 'some default'} = {}, а не {str} = {str: some default'}), и если параметр сам по себе необязателен, то по умолчанию он должен иметь значение {}. JSDoc'y деструктурируемого параметра может быть присвоено любое имя (имя не используется, но требуется компилятору).

Пример:

/**
 * @param {string} ordinary
 * @param {{num: (number|undefined), str: (string|undefined)}=} param1
 *     num: Количество раз, сколько выполнить действие
 *     str: Описание того, что делать
 */
function destructured(ordinary, {num, str = 'some default'} = {})

Запрещено:

/** @param {{x: {num: (number|undefined), str: (string|undefined)}}} param1 */
function nestedTooDeeply({x: {num, str}}) {};
/** @param {{num: (number|undefined), str: (string|undefined)}=} param1 */
function nonShorthandProperty({num: a, str: b} = {}) {};
/** @param {{a: number, b: number}} param1 */
function computedKey({a, b, [a + b]: c}) {};
/** @param {{a: number, b: string}=} param1 */
function nontrivialDefault({a, b} = {a: 2, b: 4}) {};

Деструктуризация также может быть использована для goog.require операторов, и в этом случае не должна быть перенесена на другую строку: оператор занимает одну строку, независимо от ее длины (см. ??).

5.3.8 Перечисления

Перечисления определяются добавлением аннотации @enum к обычному объекту. Дополнительные свойства не могут быть добавлены в перечисление после его определения. Перечисления должны быть константными, и все значения перечислений должны быть полностью неизменяемыми.

/**
 * Поддерживаемые температурные шкалы.
 * @enum {string}
 */
const TemperatureScale = {
  CELSIUS: 'celsius',
  FAHRENHEIT: 'fahrenheit',
};

/**
 * Перечисление с двумя вариантами.
 * @enum {number}
 */
const Option = {
  /** Используемая опция должна быть первой. */
  FIRST_OPTION: 1,
  /** Второй среди двух вариантов. */
  SECOND_OPTION: 2,
};

5.4 Классы

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

Конструкторы являются необязательными. Конструкторы подклассов должны вызывать super() перед установкой любых полей или иным обращением к this. Интерфейсы должны объявлять неметодные свойства в конструкторе.

5.4.2 Поля (fields)

Устанавливайте все поля объекта (т.е. все свойства, кроме методов) в конструкторе. Аннотируйте поля, которые никогда не переназначаются, с помощью @const. Аннотируйте непубличные поля подходящими аннотациями видимости (@private, @protected, @package) и заканчивайте все @private имена полей подчеркиванием. Поля никогда не устанавливаются в конкретный prototype класса.

Пример:

class Foo {
  constructor() {
    /** @private @const {!Bar} */
    this.bar_ = computeBar();

    /** @protected @const {!Baz} */
    this.baz = computeBaz();
  }
}

Подсказка: Свойства никогда не следует добавлять или удалять из экземпляра класса после завершения работы конструктора, так как это существенно затрудняет работу виртуальной машины (которая пытается провести оптимизацию). При необходимости, поля, которые инициализируются позже, должны быть явно установлены как undefined в конструкторе для предотвращения последующих изменений класса. Добавление @struct в объект будет проверять, что необъявленные свойства не добавляются/запрашиваются. Классы добавляют это по умолчанию.

5.4.3 Вычисляемые свойства

Вычисляемые свойства могут использоваться в классах только в том случае, если свойство является символом. Свойства в dict-стиле (то есть, вычисляемые не символьные или заключенные в кавычки ключи, как определено в ??), не допускаются. Метод [Symbol.iterator] должен быть определен для любых классов, логически итерабельных. Кроме того, Symbol следует использовать экономно.

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

5.4.4 Статические методы

Там, где это не мешает читабельности, предпочитайте модульно локальные функции, а не приватные статические методы.

Статические методы должны вызываться только для самого базового класса. Статические методы не должны вызываться для переменных, содержащих динамический экземпляр, который может быть как конструктором, так и конструктором подкласса (и должен определяться с помощью @nocollapse, если он существует), и не должны вызываться непосредственно для подкласса, который не определяет данный метод.

Запрещено:

class Base { /** @nocollapse */ static foo() {} }
class Sub extends Base {}
function callFoo(cls) { cls.foo(); }  // запрещено: не вызывайте статические методы динамически
Sub.foo();  // запрещено: не вызывайте статические методы в подклассах, которые сами их не определили

5.4.5 Объявление классов в старом стиле

Хотя классы ES6 являются предпочтительными, есть случаи, когда классы ES6 не могут быть использованы. Например:

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

  2. Фреймворки, которым необходимо значение this перед вызовом конструктора суперкласса, поскольку конструкторы с ES6 суперклассами не имеют доступа к экземпляру this до тех пор, пока не завершится вызов super.

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

goog.defineClass допускает определение, похожее на определение класса, аналогичное синтаксису класса ES6:

let C = goog.defineClass(S, {
  /**
   * @param {string} value
   */
  constructor(value) {
    S.call(this, 2);
    /** @const */
    this.prop = value;
  },

  /**
   * @param {string} param
   * @return {number}
   */
  method(param) {
    return 0;
  },
});

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

/**
  * @constructor @extends {S}
  * @param {string} value
  */
function C(value) {
  S.call(this, 2);
  /** @const */
  this.prop = value;
}
goog.inherits(C, S);

/**
 * @param {string} param
 * @return {number}
 */
C.prototype.method = function(param) {
  return 0;
};

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

Правильно определить иерархии конструкторов прототипа сложнее, чем кажется на первый взгляд! По этой причине лучше всего использовать goog.inherits из Google Closure.

5.4.6 Не взаимодействуйте напрямую с prototype

Ключевое слово class позволяет более четко и читаемо определить класс, чем определение свойств прототипа. Обычный код класса не должен иметь никаких взаимодействий с точки зрения бизнес-логики с этими объектами, хотя они все еще полезны для определения классов, как говорится в ??. Смешивать и модифицировать прототипы встроенных объектов явно — запрещено.

Исключения: Коду фреймворка (например, Polymer или Angular) может потребоваться использовать prototype, и не следует прибегать к обходным путям, чтобы избежать этого.

5.4.7 Getters и Setters

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

Исключения: бывают ситуации, когда определение геттера или сеттера неизбежно (например, фреймворки с привязкой данных, такие как Angular и Polymer, или для совместимости с внешними API, который невозможно настроить). Только в этих случаях можно использовать геттеры и сеттеры с осторожностью и при условии, что они определены с помощью короткого вида записи метода get и set или Object.defineProperties (не Object.defineProperty, что мешает переименованию свойств). Геттеры не должны изменять видимое состояние.

Запрещено:

class Foo {
  get next() { return this.nextId++; }
}

5.4.8 Переопределение toString

Метод toString может быть переопределен, но должен всегда правильно выполняться и никогда не иметь видимых побочных эффектов.

Совет: Остерегайтесь, в частности, вызова других методов из toString, так как иногда это может привести к бесконечным циклам.

5.4.9 Интерфейсы

Интерфейсы могут быть объявлены с помощью @interface или @record. Интерфейсы, объявленные с помощью @record, могут быть явно (т.е. через @implements) или неявно реализованы классом или литералом объекта.

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

Пример:

/**
 * Что-то, с чем можно взаимодействовать
 * @record
 */
class Frobnicator {
  constructor() {
    /** @type {number} Количество попыток до завершения */
    this.attempts;
  }

  /**
   * Выполняет взаимодействие в соответствии с заданной стратегией
   * @param {!FrobnicationStrategy} strategy
   */
  frobnicate(strategy) {}
}

5.4.10 Абстрактные классы

При необходимости используйте абстрактные классы. Абстрактные классы и методы должны быть аннотированы с помощью @abstract. Не используйте goog.abstractMethod. См. абстрактные классы и методы.

5.5 Функции

5.5.1 Функции верхнего уровня

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

Примеры:

/** @param {string} str */
exports.processString = (str) => {
  // Обработка строки.
};
/** @param {string} str */
const processString = (str) => {
  // Обработка строки
};

5.5.2 Вложенные функции и замыкания

Функции могут содержать вложенные определения функций. Если имеет смысл дать функции имя, функция должна быть присвоена локальной const.

5.5.3 Стрелочные функции

Стрелочные функции обеспечивают лаконичный синтаксис функций и упрощают поиск this для вложенных функций. Стрелочные функции более предпочтительны, чем ключевое слово function, особенно для вложенных функций (но см. ??).

Предпочтительнее использование стрелочных функций, чем других подходов для привязки this — такими как f.bind(this), goog.bind(f, this) и const self = this. Стрелочные функции особенно полезны для вызова callback'ов, так как они позволяют явно указывать, какие параметры должны передаваться обратному вызову, в то время как привязка (binding) будет слепо передавать все параметры.

Левая часть от стрелки содержит ноль или более параметров. Круглые скобки вокруг параметров являются необязательными, если есть только один недеструктурируемый параметр. При использовании круглых скобок можно указывать типы параметров (см. ??).

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

В правой части от стрелки находится тело функции. По умолчанию тело - это блочный оператор (ноль или более операторов, окруженных фигурными скобками). Тело также может быть неявно возвращенным выражением, если: или логика программы требует возврата значения, или оператор void предшествует вызову одной функции или метода (использование void обеспечивает возврат undefined, предотвращает утечку значений и сообщает о намерении). Форма выражения предпочтительнее, если она улучшает читабельность (например, для коротких или простых выражений).

Примеры:

/**
 * Стрелочная функция может быть задокументирована как обычная функция.
 * @param {number} numParam Число, которое нужно добавить
 * @param {string} strParam Еще одно число, которое нужно добавить
 * @return {number} Сумма двух параметров.
 */
const moduleLocalFunc = (numParam, strParam) => numParam + Number(strParam);

// Использует синтаксис выражения с `void`, потому что программная логика
// не требует возвращения значения.
getValue((result) => void alert(`Получено ${result}`));

class CallbackExample {
  constructor() {
    /** @private {number} */
    this.cachedValue_ = 0;

    // Для однострочных callback'ов, вы можете использовать определение типов для параметров.
    // Используется блочный оператор, потому что значение выражения не должно ничего
    // возвращать, и выражение не является единственным вызовом функции.
    getNullableValue((/** ?number */ result) => {
      this.cachedValue_ = result == null ? 0 : result;
    });
  }
}

Запрещено:

/**
 * Функция без параметров и без возвращаемого значения.
 * Такое использование тела выражения недопустимо, поскольку логика программы
 * не требует возвращаемого значение, и нам не хватает оператора `void`.
 */
const moduleLocalFunc = () => anotherFunction();

5.5.4 Генераторы

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

При определении функций генератора допишите * к ключевому слову function, если оно присутствует, и отделите его пробелом от имени функции. При использовании делегированных yield допишите * к ключевому слову yield.

Пример:

/** @return {!Iterator<number>} */
function* gen1() {
  yield 42;
}

/** @return {!Iterator<number>} */
const gen2 = function*() {
  yield* gen1();
}

class SomeClass {
  /** @return {!Iterator<number>} */
  * gen() {
    yield 42;
  }
}

5.5.5 Параметры и возвращаемые типы

Параметры функции и возвращаемые типы обычно должны быть задокументированы с помощью JSDoc аннотаций. Смотрите ?? для получения дополнительной информации.

5.5.5.1 Параметры по умолчанию

Допускается использование опциональных параметров с помощью оператора равенства в списке параметров. Опциональные параметры должны включать пробелы с обеих сторон оператора равенства, именоваться точно так же, как и требуемые параметры (т.е. не префиксоваться с помощью opt_), использовать суффикс = в их JSDoc-типе, идти после требуемых параметров и не использовать инициализаторы, которые создают заметные побочные эффекты. Все опциональные параметры для функций должны иметь значения по умолчанию, даже если это значение undefined. В отличие от обычных функций, абстрактный и интерфейсный методы должны опускать значения параметров по умолчанию.

Пример:

/**
 * @param {string} required Этот параметр необходим всегда
 * @param {string=} optional Этот параметр может быть опущен
 * @param {!Node=} node Другой опциональный параметр
 */
function maybeDoSomething(required, optional = '', node = undefined) {}

/** @interface */
class MyInterface {
  /**
   * Интерфейсные и абстрактные методы должны опускать значения параметров по умолчанию.
   * @param {string=} optional
   */
  someMethod(optional) {}
}

Используйте параметры по умолчанию экономно. Предпочитается деструктурирование (как в ??) для создания читаемого API, когда есть более чем один опциональный параметр, которые не имеют естественного порядка.

Примечание: В отличие от параметров по умолчанию в Python, можно использовать инициализаторы, которые возвращают новые мутируемые объекты (такие как {} или []), потому что инициализатор вычисляется каждый раз при использовании значения по умолчанию, поэтому один объект не будет совместно использоваться в нескольких вызовах.

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

5.5.5.2 Остальные параметры

Используйте параметр rest вместо доступа к arguments. Оставшиеся параметры начинаются с префикса ... в JSDoc. rest параметр должен быть последним в списке. Между ... и именем параметра нет пробела. Не называйте параметр rest var_args. Никогда не называйте локальную переменную или параметр arguments, он вносит путаницу из-за встроенного arguments.

Пример:

/**
 * @param {!Array<string>} array Это обычный параметр.
 * @param {...number} numbers Все оставшиеся параметры являются числами.
 */
function variadic(array, ...numbers) {}

5.5.6 Обобщения

Объявите общие функции и методы, когда это необходимо, с помощью @template TYPE в JSDoc над определением функции или метода.

5.5.7 Оператор расширения

Вызовы функций могут использовать оператор расширения (...). Предпочитайте оператор расширения перед Function.prototype.apply при распаковке массива или итерабельного метода в несколько параметров функции с вариативным количеством параметров. Пробел после ... отсутствует.

Пример:

function myFunction(...elements) {}
myFunction(...array, ...iterable, ...generator());

5.6 Строковые литералы

5.6.1 Используйте одиночные кавычки

Обычные строковые литералы разделяются одиночными кавычками ('), а не двойными кавычками (").

Подсказка: если строка содержит символ одиночной кавычки, рассмотрите возможность использования строки-шаблона, чтобы избежать необходимости экранирования кавычек.

Обычные строковые литералы не могут охватывать несколько строк.

5.6.2 Шаблонные литералы

Используйте шаблонные литералы (разделенные `) со сложной конкатенацией строк, особенно если речь идет о многострочных литералах. Шаблонные литералы могут занимать несколько строк.

Если шаблонный литерал охватывает несколько строк, ему не нужно поддерживать отступ блока (хотя это возможно, если добавленные пробельные символы не имеют значения).

Пример:

function arithmetic(a, b) {
  return `Это таблица с арифметическими операторами:
${a} + ${b} = ${a + b}
${a} - ${b} = ${a - b}
${a} * ${b} = ${a * b}
${a} / ${b} = ${a / b}`;
}

5.6.3 Не используйте продолжения строк

Не используйте продолжения строк (то есть завершение строки внутри строкового литерала обратным слешем) ни в обычных, ни в шаблонных строковых литералах. Хотя ES5 позволяет это, это может привести к неожиданным ошибкам, если любой пробельный символ стоит после косой черты — к тому же, он является менее очевидным для читателей.

Запрещено:

const longString = 'Это очень длинная строка, которая превышает лимит в \
    80 символов. К сожалению, она содержит длинные отрезки пустого пространства, так \
    как имеются отступы для поддержания форматирования.';

Вместо этого напишите

const longString = 'Это очень длинная строка, которая превышает лимит в ' +
    '80 символов. К сожалению, она содержит длинные отрезки пустого пространства, так ' +
    'как имеются отступы для поддержания форматирования.';

5.7 Числовые литералы

Числа могут быть указаны в десятичной, шестнадцатеричной, восьмеричной или двоичной форме. Используйте точные префиксы 0x, 0o и 0b со строчными буквами для шестнадцатеричных, восьмеричных и двоичных форм соответственно. Никогда не включайте ведущий ноль, если за ним не следуют x, o или b.

5.8 Управляющие циклы

5.8.1 Для циклов

В ES6 язык теперь имеет три различных типа циклов for. Все они могут использоваться, хотя for-of цикл должен быть в приоритете, когда это возможно.

for-in циклы можно использовать только для объектов в dict стиле (см. ??), и их не следует использовать для итераций по массиву. Object.prototype.hasOwnProperty следует использовать в for-in циклах для исключения нежелательных свойств прототипа. Предпочтите for-of и Object.keys, чем for-in, когда это возможно.

5.8.2 Исключения

Исключения являются важной частью языка и должны использоваться всякий раз, когда возникают исключительные случаи. Всегда бросайте Error или подклассы Error: никогда не выбрасывайте строковые литералы или другие объекты. Всегда используйте new при построении Error.

Эта обработка распространяется на значения Promise rejection() , поскольку Promise.rejection(obj) эквивалентно throw obj; в асинхронных функциях.

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

Предпочтение отдается выбрасыванию исключений, а не специальным подходам к обработке ошибок (таким как передача ссылки на тип контейнера ошибки или возврат объекта со свойством ошибки).

5.8.2.1 Пустые catch блоки

Очень редко корректно ничего не делать в ответ на пойманное исключение. Когда действительно уместно не предпринимать никаких действий в блоке catch, причина, по которой это оправдано, объясняется в комментариях.

try {
  return handleNumericResponse(response);
} catch (ok) {
  // это не число; это нормально — продолжаем работу
}
return handleTextResponse(response);

Запрещено:

  try {
    shouldFail();
    fail('ожидается ошибка');
  } catch (expected) {
  }

Совет: В отличие от некоторых других языков, шаблоны, подобные вышеприведенным, просто не работают, так как они будут ловить ошибку, брошенную с помощью fail. Вместо этого используйте assertThrows().

5.8.3 Switch оператор

Терминологическое примечание: Внутри фигурных скобок блока switch находятся одна или несколько групп операторов. Каждая группа операторов состоит из одной или нескольких блоков switch (либо case FOO: или default:), за которыми следует один или несколько операторов.

5.8.3.1 Пропуски вниз: должны комментироваться

Внутри блока switch каждая группа операторов либо прерывается внезапно (с помощью break, return или throw), либо помечается комментарием, указывающим на то, что выполнение будет или может быть продолжено в следующей группе операторов. Достаточно любого комментария, который передает идею продолжения (обычно пишется // fall through). Этот специальный комментарий не требуется в последней группе операторов в блоке switch.

Пример:

switch (input) {
  case 1:
  case 2:
    prepareOneOrTwo();
    // fall through (продолжение)
  case 3:
    handleOneTwoOrThree();
    break;
  default:
    handleLargeNumber(input);
}
5.8.3.2 Блок default должен присутствовать

Каждый оператор switch включает оператор default, даже если он не содержит кода. Оператор default должен быть последним.

5.9 this

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

Никогда не используйте this для обращения к глобальному объекту, контексту eval, target принадлежащему event, или ненужному call() или apply() функций.

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

Используйте операторы сравнения (===/!==), за исключением случаев, описанных ниже.

5.10.1 Исключения, где необходимо приведение типов

Поимка с помощью catch обоих значений null и undefined:

if (someObjectOrPrimitive == null) {
  // Проверка на наличие null ловит и null, и undefined для объектов и
  // примитивов, но не ловит другие неправильные значения, как 0 или пустые
  // строки.
}

5.11 Запрещенные возможности

5.11.1 with

Не используйте ключевое слово with. Это сделает ваш код более трудным для понимания. К тому же он запрещен в строгом режиме со времен ES5.

5.11.2 Динамическая оценка кода

Не используйте eval для конструктора Function(...string) (за исключением загрузчиков кода). Эти возможности потенциально опасны и просто не работают в среде CSP (политики защиты контента).

5.11.3 Автоматическая установка точки с запятой

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

5.11.4 Нестандартные функции

Не используйте нестандартные функции. Сюда относятся старые функции, которые были удалены (например, WeakMap.clear), новые функции, которые еще не стандартизованы (например, текущий рабочий проект TC39, предложения на любом этапе, или предлагаемые, но не исчерпывающие веб-стандарты), или проприетарные функции, которые реализованы только в некоторых браузерах. Используйте только те функции, которые определены в текущих стандартах ECMA-262 или WHATWG. (Обратите внимание, что проекты, настаивающие против определенных API, такие как расширения Chrome или Node.js, очевидно, могут использовать эти API). Нестандартный языковые расширения (например, предоставляемые некоторыми внешними транспайлерами — source-to-source компиляторами) запрещены.

5.11.5 Объекты-обёртки для примитивных типов

Никогда не используйте new на обертках примитивных объектов (Boolean, Number, String, Symbol), а также не включайте их в аннотации к типам.

Запрещено:

const /** Boolean */ x = new Boolean(false);
if (x) alert(typeof x);  // отобразить 'object'... — какого типа?

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

Пример:

const /** boolean */ x = Boolean(0);
if (!x) alert(typeof x);  // отобразить 'boolean', как ожидается

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

Никогда не модифицируйте встроенные типы, добавляя методы в свои конструкторы или прототипы. Избегайте зависимости от библиотек, которые это делают. Обратите внимание, что библиотека исполнения JSCompiler'a по возможности предоставляет полифиллеры (преобразуют новый код в поддерживаемый старыми браузерами), соответствующие стандартам; больше ничто не должно модифицировать встроенные объекты.

Не добавляйте символы в глобальный объект, если только это не является абсолютно необходимым (например, требуется сторонним API).

5.11.7 Опускание (), когда вызываете конструктор

Никогда не вызывайте конструктор с оператором new без использования скобок ().

Запрещено:

new Foo;

Используйте вместо этого:

new Foo();

Опущенные скобки могут привести к едва уловимым ошибкам. Эти две строки не эквивалентны:

new Foo().Bar();
new Foo.Bar();

6 Именование

6.1 Общие для всех идентификаторов правила

Идентификаторы используют только ASCII-буквы и цифры, и в небольшом количестве случаев, отмеченных ниже, подчеркивания и очень редко (когда этого требуют такие фреймворки, как Angular) знаки доллара.

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

errorCount          // Без аббревиатуры.
dnsConnectionIndex  // Большинство людей знает, что такое "DNS".
referrerUrl         // То же самое для "URL".
customerId          // "Id" используется везде, поэтому вряд ли будет неправильно понят.

Запрещено:

n                   // Бессмысленный.
nErr                // Двусмысленная аббревиатура.
nCompConns          // Двусмысленная аббревиатура.
wgcConnections      // Только ваша команда знает, что это значит.
pcReader            // Многие вещи можно назвать как "pc".
cstmrId             // Удалены внутренние символы.
kSecondsPerDay      // Не используйте венгерскую нотацию.

6.2 Правила для типов идентификатора

6.2.1 Именование пакетов

Имена всех пакетов должны быть в lowerCamelCase. Например, my.exampleCode.deepSpace, но не my.examplecode.deepspace или my.example_code.deep_space.

6.2.2 Имена классов

Имена классов, интерфейсов, записей и typedef'ов записываются в UpperCamelCase. Неэкспортируемые классы являются просто локальными: они не помечаются как @private и не заканчиваются завершающим подчеркиванием.

Имена типов обычно представляют собой существительные или фразы существительных. Например, Request, ImmutableList или VisibilityMode. Кроме того, имена интерфейсов иногда могут быть прилагательными или прилагательными фразами (например, Readable).

6.2.3 Именование методов

Имена методов записываются в lowerCamelCase. Имена методов @private должны заканчиваться завершающим подчеркиванием.

Имена методов обычно представляют собой глаголы или глагольные фразы. Например, sendMessage или stop_. Методы getter и setter для свойств никогда не требуются, но если они используются, то они должны быть названы getFoo (или опционально isFoo или hasFoo для booleans) или setFoo(value) для setters.

Подчёркивания могут также появляться в именах методов тестирования JsUnit для разделения логических компонентов имени. Одним из типичных паттернов является test<MethodUnderTest>_<state>_<expectedOutcome>, например testPop_emptyStack_throws. Нет единого правильного способа называть методы для тестирования.

6.2.4 Имена перечислений

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

6.2.5 Именование констант

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

6.2.5.1 Определение “constant”

Каждая константа представляет собой статическое свойство @const или модульно-локальные const объявления, но не все @const — это статические свойства, а модульно-локальные const — являются константами. Перед тем, как выбрать константный регистр, подумайте, действительно ли поле похоже на глубоко неизменяемую константу. Например, если какое-либо из наблюдаемых состояний этого экземпляра может измениться, то это почти наверняка не константа. Простого намерения никогда не изменять объект, как правило, недостаточно.

Примеры:

// Константы
const NUMBER = 5;
/** @const */ exports.NAMES = ImmutableList.of('Ed', 'Ann');
/** @enum */ exports.SomeEnum = { ENUM_CONSTANT: 'value' };

// Не константы
let letVariable = 'non-const';
class MyClass { constructor() { /** @const {string} */ this.nonStatic = 'non-static'; } };
/** @type {string} */ MyClass.staticButMutable = 'не @const, может быть переопределено';
const /** Set<string> */ mutableCollection = new Set();
const /** ImmutableSet<SomeMutableType> */ mutableElements = ImmutableSet.of(mutable);
const Foo = goog.require('my.Foo');  // зеркально импортируемое имя
const logger = log.getLogger('loggers.are.not.immutable');

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

6.2.5.2 Локальные псевдонимы

Локальные псевдонимы следует использовать всякий раз, когда они улучшают читабельность по сравнению с полноразмерными именами. Следуйте тем же правилам, что и goog.require (??), сохраняя последнюю часть псевдонима. Псевдонимы также могут использоваться внутри функций. Псевдонимы должны быть const.

Примеры:

const staticHelper = importedNamespace.staticHelper;
const CONSTANT_NAME = ImportedClass.CONSTANT_NAME;
const {assert, assertInstanceof} = asserts;

6.2.6 Неконсатные названия полей

Неконстантные имена полей (статические или иные) записываются в lowerCamelCase, с завершающимися подчеркиванием для приватных полей.

Эти имена обычно являются существительными или фразами существительных. Например, computedValues или index_.

6.2.7 Имена параметров

Имена параметров записываются в lowerCamelCase. Обратите внимание, что это применимо, даже если параметр ожидает конструктор.

Односимвольные имена параметров не должны использоваться в публичных методах.

Исключение: По требованию стороннего фреймворка имена параметров могут начинаться с $. Это исключение не распространяется на любые другие идентификаторы (например, локальные переменные или свойства).

6.2.8 Именование локальных переменных

Имена локальных переменных записываются в lowerCamelCase, за исключением модульно-локальных (верхнего уровня) констант, как описано выше. Имена констант в функциональных областях по-прежнему написаны в lowerCamelCase. Обратите внимание, что lowerCamelCase используется, даже если переменная содержит конструктор.

6.2.9 Имена параметров шаблона

Имена параметров шаблона должны быть краткими, однозначными или однобуквенными идентификаторами, и должны быть в ВЕРХНЕМ РЕГИСТРЕ, например TYPE или THIS.

6.2.10 Модульно-локальные имена

Имена в модулях, которые не экспортируются, являются неявно закрытыми. Они не помечены @private и не заканчиваются подчеркиванием. Это относится к классам, функциям, переменным, константам, перечислениям и другим модульно-локальным идентификаторам.

6.3 Верблюжий стиль: отдельный случай

Иногда существует более одного разумного способа преобразования английской фразы в верблюжий стиль, например, когда присутствуют аббревиатуры или необычные конструкции вроде IPv6 или iOS. Для повышения предсказуемости Google Style задает следующую исчерпывающую (почти) схему.

Начиная с изначальной формы имени:

  1. Преобразуйте фразу в обычный ASCII и удалите все апострофы. Например, Müller's algorithm может стать Muellers algorithm.
  2. Разделите этот результат на слова, разделяя их на пробелы и оставшиеся знаки препинания (обычно дефисы).
    1. Рекомендовано: если какое-либо слово уже имеет общепринятый внешний вид в camelCase, разделите его на составные части (например, AdWords становится "ad words"). Обратите внимание, что такое слово, как iOS, на самом деле, не совсем в camelCase; оно не поддается никаким соглашениям, поэтому данная рекомендация не применяется.
  3. Теперь сделайте все буквы в нижнем регистре (включая аббревиатуры), затем сделайте в верхнем регистре только первый символ:
    1. … каждого слова (чтобы вышел CamelCase в верхнем регистре) или
    2. … каждого слова, кроме первого (чтобы вышел camelCase в нижнем регистре)
  4. В конце, соедините все слова в один идентификатор.

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

Примеры:

Изначальная форма Корректно Некорректно
XML HTTP request XmlHttpRequest XMLHTTPRequest
new customer ID newCustomerId newCustomerID
inner stopwatch innerStopwatch innerStopWatch
supports IPv6 on iOS? supportsIpv6OnIos supportsIPv6OnIOS
YouTube importer YouTubeImporter YoutubeImporter*

*Приемлемо, но не рекомендуется.

Примечание: некоторые слова неоднозначны в английском языке: например, nonempty и non-empty оба правильны, поэтому имена методов checkNonempty и checkNonEmpty также правильны.

7 JSDoc

JSDoc используется для всех классов, полей и методов.

7.1 Общая форма

Основной вид форматирования блоков JSDoc показан в этом примере:

/**
 * Несколько линий текста JSDoc написаны здесь.
 * Строки переносятся, как обычно.
 * @param {number} arg Число, чтобы делать что-то.
 */
function doSomething(arg) { … }

или в этом однострочном примере:

/** @const @private {!Foo} Чуть меньше JSDoc. */
this.foo_ = foo;

Если однострочный комментарий переносится на несколько строк, он должен использовать многострочный стиль с /** и */ в своих строках.

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

7.2 Markdown

JSDoc написан на языке Markdown, хотя при необходимости может включать HTML.

Обратите внимание, что инструменты, которые автоматически извлекают JSDoc (например, JsDossier) часто игнорируют форматирование обычного текста, так что если вы сделаете так:

/**
 * Вычисляет вес на основе трех факторов:
 *   элементов отправлено
 *   элементов получено
 *   последняя временная метка
 */

это станет таким:

Вычисляет вес на основе трех факторов: элементов отправлено элементов получено последная временная метка

Вместо этого напишите список Markdown:

/**
 * Вычисляет вес на основе трех факторов:
 *  - элементов отправлено
 *  - элементов получено
 *  - последняя временная метка
 */

7.3 JSDoc теги

Стиль Google разрешает подмножество JSDoc-тегов. См. ?? для полного списка. Большинство тегов должны занимать свою собственную строку, с тегом в начале строки.

Запрещено:

/**
 * Тэг "param" должен занимать свою собственную строку и не может комбинироваться.
 * @param {number} left @param {number} right
 */
function add(left, right) { ... }

Простые теги, не требующие дополнительных данных (такие, как @private, @const, @final, @export), могут быть объединены в одну строку вместе с дополнительным типом, если это необходимо.

/**
 * Размещайте более сложные аннотации (например, "implements" и "template").
 * на своих отдельных линиях.  Несколько простых тегов (например, "export" и "final").
 * могут быть скомбинированы в одной линии.
 * @export @final
 * @implements {Iterable<TYPE>}
 * @template TYPE
 */
class MyClass {
  /**
   * @param {!ObjType} obj Отдельный объект.
   * @param {number=} num Опциональное число.
   */
  constructor(obj, num = 42) {
    /** @private @const {!Array<!ObjType|number>} */
    this.data_ = [obj, num];
  }
}

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

Общую информацию об аннотировании типов в JavaScript см. в разделе Аннотирование JavaScript для Closure Compiler и Типы в Closure Type System.

7.4 Перенос строк

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

/**
 * Иллюстрирует перенос строки для длинных описаний.
 * @param {string} foo Это параметр с описанием, слишком длинным, чтобы 
 *     быть в одной строке.
 * @return {number} Это параметр с описанием, слишком длинным, чтобы 
 *     быть в одной строке.
 */
exports.method = function(foo) {
  return 5;
};

Не делайте отступы при переносе описания @desc или @fileoverview.

7.5 Комментарии верхнего\файлового уровня

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

Пример:

/**
 * @fileoverview Описание файла, цели его использование и информация
 * о его зависимостях.
 * @package
 */

7.6 Комментарии класса

Классы, интерфейсы и записи должны быть задокументированы описанием и любыми параметрами шаблона, реализованными интерфейсами, видимостью или другими соответствующими тегами. Описание класса должно давать читателю достаточно информации, чтобы знать, как и когда использовать класс, а также любые дополнительные моменты, необходимые для корректного использования класса. Текстовые описания могут быть опущены в конструкторе. @constructor и @extends аннотации не используются с ключевым словом class, если только класс не используется для объявления @interface или не расширяет общий класс.

/**
 * Особенная цель события, которая делает полезные вещи.
 * @implements {Iterable<string>}
 */
class MyFancyTarget extends EventTarget {
  /**
   * @param {string} arg1 Аргумент, который делает это более полезным.
   * @param {!Array<number>} arg2 Список чисел для обработки.
   */
  constructor(arg1, arg2) {
    // ...
  }
};

/**
 * Записи также полезны.
 * @extends {Iterator<TYPE>}
 * @record
 * @template TYPE
 */
class Listable {
  /** @return {TYPE} Следующий элемент в строке, который будет возвращен. */
  next() {}
}

7.7 Комментарии перечисления и typedef

Все перечисления и typedefs должны быть задокументированы с помощью соответствующих тегов JSDoc (@typedef или @enum) на предшествующей строке. Публичные перечисления и typedefs также должны иметь описание. Отдельные элементы перечисления могут быть задокументированы JSDoc комментарием на предшествующей строке.

/**
 * Полезное объединение типа, которое часто используется повторно.
 * @typedef {!Bandersnatch|!BandersnatchType}
 */
let CoolUnionType;


/**
 * Типы бандерснатчей.
 * @enum {string}
 */
const BandersnatchType = {
  /** Этот вид действительно странный */
  FRUMIOUS: 'frumious',
  /** Менее важный тип */
  MANXOME: 'manxome',
};

Typedefs полезны для определения типов коротких записей или псевдонимов для объединений, сложных функций или общих типов. Следует избегать typedefs'ов для типов записей с большим количеством полей, поскольку они не позволяют документировать отдельные поля, а также использовать шаблоны или рекурсивные ссылки. Для больших типов записей предпочитайте @record.

7.8 Комментарии методов и функций

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

Описания метода, параметров и возвращаемых значений (но не типов) могут быть опущены, если они очевидны из остальной части JSDoc'a метода или из его определения.

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

Если метод переопределяет метод суперкласса, он должен включать в себя примечание @override. Переопределенные методы наследуют все аннотации JSDoc от метода суперкласса (включая аннотации видимости) и должны быть опущены в переопределенном методе. Однако, если в аннотациях типа какой-либо тип уточнен, все @param и @return аннотации должны быть указаны явно.

/** Класс, который что-то делает. */
class SomeClass extends SomeBaseClass {
  /**
   * Взаимодействует с экземпляром MyClass и что-то возвращает.
   * @param {!MyClass} obj Объект, который по какой-то причине требует детального
   *     описания, которое занимает несколько строк.
   * @param {!OtherClass} obviousOtherClass
   * @return {boolean} Если что-то произошло.
   */
  someMethod(obj, obviousOtherClass) { ... }

  /** @override */
  overriddenMethod(param) { ... }
}

/**
 * Демонстрирует, как функции верхнего уровня следуют тем же правилам. Данная функция
 * создает массив.
 * @param {TYPE} arg
 * @return {!Array<TYPE>}
 * @template TYPE
 */
function makeArray(arg) { ... }

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

function /** string */ foo(/** number */ arg) {...}

Если вам нужно описание или теги, используйте один JSDoc комментарий над методом. Например, методы, возвращающие значения, должны иметь тег @return.

class MyClass {
  /**
   * @param {number} arg
   * @return {string}
   */
  bar(arg) {...}
}
// Недопустимый однострочный JSDoc.

class MyClass {
  /** @return {string} */ foo() {...}
}

/** Function description. */ bar() {...}

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

promise.then(
    /** @return {string} */
    (/** !Array<string> */ items) => {
      doSomethingWith(items);
      return items[0];
    });

Для функциональных выражений, см. ??.

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

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

Публично экспортируемые константы комментируются так же, как и свойства.

/** My class. */
class MyClass {
  /** @param {string=} someString */
  constructor(someString = 'default string') {
    /** @private @const {string} */
    this.someString_ = someString;

    /** @private @const {!OtherType} */
    this.someOtherThing_ = functionThatReturnsAThing();

    /**
     * Максимальное количество вещей на панели.
     * @type {number}
     */
    this.someProperty = 4;
  }
}

/**
 * Сколько раз мы попробуем, прежде чем прекратить.
 * @const {number}
 */
MyClass.RETRY_COUNT = 33;

7.10 Аннотации типа

Аннотации типов находятся в тегах @param, @return, @this и @type, и опционально в тегах @const, @export и на любых тегах видимости. Аннотации типа, прикрепленные к тегам JSDoc, всегда должны быть заключены в фигурные скобки.

7.10.1 Nullability

Система типов определяет модификаторы ! и ? для ненулевых и нулевых соответственно. Эти модификаторы должны предшествовать типу.

Модификаторы Nullability имеют различные требования к различным типам, которые делятся на две категории:

  1. Аннотации типов для примитивов (string, number, boolean, symbol, undefined, null) и литералов ({function(...): ...} и {{foo: string...}}) всегда ненулевые по умолчанию. Используйте модификатор ?, чтобы сделать его нулевым, но опускайте ненужный !.
  2. Ссылочные типы (обычно все, что угодно в ВверхнемВерблюжьемСтиле, включая some.namespace.ReferenceType) относятся к классу, перечислению, записи или typedef, определенному в другом месте. Так как эти типы могут быть или не быть нулевыми, невозможно определить по одному только имени, является ли оно нулевым или нет. Всегда используйте явные модификаторы ? и ! для этих типов, чтобы предотвратить двусмысленность при использовании в конкретных местах.

Плохо:

const /** MyObject */ myObject = null; // Непримитивные типы должны быть аннотированы.
const /** !number */ someNum = 5; // Примитивы по умолчанию не могут быть нулевыми.
const /** number? */ someNullableNum = null; // ? должно предшествовать типу.
const /** !{foo: string, bar: number} */ record = ...; // Всегда ненулевой.
const /** MyTypeDef */ def = ...; // Не ясно, что MyTypeDef нулевой.

// Не уверен, что объект (может быть нулевым), перечисление (ненулевой, если не указано иначе)
// или typedef (зависит от определения).
const /** SomeCamelCaseName */ n = ...;

Хорошо:

const /** ?MyObject */ myObject = null;
const /** number */ someNum = 5;
const /** ?number */ someNullableNum = null;
const /** {foo: string, bar: number} */ record = ...;
const /** !MyTypeDef */ def = ...;
const /** ?SomeCamelCaseName */ n = ...;

7.10.2 Преобразования типов

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

/** @type {number} */ (x)

7.10.3 Типы параметров шаблона

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

Плохо:

const /** !Object */ users = {};
const /** !Array */ books = [];
const /** !Promise */ response = ...;

Хорошо:

const /** !Object<string, !User> */ users = {};
const /** !Array<string> */ books = [];
const /** !Promise<!Response> */ response = ...;

const /** !Promise<undefined> */ thisPromiseReturnsNothingButParameterIsStillUseful = ...;
const /** !Object<string, *> */ mapOfEverything = {};

Случаи, когда параметры шаблона не должны использоваться:

7.10.4 Функциональные выражения

Терминологическое уточнение: функциональные выражения относятся к аннотации типов функций с ключевым словом function в аннотации (см. примеры ниже).

Там, где дано определение функции, не используйте функциональные выражения. Указывайте типы параметров и возвращаемых функций с помощью @param и @return, либо со строчными аннотациями (см. ??). Это включает анонимные функции и функции, определенные и назначенные в const (где функция jsdoc появляется над всем выражением назначения).

Функциональные выражения необходимы, например, внутри @typedef, @param или @return. Используйте его также для переменных или свойств типа функции, если они не инициализируются сразу с определением функции.

  /** @private {function(string): string} */
  this.idGenerator_ = googFunctions.identity;

При использовании функционального выражения всегда указывайте тип возврата явно. В противном случае типом возврата по умолчанию является undefined (?), что приводит к странному и неожиданному поведению и редко является тем, что действительно желательно.

Плохо — ошибка типа, но предупреждение не выдается:

/** @param {function()} generateNumber */
function foo(generateNumber) {
  const /** number */ x = generateNumber();  // Здесь нет ошибки типа времени компиляции.
}

foo(() => 'точно не число');

Хорошо:

/**
 * @param {function(): *} inputFunction1 Может вернуть любой тип.
 * @param {function(): undefined} inputFunction2 Точно ничего не
 *      возвращает.
 * Внимание: тип возвращаемого значения `foo` подразумевает {undefined}.
 */
function foo(inputFunction1, inputFunction2) {...}

7.10.5 Пробел

В пределах аннотации типа, после каждой запятой или двоеточия требуется один пробел или разрыв строки. Дополнительные разрывы строк могут быть вставлены для улучшения читабельности или во избежание превышения лимита в 80 символов. Эти разрывы должны быть выбраны и снабжены отступами в соответствии с рекомендациями (например, ?? и ??). Никакие другие пробельные символы не допускаются в аннотациях типов.

Хорошо:

/** @type {function(string): number} */

/** @type {{foo: number, bar: number}} */

/** @type {number|string} */

/** @type {!Object<string, string>} */

/** @type {function(this: Object<string, string>, number): string} */

/**
 * @type {function(
 *     !SuperDuperReallyReallyLongTypedefThatForcesTheLineBreak,
 *     !OtherVeryLongTypedef): string}
 */

/**
 * @type {!SuperDuperReallyReallyLongTypedefThatForcesTheLineBreak|
 *     !OtherVeryLongTypedef}
 */

Плохо:

// Пробел должен быть только после двоеточия
/** @type {function(string) : number} */

// Поставьте пробелы после двоеточий и запятых
/** @type {{foo:number,bar:number}} */

// Не должно быть пробелов в объединениях типов
/** @type {number | string} */

7.11 Аннотации видимости

Аннотации видимости (@private, @package, @protected) могут быть указаны в @fileoverview или на любом экспортированном символе или свойстве. Не указывайте видимость для локальных переменных, будь то внутри функции или на верхнем уровне модуля. Все имена @private должны заканчиваться подчеркиванием.

8 Политика

8.1 С проблемами, не указанными в Google Style: Будьте последовательны!

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

8.2 Предупреждения компилятора

8.2.1 Используйте стандартный набор предупреждений

Насколько это возможно, проекты должны использовать --warning_level=VERBOSE.

8.2.2 Как работать с предупреждениями

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

После того, как вы увидели предупреждение, попробуйте следующие шаги:

  1. Исправьте его или временно поправьте. Сделайте попытку полностью устранить предупреждение или найдите другой способ решить проблему, который позволит полностью избежать предупреждения.
  2. В другом случае, определите, не ложная ли это тревога. Если вы убеждены, что предупреждение недействительно, а код на самом деле безопасен и корректен, добавьте комментарий, чтобы убедить читателя в этом и примените аннотацию @suppress.
  3. Иначе, оставьте TODO комментарий. Это последнее средство. Если вы делаете это, не подавляйте предупреждение. Предупреждение должно быть видимым до тех пор, пока оно не будет должным образом исправлено.

8.2.3 Подавляйте предупреждения в самом узком диапазоне

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

Пример

/** @suppress {uselessCode} Нераспознанная декларация 'use asm' */
function fn() {
  'use asm';
  return 0;
}

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

8.3 Устаревание

Пометьте устаревшие методы, классы или интерфейсы аннотациями @deprecated. Комментарий к устаревшим методам должен содержать простые и понятные указания по исправлению для других людей.

8.4 Код не в Google Style

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

8.4.1 Переформатируйте существующий код

При обновлении стиля существующего кода, следуйте данным рекомендациям:

  1. Необязательно изменять весь существующий код в соответствии с текущими рекомендациями по стилю. Переформатирование существующего кода является компромиссом между существованием старого формата и нового (code churn). Правила стиля меняются с течением времени, и такого рода подстройки для поддержания соответствия создают ненужные изменения. Однако, если в файл вносятся новые изменения, он должен быть в Google Style.
  2. Будьте осторожны, чтобы не допустить, чтобы исправление кода на новый стиль мешало фокусироваться на основном коде. Если вы обнаружили, что вносите много изменений в стиль, которые не являются критичными, делайте эти изменения в отдельных файлах или участках кода.

8.4.2 Новый добавленный код: используйте Google Style

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

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

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

8.5 Локальные правила стиля

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

8.6 Сгенерированный код: в основном без правил

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

9 Приложения

9.1 Ссылки на теги JSDoc

JSDoc служит нескольким целям в JavaScript. Помимо того, что он используется для создания документации, он также используется для управления инструментарием. Наиболее известными являются аннотации типа Closure Compiler.

9.1.1 Аннотации типа и другие Closure Compiler аннотации

Документация для JSDoc, используемая компилятором, описана в Аннотирование JavaScript для Closure Compiler и Типы в системе типов Closure.

9.1.2 Документирующие аннотации

В дополнение к JSDoc, описанному в Аннотирование JavaScript для Closure Compiler следующие теги являются общими и хорошо поддерживаются различными инструментами генерации документации (такими как JsDossier) для чисто документационных целей.

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

9.1.2.1 @author or @owner - Не рекомендуется.

Не рекомендуется.

Синтаксис: @author username@google.com (First Last).

/**
 * @fileoverview Утилиты для работы с текстами.
 * @author kuth@google.com (Uthur Pendragon)
 */

Документирование автора файла или владельца тестов обычно используются только в комментарии @fileoverview. Тэг @owner используется инструментальной панелью модульного теста для определения того, кому принадлежат результаты тестов.

9.1.2.2 @bug

Синтаксис: @bug номер бага.

/** @bug 1234567 */
function testSomething() {
  // …
}

/**
 * @bug 1234568
 * @bug 1234569
 */
function testTwoBugs() {
  // …
}

Показывает, какие есть ошибки в тестах данной функции.

Если ошибок несколько, они должны иметь свою строку @bug, чтобы поиск регрессионных тестов был максимально простым.

9.1.2.3 @codeУстарело. Не используйте.

Устарело. Не используйте. Вместо этого используйте обратные ссылки Markdown.

Синтаксис: {@code ...}

Исторически `BatchItem` был написан как {@code BatchItem}.

/** Обработка ожидаемого экземпляра `BatchItem`. */
function processBatchItems() {}

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

9.1.2.4 @desc

Синтаксис: @desc сообщения.

/** @desc Уведомление пользователя о том, что его учетная запись была создана. */
exports.MSG_ACCOUNT_CREATED = goog.getMsg(
    'Ваша учетная запись была успешно создана.');
9.1.2.5 @link

Синтаксис: {@link ...}

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

/** Обработка ожидаемого экземпляра {@link BatchItem}. */
function processBatchItems() {}

Историческое примечание: @link теги также используются для создания внешних ссылок в генерируемой документации. Для внешних ссылок всегда используйте синтаксис ссылки Markdown:

/**
 * Этот класс реализует полезное подмножество
 * [нативных интерфейсов Event](https://dom.spec.whatwg.org/#event).
 */
class ApplicationEvent {}
9.1.2.6 @see

Синтаксис: @see Link.

/**
 * Добавляет один элемент в небезопасном режиме.
 * @see #addSafely
 * @see goog.Collect
 * @see goog.RecklessAdder#add
 */

Ссылка на поиск другой функции или метода класса.

9.1.2.7 @supported Описание

Синтаксис: @supported Описание.

/**
 * @fileoverview Event Manager
 * Предоставляет абстрактный интерфейс к системам событий браузеров.
 * @supported IE10+, Chrome, Safari
 */

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

9.1.3 Специальные аннотации для фреймворков

Следующие аннотации относятся к конкретному фреймворку.

9.1.3.1 @ngInject for Angular 1
9.1.3.2 @polymerBehavior for Polymer

https://github.com/google/closure-compiler/wiki/Polymer-Pass.

9.1.4 Примечания к стандартным аннотациям Closure Compiler

Раньше следующие теги были стандартными, но теперь они устарели.

9.1.4.1 @exposeУстарел. Не используйте.

Устарел. Не используйте. Используйте вместо этого @export и/или @nocollapse.

9.1.4.2 @inheritDocУстарел. Не используйте.

Устарел. Не используйте. Используйте вместо этого @override.

9.2 Правила стиля, вызывающие недопонимание

Вот коллекция менее известных или часто непонятных фактов о Google Style для JavaScript. (Следующие утверждения верны; причем эти случаи не выдуманы).

Для поддержки различных аспектов Google Style существуют следующие инструменты.

9.3.1 Closure Compiler

Эта программа выполняет проверку типа и другие проверки, оптимизации и другие преобразования (такие, как даунгрейд кода с ECMAScript 6 на ECMAScript 5).

9.3.2 clang-format

Данная программа переформатирует исходный код JavaScript в Google Style, а также применяет ряд необязательных, но часто повышающих читабельность практик форматирования. Вывод, полученный с помощью clang-format, соответствует руководству по стилю.

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

9.3.3 Линтер Closure Compiler

Данная программа проверяет на наличие различных ошибок и антишаблонов.

9.3.4 Фреймворк Conformance

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

Эти правила обычно используются для обеспечения соблюдения критических ограничений (таких, как определение глобальных объектов, которые могут нарушить код проекта) и шаблонов безопасности (таких, как использование eval или присвоение innerHTML), или более простых для улучшения качества кода.

Для получения дополнительной информации обратитесь к официальной документации JS Conformance Framework.

9.4 Исключение устаревших платформ

9.4.1 Описание

В этом разделе описаны исключения и дополнительные правила, которым необходимо следовать, когда современный синтаксис ECMAScript 6 недоступен авторам кода. Исключения из рекомендуемого стиля необходимы в тех случаях, когда синтаксис ECMAScript 6 недоступен:

9.4.2 Использование var

9.4.2.1 var имеет не блочную область видимости

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

for (var i = 0; i < 3; ++i) {
  var iteration = i;
  setTimeout(function() { console.log(iteration); }, i*1000);
}

// лог 2, 2, 2 -- а не 0, 1, 2
// потому что `iteration` имеет область видимости в пределах функции, а не локально для цикла.

9.4.2.2 Объявляйте переменные как можно ближе к месту первого использования

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

function sillyFunction() {
  var count = 0;
  for (var x in y) {
    // "count" может быть объявлена здесь, но не делайте так
    count++;
  }
  console.log(count + ' элементов в y');
}
9.4.2.3 Использование @const для константных значений

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

9.4.3 Не используйте объявления функций с видимостью в пределах блока

Не делайте так:

if (x) {
  function foo() {}
}

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

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

if (x) {
  var foo = function() {};
}

9.4.4 Управление зависимостями при помощи goog.provide/goog.require

9.4.4.1 Резюме

Предупреждение: управление зависимостями с помощью goog.provide устарело. Все новые файлы, даже в проектах, использующих goog.provide для старых файлов, должны использовать goog.module. Следующие правила предназначены только для уже существующих goog.provide файлов.

goog.provide операторы должны быть сгруппированы и помещены первыми. Все утверждения goog.require должны следовать за ними. Оба списка должны быть разделены пустой строкой.

Как и при импорте операторов в других языках, операторы goog.provide и goog.require должны быть написаны в одной строке, даже если они превышают ограничение длины строки в 80 символов.

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

goog.provide('namespace.MyClass');
goog.provide('namespace.helperFoo');

goog.require('an.extremelyLongNamespace.thatSomeoneThought.wouldBeNice.andNowItIsLonger.Than80Columns');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classes');
goog.require('goog.dominoes');

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

Делайте так:

goog.provide('namespace.MyClass');

А не:

goog.provide('namespace.MyClass');
goog.provide('namespace.MyClass.CONSTANT');
goog.provide('namespace.MyClass.Enum');
goog.provide('namespace.MyClass.InnerClass');
goog.provide('namespace.MyClass.TypeDef');
goog.provide('namespace.MyClass.staticMethod');

Могут также предоставляться (provide) члены в пространствах имен:

goog.provide('foo.bar');
goog.provide('foo.bar.CONSTANT');
goog.provide('foo.bar.method');
9.4.4.2 Использование псевдонимов с goog.scope

Предупреждение: goog.scope устарел. Новые файлы не должны использовать goog.scope даже в проектах с существующим использованием goog.scope.

goog.scope может быть использован для сокращения ссылок на символы, расположенных в пространстве имен кода с использованием управления зависимостями при помощи goog.provide/goog.require.

В каждый файл может быть добавлен только один вызов goog.scope. Всегда помещайте его в глобальную область видимости.

Вызову goog.scope(function()) { должна предшествовать ровно одна пустая строка и следовать после операторов goog.provide, goog.require или комментариев верхнего уровня. Вызов должен быть закрыт в последней строке файла. Добавьте // goog.scope к закрывающему выражению области видимости. Отделите комментарий от точки с запятой двумя пробелами.

Аналогично пространствам имен в C++, не отступайте под объявлениями goog.scope. Вместо этого продолжайте без отступа.

Используйте псевдонимы только для имен, которые не будут переназначены другому объекту (например, большинство конструкторов, перечислений и пространств имен). Не делайте так (см. ниже, как создать псевдоним для конструктора):

goog.scope(function() {
var Button = goog.ui.Button;

Button = function() { ... };
...

Имена должны быть такими же, как и последнее свойство глобального объекта, на который они накладывают псевдонимы.

goog.provide('my.module.SomeType');

goog.require('goog.dom');
goog.require('goog.ui.Button');

goog.scope(function() {
var Button = goog.ui.Button;
var dom = goog.dom;

// Псевдоним нового типа после декларации конструктора.
my.module.SomeType = function() { ... };
var SomeType = my.module.SomeType;

// Объявляйте методы в прототипе, как обычно:
SomeType.prototype.findButton = function() {
  // Кнопка с псевдонимом как выше.
  this.button = new Button(dom.getElement('my-button'));
};
...
});  // goog.scope
9.4.4.3 goog.forwardDeclare

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

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

Оператор goog.forwardDeclare должен следовать тем же правилам стиля, что и goog.require и goog.requireType. Весь блок goog.forwardDeclare, goog.require и goog.requireType операторов должен сортироваться в алфавитном порядке.