Frontend Guidelines

Правила и руководства оформления, форматирования HTML, СSS и JavaScript кода. Его цель — повысить качество кода и облегчить совместную работу и поддержку инфраструктуры.

От переводчика

Приветствую всех снова, меня зовут Максим Иванов, и сегодня я подготовил перевод, который, возможно, окажется для вас полезным. Бенджамин Де Кук (Benjamin De Cock), разработчик из Бельгии, собрал некоторые указания по оформлению кода, которые позиционируют себя как лучшие практики по написанию HTML, CSS, JS. Конечно, существует множество рекомендаций, например, есть хороший гайдлайн от Google, наверное, есть еще что-то, однако, если следовать хотя бы некоторым из них, то можно надеяться, что ваш код станет лучше. В отдельных случаях следование этим гайдлайнам не полезно, а совсем наоборот. В общем и целом, все зависит от вашего опыта и виденья дела, если вы новичок, то скорее вам будет полезно оценить то, что пишут другие и в обществе считается верным, если вы гуру, то наверное вам и не нужны гайдлайны, которые написаны непонятно кем на ваш взгляд. Итак, приступим.


HTML


  1. Семантика
  2. Лаконичность
  3. Читабельность
  4. Язык документа
  5. Эффективность


CSS


  1. Точка с запятой
  2. Блочная модель
  3. Потоки
  4. Позиционирование
  5. Селекторы
  6. Специфичность
  7. Переопределения
  8. Наследование
  9. Лаконичность
  10. Англоязычные обозначения
  11. Вендорные префиксы
  12. Анимация
  13. Единицы измерения
  14. Цветовая модель
  15. Отрисовка
  16. Хаки


JavaScript


  1. Производительность
  2. Независимость от внешнего состояния
  3. Нативность
  4. Принудительное сравнение
  5. Циклы
  6. Аргументы
  7. Использование метода apply
  8. Использование метода bind
  9. Методы высшего порядка
  10. Композиции
  11. Кэширование
  12. Переменные
  13. Условия
  14. Итератор
  15. Объект как ассоциативный массив
  16. Каррирование
  17. Читабельность
  18. Повторное использование кода
  19. Зависимости


HTML


Семантика


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

<!-- плохо -->
<div id="main">
  <div class="article">
    <div class="header">
      <h1>Blog post</h1>
      <p>Published: <span>21st Feb, 2015</span></p>
    </div>
    <p>…</p>
  </div>
</div>

<!-- хорошо -->
<main>
  <article>
    <header>
      <h1>Blog post</h1>
      <p>Published: <time datetime="2015-02-21">21st Feb, 2015</time></p>
    </header>
    <p>…</p>
  </article>
</main>


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

<!-- плохо -->
<h1>
  <figure>
    <img alt=Company src=logo.png>
  </figure>
</h1>

<!-- хорошо -->
<h1>
  <img alt=Company src=logo.png>
</h1>


Лаконичность


Старайтесь кратко писать код. Забудьте о своих старых привычках, перетекающих из XHTML.

<!-- плохо -->
<!doctype html>
<html lang=en>
  <head>
    <meta http-equiv=Content-Type content="text/html; charset=utf-8" />
    <title>Contact</title>
    <link rel=stylesheet href=style.css type=text/css />
  </head>
  <body>
    <h1>Contact me</h1>
    <label>
      Email address:
      <input type=email placeholder=you@email.com required=required />
    </label>
    <script src=main.js type=text/javascript></script>
  </body>
</html>

<!-- хорошо (но не семантично) -->
<!doctype html>
<html lang=en>
  <meta charset=utf-8>
  <title>Contact</title>
  <link rel=stylesheet href=style.css>

  <h1>Contact me</h1>
  <label>
    Email address:
    <input type=email placeholder=you@email.com required>
  </label>
  <script src=main.js></script>
</html>


Читабельность


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

— использовать атрибут alt правильно
— связывайте имена (links) и элементы (buttons) и называйте их по смыслу (<div class=button> — это зверство)
— не полагайтесь на название цветов, в качестве информации
— явно связывайте определенной меткой и элемент формы

<!-- плохо -->
<h1><img alt="Logo" src="logo.png"></h1>

<!-- хорошо -->
<h1><img alt="My Company, Inc." src="logo.png"></h1>


Язык документа


Определении языка и кодировки символов не является обязательным, однако рекомендуется всегда объявлять их на уровне документа, даже если они указаны в заголовках HTTP. Приоритетной кодировкой символов является UTF-8.

<!-- плохо -->
<!doctype html>
<title>Привет, мир.</title>

<!-- хорошо -->
<!doctype html>
<html lang=ru>
  <meta charset=utf-8>
  <title>Привет, мир.</title>
</html>


Эффективность


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

