Введение

Принципы разработки программного обеспечения из книги Роберта С. Мартина Чистый код, адаптированные для JavaScript. Это не руководство по стилю. Это руководство по созданию читабельного, многократно используемого и рефакторингового программного обеспечения на JavaScript.

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

Нашему ремеслу разработки программного обеспечения чуть более 50 лет, и мы все еще многому учимся. Когда архитектура программного обеспечения будет такой же старой, как и сама архитектура, возможно, тогда у нас будут более жесткие правила, которым нужно следовать. А пока пусть эти рекомендации послужат пробным камнем для оценки качества кода JavaScript, создаваемого вами и вашей командой.

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

Переменные

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

Плохой:

const yyyymmdstr = moment().format("YYYY/MM/DD");

Хороший:

const currentDate = moment().format("YYYY/MM/DD");

Используйте один и тот же словарь для одного и того же типа переменной

Плохой:

getUserInfo();
getClientData();
getCustomerRecord();

Хороший:

getUser();

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

Мы прочитаем больше кода, чем когда-либо напишем. Важно, чтобы код, который мы пишем, был доступен для чтения и поиска. Не называя переменные, которые в конечном итоге имеют значение для понимания нашей программы, мы вредим нашим читателям. Сделайте ваши имена доступными для поиска. Такие инструменты, как buddy.js и ESLint, могут помочь идентифицировать безымянные константы.

Плохой:

// What the heck is 86400000 for?
setTimeout(blastOff, 86400000);

Хороший:

// Declare them as capitalized named constants.
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;

setTimeout(blastOff, MILLISECONDS_PER_DAY);

Используйте объясняющие переменные

Плохой:

const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
  address.match(cityZipCodeRegex)[1],
  address.match(cityZipCodeRegex)[2]
);

Хороший:

const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);

Избегайте ментального картирования

Явное лучше неявного.

Плохой:

const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(l => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // Wait, what is `l` for again?
  dispatch(l);
});

Хороший:

const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(location => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch(location);
});

Не добавляйте ненужный контекст

Если имя вашего класса/объекта говорит вам о чем-то, не повторяйте это в имени вашей переменной.

Плохой:

const Car = {
  carMake: "Honda",
  carModel: "Accord",
  carColor: "Blue"
};

function paintCar(car, color) {
  car.carColor = color;
}

Хороший:

const Car = {
  make: "Honda",
  model: "Accord",
  color: "Blue"
};

function paintCar(car, color) {
  car.color = color;
}

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

Параметры по умолчанию часто чище, чем короткое замыкание. Имейте в виду, что если вы их используете, ваша функция будет предоставлять значения по умолчанию только для undefined аргументов. Другие «ложные» значения, такие как '', "", false, null, 0 и NaN, не будут заменены значением по умолчанию.

Плохой:

function createMicrobrewery(name) {
  const breweryName = name || "Hipster Brew Co.";
  // ...
}

Хороший:

function createMicrobrewery(name = "Hipster Brew Co.") {
  // ...
}

Функции

Аргументы функции (в идеале 2 или меньше)

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

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

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

Чтобы было очевидно, какие свойства ожидает функция, вы можете использовать синтаксис деструктурирования ES2015/ES6. Это имеет несколько преимуществ:

  1. Когда кто-то смотрит на сигнатуру функции, сразу становится ясно, какие свойства используются.
  2. Его можно использовать для имитации именованных параметров.
  3. Деструктуризация также клонирует указанные примитивные значения объекта-аргумента, переданного в функцию. Это может помочь предотвратить побочные эффекты. Примечание: объекты и массивы, деструктурированные из объекта-аргумента, НЕ клонируются.
  4. Линтеры могут предупреждать вас о неиспользуемых свойствах, что было бы невозможно без деструктурирования.

Плохой:

function createMenu(title, body, buttonText, cancellable) {
  // ...
}

createMenu("Foo", "Bar", "Baz", true);

Хороший:

function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});

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

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

Плохой:

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();
}

Имена функций должны говорить, что они делают

Плохой:

function addToDate(date, month) {
  // ...
}

const date = new Date();
// It's hard to tell from the function name what is added
addToDate(date, 1);

Хороший:

function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);

Функции должны быть только одного уровня абстракции

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

Плохой:

