Простота кода JavaScript

JavaScript
Простота кода JavaScript

Единственный способ проверить качество вашего кода: сколько раз люди говорят f*k, когда смотрят на ваш код.

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

Эта статья не руководство по стилю кода, она посвящена коду.可读性,复用性,扩展性Проводить исследования.

Мы обсудим несколько аспектов:

  1. Переменная
  2. функция
  3. Объекты и структуры данных
  4. Добрый
  5. SOLID
  6. тестовое задание
  7. асинхронный
  8. обработка ошибок
  9. стиль кода
  10. Примечания

Переменная

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

Bad:

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

Good:

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

↑ вернуться к началу

быть единым

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

Bad:

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

Good:

getUser()

↑ вернуться к началу

Каждая константа должна быть названа

Можно использоватьbuddy.jsилиESLintОбнаружение безымянных констант в коде.

Bad:

// 三个月之后你还能知道 86400000 是什么吗?
setTimeout(blastOff, 86400000);

Good:

const MILLISECOND_IN_A_DAY = 86400000;

setTimeout(blastOff, MILLISECOND_IN_A_DAY);

↑ вернуться к началу

можно описать

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

Bad:

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

Good:

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

↑ вернуться к началу

прямо в точку

Bad:

const locations = ['Austin', 'New York', 'San Francisco'];
locations.forEach((l) => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // 需要看其他代码才能确定 'l' 是干什么的。
  dispatch(l);
});

Good:

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

↑ вернуться к началу

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

Если вы создаете объект car, нет необходимости называть его цвет carColor.

Bad:

const car = {
  carMake: 'Honda',
  carModel: 'Accord',
  carColor: 'Blue'
};

function paintCar(car) {
  car.carColor = 'Red';
}

Good:

const car = {
  make: 'Honda',
  model: 'Accord',
  color: 'Blue'
};

function paintCar(car) {
  car.color = 'Red';
}

↑ вернуться к началу

использовать по умолчанию

Bad:

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

Good:

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

↑ вернуться к началу

функция

Чем меньше параметров, тем лучше

Если параметров больше двух, используйтеES2015/ES6синтаксис деструктурирования, независимо от порядка аргументов.

Bad:

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

Good:

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

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

↑ вернуться к началу

делать только одно

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

Bad:

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

Good:

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

↑ вернуться к началу

Как подсказывает название

Глядя на имя функции, вы должны знать, что она делает.

Bad:

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

const date = new Date();

// 很难知道是把什么加到日期中
addToDate(date, 1);

Good:

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

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

↑ вернуться к началу

Требуется только один уровень абстракции

Если функция слишком много вложена, это затруднит ее повторное использование и тестирование.

Bad:

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

Good:

function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const ast = lexer(tokens);
  ast.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 lexer(tokens) {
  const ast = [];
  tokens.forEach((token) => {
    ast.push( /* ... */ );
  });

  return ast;
}

↑ вернуться к началу

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

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

Для оптимизации повторяющегося кода требуется сильная способность к абстракции, неправильная абстракция лучше, чем повторяющийся код. Итак, в абстрактном процессе должно следоватьSOLIDв общем(SOLIDчто это такое? Об этом позже).

Bad:

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

Good:

function showEmployeeList(employees) {
  employees.forEach(employee => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();
    const data = {
      expectedSalary,
      experience,
    };
    
    switch(employee.type) {
      case 'develop':
        data.githubLink = employee.getGithubLink();
        break
      case 'manager':
        data.portfolio = employee.getMBAProjects();
        break
    }
    render(data);
  })
}

↑ вернуться к началу

Объект устанавливает свойства по умолчанию

Bad:

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

Good:

const menuConfig = {
  title: 'Order',
  // 'body' key 缺失
  buttonText: 'Send',
  cancellable: true
};

function createMenu(config) {
  config = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);

  // config 就变成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

↑ вернуться к началу

Не передавать параметр флага

Оценка логики выполнения по истинности или ложности флага нарушает принцип, согласно которому функция выполняет одно действие.

Bad:

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

Good:

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

↑ вернуться к началу

Как избежать побочных эффектов (часть 1)

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

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

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

Bad:

// 全局变量被一个函数引用
// 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。
var name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
  name = name.split(' ');
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

Good:

var name = 'Ryan McDermott';
var newName = splitIntoFirstAndLastName(name)

function splitIntoFirstAndLastName(name) {
  return name.split(' ');
}

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

↑ вернуться к началу

Как избежать побочных эффектов (часть вторая)

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

Предположим, мы пишем корзину, которая проходитaddItemToCart()Способ добавить товар в корзину, изменить购物车数组. позвони в это времяpurchase()метод покупки, благодаря передаче по ссылке, приобретенный购物车数组Точно последние данные.

Выглядит нормально, верно?

Если сеть отключается, когда пользователь нажимает кнопку «Купить»,purchase()Метод вызывается неоднократно, и в то же время пользователь добавляет новые элементы, после чего сеть восстанавливается. Такpurchase()способ получить购物车数组неправильно.