<!-- плохо -->
<!doctype html>
<meta charset=utf-8>
<script src=analytics.js></script>
<title>Hello, world.</title>
<p>...</p>

<!-- хорошо -->
<!doctype html>
<meta charset=utf-8>
<title>Hello, world.</title>
<p>...</p>
<script src=analytics.js></script>


CSS


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


В то время как точка с запятой является чисто технически разделителем правил, CSS всегда относится к точке запятой, как к оператору завершения.

/* плохо */
div {
  color: red
}

/* хорошо */
div {
  color: red;
}


Box model


Блочная модель, в идеале, должно быть одинаковой для всего документа. Глобальное правило * { box-sizing: border-box; } это нормально, однако нужно помнить, что вы не можете изменить блочную модель у специфичных элементов.

/* плохо */
div {
  width: 100%;
  padding: 10px;
  box-sizing: border-box;
}

/* хорошо */
div {
  padding: 10px;
}


Потоки


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

/* плохо */
img {
  display: block;
}

/* хорошо */
img {
  vertical-align: middle;
}


Аналогично, не вытаскивайте из основного потока элементы, если вы можете обойтись без этого.

/* плохо */
div {
  width: 100px;
  position: absolute;
  right: 0;
}


/* хорошо */
div {
  width: 100px;
  margin-left: auto;
}


Позиционирование


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

display: block;
display: flex;
position: relative;
position: sticky;
position: absolute;
position: fixed;


Селекторы


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

/* плохо */
div:first-of-type :last-child > p ~ *

/* хорошо */
div:first-of-type .info


Избегайте перегрузку селекторов.

/* плохо */
img[src$=svg], ul > li:first-child {
  opacity: 0;
}

/* хорошо */
[src$=svg], ul > :first-child {
  opacity: 0;
}


Специфичность


Не используйте значения и селекторы, которые нельзя переопределить. Минимизируйте использование id (идентификаторов) и избегайте правило !important

/* плохо */
.bar {
  color: green !important;
}
.foo {
  color: red;
}

/* хорошо */
.foo {
  color: red;
}
.foo.bar {
  color: green;
}


Переопределения


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

/* плохо */
li {
  visibility: hidden;
}
li:first-child {
  visibility: visible;
}

/* хорошо */
li + li {
  visibility: hidden;
}


Наследование


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

/* плохо */
div h1, div p {
  text-shadow: 0 1px 0 #fff;
}

/* хорошо */
div {
  text-shadow: 0 1px 0 #fff;
}


Лаконичность


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

/* плохо */
div {
  transition: all 1s;
  top: 50%;
  margin-top: -10px;
  padding-top: 5px;
  padding-right: 10px;
  padding-bottom: 20px;
  padding-left: 10px;
}

/* хорошо */
div {
  transition: 1s;
  top: calc(50% - 10px);
  padding: 5px 10px 20px;
}


Англоязычные обозначения


Предпочтительно использовать английскую математику.

/* плохо */
:nth-child(2n + 1) {
  transform: rotate(360deg);
}

/* хорошо */
:nth-child(odd) {
  transform: rotate(1turn);
}


Вендорные префиксы


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

/* плохо */
div {
  transform: scale(2);
  -webkit-transform: scale(2);
  -moz-transform: scale(2);
  -ms-transform: scale(2);
  transition: 1s;
  -webkit-transition: 1s;
  -moz-transition: 1s;
  -ms-transition: 1s;
}

/* хорошо */
div {
  -webkit-transform: scale(2);
  transform: scale(2);
  transition: 1s;
}


Анимация


Иногда предпочтительнее использовать переходы (transitions), нежели анимацию (animations). Избегайте анимирование свойств в пользу opacity и transform.

/* плохо */
div:hover {
  animation: move 1s forwards;
}
@keyframes move {
  100% {
    margin-left: 100px;
  }
}

/* хорошо */
div:hover {
  transition: 1s;
  transform: translateX(100px);
}


Единицы измерения


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

/* плохо */
div {
  margin: 0px;
  font-size: .9em;
  line-height: 22px;
  transition: 500ms;
}

/* хорошо */
div {
  margin: 0;
  font-size: .9rem;
  line-height: 1.5;
  transition: .5s;
}


Цветовая модель


Если нужна прозрачность, использовать rgba. В противном случае, всегда используйте шестнадцатеричный формат.

/* плохо */
div {
  color: hsl(103, 54%, 43%);
}

/* хорошо */
div {
  color: #5a3;
}


Отрисовка


Избегайте http-запросов, если ресурс легко воспроизводится с помощью CSS.

/* плохо */
div::before {
  content: url(white-circle.svg);
}

/* хорошо */
div::before {
  content: "";
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
}


Хаки


Не используйте их.

/* плохо */
div {
  // position: relative;
  transform: translateZ(0);
}

/* хорошо */
div {
  /* position: relative; */
  will-change: transform;
}


