Перевод статьи «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
-условиями, но это вовсе не обязательно. Порой лучше вернуть булево значение напрямую.
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, а также подбор подходящих методов — хороший шаг на пути к этой цели.
Именно такие маленькие детали позволяют определить, насколько опытный или неопытный программист писал этот код.
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]