function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      // ...
    });
  });
  const ast = [];
  tokens.forEach(token => {
    // lex...
  });
  ast.forEach(node => {
    // parse...
  });
}

Хороший:

function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => {
    // parse...
  });
}

function tokenize(code) {
  const REGEXES = [
    // ...
  ];
  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      tokens.push(/* ... */);
    });
  });
  return tokens;
}
function parse(tokens) {
  const syntaxTree = [];
  tokens.forEach(token => {
    syntaxTree.push(/* ... */);
  });
  return syntaxTree;
}

Удалить повторяющийся код

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

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

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

Правильная абстракция имеет решающее значение, поэтому вы должны следовать принципам SOLID, изложенным в разделе Классы. Плохие абстракции могут быть хуже дублирующегося кода, так что будьте осторожны! Сказав это, если вы можете сделать хорошую абстракцию, сделайте это! Не повторяйтесь, иначе вы обнаружите, что обновляете несколько мест в любое время, когда захотите изменить что-то одно.

Плохой:

function showDeveloperList(developers) {
  developers.forEach(developer => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    const data = {
      expectedSalary,
      experience,
      githubLink
    };

    render(data);
  });
}
function showManagerList(managers) {
  managers.forEach(manager => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };
    render(data);
  });
}

Хороший:

function showEmployeeList(employees) {
  employees.forEach(employee => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();

    const data = {
      expectedSalary,
      experience
    };
    switch (employee.type) {
      case "manager":
        data.portfolio = employee.getMBAProjects();
        break;
      case "developer":
        data.githubLink = employee.getGithubLink();
        break;
    }
    render(data);
  });
}

Установите объекты по умолчанию с помощью Object.assign

Плохой:

const menuConfig = {
  title: null,
  body: "Bar",
  buttonText: null,
  cancellable: true
};
function createMenu(config) {
  config.title = config.title || "Foo";
  config.body = config.body || "Bar";
  config.buttonText = config.buttonText || "Baz";
  config.cancellable =
    config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);

Хороший:

const menuConfig = {
  title: "Order",
  // User did not include 'body' key
  buttonText: "Send",
  cancellable: true
};

function createMenu(config) {
  let finalConfig = Object.assign(
    {
      title: "Foo",
      body: "Bar",
      buttonText: "Baz",
      cancellable: true
    },
    config
  );
  return finalConfig
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}
createMenu(menuConfig);

Не используйте флаги в качестве параметров функции

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

Плохой:

function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

Хороший:

function createFile(name) {
  fs.create(name);
}
function createTempFile(name) {
  createFile(`./temp/${name}`);
}

Избегайте побочных эффектов (часть 1)

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

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

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

Плохой:

// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";

function splitIntoFirstAndLastName() {
  name = name.split(" ");
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];

Хороший:

function splitIntoFirstAndLastName(name) {
  return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

Избегайте побочных эффектов (часть 2)

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

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

Пользователь нажимает кнопку «Купить», которая вызывает функцию purchase, которая порождает сетевой запрос и отправляет массив cart на сервер. Из-за плохого сетевого подключения функция purchase вынуждена повторять запрос. А что, если тем временем пользователь случайно нажмет кнопку «Добавить в корзину» на товаре, который ему на самом деле не нужен, до того, как начнется сетевой запрос? Если это произойдет и начнется сетевой запрос, то эта функция покупки отправит случайно добавленный элемент, потому что массив cart был изменен.

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

Два предостережения, чтобы упомянуть об этом подходе:

  1. Могут быть случаи, когда вы действительно хотите изменить входной объект, но когда вы примете эту практику программирования, вы обнаружите, что такие случаи довольно редки. Большинство вещей можно изменить так, чтобы не было побочных эффектов!
  2. Клонирование больших объектов может быть очень затратным с точки зрения производительности. К счастью, на практике это не представляет большой проблемы, потому что существуют отличные библиотеки, которые позволяют использовать такой подход к программированию быстро и не так требовательно к памяти, как если бы вы вручную клонировали объекты и массивы.

Плохой:

const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

Хороший:

const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};

Не пишите в глобальные функции

