Тернарный оператор в JavaScript: за, против, подводные камни. Часть 2

0
227
views

Перевод второй части статьи «Rethinking the JavaScript ternary operator».

В первой части статьи Джеймса Синклера рассказывалось о том, какие проблемы возникают при использовании тернарных операторов и if-предложений в JavaScript-коде. Также автор разобрал тему отличия тернарных выражений от if-предложений. Во второй части статьи Джеймс Синклер рассказывает о том, как сделать использование предложений более безопасным, а тернарные выражения — более читаемыми.

Ответственное и использование условий

Так что же делать? Тернарные выражения не хороши. Да и if-предложения тоже не безупречны. Что ж теперь? Использовать другой язык?

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

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

Некоторые предложения лучше других

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

Самые опасные предложения — имеющие блоки (части с фигурными скобками). Сюда входят if-предложения, циклы for, циклы while и предложения switch-case. Они опасны, потому что для них единственный способ сделать что-то полезное — вызвать какой-нибудь побочный эффект. Что-то, что должно выйти за зону видимости блока и изменить окружение.

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

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

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

Более безопасные if-предложения

Для написания более безопасных if-предложений я следую простому правилу. Первая ветка «then» («тогда») должна заканчиваться возвратом (return). Таким образом, хотя if-предложение само по себе и не вернет значение, его вернет внешняя функция. Например:

if (someCondition) {
    return resultOfMyCalculation();
}

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

Тернарные выражения с лучшей читаемостью

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

const ten     = Ratio.fromPair(10, 1);
const maxYVal = Ratio.fromNumber(Math.max(...yValues));
const minYVal = Ratio.fromNumber(Math.min(...yValues));

// Create four extra variables to label the bits that go in the
// ternary. It's now clearer what each calculation is for.
const rangeEmpty = maxYVal.minus(minYVal).isZero();
const roundRange = ten.pow(maxYVal.minus(minYVal).floorLog10());
const zeroRange  = maxYVal.isZero() ? Ratio.one : maxYVal;
const defaultRng = ten.pow(maxYVal.plus(zeroRange).floorLog10());

// Piece together the final ternary out of the variables.
const yAxisRange = !rangeEmpty ? roundRange : defaultRng;

Кто-то может заметить, что теперь мы делаем ненужные вычисления. Нам не нужно вычислять zeroRange или defaultRng, если rangeEmptyfalse. Чтобы этого избежать, мы можем использовать функции.

const ten     = Ratio.fromPair(10, 1);
const maxYVal = Ratio.fromNumber(Math.max(...yValues));
const minYVal = Ratio.fromNumber(Math.min(...yValues));

// Create two functions so we only calculate the range we need.
const rangeEmpty = maxYVal.minus(minYVal).isZero();
const roundRange = () => ten.pow(maxYVal.minus(minYVal).floorLog10());
const defaultRng = () => {
    const zeroRange  = maxYVal.isZero() ? Ratio.one : maxYVal;
    return ten.pow(maxYVal.plus(zeroRange).floorLog10());
};

// Piece together the final ternary using our two new functions.
const yAxisRange = !rangeEmpty ? roundRange() : defaultRng();

Теперь код стал намного длиннее, чем был. Но это не обязательно плохо. Мы же отдаем предпочтение ясности, а не краткости, верно? В нашем варианте намерение автора кода понятнее.

А как насчет вложенных тернарных операторов? Это всегда плохо? Нет. Если вы будете внимательны при вертикальном выравнивании, тернарные выражения даже с глубокой вложенностью могут легко читаться. На самом деле я часто предпочитаю их, а не предложения case-switch. В частности — когда у меня есть что-то вроде справочной таблицы. Например:

const xRangeInSecs = (Math.max(...xValues) - Math.min(...xValues));
// prettier-ignore
const xAxisScaleFactor =
    (xRangeInSecs <= 60)       ? 'seconds' :
    (xRangeInSecs <= 3600)     ? 'minutes' :
    (xRangeInSecs <= 86400)    ? 'hours'   :
    (xRangeInSecs <= 2592000)  ? 'days'    :
    (xRangeInSecs <= 31536000) ? 'months'  :
    /* otherwise */              'years';

Если вы используете инструмент форматирования вроде Prettier, вам придется его отключить. Для этого можете использовать комментарий, который я оставил в примере кода.

Это требует дополнительных усилий, но использовать тернарные выражения и if-предложения ответственно — вполне реально. Да, это имеет свою цену. Возможно, вам придется не только лишние усилия приложить, но и противостоять линтерам и стандартам написания кода. И люди точно будут запихивать в тернарные выражения слишком много всего. Это будет происходить и из-за лени, и из-за того, что они просто не умеют иначе. Но я считаю, что это все равно лучше, чем слепо считать if-предложения «безопасными».

О будущем

Несмотря на то, что мы можем писать надежные условия, наши варианты ограничены. Но есть надежда, что это изменится. Обратите внимание на предложение TC39 “do expressions”. Это дополнение к языку позволит нам превратить многие предложения в выражения. Например, мы сможем писать код следующим образом:

let x = do {
  if (foo()) { f() }
  else if (bar()) { g() }
  else { h() }
};

Блок do может содержать любое количество предложений и возвращать «законченное значение». То есть, последнее вычисленное значение перед окончанием блока do.

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

Это предложение было представлено собранию TC39 в июне 2020 года, но пока не продвинулось дальше первой стадии (по крайней мере, на момент написания этой статьи). Так что пройдет некоторое время, прежде чем изменения дойдут до браузеров и Node. А пока, если вам интересно, для этого есть Babel transform.

И напоследок. Возможно, было бы хорошей идеей пересмотреть оператор запятой. Но это тема для отдельной статьи.

Заключение

В принципе, большинство из нас согласны с тем, что ясность кода важнее краткости. В связи с этим не удивительно, что на тернарные операторы люди поглядывают искоса. Но стоит учесть, что стремление к краткости и желание выпендриться — не единственные причины для использования тернарных выражений. Я также советую вам обратить самое пристальное внимание на ваши if-предложения. То, что нам хорошо знакомо, не обязательно безопасно.

Кстати, если вы хотите настроить ESLint, чтобы указать свои предпочтения по части тернарных выражений, знайте, что Кайл Симпсон создал отличный плагин для ESlint. Лично я бы не оставлял значения по умолчанию. Но он в любом случае дает гораздо больше контроля, чем встроенные правила ESLint.

frontend logo

Хочешь проверить свои знания по фронтенду?

Подпишись на наш канал с тестами по HTML/CSS/JS в Telegram!

×

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

Please enter your comment!
Please enter your name here