Три шага, чтобы перенести защищенные операторы в JavaScript для более безопасного и лучшего кода и отказаться от частого использования операторов «else».

Эдсгер В. Дейкстра, лауреат Премии Тьюринга и сотрудник ACM, изобретатель словосочетания структурированное программирование и один из первых факторов, способствовавших превращению компьютерного программирования в научную дисциплину, внес большой вклад в теорию программирования. В этой статье мы вернемся к его очень старой идее 1975 года! - чтобы обсудить, как вам следует прекратить использование операторов else (по крайней мере, в некоторых формах) и вместо этого применить защищенные операторы для более безопасного и ясного программирования. Мы покажем, что такое скрытые утверждения, затем увидим, какие проблемы могут быть с if утверждениями, и, наконец, мы увидим, как решить наши проблемы с помощью осторожных заявлений.

Охраняемые заявления

Что такое скрытые утверждения и как они работают? По сути, защищенный оператор - это обычный оператор, которому предшествует логическая защита, бинарный тест; оператор выполняется только в том случае, если его защита истинна.

conditionstatement

Должно быть очевидно, что вам не всегда нужны охранники; защищенные операторы уместны только в контексте структур выбора (примерно эквивалентных операторам if или switch) и структур повторения (сродни операторам while). Мы не будем здесь иметь дело со вторым, а сосредоточимся на первом, что, как мы увидим, делает код JavaScript более понятным и безопасным.

Охраняемый отбор

Давайте сначала проследим исходный стиль Дейкстры и посмотрим на структуру управления выбором, как он ее определил; позже мы сделаем (почти) эквивалентную версию на JavaScript. Мы будем использовать ту же нотацию, которую использовал сам Дийскстра, с if и fi, ограничивающими всю структуру, и символом «▯» (да, квадратный блок!), Используемым для разделения операторов.

if condition₁ → statement₁
 ▯ condition₂ → statement₂
 ▯ condition₃ → statement...
conditionₙstatementₙ
fi

Каждое защищенное утверждение состоит из предохранителя (условие, которое оценивается как истинное или ложное) плюс утверждение; оператор может быть выполнен только в том случае, если его защита верна. (Почему «может» вместо «должен»? Продолжайте читать!) Если два или более охранника верны, тогда у нас будет неопределенность, и любое из охраняемых утверждений (но только одно!) Будет выполнен. У нас есть простой пример в следующем коде, который устанавливает для переменной m максимум из двух переменных x и y.

if x ≥ y → m := x 
 ▯ y ≥ x → m := y
fi

Если x больше y, выполняется первая инструкция. Если y больше, выполняется вторая инструкция. И, чтобы было интересно, если x и y равны, любой из двух операторов будет выполнен - ​​но в любом случае программа будет (должна!) верный! Эта неопределенность может показаться странной, но на самом деле она помогает лучше понять программу; ясно видеть, что это правильно, и есть симметрия в коде, которая соответствует симметрии в определении максимума; элегантно!

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

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

if (condition1) {
   statement1
} else if (condition2) {
   statement2
} else {
   statement3
}

Когда выполняется второй оператор? Вы должны мысленно отрицать первое условие, чтобы ответить на этот вопрос; нетривиально. А когда дойдет до третьего утверждения? В этом случае вы должны отменить оба предыдущих условия и выполнить соединение этих результатов, чтобы получить ответ; Сильнее!

Отмена пропущенных охранников

Есть важная оговорка, которой вы, возможно, и не ожидаете: если ни один из охранников не соответствует действительности, структура if...fi прервет выполнение! В случае, если существует вероятность, что ни один из охранников не будет правдой, предоставляется skip заявление. (В JavaScript будет достаточно комментария или пустого блока.) Вы можете найти такой код; В частности, отметим последний вариант.

if condition₁ → statement₁
 ▯ condition₂ → statement₂
 ▯ condition₃ → statement₃
 ...
 ▯ conditionₙskip
fi