Загрязнять глобальные переменные — плохая практика в JavaScript, потому что вы можете столкнуться с другой библиотекой, и пользователь вашего API ничего не смыслит, пока не получит исключение в рабочей среде. Давайте подумаем над примером: что, если бы вы захотели расширить родной метод JavaScript Array, добавив в него метод diff, который мог бы показывать разницу между двумя массивами? Вы можете написать свою новую функцию в Array.prototype, но она может конфликтовать с другой библиотекой, которая пытается сделать то же самое. Что, если эта другая библиотека просто использовала diff, чтобы найти разницу между первым и последним элементами массива? Вот почему было бы намного лучше просто использовать классы ES2015/ES6 и просто расширить Array global.

Плохой:

Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

Хороший:

class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

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

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

Плохой:

const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

let totalOutput = 0;
for (let i = 0; i < programmerOutput.length; i++) {
  totalOutput += programmerOutput[i].linesOfCode;
}

Хороший:

const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

const totalOutput = programmerOutput.reduce(
  (totalLines, output) => totalLines + output.linesOfCode,
  0
);

Инкапсулировать условные операторы

Плохой:

if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}

Хороший:

function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

Избегайте отрицательных условий

Плохой:

function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}

Хороший:

function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

Избегайте условных выражений

Это кажется невыполнимой задачей. Услышав это впервые, большинство людей говорят: «Как я могу что-то делать без утверждения if?» Ответ заключается в том, что во многих случаях вы можете использовать полиморфизм для решения одной и той же задачи. Второй вопрос обычно звучит так: «Ну, это здорово, но зачем мне это делать?» Ответ заключается в предыдущей концепции чистого кода, которую мы узнали: функция должна делать только одну вещь. Когда у вас есть классы и функции, содержащие if операторов, вы сообщаете своему пользователю, что ваша функция выполняет несколько действий. Помните, просто сделайте одну вещь.

Плохой:

class Airplane {
  // ...
  getCruisingAltitude() {
    switch (this.type) {
      case "777":
        return this.getMaxAltitude() - this.getPassengerCount();
      case "Air Force One":
        return this.getMaxAltitude();
      case "Cessna":
        return this.getMaxAltitude() - this.getFuelExpenditure();
    }
  }
}

Хороший:

class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getPassengerCount();
  }
}
class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude();
  }
}
class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getFuelExpenditure();
  }
}

Избегайте проверки типов (часть 1)

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

Плохой:

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"));
}

Избегайте проверки типов (часть 2)

Если вы работаете с базовыми примитивными значениями, такими как строки и целые числа, и не можете использовать полиморфизм, но все же чувствуете потребность в проверке типов, вам следует рассмотреть возможность использования TypeScript. Это отличная альтернатива обычному JavaScript, поскольку он предоставляет вам статическую типизацию поверх стандартного синтаксиса JavaScript. Проблема с ручной проверкой типов в обычном JavaScript заключается в том, что для того, чтобы сделать это хорошо, требуется так много лишних формулировок, что ложная «безопасность типов», которую вы получаете, не компенсирует потерю читабельности. Содержите свой JavaScript в чистоте, пишите хорошие тесты и проводите хорошие проверки кода. В противном случае сделайте все это, но с помощью TypeScript (который, как я уже сказал, является отличной альтернативой!).

Плохой:

function combine(val1, val2) {
  if (
    (typeof val1 === "number" && typeof val2 === "number") ||
    (typeof val1 === "string" && typeof val2 === "string")
  ) {
    return val1 + val2;
  }

  throw new Error("Must be of type String or Number");
}

Хороший:

function combine(val1, val2) {
  return val1 + val2;
}

Не переоптимизируйте

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

Плохой:

// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

Хороший:

for (let i = 0; i < list.length; i++) {
  // ...
}

Удалить мертвый код

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

Плохой:

function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

Хороший:

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

Объекты и структуры данных

Используйте геттеры и сеттеры

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

  • Если вы хотите сделать больше, чем получить свойство объекта, вам не нужно искать и изменять каждый метод доступа в вашей кодовой базе.
  • Упрощает добавление проверки при выполнении set.
  • Инкапсулирует внутреннее представление.
  • Легко добавить ведение журнала и обработку ошибок при получении и настройке.
  • Вы можете лениво загружать свойства вашего объекта, скажем, получать его с сервера.

Плохой:

function makeBankAccount() {
  // ...
return {
    balance: 0
    // ...
  };
}
const account = makeBankAccount();
account.balance = 100;