JavaScript


Производительность


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

/* плохо (хотя и намного быстрее) */
const arr = [1, 2, 3, 4];
const len = arr.length;
var i = -1;
var result = [];
while (++i < len) {
  var n = arr[i];
  if (n % 2 > 0) continue;
  result.push(n * n); 
}

/* хорошо */
const arr = [1, 2, 3, 4];
const isEven = n => n % 2 == 0; // инициализация анонимной стрелочной функции
const square = n => n * n; // синтаксис: (param1, param2, …, paramN) => expression

const result = arr.filter(isEven).map(square);



К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Стрелочные функции и перебирающие методы массивов

Независимость от внешнего состояния


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

/* плохо */
const merge = (target, ...sources) => Object.assign(target, ...sources);
merge({ foo: "foo" }, { bar: "bar" }); // => { foo: "foo", bar: "bar" }

/* хорошо */
const merge = (...sources) => Object.assign({}, ...sources);
merge({ foo: "foo" }, { bar: "bar" }); // => { foo: "foo", bar: "bar" }



К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Остаточные параметры

Нативность


Полагайтесь на стандартные методы, как можно больше.

/* плохо */
const toArray = obj => [].slice.call(obj);

/* хорошо */
const toArray = (() =>
  Array.from ? Array.from : obj => [].slice.call(obj)
)();


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Массивоподобные объекты

Принудительное сравнение


Лишние сравнения не имеют смысл, избегайте их.

/* плохо */
if (x === undefined || x === null) { /* ... */  }

/* хорошо */
if (x == undefined) { /* ... */  }


К прочтению:
1. Сравнение с null и undefined

Циклы


Не используйте циклы, так как они заставляют вас использовать изменяемые объекты. Полагайтесь на методы Array.prototype.

/* плохо */
const sum = arr => {
  var sum = 0;
  var i = -1;
  for (;arr[++i];) {
    sum += arr[i];
  }
  return sum;
};

sum([1, 2, 3]); // => 6

/* хорошо */
const sum = arr => arr.reduce((x, y) => x + y);

sum([1, 2, 3]); // => 6


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Суммирование всех значений в массиве

Если вы не можете использовать методы Array.prototype, используйте рекурсию.

/* плохо */
const createDivs = howMany => {
  while (howMany--) {
    document.body.insertAdjacentHTML("beforeend", "<div></div>");
  }
};

createDivs(5);

/* плохо */
const createDivs = howMany =>
  [...Array(howMany)].forEach(() =>
    document.body.insertAdjacentHTML("beforeend", "<div></div>")
  );

createDivs(5);


/* хорошо */
const createDivs = howMany => {
  if (!howMany) return;
  document.body.insertAdjacentHTML("beforeend", "<div></div>");
  return createDivs(howMany - 1);
};

createDivs(5);


Вот общая функция Loop, делающая рекурсию проще в использовании.

К прочтению:
1. Babeljs (пример в ES5): плохо (или) vs хорошо
2. insertAdjacentHTML() работает быстрее, чем манипуляции с innerHTML()

Аргументы


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

/* плохо */
const sortNumbers = () =>
  Array.prototype.slice.call(arguments).sort();

/* хорошо */
const sortNumbers = (...numbers) => numbers.sort();


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Доступ к аргументам

Использование метода apply


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

const greet = (first, last) => `Hi ${first} ${last}`;
const person = ["John", "Doe"];

/* плохо */
greet.apply(null, person);

/* хорошо */
greet(...person);


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Оператор распространения

Использование метода bind


Не используйте bind(), когда есть более идиоматические подходы.

/* плохо */
["foo", "bar"].forEach(func.bind(this));

/* хорошо */
["foo", "bar"].forEach(func, this);


/* плохо */
const person = {
  first: "John",
  last: "Doe",
  greet() {
    const full = function() {
      return `${this.first} ${this.last}`;
    }.bind(this);
    return `Hello ${full()}`;
  }
}

/* хорошо */
const person = {
  first: "John",
  last: "Doe",
  greet() {
    const full = () => `${this.first} ${this.last}`;
    return `Hello ${full()}`;
  }
}


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. bind(), а также итераторы в JavaScript

Методы высшего порядка


Избегайте вложенных функций там где это не нужно.

/* плохо */
const result = [1, 2, 3].map(num => String(num)); // => ["1", "2", "3"]

/* хорошо */
const result = [1, 2, 3].map(String);  // Приведение всех элементов к типу string


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Отображение массива чисел с использованием функции

Композиции


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

const plus1 = a => a + 1;
const mult2 = a => a * 2;

/* плохо */
mult2(plus1(5)); // => 12

/* хорошо */
const pipeline = (...funcs) => val => funcs.reduce((a, b) => b(a), val);
const addThenMult = pipeline(plus1, mult2);