С логической точки зрения это равносильно утверждению, что дизъюнкция всех условий должна быть тавтологией: condition condition condition conditionₙtrue. Также помните, что порядок скрытых заявлений на самом деле не имеет значения; skip мог появиться в любой позиции.

Почему это так? Ключевым моментом является то, что разработчик при написании заявления о выборе должен учитывать все условия, в том числе те, в которых ничего не должно быть сделано. Добавление простого комментария к эффекту «здесь нечего делать» занимает всего минуту, не влияет на производительность разумным образом и помогает каждому будущему программисту, которому придется читать и понимать этот код. , так что это верная идея. Мы увидели новый стиль if заявлений; давайте теперь посмотрим, что не так с теми, которые у нас есть.

Проблемы с операторами if / else

Если вы посмотрите в Интернете, вы можете найти несколько статей, предлагающих альтернативы if/else операторам, такие как использование тройного ?: оператора, или работа с switch операторами, или применение библиотек сопоставления с образцом Например, Z или другие ... но в этой статье я предлагаю, что лучшим решением будет вернуться (путь!) Обратно к шаблону с именем guarded statement и продолжать использовать if операторы, хотя и в особый, ограниченный способ. Давайте сначала рассмотрим пару проблемных ситуаций, прежде чем думать, как их исправить.

Действительно ли бинарные опционы бинарны?

Давайте сначала проведем быструю проверку: что не так с этим кодом? И да, я признаю, что вопрос довольно сложный, потому что с кодом может быть все в порядке, но ...

if (gender==="male") { 
    ... // do something for males
}

Глядя на код, я бы, наверное, спросил себя: «правильно ли, что мы ничего не делаем для женщин? может быть, разработчик забыл об этом? ». Разработчик мог бы улучшить понимание кода, добавив else, как показано ниже - даже если добавленный код был просто комментарием, объясняющим, что для женщин ничего делать не нужно - и, возможно, он обнаружил бы, что он забыл какое-то важное дело!

if (gender==="male") { 
    ... // do something for males
} else {
    ... // do something for females
}

Это лучше - мы знаем, что разработчик не просто забыл один случай, а, скорее, он принял во внимание… и даже если ничего не нужно было делать, мы знаем, что он рассмотрел все варианты. Или он? Что произойдет, если поле gender получено из базы данных, а значение не было сохранено, поэтому gender могло бытьnull? (Или, альтернативный вариант: если поле gender появилось из раскрывающегося вопроса в опросе, в который был добавлен дополнительный параметр «Предпочитаю не говорить»?) Правильно ли мы справимся с этим кейс? Еще одна попытка исправить это может быть:

if (gender==="male") { 
    ... // do something for males
} else if (gender !== null) {
    ... // do something for females
}

Но, чтобы вам не слишком понравилось это решение, подумайте, что в других местах кода оно могло бы читаться так:

if (gender==="female") { 
    ... // do another thing for females
} else if (gender !== null) {
    ... // do another thing for males
}

elseoption слишком общий, и у нас проблемы - если gender отсутствует, мы даже не обрабатываем его последовательно в разных частях нашего кода! Наша изначально бинарная альтернатива (мужской / не мужской или мужской / женский) на самом деле имеет несколько вариантов, и наш код просто недостаточно хорош.

Неужели сложные условия слишком просты?

Давайте рассмотрим более сложное условие. Что вы думаете об этом коде?

if (citizen==="yes" && age>21) { 
    // do something for adult citizens
} else {
    // do something else for not adult citizens 
}

Хорошо, здесь те же проблемы, что и раньше; может быть, некоторые люди попадают в else часть, а им нет. Вы можете попытаться избежать некоторых проблем с новой версией, подобной этой.

if (citizen==="yes" && age>21) { 
    // do something for adult citizens
} else if (citizen==="no" || age<=21) {
    // do something else for not adult citizens
}