Хороший:

function makeBankAccount() {
  // this one is private
  let balance = 0;

 // a "getter", made public via the returned object below
  function getBalance() {
    return balance;
  }
  // a "setter", made public via the returned object below
  function setBalance(amount) {
    // ... validate before updating the balance
    balance = amount;
  }
  return {
    // ...
    getBalance,
    setBalance
  };
}
const account = makeBankAccount();
account.setBalance(100);

Сделать объекты закрытыми членами

Этого можно добиться с помощью замыканий (для ES5 и ниже).

Плохой:

const Employee = function(name) {
  this.name = name;
};

Employee.prototype.getName = function getName() {
  return this.name;
};
const employee = new Employee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined

Хороший:

function makeEmployee(name) {
  return {
    getName() {
      return name;
    }
  };
}

const employee = makeEmployee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe

Классы

Предпочитайте классы ES2015/ES6 простым функциям ES5.

Очень сложно получить удобочитаемое наследование классов, конструкции и определения методов для классических классов ES5. Если вам нужно наследование (и знайте, что может и не понадобиться), отдайте предпочтение классам ES2015/ES6. Однако отдавайте предпочтение небольшим функциям, а не классам, пока вам не понадобятся более крупные и сложные объекты.

Плохой:

const Animal = function(age) {
  if (!(this instanceof Animal)) {
    throw new Error("Instantiate Animal with `new`");
  }
  this.age = age;
};
Animal.prototype.move = function move() {};
const Mammal = function(age, furColor) {
  if (!(this instanceof Mammal)) {
    throw new Error("Instantiate Mammal with `new`");
  }
  Animal.call(this, age);
  this.furColor = furColor;
};
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};
const Human = function(age, furColor, languageSpoken) {
  if (!(this instanceof Human)) {
    throw new Error("Instantiate Human with `new`");
  }
  Mammal.call(this, age, furColor);
  this.languageSpoken = languageSpoken;
};
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};

Хороший:

class Animal {
  constructor(age) {
    this.age = age;
  }
  move() {
    /* ... */
  }
}
class Mammal extends Animal {
  constructor(age, furColor) {
    super(age);
    this.furColor = furColor;
  }
  liveBirth() {
    /* ... */
  }
}
class Human extends Mammal {
  constructor(age, furColor, languageSpoken) {
    super(age, furColor);
    this.languageSpoken = languageSpoken;
  }
  speak() {
    /* ... */
  }
}

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

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

Плохой:

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }
  setMake(make) {
    this.make = make;
  }
  setModel(model) {
    this.model = model;
  }
  setColor(color) {
    this.color = color;
  }
  save() {
    console.log(this.make, this.model, this.color);
  }
}
const car = new Car("Ford", "F-150", "red");
car.setColor("pink");
car.save();

Хороший:

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }
  setMake(make) {
    this.make = make;
    // NOTE: Returning this for chaining
    return this;
  }
  setModel(model) {
    this.model = model;
    // NOTE: Returning this for chaining
    return this;
  }
  setColor(color) {
    this.color = color;
    // NOTE: Returning this for chaining
    return this;
  }
  save() {
    console.log(this.make, this.model, this.color);
    // NOTE: Returning this for chaining
    return this;
  }
}
const car = new Car("Ford", "F-150", "red").setColor("pink").save();

Предпочитайте композицию наследованию

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

Вы можете задаться вопросом: «Когда мне следует использовать наследование?» Это зависит от вашей проблемы, но это достойный список случаев, когда наследование имеет больше смысла, чем композиция:

  1. Ваше наследование представляет собой отношение «является», а не отношение «имеет» (человек->животное против пользователя->детали пользователя).
  2. Вы можете повторно использовать код из базовых классов (люди могут двигаться, как и все животные).
  3. Вы хотите внести глобальные изменения в производные классы, изменив базовый класс. (Изменять расход калорий всех животных при движении).

Плохой:

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
// ...
}
// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }
  // ...
}

Хороший:

class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }
// ...
}
class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}

ТВЕРДЫЙ

Принцип единой ответственности (SRP)