Чтобы избежать этой проблемы, нам нужно клонировать каждый раз, когда мы добавляем продукт.购物车数组и вернуть новый массив.

Bad:

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

Good:

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

↑ вернуться к началу

Не пишите глобальные методы

В JavaScript никогда не загрязняйте глобальные объекты, что может привести к непредсказуемым ошибкам в рабочей среде. Например, если выArray.prototypeдобавить новыйdiffметод определения разницы между двумя массивами. И ваш коллега собирается сделать что-то подобное, но егоdiffМетод используется для определения разницы между первыми элементами двух массивов. Очевидно, что ваши методы будут конфликтовать.При возникновении таких проблем мы можем использовать синтаксис ES2015/ES6 для их исправления.Arrayрасширять.

Bad:

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

Good:

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

↑ вернуться к началу

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

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

Bad:

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

Good:

const programmerOutput = [
  {
    name: 'Uncle Bobby',
    linesOfCode: 500
  }, {
    name: 'Suzie Q',
    linesOfCode: 1500
  }, {
    name: 'Jimmy Gosling',
    linesOfCode: 150
  }, {
    name: 'Gracie Hopper',
    linesOfCode: 1000
  }
];
let totalOutput = programmerOutput
  .map(output => output.linesOfCode)
  .reduce((totalLines, lines) => totalLines + lines, 0)

↑ вернуться к началу

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

Bad:

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

Good:

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

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

↑ вернуться к началу

Старайтесь не использовать условные предложения «не»

Bad:

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

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

Good:

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

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

↑ вернуться к началу

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

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

О: Большинство сценариев можно заменить полиморфизмом.

В: Можно использовать полиморфизм, но почему нельзя использовать условные операторы?

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

Bad:

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

Good:

class Airplane {
  // ...
}
// 波音777
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?

Bad:

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

Good:

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

↑ вернуться к началу

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

Если вам нужно выполнить статическую проверку типов, таких как строки, целые числа и т. д., рекомендуется TypeScript, иначе ваш код станет вонючим и длинным.

Bad:

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

Good:

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

↑ вернуться к началу

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

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

Bad:

// 在老的浏览器中,由于 `list.length` 没有做缓存,每次迭代都会去计算,造成不必要开销。
// 现代浏览器已对此做了优化。
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

Good:

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

↑ вернуться к началу

Удалить устаревший код

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

Если вы забудете об этом, код останется там навсегда.

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

Bad:

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

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

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

Good:

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

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

↑ вернуться к началу

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

использоватьget,setметоды работы с данными

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

Bad:

function makeBankAccount() {
  // ...

  return {
    balance: 0,
    // ...
  };
}

const account = makeBankAccount();
account.balance = 100;

Good:

function makeBankAccount() {
  // 私有变量
  let balance = 0;

  function getBalance() {
    return balance;
  }
  
  function setBalance(amount) {
    // ... 在更新 balance 前,对 amount 进行校验
    balance = amount;
  }

  return {
    // ...
    getBalance,
    setBalance,
  };
}

const account = makeBankAccount();
account.setBalance(100);

↑ вернуться к началу

использовать приватные переменные

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

Bad:

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

Good:

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 не было синтаксиса классов, а моделировать классы можно было только в виде конструкторов, что было очень плохо читабельно.

Bad:

// 动物
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() {};

Good:

// 动物
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() {};
}

↑ вернуться к началу

цепной вызов

Этот шаблон весьма полезен и может быть найден во многих библиотеках, таких как jQuery, Lodash и т.д. Это делает ваш код чистым и элегантным. Это также очень просто реализовать, просто верните это в конце метода класса.

Bad:

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

Good:

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
    return this;
  }

  setModel(model) {
    this.model = model;
    return this;
  }

  setColor(color) {
    this.color = color;
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    return this;
  }
}

const car = new Car('Ford','F-150','red')
  .setColor('pink');
  .save();

↑ вернуться к началу

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

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

Bad:

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // ...
}

// TaxData(税收信息)并不是属于 Employee(雇员),而是包含关系。
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

Good:

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);
  }
  // ...
}

↑ вернуться к началу

SOLID

SOLID — это сочетание первых букв нескольких слов соответственно单一功能原则,开闭原则,里氏替换原则,接口隔离原则так же как依赖反转原则.

принцип единой функции

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

Bad:

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

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}

Good:

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

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

↑ вернуться к началу

принцип открыто-закрыто

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

Bad:

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) => {
        // 传递 response 并 return
      });
    } else if (this.adapter.name === 'httpNodeAdapter') {
      return makeHttpCall(url).then((response) => {
        // 传递 response 并 return
      });
    }
  }
}

function makeAjaxCall(url) {
  // 处理 request 并 return promise
}

function makeHttpCall(url) {
  // 处理 request 并 return promise
}

Good:

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

  request(url) {
    // 处理 request 并 return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'nodeAdapter';
  }

  request(url) {
    // 处理 request 并 return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then((response) => {
      // 传递 response 并 return
    });
  }
}

