Паттерны написания кода на JavaScript, выдающие разработчика-джуниора

0
752
views

Перевод статьи «JS Coding Patterns that give you away as a Junior Developer».

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

Большинство проблем в программировании возможно решить многими способами, и оценить, какой способ лучше, а какой — хуже, может быть не так-то просто. Порой все сводится к личным предпочтениям, а порой какие-то подходы просто лучше других. («Лучше/хуже» имеется в виду с точки зрения производительности, краткости, читаемости).

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

1. Злоупотребление if и else

Скажем, мы пишем класс, используемый для представления персонажей «Симпсонов». Конструктор класса принимает имя, фамилию и род занятий персонажа.

В коде, приведенном ниже, создается этот класс и инициируется экземпляр — edna.

class Character {
  constructor (firstName, lastName, occupation) {
    this.firstName = firstName
    this.lastName = lastName
    this.occupation = occupation
  }
}
const edna = new Character(
  'Edna',
  'Krabappel',
  'Elementary School Teacher'
)

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

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

class Character {
  constructor (firstName, lastName, occupation) {
    this.firstName = firstName
    this.lastName = lastName
    this.occupation = occupation
  }

  get isSimpson () {
    if (this.lastName === 'Simpson') {
      return true
    } else {
      return false
    }
  }
}
const edna = new Character(
  'Edna',
  'Krabappel',
  'Elementary School Teacher'
)
console.log(edna.isSimpson) // Logs false, as expected

Этот код работает, но он излишне многословен. Для начала, блок else здесь не нужен. Если условие соблюдается, функция вернет значение и завершит работу: до альтернативы в блоке else дело не дойдет.

Поэтому мы можем упростить метод:

get isSimpson () {
  if (this.lastName === 'Simpson') {
    return true
  }
  return false
}

В целом, с точки рения стиля предпочтительнее избегать блоков else, потому что таким образом снижается вложенность. Это, конечно, не всегда возможно, но зачастую — вполне.

Но даже с таким улучшением метод все равно не слишком продуман. Поскольку геттер должен возвращать в качестве output булево значение, блок if вообще не нужен.

Этот код делает все то же самое:

get isSimpson () {
  return this.lastName === 'Simpson'
}

Теперь код стал намного лучше. Операторы сравнения часто комбинируются с if-условиями, но это вовсе не обязательно. Порой лучше вернуть булево значение напрямую.

Photo by Hussam Abd on Unsplash

2. Использование функционального программирования нефункциональным способом

С массивами в JavaScript можно работать либо процедурно, либо функционально.

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

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

Допустим, у нас есть массив объектов Character и мы хотим использовать эти данные для создания массива имен.

/ An example input array could look like this:
const characters = [
  new Character(
    'Edna',
    'Krabappel',
    'Elementary School Teacher'
  ),
  new Character(
    'Lisa',
    'Simpson',
    'Student'
  ),
  new Character(
    'Moe',
    'Szyslak',
    'Bartender'
  ),  
]
// In that case the output we are looking for would look like this:
[
  'Edna Krabappel',
  'Lisa Simpson',
  'Moe Szyslak'
]

Наш первый шаг — добавление к классу Character геттера, который будет возвращать полное имя персонажа:

get fullName () {
  return `${this.firstName} ${this.lastName}`
}

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

const names = []
characters.forEach(character => {
  names.push(character.fullName)
})

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

Вместо возврата значения каждая итерация цикла изменяет внешнюю переменную names. Тот же эффект можно легко достигнуть при помощи цикла for:

const names = []
for (let character of characters) {
  names.push(character.fullName)
}

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

const names = characters.reduce((names, character) => {
  return names.concat(character.fullName)
}, [])

Эта попытка позволяет избежать проблем, связанных с forEach, но решение по-прежнему не идеально.

Проблема кроется в слове «reduce». Функциональное программирование позволяет избежать объявления внешних переменных и мутации, но оно имеет еще одно важное преимущество: читаемость.

При помощи функциональных методов, таких как filter или reduce, можно сделать код более выразительным и читаемым. Конечно, если использовать их правильно.

Например, если программист видит, что массив «фильтруется» (применяется filter), он может предположить, что на вход подается некое множество элементов, а на выходе будет подмножество. Невыведенные элементы будут «отфильтрованы».

Аналогично, если программист видит, что к массиву применяется reduce (англ. reduce — сокращать, уменьшать), он может предположить, что функция принимает на вход набор данных и сокращает его, а значит, набор на выходе будет более компактным. Например, можно «сократить» список оценок до средней.

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

Возвращаемся к нашему примеру. Это решение не идеально, потому что слово «reduce» не точно описывает происходящее. Поскольку наша цель — возвращать в output по одному экземпляру для каждого входящего экземпляра, куда лучшим выбором будет map. Кроме того, это еще и короче:

const names = characters.map(character => character.fullName)

Итоги

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

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

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here