Как сказано в «Чистом коде», «у класса никогда не должно быть более одной причины для изменения». Заманчиво набить класс большим количеством функций, например, когда вы можете взять только один чемодан на рейс. Проблема в том, что ваш класс не будет концептуально целостным, и это даст ему множество причин для изменений. Важно свести к минимуму количество раз, когда вам нужно изменить класс. Это важно, потому что, если в одном классе слишком много функций, и вы изменяете его часть, может быть трудно понять, как это повлияет на другие зависимые модули в вашей кодовой базе.

Плохой:

class UserSettings {
  constructor(user) {
    this.user = user;
  }
  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }
  verifyCredentials() {
    // ...
  }
}

Хороший:

class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}
class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }
  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}

Открытый/закрытый принцип (OCP)

Как заявил Бертран Мейер, «программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации». Что это значит? Этот принцип в основном гласит, что вы должны разрешить пользователям добавлять новые функции без изменения существующего кода.

Плохой:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
}
class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }
  fetch(url) {
    if (this.adapter.name === "ajaxAdapter") {
      return makeAjaxCall(url).then(response => {
        // transform response and return
      });
    } else if (this.adapter.name === "nodeAdapter") {
      return makeHttpCall(url).then(response => {
        // transform response and return
      });
    }
  }
}
function makeAjaxCall(url) {
  // request and return promise
}
function makeHttpCall(url) {
  // request and return promise
}

Хороший:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }

  request(url) {
    // request and return promise
  }
}
class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
  request(url) {
    // request and return promise
  }
}
class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }
  fetch(url) {
    return this.adapter.request(url).then(response => {
      // transform response and return
    });
  }
}

Принцип замещения Лисков (LSP)

Это пугающий термин для очень простой концепции. Формально он определяется так: «Если S является подтипом T, то объекты типа T могут быть заменены объектами типа S (т. е. объекты типа S могут заменять объекты типа T) без изменения каких-либо желаемых свойств этой программы. (правильность, выполненное задание и т.д.)». Это еще более страшное определение.

Лучшим объяснением этого является то, что если у вас есть родительский класс и дочерний класс, то базовый класс и дочерний класс можно использовать взаимозаменяемо без получения неправильных результатов. Это все еще может сбивать с толку, поэтому давайте взглянем на классический пример Square-Rectangle. Математически квадрат — это прямоугольник, но если вы моделируете его, используя отношение «есть-а» посредством наследования, вы быстро столкнетесь с проблемами.

Плохой:

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }
  render(area) {
    // ...
  }
  setWidth(width) {
    this.width = width;
  }
  setHeight(height) {
    this.height = height;
  }
  getArea() {
    return this.width * this.height;
  }
}
class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }
  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}
function renderLargeRectangles(rectangles) {
  rectangles.forEach(rectangle => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
    rectangle.render(area);
  });
}
const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

Хороший:

class Shape {
  setColor(color) {
    // ...
  }
  render(area) {
    // ...
  }
}
class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }
  getArea() {
    return this.width * this.height;
  }
}
class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }
  getArea() {
    return this.length * this.length;
  }
}
function renderLargeShapes(shapes) {
  shapes.forEach(shape => {
    const area = shape.getArea();
    shape.render(area);
  });
}
const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);

Принцип разделения интерфейсов (ISP)

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

Интернет-провайдер заявляет, что «клиенты не должны зависеть от интерфейсов, которые они не используют». Интерфейсы — это неявные контракты в JavaScript из-за утиной печати.

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

Плохой:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.settings.animationModule.setup();
  }
  traverse() {
    // ...
  }
}
const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  animationModule() {} // Most of the time, we won't need to animate when traversing.
  // ...
});

Хороший:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.options = settings.options;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }
  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }
  traverse() {
    // ...
  }
}
const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  options: {
    animationModule() {}
  }
});

Принцип инверсии зависимостей (DIP)

Этот принцип устанавливает две важные вещи:

  1. Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Сначала это может быть трудно понять, но если вы работали с AngularJS, вы видели реализацию этого принципа в виде внедрения зависимостей (DI). Хотя это не идентичные концепции, DIP не позволяет высокоуровневым модулям знать детали своих низкоуровневых модулей и настраивать их. Это можно сделать через DI. Огромным преимуществом этого является то, что он уменьшает связь между модулями. Связывание — очень плохой шаблон разработки, потому что он затрудняет рефакторинг вашего кода.