Возможно, это легче понять - по крайней мере, мне не нужно отрицать логическое выражение в своей голове! - но это действительно ничего не решает, даже добавляя возможность неправильной логики; что бы произошло, если бы age<=21 вместо этого было написано какage<21? Правильно, люди ровно 21 года «провалились бы в щель» и никуда бы не попали! Что еще хуже, мы не сможем легко обнаружить этот случай. Опять же, else часть является слишком общей и на самом деле недостаточно хорошо проверяет, когда ее следует выполнять. Другими словами, изначально у нас было сложное условие, а часть else была слишком общей, слишком простой, включая случаи, которых не должно быть.

В JavaScript тесты всегда бинарны (истина / ложь), но реальная жизнь часто предлагает больше возможностей, а двоичный if - всего лишь ожидающая ошибка. Кроме того, мы должны постараться быть как можно более конкретными: например, gender==="female" вместо общего gender!=="male"). Итак, проблема в предыдущих примерах в основном заключается в том, что мы не проверяем внимательно, при каких условиях мы выполняем определенные операции. Мы не можем просто так писать if утверждения; нам нужен более безопасный способ сделать это. Давайте повернем время почти на полвека назад и посмотрим на осторожные заявления.

Три шага для защищенного выбора в JavaScript

Теперь мы видели осторожные утверждения и распространенные утверждения if; как мы можем применить первое для лучшего и безопасного JavaScript? Есть всего три правила, которым нужно следовать:

  • Все if операторы должны заканчиваться else, вызывающим исключение, если предыдущая защита не была удовлетворена.
if (...) {
    ... 
} else throw new Error();
  • В противном случае вы никогда не сможете использовать простой else; он всегда должен быть else if с другим охранником.
  • Если вам нужен оператор skip, используйте комментарий, дающий более подробное объяснение причины, по которой ничего не требуется.

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

if (gender==="male") { 
    ... // do something for males
} else if (gender==="female") {
    ... // do something for females
} else throw new Error();

Вторым случаем, для взрослых граждан, станет:

if (citizen==="yes" && age>21) { 
    // do something for adult citizens
} else if (citizen==="no" || age<=21) {
    // do something else for not adult citizens
} else throw new Error();

Обратите внимание, что все проблемы здесь теперь исправлены - или, если быть более точным, по крайней мере, они будут обнаружены. Если gender будет чем-то другим, кроме «мужского» или «женского», будет выдана ошибка, и разработчик поймет, что какое-то предположение неверно, и исправит код. Во втором случае, если citizen имеет другое значение, а не «да» или «нет», или если программист напутал и неправильно написал age<21, всякий раз, когда обнаруживается непредвиденный случай, программа останавливается вместо того, чтобы слепо идти вперед и выдавать неправильные полученные результаты.

Резюме

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

Следуя примеру Э. В. Дейкстры, эту статью можно было бы назвать «утверждения If / else, считающиеся вредными», но я не хотел заходить так далеко; в конце концов, if утверждения - не проблема; ленивость и пропуск необходимых тестов - вот в чем причина.

Написание if заявлений таким образом просто добавляет немного многословности, но я чувствую, что дополнительная безопасность или предупреждения заслуживают этого; что ты думаешь?

Ссылки и дополнительная литература

Все от Дейкстры: его оригинальную заметку EWD 472 1975 года можно увидеть здесь, транскрипцию здесь и его CACM (Сообщения ACM) Защищенные команды, недетерминированность и формальная производная программ Статья здесь . Кроме того, нельзя пропустить его лекцию ACM Тьюринга 1972 года « Скромный программист » - и, если у вас получится, его книгу 1976 года Дисциплина программирования Тоже стоит прочитать.

Наконец, не пропустите его заметку EWD 215 « Дело против заявления GO TO », которая была опубликована в марте 1968 года как письмо с (теперь уже легендарным) Go - Заявление считается вредным ”заголовком. В этом письме Дейкстра раскритиковал «« спагетти-код », предложив взамен парадигму структурного программирования. Это письмо вызвало множество мнений, в том числе противоположную точку зрения с ответом ‘ GOTO считается вредным считается вредным» »!