RUVDS.com corporate blog
Website development
JavaScript
Perfect code
June 6

Рекомендации по написанию чистого кода на JavaScript

Original author: Milos Protic
Translation
Если вы заботитесь о самом коде, и о том, как он написан, а не заняты лишь тем, чтобы создавать работающие программы, это означает что вы стремитесь к тому, чтобы ваш код был чистым. Профессиональный разработчик пишет код не только в расчёте на компьютеры, но и в расчёте на себя самого, встретившего этот код в будущем, и в расчёте на других программистов. Код, который вы пишете, не исчезает навсегда в недрах компьютера. Он живёт, изменяется, и, если написан плохо, вполне может сильно расстроить того, кому придётся редактировать его после того, как вы его написали. Вполне возможно, что этим «кем-то» будете именно вы.



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

Код и WTF-вопросы


Сущность WTF-вопросов, вроде «WTF is that?», сводится к крайнему удивлению и возмущению. По-русски эти чувства можно выразить вопросом «Какого чёрта?». «Чёрт», в зависимости от ситуации, вполне может уступить место чему-нибудь совершенно непечатному. Сколько раз вам доводилось дописывать чей-то код и при этом задаваться подобными вопросами?

Задавая себе WTF-вопросы о чужом коде, программисты спрашивают себя о том, что это такое (WTF is that?), что пытался сделать автор кода (WTF did you do here?), зачем в коде присутствует та или иная конструкция (WTF is this for?)

Вот картинка, в соответствии с которой единственным достоверным показателем качества кода является количество WTF-вопросов в минуту.


Слева — хороший код. Справа — плохой

А если серьёзно, то для того чтобы помочь вам настроиться на размышления о чистом коде, процитируем Роберта Мартина, известного как дядюшка Боб: «Даже плохой программный код может работать. Однако если код не является «чистым», это всегда будет мешать развитию проекта».

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

1. Строгие проверки на равенство


Стремитесь к тому, чтобы вместо == использовать ===.

// Если необдуманно использовать оператор == - это может серьёзно повлиять на программную логику. Это можно сравнить с тем, что некто ожидает, что пойдёт налево, но по какой-то причине вдруг идёт направо.
0 == false // true
0 === false // false
2 == "2" // true
2 === "2" // false

// пример
const value = "500";
if (value === 500) {
  console.log(value);
  // этот код не выполнится
}

if (value === "500") {
  console.log(value);
  // этот код выполнится
}

2. Переменные


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

let daysSLV = 10;
let y = new Date().getFullYear();

let ok;
if (user.age > 30) {
  ok = true;
}

Хорошо:

const MAX_AGE = 30;
let daysSinceLastVisit = 10;
let currentYear = new Date().getFullYear();

...

const isUserOlderThanAllowed = user.age > MAX_AGE;

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

Плохо:

let nameValue;
let theProduct;

Хорошо:

let name;
let product;

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

Плохо:

const users = ["John", "Marco", "Peter"];
users.forEach(u => {
  doSomething();
  doSomethingElse();
  // ...
  // ...
  // ...
  // ...
  // Тут перед нами ситуация, в которой возникает WTF-вопрос "Для чего используется `u`?"
  register(u);
});

Хорошо:

const users = ["John", "Marco", "Peter"];
users.forEach(user => {
  doSomething();
  doSomethingElse();
  // ...
  // ...
  // ...
  // ...
  register(user);
});

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

Плохо:

const user = {
  userName: "John",
  userSurname: "Doe",
  userAge: "28"
};

...

user.userName;

Хорошо:

const user = {
  name: "John",
  surname: "Doe",
  age: "28"
};

...

user.name;

3. Функции


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

Плохо:

function notif(user) {
  // реализация
}

Хорошо:

function notifyUser(emailAddress) {
  // реализация
}

Избегайте использования длинных списков аргументов. В идеале функции следует иметь два или меньшее количество аргументов. Чем меньше у функции аргументов — тем легче будет её тестировать.

Плохо:

function getUsers(fields, fromDate, toDate) {
  // реализация
}

Хорошо:

function getUsers({ fields, fromDate, toDate }) {
  // реализация
}

getUsers({
  fields: ['name', 'surname', 'email'],
  fromDate: '2019-01-01',
  toDate: '2019-01-18'
})

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

Плохо:

function createShape(type) {
  const shapeType = type || "cube";
  // ...
}

Хорошо:

function createShape(type = "cube") {
  // ...
}

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

Плохо:

function notifyUsers(users) {
  users.forEach(user => {
    const userRecord = database.lookup(user);
    if (userRecord.isVerified()) {
      notify(user);
    }
  });
}

Хорошо:

function notifyVerifiedUsers(users) {
  users.filter(isUserVerified).forEach(notify);
}

function isUserVerified(user) {
  const userRecord = database.lookup(user);
  return userRecord.isVerified();
}

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

Плохо:

const shapeConfig = {
  type: "cube",
  width: 200,
  height: null
};

function createShape(config) {
  config.type = config.type || "cube";
  config.width = config.width || 250;
  config.height = config. height || 250;
}

createShape(shapeConfig);

Хорошо:

const shapeConfig = {
  type: "cube",
  width: 200
  // Значение ключа 'height' не задано
};

function createShape(config) {
  config = Object.assign(
    {
      type: "cube",
      width: 250,
      height: 250
    },
    config
  );

  ...
}

createShape(shapeConfig);

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

Плохо:

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

Хорошо:

function createFile(name) {
  fs.create(name);
}

function createPublicFile(name) {
  createFile(`./public/${name}`);
}

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

Плохо:

Array.prototype.myFunc = function myFunc() {
  // реализация
};

Хорошо:

class SuperArray extends Array {
  myFunc() {
    // реализация
  }
}

4. Условные конструкции


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

Плохо:

function isUserNotBlocked(user) {
  // реализация
}

if (!isUserNotBlocked(user)) {
  // реализация
}

Хорошо:

function isUserBlocked(user) {
  // реализация
}

if (isUserBlocked(user)) {
  // реализация
}

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

Плохо:

if (isValid === true) {
  // что-то сделать...
}

if (isValid === false) {
  // что-то сделать...
}

Хорошо:

if (isValid) {
  // что-то сделать...
}

if (!isValid) {
  // что-то сделать...
}

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

Плохо:

class Car {
  // ...
  getMaximumSpeed() {
    switch (this.type) {
      case "Ford":
        return this.someFactor() + this.anotherFactor();
      case "Mazda":
        return this.someFactor();
      case "McLaren":
        return this.someFactor() - this.anotherFactor();
    }
  }
}

Хорошо:

class Car {
  // ...
}

class Ford extends Car {
  // ...
  getMaximumSpeed() {
    return this.someFactor() + this.anotherFactor();
  }
}

class Mazda extends Car {
  // ...
  getMaximumSpeed() {
    return this.someFactor();
  }
}

class McLaren extends Car {
  // ...
  getMaximumSpeed() {
    return this.someFactor() - this.anotherFactor();
  }
}

5. ES-классы


Классы появились в JavaScript сравнительно недавно. Их можно назвать «синтаксическим сахаром». В основе того, что получается при использовании классов, лежат, как и прежде, прототипы объектов. Но код, в котором применяются классы, выглядит иначе. В целом, если есть такая возможность, ES-классы стоит предпочесть обычным функциям-конструкторам.

Плохо:

const Person = function(name) {
  if (!(this instanceof Person)) {
    throw new Error("Instantiate Person with `new` keyword");
  }

  this.name = name;
};

Person.prototype.sayHello = function sayHello() { /**/ };

const Student = function(name, school) {
  if (!(this instanceof Student)) {
    throw new Error("Instantiate Student with `new` keyword");
  }

  Person.call(this, name);
  this.school = school;
};

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.printSchoolName = function printSchoolName() { /**/ };

Хорошо:

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    /* ... */
  }
}

class Student extends Person {
  constructor(name, school) {
    super(name);
    this.school = school;
  }

  printSchoolName() {
    /* ... */
  }
}

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

Плохо:

class Person {
  constructor(name) {
    this.name = name;
  }

  setSurname(surname) {
    this.surname = surname;
  }

  setAge(age) {
    this.age = age;
  }

  save() {
    console.log(this.name, this.surname, this.age);
  }
}

const person = new Person("John");
person.setSurname("Doe");
person.setAge(29);
person.save();

Хорошо:

class Person {
  constructor(name) {
    this.name = name;
  }

  setSurname(surname) {
    this.surname = surname;
    // Возвратим this для получения возможности объединять вызовы методов в цепочки
    return this;
  }

  setAge(age) {
    this.age = age;
    // Возвратим this для получения возможности объединять вызовы методов в цепочки
    return this;
  }

  save() {
    console.log(this.name, this.surname, this.age);
    // Возвратим this для получения возможности объединять вызовы методов в цепочки
    return this;
  }
}

const person = new Person("John")
    .setSurname("Doe")
    .setAge(29)
    .save();

6. О том, чего лучше не делать


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

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

Теперь поговорим о неиспользуемом коде. Это — код, который присутствует в кодовой базе, но совершенно ничего не делает. Такое случается, например, когда на определённом этапе разработки решают, что в некоем фрагменте программы больше нет смысла. Для того чтобы избавиться от подобных фрагментов кода, нужно внимательно просмотреть кодовую базу и убрать их. Легче всего избавляться от такого кода в тот момент, когда решено, что он больше не нужен. Позже можно забыть о том, для чего он использовался. Это значительно усложнит борьбу с ним.

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


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

Итоги


Здесь мы обсудили лишь малую часть тех действий, которые можно предпринять для улучшения кода. Автор этого материала полагает, что о рассмотренных здесь принципах часто забывают. Программисты пытаются им следовать, но, по разным причинам, не всегда в этом преуспевают. Возможно, в самом начале работы над проектом все помнят о важности чистого кода, в результате программа получается аккуратной. Потом же, по мере приближения дедлайнов, о чистоте кода часто забывают, обращая внимание лишь на то, что отмечено как TODO или REFACTOR. В такие моменты заказчик проекта будет настаивать на том, чтобы проект завершили в срок, а не на том, чтобы его код был бы чистым.

Мы довольно часто публикуем материалы, посвящённые проблеме написания качественного JavaScript-кода. Если вам эта тема интересна — вот несколько ссылок.


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

Уважаемые читатели! Доводилось ли вам задаваться WTF-вопросами при чтении чужого кода?



+33
16.5k 221
Comments 14
Top of the day