Как указывалось ранее, в JavaScript нет интерфейсов, поэтому абстракции, от которых зависят, — это неявные контракты. То есть методы и свойства, которые объект/класс предоставляет другому объекту/классу. В приведенном ниже примере неявный контракт заключается в том, что любой модуль запроса для InventoryTracker будет иметь метод requestItems.

Плохой:

class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

requestItem(item) {
    // ...
  }
}
class InventoryTracker {
  constructor(items) {
    this.items = items;
    // BAD: We have created a dependency on a specific request implementation.
    // We should just have requestItems depend on a request method: `request`
    this.requester = new InventoryRequester();
  }
  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}
const inventoryTracker = new InventoryTracker(["apples", "bananas"]);
inventoryTracker.requestItems();

Хороший:

class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}
class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }
  requestItem(item) {
    // ...
  }
}
class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ["WS"];
  }
  requestItem(item) {
    // ...
  }
}
// By constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses WebSockets.
const inventoryTracker = new InventoryTracker(
  ["apples", "bananas"],
  new InventoryRequesterV2()
);
inventoryTracker.requestItems();

Тестирование

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

Нет никаких оправданий тому, чтобы не писать тесты. Существует множество хороших фреймворков для тестирования JS, так что найдите тот, который предпочитает ваша команда. Когда вы найдете тот, который работает для вашей команды, старайтесь всегда писать тесты для каждой новой функции/модуля, которую вы вводите. Если вы предпочитаете метод разработки через тестирование (TDD), это прекрасно, но главное — просто убедиться, что вы достигли своих целей охвата, прежде чем запускать какую-либо функцию или проводить рефакторинг существующей.

Одна концепция на тест

Плохой:

import assert from "assert";

describe("MomentJS", () => {
  it("handles date boundaries", () => {
    let date;
    date = new MomentJS("1/1/2015");
    date.addDays(30);
    assert.equal("1/31/2015", date);
    date = new MomentJS("2/1/2016");
    date.addDays(28);
    assert.equal("02/29/2016", date);
    date = new MomentJS("2/1/2015");
    date.addDays(28);
    assert.equal("03/01/2015", date);
  });
});

Хороший:

import assert from "assert";

describe("MomentJS", () => {
  it("handles 30-day months", () => {
    const date = new MomentJS("1/1/2015");
    date.addDays(30);
    assert.equal("1/31/2015", date);
  });
  it("handles leap year", () => {
    const date = new MomentJS("2/1/2016");
    date.addDays(28);
    assert.equal("02/29/2016", date);
  });
  it("handles non-leap year", () => {
    const date = new MomentJS("2/1/2015");
    date.addDays(28);
    assert.equal("03/01/2015", date);
  });
});

параллелизм

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

Обратные вызовы не являются чистыми и вызывают чрезмерную вложенность. В ES2015/ES6 обещания являются встроенным глобальным типом. Используй их!

Плохой:

import { get } from "request";
import { writeFile } from "fs";

get(
  "https://en.wikipedia.org/wiki/Robert_Cecil_Martin",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      writeFile("article.html", body, writeErr => {
        if (writeErr) {
          console.error(writeErr);
        } else {
          console.log("File written");
        }
      });
    }
  }
);

Хороший:

import { get } from "request-promise";
import { writeFile } from "fs-extra";

get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
  .then(body => {
    return writeFile("article.html", body);
  })
  .then(() => {
    console.log("File written");
  })
  .catch(err => {
    console.error(err);
  });

Async/Await даже чище, чем Promises

Обещания — очень чистая альтернатива обратным вызовам, но ES2017/ES8 предлагает асинхронность и ожидание, которые предлагают еще более чистое решение. Все, что вам нужно, это функция с префиксом в ключевом слове async, и тогда вы сможете императивно написать свою логику без цепочки функций then. Используйте это, если вы можете воспользоваться функциями ES2017/ES8 уже сегодня!

Плохой:

import { get } from "request-promise";
import { writeFile } from "fs-extra";

get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
  .then(body => {
    return writeFile("article.html", body);
  })
  .then(() => {
    console.log("File written");
  })
  .catch(err => {
    console.error(err);
  });

Хороший:

import { get } from "request-promise";
import { writeFile } from "fs-extra";

async function getCleanCodeArticle() {
  try {
    const body = await get(
      "https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
    );
    await writeFile("article.html", body);
    console.log("File written");
  } catch (err) {
    console.error(err);
  }
}
getCleanCodeArticle()

