Перевод книги Райана Макдермота clean-code-javascript

Оглавление:





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


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

Идеальная ситуация — отсутствие аргументов. Один или два аргумента — хорошо, а трех уже следует избегать.

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

Плохо:

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

Хорошо:

const menuConfig = {
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
};

function createMenu(config) {
  // ...
}

createMenu(menuConfig);

Функция должна решать одну задачу


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

Плохо:

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

Хорошо:

function emailClients(clients) {
  clients
    .filter(isClientActive)
    .forEach(email);
}

function isClientActive(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

Названия функций должны описывать их назначение


Плохо:

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

const date = new Date();

// По имени функции трудно сказать, что именно добавляется
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) => {
    // правило...
  });

  ast.forEach((node) => {
    // парсинг...
  });
}

Хорошо:

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

function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const ast = lexer(tokens);
  ast.forEach((node) => {
    // парсинг...
  });
}

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


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

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

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

Создание правильной абстракции — вопрос невероятной важности, и именно поэтому вы должны следовать принципам 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 showList(employees) {
  employees.forEach((employee) => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();

    let portfolio = employee.getGithubLink();

    if (employee.type === 'manager') {
      portfolio = employee.getMBAProjects();
    }

    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    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',
  //  Пользователь не имеет свойства 'body'
  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);

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


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

Плохо:

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)


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

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

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

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

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

Плохо:

// Глобальная переменная, на которую ссылается последующая функция.
// Если бы у нас была еще одна функция, которая бы работала с именем name как со строкой,
// то обнаружив массив, он непременно бы поломалась
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 примитивы передаются по значению, а объекты и массивы передаются по
ссылке. В случае объектов и массивов, если наша функция вносит изменения
в корзину (массив), например, путем добавления элемента в массив,
то любая другая функция, которая использует эту корзину (массив) будет зависеть от этого
добавления. Это может быть и хорошо и плохо в разных случаях. Давайте представим себе плохую ситуация:

Пользователь нажимает на кнопку «Покупка», которая вызывает функцию `purchase`, что отправляет данные из корзины (массив) на сервер. В случае плохого подключения к сети `функция purchase` должена отправить повторный запрос. Теперь, что, если в то же время пользователь случайно нажимает кнопку «Добавить в корзину», но пока не хочет покупать товар?
Если это произойдет, и начинается запрос сети, то функция `purchase`
пошлет случайно добавленный элемент, поскольку он имеет ссылку на предыдущую корзину (массив), модифицированую функцией `addItemToCart`. Отличное решение было бы для `addItemToCart` всегда клонировать корзину, отредактировать и вернуть клон. Это гарантирует, что никакие другие функции, которые зависят от корзины не будут зависеть от каких-либо изменений.

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


Плохо:

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

Хорошо:

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


Не переопределяйте глобальные функции


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