addThenMult(5); // => 12


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо

Кеширование


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

/* плохо */
const contains = (arr, value) =>
  Array.prototype.includes
    ? arr.includes(value)
    : arr.some(el => el === value);
contains(["foo", "bar"], "baz"); // => false

/* хорошо */
const contains = (() =>
  Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)();
contains(["foo", "bar"], "baz"); // => false


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Определяем, находится ли одна строка внутри другой, а также делаем проверку значений элементов массива

Переменные


Используйте const вместо let и let вместо var.

/* плохо */
var me = new Map();
me.set("name", "Ben").set("country", "Belgium");

/* хорошо */
const me = new Map();
me.set("name", "Ben").set("country", "Belgium");


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Константы, оператор let, и немного об ассоциативном массиве

Условия


Будьте привержены концепции IIFE's (немедленно выполняемый функтор), используйте возвращаемые значения в условиях (if, else if, else и switch).

/* плохо */
var grade, result = 50;
if (result < 50)
  grade = "bad";
else if (result < 90)
  grade = "good";
else
  grade = "excellent";
  
// grade = "good";

/* хорошо */
let result = 50;
const grade = (() => {
  if (result < 50)
    return "bad";
  if (result < 90)
    return "good";
  return "excellent";
})(); // grade = "good";


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо (или)
2. Немедленно вызываемые функции

Итераторы


Избегайте цикл перебора for...in

const shared = { foo: "foo" };
const obj = Object.create(shared, {
  bar: {
    value: "bar",
    enumerable: true
  }
});

/* плохо */
for (var prop in obj) {
  if (obj.hasOwnProperty(prop))
    console.log(prop); // => bar
}

/* хорошо */
Object.keys(obj).forEach(prop => console.log(prop));


Разница между циклом и методом в том, что цикл перечисляет свойства и из цепочки прототипов.

К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Использование hasOwnProperty для проверки существования свойства и обход свойств объекта

Объект как ассоциативный массив


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

/* плохо */
const me = {
  name: "Ben",
  age: 30
};
var meSize = Object.keys(me).length;
meSize; // => 2
me.country = "Belgium"; // можно 
meSize++;
meSize; // => 3

/* хорошо */
const me = new Map();
me.set("name", "Ben");
me.set("age", 30);
me.size; // => 2
me.set("country", "Belgium");
me.size; // => 3


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Коллекция для хранения записей вида ключ: значение

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


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

/* плохо */
const sum = a => b => a + b;
sum(5)(3); // => 8

/* хорошо */
const sum = (a, b) => a + b;
sum(5, 3); // => 8


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Каррирование и примеры

Читабельность


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

/* плохо */
foo || doSomething();

/* плохо */
void function() { /* IIFE */ }();

/* плохо */
const n = ~~3.14;

/* хорошо */
if (!foo) doSomething();

/* хорошо */
(function() { /* IIFE */ }());

/* хорошо */
const n = Math.floor(3.14);


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

Повторное использование кода


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

/* плохо */
let last = arr[arr.length - 1];

/* плохо */
const product = (a, b) => a * b;
const triple = n => n * 3;

/* хорошо */
const first = arr => arr[0]; // => first(arr);
const last = arr => first(arr.slice(-1)); // => last(arr);

/* хорошо */
const product = (a, b) => a * b;
const triple = product.bind(null, 3);


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо

Зависимости


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

/* плохо */
var _ = require("underscore");
_.compact(["foo", 0]));
_.unique(["foo", "foo"]);
_.union(["foo"], ["bar"], ["foo"]);

/* хорошо */
const compact = arr => arr.filter(el => el);
const unique = arr => [...Set(arr)];
const union = (...arr) => unique([].concat(...arr));

compact(["foo", 0]);
unique(["foo", "foo"]);
union(["foo"], ["bar"], ["foo"]);


К прочтению:
1. Babeljs (пример в ES5): плохо vs хорошо
2. Путь JavaScript модуля и документация по библиотеке Underscore

Комментарии (38)


  1. seokirill
    05.02.2016 09:18

    комментарии сплошь отрицательные, а статья в огромном плюсе. странно сие


  1. QtRoS
    05.02.2016 10:53
    -1

    Кто-нибудь может объяснить, почему первый пример к Который этому правилу помечен как «плохо»?!


  1. friday
    05.02.2016 20:07
    +1

    Меня умиляет манера изложения: «это — хорошо, это — плохо. dixi». Объяснить, аргументировать, почему так, а не так — не, зачем.


  1. shoorick
    06.02.2016 00:19

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

    Ну да, конечно.
    Допустим, в HTTP-заголовке идёт одна кодировка, в документе указана другая — такое бывает, видел много раз. Внимание, вопрос: в какой кодировке клиент увидит страницу?

    Мне кажется, указывать кодировку достаточно один раз — в HTTP-заголовке.