Введение

«Запах кода — это поверхностный признак, который обычно соответствует более глубокой проблеме в системе». — Мартин Фаулер

«Плохой запах кода может быть индикатором факторов, которые способствуют техническому долгу». - Роберт С. Мартин

Определения Мартина Фаулера и Роберта С. Мартина дополняют друг друга, потому что определение Мартина Фаулера указывает на ключ к проблеме с программным обеспечением, а определение Роберта Мартина относится к побочному эффекту, вызванному запахами кода.

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

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

Сделайте это понятным

Выбирайте понятные и короткие имена для переменных и функций.

Неверные имена переменных

theproduct, src, rslt

Лучшие имена переменных

product, source, result

Также неправильные имена переменных

countFromOneToTen, createNewMemberIfNotExists

Избегайте описания значения именем вашей переменной или функции.

Может не иметь смысла в некоторых странах

isOverEighteen()

Теперь это понятно каждому

isLegalAge()

Ваш код — это история — сделайте так, чтобы за вашей сюжетной линией было легко следить!

Избегайте глобальных переменных

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

Плохо

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

var current = null;
var labels = {
'index':'index',
'about':'about',
'contact':'contact'
};
function init(){
};
function show(){
current = 1;
};
function hide(){
show();
};

Повтор имени модуля, другой синтаксис для внутренних функций.

module = function(){
var labels = {
'index':'index',
'about':'about',
'contact':'contact'
};
return {
current:null,
init:function() { },
show:function() { module.current = 1; },
hide:function() { module.show(); }
} }();

Хорошо

Гораздо более последовательно, не так ли? Увидеть разницу.

module = function(){
var current = null;
var labels = {
'home':'home',
'articles':'articles',
'contact':'contact'
};
var init = function(){ };
var show = function(){ current = 1; };
var hide = function(){ show(); }
return{init:init, show:show, current:current}
}();
module.init();

Придерживайтесь строгого стиля кодирования

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

Подтвердите свой код: http://www.jslint.com/

Комментируйте столько, сколько нужно, но не больше

«Хороший код сам себя объясняет» — высокомерная фраза.
Комментируйте то, что вам нужно — но вам не обязательно писать свою историю жизни.

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

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

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

Думай просто

Пример

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

Плохо

var form = document.getElementById('mainform');
var inputs = form.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' && inputs.value === ''){
inputs[i].style.borderColor = '#f00';
inputs[i].style.borderStyle = 'thin';
inputs[i].style.borderWidth = '2px';
}
}

Хорошо
Людям не нужно менять код JavaScript, чтобы изменить дизайн.

var form = document.getElementById('mainform');
var inputs = form.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' && inputs.value === '') { inputs[i].className+=' error'; }
}

Используйте сокращенные обозначения

Сокращенные обозначения облегчают чтение кода

Плохо
var direction;
if(x>100) {direction=1;} else {direction = -1;}

Хорошо
var direction; (x>100) ? 1:-1;

Плохо
if(isValid) {gotoLogin()}

Хорошо
isValid && gotoLogin();

Оба приведенных выше выражения работают одинаково. Только когда isValid истинно, gotoLogin() функция будет выполнена.

Улучшенная настройка и перевод

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

Например:

carousel = function(){
var config = {
CSS:{
classes:{ current:'current', scrollContainer:'scroll' },
IDs:{ maincontainer:'carousel' }
},
labels:{
previous:'back',
next:'next',
auto:'play'
},
settings:{
amount:5,
skin:'blue',
autoplay:false }, };
function init(){ };
function scroll(){ };
function highlight(){ };
return {config:config,init:init}
}();

Не доверяйте никаким данным

Хороший код не доверяет никаким данным.

Не верьте HTML-документу

Любой пользователь может изменить его для своих целей.

Не верьте, что данные, поступающие в вашу функцию, имеют правильный формат.

Протестируйте с помощью Type Of, а затем сделайте с ним что-нибудь.

Не ожидайте, что элементы в DOM будут доступны.

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

Никогда не используйте JavaScript для защиты чего-либо.

JavaScript так же легко взломать, как и закодировать :)

Оптимизировать циклы