Плохо:

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
  .map((programmer) => programmer.linesOfCode)
  .reduce((acc, linesOfCode) => acc + 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.peddle(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 в том, что иллюзия безопасности, которую она создает, никак не компенсируется потерей читабельности из-за многословности кода. Держите ваш код в чистоте, пишите хорошие тесты и делайте эффективные ревизии кода. Или делайте все то же самое, но с помощью 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;
}

Не оптимизируйте сверх меры


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

Плохо:

// В старых браузерах каждая итерация с некэшированной `list.length` будет дорогостоящей
// из-за перерасчета `list.length`. В современных браузерах это оптимизировано.
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');
Поделиться с друзьями
-->

Комментарии (43)


  1. sshikov
    15.01.2017 19:46
    +9

    >Идеальная ситуация — отсутствие аргументов.

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


    1. mayorovp
      16.01.2017 06:24
      +3

      Текущее время или ГСЧ — это не чистые функции.


      1. sshikov
        16.01.2017 09:10
        +1

        Я не говорил, что она чистая. Я имел в виду, что она — достаточно редкий пример практически полезной функции без параметров.


  1. Holix
    15.01.2017 21:25
    +3

    Избегайте необдуманного следования правилам моды. Сегодня они помогут, а завтра подведут.


  1. GeMir
    15.01.2017 21:27

    Вот бы ещё сглаживание тексту на иллюстрации к статье…


    1. 776166
      15.01.2017 21:29

      Вот ещё бы без ненужных иллюстраций к статье.


    1. BoryaMogila
      15.01.2017 21:47

      Заменил


  1. DistortNeo
    15.01.2017 23:11
    +1

    Поругался в предыдущей теме, поругаюсь и здесь: чем простой цикл плох? Зачем вместо него городить неочевидную конструкцию map-reduce? И почему именно крокодил в виде


    .reduce((acc, linesOfCode) => acc + linesOfCode, 0);

    когда достаточно было бы написать .sum();


    1. ReklatsMasters
      15.01.2017 23:24
      -3

      Эта конструкция помогает избежать засорения скопа ненужными переменными + занимает меньше места. Хотя и проигрывает в скорости циклу. Конструкция очевидна, если вы знаете es5. А на дворе es6 давно.


      1. kahi4
        16.01.2017 01:14
        +2

        Let и const (const в конструкции for of) тоже не засоряют скоуп. Меньше места занимает только в коде, в памяти заметно больше, да и то есть ситуации, когда через for код получается компактнее. Это я уже молчу про то, что конструкция reduce в примерах посложнее суммы обычно настолько нечитаемая, что даже вы не то, что через пол года, через пол часа уже не поймете что он делает. Особенно если захотелось написать лаконично.


        И в скорости проигрывает не всегда, не настолько интерпретатор js тупой (впрочем, зачастую, это гадание на кофейной гуще, сможет ли он оптимизировать или нет).


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


        1. faiwer
          16.01.2017 08:09
          +1

          const в конструкции for of

          К сожалению, Firefox упорно падал на любой попытке затолкать const в for. А let переменные там же, не обновлялись при каждой итерации. Это доставило мне много "славных" моментов debug-а, когда я использовал одну из таких переменных внутри новых Promise-ов. Бррр. Возможно такое поведение уже исправили.


          for(const a of [1, 2, 3]){ }
          // SyntaxError: missing = in const declaration
          for(let a of [1, 2, 3]){ setTimeout(() => alert(a)) }
          // 3 3 3

          Хм. Нет. 50.1.0


          1. Shannon
            16.01.2017 12:41
            +1

            let и const в 51 всё таки починили

            for(const i of [1, 2, 3]) console.log(i)
            1
            2
            3
            

            for(let a of [1, 2, 3]) { setTimeout(() => console.log(a)) }
            1
            2
            3
            

            Подстава конечно с let от лисы, хоть об этом и говорится в https://kangax.github.io/compat-table/es6/, но это только если раскрыть ветвь
            let/const for/for-in loop iteration scope no
            


            1. faiwer
              16.01.2017 13:37

              Ух, спасибо за новость. Как бальзам на душу.


        1. ReklatsMasters
          16.01.2017 10:52
          +3

          Абсолютно согласен почти со всем. У любой конструкции есть свои преимущества и недостатки. Очевидно, что писать нужно так, чтоб читаемость не страдала.


          скорости проигрывает не всегда

          Старый добрый for-i всё ещё уделывает всех. Если у вас другие данные, приведите тесты. А пока вот мой https://repl.it/FKLZ:


          node 6.9.1 (V8 5.1):


          for-of: 639.740ms
          for-i: 439.640ms
          reduce: 921.640ms

          node 7.4.0 (V8 5.4)


          for-of: 410.190ms
          for-i: 66.675ms
          reduce: 316.617ms


      1. DistortNeo
        16.01.2017 01:49
        +2

        Так объявите функцию sum — сэкономится куча места и существенно вырастет читаемость.


        Вообще, обратил внимание, что в обычных языках я никогда не использовал reduce в общем виде (он же Aggregate в C#, например), всегда хватало методов типа Sum, Average, Count и т.д.


        1. lumini
          16.01.2017 06:10
          +1

          Часто видел Aggregate в чужом коде. Но неизменно оказывалось, что это результат автоматической замены ReSharper'ом цикла foreach.


    1. TheShock
      16.01.2017 04:06
      +6

      Зачем вместо него городить неочевидную конструкцию map-reduce? И почему именно крокодил в виде

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


      1. faiwer
        16.01.2017 09:32
        +5

        Сегодня вот наткнулся на Rethinking JavaScript: Death of the For Loop. Смерть циклам. Заголовки js-статей всё пафоснее и пафоснее. Вскоре появятся слова из фентези, видать. А потом и до таких слов как "хтонический ужас" доберутся.


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


        1. Shannon
          16.01.2017 13:10
          +2

          Статье не хватает следующей части под названием «1000 и 1 способ придумать название для расплодившихся на ровном месте функций-однострочников»

          Даже cat.name не избежал участи превратиться в

          const getName = cat => cat.name
          


    1. sshikov
      16.01.2017 09:13

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

      В случае записи в виде функций это получается более ясно (хотя непривычному глазу — возможно и не сразу).


    1. MrGobus
      16.01.2017 10:59
      +2

      Простой цикл не просто неплох, он хорош.
      Первое это перфоманс, у меня на последнем хроме он опережает foreach в разы.
      Замерить можно тут: for vs forEach
      Второе, это память, каждая такая супер функция создает новый массив выборки.
      Ну и наконец читабельность. С моей точки зрения, код теряет наглядность, особенно если функции проверки вынести далеко от кода вызова или в другой файл.


      1. faiwer
        16.01.2017 11:04
        +1

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


  1. faiwer
    16.01.2017 08:01
    +3

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


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


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


    И ещё. По поводу совета предпочитать функциональный подход. Тут одна беда пришла незаметно. У нас появились долгожданные генераторы и async-и. И вот ну очень уж плохо они дружат с функциональным подходом. Начинается монстро-код с Promise.all и прочими шаманствами.


    1. mayorovp
      16.01.2017 09:02

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


      Скорее надо вспомнить про шаманство с reduce, в которые превращается простой императивный цикл. И при для нахождения суммы еще понятно что reduce делает — то при использовании then уже мало кто способен сходу понять что вообще в коде происходит.


    1. sshikov
      16.01.2017 09:15
      +1

      +1 по поводу switch и полиморфизма. Кроме того, наличие if и вообще ветвлений в коде никаким образом не показывает, что у этого кода два и более назначений. Вовсе нет.


    1. faiwer
      16.01.2017 09:26
      +1

      Ещё вспомнилось про параметры и объект-параметр. Тут палка о двух концах. Если параметры передаются явно и отдельно, то мы получаем более предсказуемый и однозначный код, нежели когда передаётся объект-параметр. Однако когда число параметров вырастает, скажем, до 5-7, это всё выливается в такой геморрой (особенно если код очень полиморфичен)… Брр… Приходится балансировать, глядя на реальную задачу.


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


      Ещё касательно совета не использовать флаги. Ну с таким то примером и правда не сложно вынести обёртку в отдельный метод. А что если флаг не заменяется простой заменой аргументов функции?


      1. raveclassic
        16.01.2017 11:47

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


      1. mayorovp
        16.01.2017 12:48
        +2

        Можно же использовать объект-параметр вместе с деструктуризацией. Код будет столь же предсказуемым и однозначным.


        1. faiwer
          16.01.2017 13:44

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


          К тому же, ИМХО, деструктуризация в сигнатуре выглядит… ужасно. Честно говоря я не сторонник того, чтобы сигнатура метода содержала в себе столько логики, сколько её сейчас туда пихают. Ещё и дефолтные значения. А учитывая, что деструктуризация бывает многоуровневой...


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


          1. raveclassic
            16.01.2017 14:01

            предсказуемый и однозначный код
            Ну кстати, предсказуемость и однозначность можно получить введением типизации для этих объектов-параметров через flow/ts. Тогда можно положиться на IDE и не держать постоянно в голове всю структуру.
            type TFooConfig = {
              foo: string,
              bar?: number
            }
            export const foo = (config: TFooConfig) => {
            }
            


            1. i360u
              16.01.2017 14:14

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


              1. raveclassic
                16.01.2017 14:18
                +1

                А в конструктор этот как значения передавать? Снова через аргументы или через еще один объект?


                1. i360u
                  16.01.2017 14:35
                  -1

                  А в чем проблема? Берете и передаете так-же, как передавали бы в функцию. Конструктор для сериализации/формализации + подсветка в IDE. Если у вас объект с параметрами формируется где-то рядом в функции, где происходит и вызов метода с этими параметрами, то это все вообще делать не нужно, тем более тащить сюда flow.


          1. mayorovp
            16.01.2017 14:11

            Да ну? Вот есть строка с вызовом метода — foo(5, 7, 'bar'). И что можно сказать на нее глядя?


            1. faiwer
              16.01.2017 14:17

              Какие-то магические константы? 5, 7, 'bar'? :) Вместо них, в реальном коде, будет что-нибудь типа widget.id, position, mode. И сказать можно будет гораздо большее.


              1. mayorovp
                16.01.2017 14:22

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


  1. i360u
    16.01.2017 12:37
    +1

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

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

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


    Использовать Object.assign тоже не очень правильно, есть риск унаследовать кучу мусора прилетевшую в config например из DOM. Нужно как-то так:


    const menuConfig = {
      title: null,
      body: 'Bar',
      buttonText: null,
      cancellable: true
    };
    Object.keys(menuConfig).forEach((prop) => {
            if (config.hasOwnProperty(prop)) {
             menuConfig[prop] = config[prop];
            }
          });

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

    Это еще почему?


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

    Если избегать, то уж лучше как-то так:


    let doSomethingIf = {
    value1: () => {doSomething(1);},
    value2: () => {doSomething(2);},
    value3: () => {doSomething(3);},
    };
    doSomethingIf[value || 'value1' ]();


    1. raveclassic
      16.01.2017 12:45

      Если избегать, то уж лучше как-то так:
      Я надеюсь, вы несерьезно.


      1. i360u
        16.01.2017 13:03

        Да, сорри, упустил контекст совета в статье. Нужно было подзаголовок иначе сформулировать… Хотя плодить классы на каждый вариант свойств данных тоже совсем "не айс", ИМХО классы должны оставаться на уровне абстракций.


      1. Apathetic
        16.01.2017 22:25
        +1

        Поясните, если не затруднит.


        1. mayorovp
          16.01.2017 22:29

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


          1. raveclassic
            16.01.2017 23:44

            Да и вообще, если использовать tagged unions и тип never с парой флагов из последнего TS, то это один в один ADT и pattern matching с полноценными exhaustive свитчами, так что все эти нагромождения абстракций ради нагромождения абстракций становятся ни к чему.


  1. raveclassic
    16.01.2017 12:45

    del