Обработка ошибок

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

Не игнорируйте пойманные ошибки

Если ничего не делать с пойманной ошибкой, это не дает вам возможности исправить или отреагировать на указанную ошибку. Регистрация ошибки в консоли (console.log) не намного лучше, так как часто она может потеряться в море вещей, напечатанных на консоли. Если вы заключаете какой-либо фрагмент кода в try/catch, это означает, что вы считаете, что там может произойти ошибка, и поэтому у вас должен быть план или создание пути кода на случай ее возникновения.

Плохой:

try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

Хороший:

try {
  functionThatMightThrow();
} catch (error) {
  // One option (more noisy than console.log):
  console.error(error);
  // Another option:
  notifyUserOfError(error);
  // Another option:
  reportErrorToService(error);
  // OR do all three!
}

Не игнорируйте отвергнутые обещания

По той же причине не следует игнорировать пойманные ошибки из try/catch.

Плохой:

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    console.log(error);
  });

Хороший:

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    // One option (more noisy than console.log):
    console.error(error);
    // Another option:
    notifyUserOfError(error);
    // Another option:
    reportErrorToService(error);
    // OR do all three!
  });

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

Форматирование субъективно. Как и во многих других правилах, здесь нет жестких и быстрых правил, которым вы должны следовать. Главное НЕ СПОРИТЬ по поводу форматирования. Есть тонны инструментов для автоматизации этого. Используйте один! Для инженеров спорить о форматировании — пустая трата времени и денег.

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

Используйте последовательную капитализацию

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

Плохой:

const DAYS_IN_WEEK = 7;
const daysInMonth = 30;

const songs = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const Artists = ["ACDC", "Led Zeppelin", "The Beatles"];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}

Хороший:

const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;

const SONGS = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const ARTISTS = ["ACDC", "Led Zeppelin", "The Beatles"];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}

Вызывающие и вызываемые функции должны быть близки

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

Плохой:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }
  lookupPeers() {
    return db.lookup(this.employee, "peers");
  }
  lookupManager() {
    return db.lookup(this.employee, "manager");
  }
  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }
  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }
  getManagerReview() {
    const manager = this.lookupManager();
  }
  getSelfReview() {
    // ...
  }
}
const review = new PerformanceReview(employee);
review.perfReview();

Хороший:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }
  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }
  lookupPeers() {
    return db.lookup(this.employee, "peers");
  }
  getManagerReview() {
    const manager = this.lookupManager();
  }
  lookupManager() {
    return db.lookup(this.employee, "manager");
  }
  getSelfReview() {
    // ...
  }
}
const review = new PerformanceReview(employee);
review.perfReview();

Комментарии

Комментируйте только то, что имеет сложную бизнес-логику.

Комментарии являются извинением, а не требованием. Хороший код в основном документирует сам себя.

Плохой:

function hashIt(data) {
  // The hash
  let hash = 0;

  // Length of string
  const length = data.length;
  // Loop through every character in data
  for (let i = 0; i < length; i++) {
    // Get character code.
    const char = data.charCodeAt(i);
    // Make the hash
    hash = (hash << 5) - hash + char;
    // Convert to 32-bit integer
    hash &= hash;
  }
}

Хороший:

function hashIt(data) {
  let hash = 0;
  const length = data.length;
  for (let i = 0; i < length; i++) {
    const char = data.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    // Convert to 32-bit integer
    hash &= hash;
  }
}

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

Контроль версий существует не просто так. Оставьте старый код в своей истории.

Плохой:

doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();

Хороший:

doStuff();

Не иметь комментариев журнала

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

Плохой:

/**
 * 2016-12-20: Removed monads, didn't understand them (RM)
 * 2016-10-01: Improved using special monads (JP)
 * 2016-02-03: Removed type-checking (LI)
 * 2015-03-14: Added combine with type-checking (JR)
 */
function combine(a, b) {
  return a + b;
}

Хороший:

function combine(a, b) {
  return a + b;
}

Избегайте позиционных маркеров

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

Плохой:

////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
  menu: "foo",
  nav: "bar"
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
  // ...
};

Хороший:

$scope.model = {
  menu: "foo",
  nav: "bar"
};

const actions = function() {
  // ...
};






Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .