Чистый javascript. Объекты и структуры данных. Асинхронность. Обработка ошибок

https://github.com/ryanmcdermott/clean-code-javascript
  • Перевод
Перевод книги Райана Макдермота clean-code-javascript

Оглавление:






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


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


В javascript отсутствуют ключевые слова private и public, что усложняет реализацию классов. Лучше использовать геттеры и сеттеры для доступа к свойствам объекта, чем напрямую к ним обращаться. Вы спросите «Зачем?». Вот несколько причин:

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

Плохо:

class BankAccount {
  constructor() {
    this.balance = 1000;
  }
}

const bankAccount = new BankAccount();

// Покупаем, например, обувь...
bankAccount.balance -= 100;

Хорошо:

class BankAccount {
  constructor(balance = 1000) {
    this._balance = balance;
  }

  // It doesn't have to be prefixed with `get` or `set` to be a getter/setter
  set balance(amount) {
    if (this.verifyIfAmountCanBeSetted(amount)) {
      this._balance = amount;
    }
  }

  get balance() {
    return this._balance;
  }

  verifyIfAmountCanBeSetted(val) {
    // ...
  }
}

const bankAccount = new BankAccount();

// Покупаем, например, обувь...
bankAccount.balance -= shoesPrice;

// получаем баланс
let balance = bankAccount.balance;

Реализуйте приватные свойства ваших объектов


Это возможно с помощью замыканий.

Плохо:

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

Хорошо:

const Employee = function (name) {
  this.getName = function getName() {
    return 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: John Doe


Асинхронность


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


Колбеки приводят к чрезмерной вложенности и плохой читаемости кода.

Плохо:

const request = require('request');
const fs = require('fs');

const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';

request.get(url, (requestErr, response) => {
  if (requestErr) {
    console.error(requestErr);
  } else {
    fs.writeFile('article.html', response.body, (writeErr) => {
      if (writeErr) {
        console.error(writeErr);
      } else {
        console.log('File written');
      }
    });
  }
});

Хорошо:

const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');

const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';

requestPromise.get(url)
  .then((response) => {
    return fsPromise.writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

Async/Await делает код чище, чем промисы


Промисы очень хорошая альтернатива колбекам, но в ES2017 / ES8 спецификации появился аsync/аwait, который предлагает ещё лучшее решение. Все, что вам нужно, это написать функцию с префиксом async, внутри которой вы можете писать вашу асинхронную логику императивно. аsync/аwait можно использовать прямо сейчас при помощи babel.

Плохо:

const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');

const url = 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin';

requestPromise.get(url)
  .then((response) => {
    return fsPromise.writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

Хорошо:

const requestPromise = require('request-promise');
const fsPromise = require('fs-promise');

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


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


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

Не игнорируйте отловленные ошибки


Ничего не делая с пойманной ошибкой, вы теряете возможность исправить ошибку или отреагировать на неё когда-либо. Вывод ошибки в консоль(console.log(error)) не дает лучшего результата, потому что ошибка может потеряться среди выводимых записей в консоль. Если вы заворачиваете кусок кода в try / catch, значит вы предполагаете возникновение ошибки. В таком случае вы должны иметь запасной план.

Плохо:

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

Хорошо:

try {
  functionThatMightThrow();
} catch (error) {
  // Один из вариантов (более заметный, чем console.log):
  console.error(error);
  // Другой вариант - известить пользователя про ошибку:
  notifyUserOfError(error);
  // И еще вариант - отправить ошибку на сервер :
  reportErrorToService(error);
  // Или используйте все три варианта!
}

Не игнорируйте ошибки, возникшие в промисах


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

Плохо:

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

Хорошо:

getdata()
.then((data) => {
  functionThatMightThrow(data);
})
.catch((error) => {
  // Один из вариантов (более заметный, чем console.log):
  console.error(error);
  // Другой вариант - известить пользователя про ошибку:
  notifyUserOfError(error);
  // И еще вариант - отправить ошибку на сервер :
  reportErrorToService(error);
  // Или используйте все три варианта!
});
Поделиться публикацией
Комментарии 10
    +8
    Реализуйте приватные свойства ваших объектов

    Т.е. не "ребята, смотрите как можно организовать работу с приватными значениями", а "реализуйте". Повелительное наклонение. Брр… Это известный паттерн в JS, но не более того. Есть и другие. В данном случае вы ещё попробуйте увязать его с class-ми. Тут же в прошлой статье, кажется, речь шла про "используйте классы".


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

    О да. Создавайте новые методы при создании каждого экземпляра класса. К чёрту прототипы. Какие такие прототипы? Наследование? Какое-такое наследование? Зато у нас приватные поля. Ну да ладно. Такой подход имеет право на существование и даже активно используется. Но это даже не рекомендация. Это просто один из вариантов.


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

    На этом моменте я просто поперхнулся. Чего? Что я сейчас читаю? Речь идёт не о "бывает удобно использовать get-ы и set-ы в некоторых исключительных ситуациях", а об "используйте их вместо полей повсеместно". На кой чёрт? Ах, да, там же написано:


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

    Реализовать дольше? WAT? Russian. Do you speak it? Okay.


    Т.е. если для получения значения по его имени вам внезапно потребуется нечто "дольшее", чем обратиться к той области памяти где это значение лежит… То логично, что вам потребуется поправить и какой-то другой код, который работает с этим. Т.е. было всё статично, быстро и предельно однозначно, очевидно. А теперь потребовалось разбавить всё это щепоткой логики. Это уже сама по себе ситуация дрянная. А автор предлагает эту поднаготную скрывать. За геттерами и сеттерами. Причём делать это заранее, исходя из "ну а вдруг понадобится, мало ли". Такого рвения расставлять себе повсюду грабли я ранее не встречал.


    Валидацию легко реализовать на уровне реализации сеттера

    С одной стороны легко. С другой стороны валидация это штука тонкая. Принудительно валидировать поле при его обновлении дело… сомнительное. Посудите сами: валидация нужна не всегда. Ой? Валидация может быть асинхронной. Ой? Валидация может быть несколько тяжёлой. Ой? Валидация может зависеть от ряда других полей? Дважды ой? Что если вы их последовательно устанавливаете сериализуя какой-то объект? Всё как-то сразу стало плохо и сложно. Делать валидацию по сеттеру можно. Но нужно? И часто ли это того стоит?


    Инкапсуляция внутреннего состояния объекта

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


    Легко добавить логирование и обработку ошибок на уровне геттеров и сеттеров

    Скажем в целях профилирования или debug-нга, это имеет смысл. Но едва ли за их пределами. Напомню геттеры и сеттеры даже в самом простом виде штуки тяжёлые.


    Наследуя этот класс, вы можете переопределить функциональность по умолчанию

    И сделать и без того не очевидное поведение ещё более не очевидным. Даёшь больше багов богу багов.


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

    Вот тут моя челюсть выпала повторно. Т.е. было поле .name и в нём было имя. А теперь… В зависимости от положения спутников Сатурна поле может вернуть как значение, так и… ничего, а само загрузить асинхронно (или ещё хуже — синхронно) что-то с какого-то сервера? Да вы (автор, не переводчик) в своём уме?


    Геттеры и сеттеры интересные, и местами удобные, инструменты. Но такие… dark side. Их нужно использовать с умом и только в тех местах, где их многочисленные минусы с лихвой покрываются плюсами. В 99+% случаев если вам стало тесно в plain data, нужно писать методы, а не геттеры и сеттеры. И да, править код под это дело.


    У меня только 1 вопрос. Это правда стоило того, чтобы это переводить?

      0
      Во многих случаях с вами согласен. Работу с геттерами и сеттерами надо было подать совсем по другому, но так как я пишу перевод то я решил не менять эту часть. Я перевожу полностью работу автора и считаю надо переводить все. В своей практике использую геттеры и сеттеры в 1% случаев, не более.
      +4
      Райан уже оправдывается за свои примеры на гитхабе ryanmcdermott/clean-code-javascript. Но вообще, делать require в функциях, в колбеках от ввода/вывода, и на промисах — это не то, чтобы не чистый код, это лютый ужас. А новички, которым эта книга предназначена, такого почитают и потом мы встречаем require внутри циклов или внутри Array.map()
        0
        Согласен, уже поправил.
          +1
          Отступы, длинные строчки, описательные пременные. Лучше взять примеры тут, я поправил: tshemsedinov/clean-code-javascript/tree/max-line-length-80
            0
            Спасибо за правки
              +1
              И уберите со всех статей теги: ReactJS, AngularJS. Ни одна из них ни каким боком к ним не отностися. Вас же минусуют за них.

              Можно оставить тег Node.JS только для этой статьи, а на других тоже уберите, это все про чистый JavaScript и только про JavaScript.
                –3
                Я думаю что разработчикам, которые изучают React.js и Angular.js и Node.js важно знать эти вещи. Часто новички не зная основ берутся за фреймворки и ноду.
                  0
                  По такой логике можно было бы добавить хабы: Разработка веб-сайтов, HTML, jQuery. Те, кто хочет читать статьи по JavaScript, тот подписывается на JavaScript. А если человек подписан на Node.js, но не подписан на JavaScript, то он не хочет видеть эти статьи.

                  Вот лучше добавить: Совершенный код, Проектирование и рефакторинг.
                    +2

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

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

        Самое читаемое