↑ вернуться к началу

Принцип замены Лисков

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

Bad:

// 长方形
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(); 
    rectangle.render(area);
  });
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

Good:

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

↑ вернуться к началу

Принцип разделения интерфейса

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

Bad:

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

  setup() {
    this.rootNode = this.settings.rootNode;
    this.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

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

Good:

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

↑ вернуться к началу

Принцип инверсии зависимости

Всего два момента:

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

Подводя итог в двух словах, расцепление.

Bad:

// 库存查询
class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

// 库存跟踪
class InventoryTracker {
  constructor(items) {
    this.items = items;

    // 这里依赖一个特殊的请求类,其实我们只是需要一个请求方法。
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach((item) => {
      this.requester.requestItem(item);
    });
  }
}

const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();

Good:

// 库存跟踪
class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach((item) => {
      this.requester.requestItem(item);
    });
  }
}

// HTTP 请求
class InventoryRequesterHTTP {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

// webSocket 请求
class InventoryRequesterWS {
  constructor() {
    this.REQ_METHODS = ['WS'];
  }

  requestItem(item) {
    // ...
  }
}

// 通过依赖注入的方式将请求模块解耦,这样我们就可以很轻易的替换成 webSocket 请求。
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterHTTP());
inventoryTracker.requestItems();

↑ вернуться к началу

тестовое задание

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

PS: если вам сложно тестировать код, вам следует оптимизировать его.

Упрощение

Bad:

import assert from 'assert';

describe('MakeMomentJSGreatAgain', () => {
  it('handles date boundaries', () => {
    let date;

    date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);

    date = new MakeMomentJSGreatAgain('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);

    date = new MakeMomentJSGreatAgain('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

Good:

import assert from 'assert';

describe('MakeMomentJSGreatAgain', () => {
  it('handles 30-day months', () => {
    const date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);
  });

  it('handles leap year', () => {
    const date = new MakeMomentJSGreatAgain('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);
  });

  it('handles non-leap year', () => {
    const date = new MakeMomentJSGreatAgain('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

↑ вернуться к началу

асинхронный

больше никаких обратных вызовов

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

Bad:

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

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

Good:

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

↑ вернуться к началу

Async/Await более лаконичен, чем Promises

Bad:

import { get } from 'request-promise';
import { writeFile } from 'fs-promise';

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

Good:

import { get } from 'request-promise';
import { writeFile } from 'fs-promise';

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

↑ вернуться к началу

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

Не игнорируйте создание исключений

Bad:

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

Good:

try {
  functionThatMightThrow();
} catch (error) {
  // 这一种选择,比起 console.log 更直观
  console.error(error);
  // 也可以在界面上提醒用户
  notifyUserOfError(error);
  // 也可以把异常传回服务器
  reportErrorToService(error);
  // 其他的自定义方法
}

↑ вернуться к началу

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

Bad:

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

Good:

getdata()
  .then((data) => {
    functionThatMightThrow(data);
  })
  .catch((error) => {
    // 这一种选择,比起 console.log 更直观
    console.error(error);
    // 也可以在界面上提醒用户
    notifyUserOfError(error);
    // 也可以把异常传回服务器
    reportErrorToService(error);
    // 其他的自定义方法
  });

↑ вернуться к началу

стиль кода

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

Постоянная капитализация

Bad:

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 {}

Good:

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 {}

↑ вернуться к началу

объявить перед вызовом

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

Bad:

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

Good:

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

↑ вернуться к началу

Примечания

Аннотировать нужно только бизнес-логику

Комментарии к коду не чем больше, тем лучше.

Bad:

function hashIt(data) {
  // 这是初始值
  let hash = 0;

  // 数组的长度
  const length = data.length;

  // 循环数组
  for (let i = 0; i < length; i++) {
    // 获取字符代码
    const char = data.charCodeAt(i);
    // 修改 hash
    hash = ((hash << 5) - hash) + char;
    // 转换为32位整数
    hash &= hash;
  }
}

Good:

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;

    // 转换为32位整数
    hash &= hash;
  }
}

↑ вернуться к началу

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

Цель git — сохранить ваш старый код, поэтому быстро удаляйте закомментированный код.

Bad:

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

Good:

doStuff();

↑ вернуться к началу

Не вести дневник

Помните, что у вас есть git! ,git logможет помочь вам сделать это.

Bad:

/**
 * 2016-12-20: 删除了 xxx
 * 2016-10-01: 改进了 xxx
 * 2016-02-03: 删除了第12行的类型检查
 * 2015-03-14: 增加了一个合并的方法
 */
function combine(a, b) {
  return a + b;
}

Good:

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

↑ вернуться к началу

Комментарии не нужно выделять

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

Bad:

////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
  menu: 'foo',
  nav: 'bar'
};

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

Good:

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

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

↑ вернуться к началу

Спасибо за прочтение!

ref

Переведено сryanmcdermottиз"чистый код-javascript", в этой статье были внесены некоторые изменения в исходный текст.