Циклы иногда могут быть ужасно медленными в JavaScript.
На самом деле большую часть времени это происходит потому, что вы делаете в них что-то бессмысленное.
Не заставляйте JavaScript считывать длину массива на каждой итерации цикла for. Сохраните значение длины в другой переменной.Держите код, требующий больших вычислений, вне циклов. Это включает в себя регулярные выражения, но в первую очередь манипулирование DOM.
Вы можете создавать узлы DOM в цикле, но не вставлять их в документ.

Используйте Javascript для функциональности, а не контента

Если вы создаете много HTML-кода в JavaScript, возможно, вы делаете что-то не так.

Создавать с помощью DOM неудобно, использовать innerHTML рискованно (ошибка IE Operation Aborted), и трудно отслеживать качество создаваемого вами HTML.

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

Использовать === Сравнение

При использовании === ваши переменные будут преобразованы в соответствующие типы.

0 == ""; true
0 === ""; //false

Оператор вызывает сравнение значений и типов.

Не объявляйте ненужные переменные

Больше переменных означает больше места в памяти, и это не очень хорошо.

Например, вместо того, чтобы писать это:

const sidebar = sidebar.querySelector(#sidebar);
const paragraph = foo.querySelector('p');

paragraph.textContent = 'foo';

Вы можете написать это:

document.querySelector('#sidebar p').textContent = 'foo';

В первом случае у нас было две переменные. Теперь у нас нет переменных.

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

This makes sure you have a single default export. 
Bad
import * as Foo from './Foo';
 
Good
import Foo from './Foo';

Старайтесь избегать ненужных троичных операторов

Плохо

const bo = a ? a : b;

Хорошо

const bo = a || b;

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

Когда вы вызываете функцию и забываете передать ей параметр.

function logNumber(num) {
if (num === undefined) {
num = 25;
}
console.log(num);
}
номер_журнала();

ES6 представил параметры по умолчанию для функции. Ты можешь это сделать:

function logNumber(num = 25) {
console.log(num);
}
logNumber();

Это хорошая привычка — присваивать аргументам значения по умолчанию.

Не используйте новый объект()

Использовать {} вместо new Object()
Использовать "" вместо new String()
Использовать 0 вместо new Number()
Использовать false вместо new Boolean()
Использовать [] вместо new Array()
Использовать /()/ вместо new RegExp()
Использовать function (){} вместо из new Function()

Преимущества:

Код короче и читабельнее
Код безопаснее. Литералы по-прежнему будут работать после переопределения конструкторов Object
Это немного быстрее.

Используйте логическое или

Логическое ИЛИ (||) оператор вернет true если хотя бы один из операндов true.

const arg1 = null;
let arg2
if(arg1) {
arg2 = arg1;
}
else {
arg2 = «Default»;< br /> }
// используем это
const arg2 = arg1 || "Дефолт"

Итак, && возвращает первое ложное значение, а || возвращает первое истинное значение. Обратите внимание, что использование логической цепочки операторов может привести к проблемам с читабельностью в некоторых сценариях.

Если операторы

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

Проблема

if(value === 'duck' || value === 'dog' || value === 'cat') {
  // ...
}

Решение

const options = ['duck', 'dog', 'cat'];
if (options.includes(value)) {
  // ...
}

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

Ранний выход

Есть дюжина способов назвать этот принцип, но я выбрал название «Ранний выход».

function handleEvent(event) {
  if (event) {
    const target = event.target;
    if (target) {
      // Your awesome piece of code that uses target
    }
  }
}

Здесь мы пытаемся проверить, не является ли объект event ложным и доступно ли свойство target . Теперь проблема в том, что мы уже используем 2 оператора if .
Давайте посмотрим, как вы можете сделать здесь «ранний выход».

function handleEvent(event) {
  if (!event || !event.target) {
    return;
  }
  // Your awesome piece of code that uses target
}

Применяя здесь «ранний выход», вы проверяете, не являются ли event и event.target ложными. Сразу видно, что мы уверены, что event.target не ложно. Также ясно, что никакой другой код не выполняется, если это утверждение ложно.

Уникальные значения

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

const numbers = [1, 2, 3, 4, 5, 6, 2, 7, 4]
const uniqueNumbers = Array.from(new Set(numbers)) 
console.log(uniqueNumbers) // [1, 2, 3, 4, 5, 6, 7]

Причина, по которой это работает, заключается в том, что значения в объекте Set могут встречаться только один раз. Это означает, что набор работает только с уникальными значениями. Все, что мы делаем с помощью этого маленького трюка, — это приводим наш первоначальный массив к Set, а затем преобразуем его обратно в массив. Таким образом, повторяющиеся значения будут отфильтрованы.

Подсчет значений

Допустим, у нас есть массив, содержащий заказы, которые выглядят так:

const orders = [
{ число: 78, ндс: 12, всего: 101, },
{ число: 83, ндс: 5, всего: 40, },
{ число: 84 , ндс: 18, всего: 67, }
]

Мы хотим знать, какова общая сумма всех заказов, чтобы знать, какой у нас доход. Один из способов сделать это — использовать цикл for.

let total = for(let i = 0; i ‹ orders.length; i++) {
total += orders[i].total
}

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

orders.reduce((acc, cur) => acc + cur.total, 0) // 208

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

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

Последний трюк, который мы рассмотрим в этой статье, касается литералов шаблонов. Литералы шаблонов позволяют элегантно объединять строки. Таким образом, вам больше не придется объединять строки с помощью знака плюс.

Вероятно, вы объединяли строки с помощью знака «плюс», который выглядит следующим образом:

const first = "John"
const last = "Doe" const full = first + " " + last

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

const full = first + " " + lastconst full = `${first} ${last}`

Не навязывайте необходимость запоминания контекста переменной

Плохо

const products = ["T-Shirt", "Shoes", "Jackets", "Gloves"];
products.forEach(p => {
  doSomething();
  doSomethingElse();
  // ...
  // ...
  // What is `p` for?
  Add(p);
});

Хорошо

const products = ["T-Shirt", "Shoes", "Jackets", "Gloves"];
products.forEach(product => {
  doSomething();
  doSomethingElse();
  // ...
  // ...
  // ...
  register(product);
});

Используйте ‘Object.assign’ для установки объектов по умолчанию

Плохо

const shapeConfig = {
  type: "circle",
  width: 150,
  height: null
};
function createShape(config) {
  config.type = config.type || "circle";
  config.width = config.width || 300;
  config.height = config.height || 300;
}
createShape(shapeConfig);

Хорошо

const shapeConfig = {
  type: "circle",
  width: 200
  // Exclude the 'height' key
};
function createShape(config) {
  config = Object.assign(
    {
      type: "circle",
      width: 400,
      height: 400
    },
    config
  );
  ...
}
  
createShape(shapeConfig);

Никогда не используйте var вместо let

Простое использование let поможет решить любые проблемы с областью действия, которые var вызывает в JavaScript.

Соглашения об именах в JavaScript

let должен быть верблюжий регистр
const если в верхней части файла используйте заглавный регистр змеи. MY_CONST. Если не в начале файла используется camelCase
class должно быть PascalCasing. MyClass
functions должно быть camelCase . MyFunction

Используйте стрелочные функции ES6, где это возможно

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

Ошибка(-и):
varmultit = function(a, b) {
return a* b;
};

Pass: 
const multiply = (a, b) => { return a * b};

Имена файлов в нижнем регистре

MyFile.js должно быть myFile.js

Использование массива. каждый

Что, если вам дадут набор питомцев и попросят проверить, есть ли у всех питомцев четыре ноги или нет?
Как правило, мы делаем следующее:

const pets = [
{ имя: 'кошка', nLegs: 4}, { имя: 'змея', nLegs: 0}, { имя: 'собака', nLegs: 4 },
{ имя : 'bird', nLegs: 2 } ];
const check = (pets) =› {
for (let i = 0; i ‹ pets.length; i++) {
if ( pets[i].nLegs != 4) {
return false;
}
}
return true;
}
check(pets); // ложный

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

пусть areAllFourLegs = pets.every(p => p.nLegs === 4);

Вспомогательные функции

Мы также получаем операторы else, которые напрямую не приводят к ошибке return. Обычно это происходит из-за некоторой логики особого случая, которая не изолирована должным образом. Например

let charities
  if (country != "") {
    if (tier != "") {
      charities = getCharitiesByCampaignCountryAndTier(campaign, country, tier)
    } else {
      charities = getCharitiesByCampaignAndCountry(campaign, country)
    }
  } else {
    charities = getCharitiesByCampaign(campaign)
  } 
// Do something with charities

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

function getCharities(campaign, country, tier) {
  if (country == "") {
    return getCharitiesByCampaign(campaign)
  }
  if (tier == "") {
    return getCharitiesByCampaignAndCountry(campaign, country)
  }
  return getCharitiesByCampaignCountryAndTier(campaign, country, tier)
}

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

Избегайте чрезмерной вложенности

Код становится нечитаемым после определенного уровня вложенности.
Очень плохая идея — вкладывать циклы в циклы, так как это также означает заботу о нескольких переменных итератора (i,j,k,l,m…).

function renderProfiles(o){
   var out = document.getElementById('profiles');
   for(var i=0;i<o.members.length;i++){
      var ul = document.createElement('ul');
      var li = document.createElement('li');
      li.appendChild(document.createTextNode(o.members[i].name));
      var nestedul = document.createElement('ul');
      for(var j=0;j<o.members[i].data.length;j++){
         var datali = document.createElement('li');
         datali.appendChild(
            document.createTextNode(
               o.members[i].data[j].label + ' ' +
               o.members[i].data[j].value
            )
         );
         nestedul.appendChild(detali);
      }
      li.appendChild(nestedul);
   }
   out.appendChild(ul);
}

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

function renderProfiles(o){
   var out = document.getElementById('profiles');
   for(var i=0;i<o.members.length;i++){
      var ul = document.createElement('ul');
      var li = document.createElement('li');
      li.appendChild(document.createTextNode(data.members[i].name));
      li.appendChild(addMemberData(o.members[i]));
   }
   out.appendChild(ul);
}
function addMemberData(member){
   var ul = document.createElement('ul');
   for(var i=0;i<member.data.length;i++){
      var li = document.createElement('li');
      li.appendChild(
         document.createTextNode(
            member.data[i].label + ' ' +
            member.data[i].value
         )
      );
   }
   ul.appendChild(li);
   return ul;
}

Подумайте о плохих редакторах и маленьких экранах.

Используйте цепочку методов

Многие библиотеки, такие как Jquery и Lodash, используют эту технику. Если у вас есть метод, который принимает что-то вроде 10 параметров, лучше использовать цепочку методов.

Плохо

class Product {
  
  constructor(name) {
    this.name = name;
  }
  
  setPrice(price) {
    this.price = price;
  }
  
  setUnits(units) {
    this.units = units;
  }
  
  save() {
    console.log(this.name, this.price, this.units);
  }
}
const product = new Product("Bag");
product.setPrice(23.99);
product.setUnits(12);
product.save();

Хорошо

class Product {
  
  constructor(name) {
    this.name = name;
  }
  
  setName(name) {
    this.name = name;
    // Return this for chaining
    return this;
  }
  
  setPrice(price) {
    this.price = price;
    // Return this for chaining
    return this;
  }
  
  save() {
    console.log(this.name, this.price, this.units);
    // Return this for chaining
    return this;
  }
}
const product = new Product("T-Shirt")
    .setName("Jeans")
    .setAge(31.99)
    .save();

Use Prettier 
Prettier is an opinionated code formatter.
It enforces a consistent style by parsing your code and re-printing it with its own rules.
https://prettier.io/

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

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

Плохо:

// What 12300000 is for?
setTimeout(blastOff, 12300000);

Хорошо:

// Give a meaningful names them
const MILLISECONDS_IN_A_DAY = 60 * 60 * 24 * 1000; //86400000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

Функции должны делать одно дело

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

Плохо:

function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

Хорошо:

function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

Избегайте проверки типов

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

Плохо:

function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location("texas"));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location("texas"));
  }
}

Хорошо:

function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}