Россия, Московская область, Сергиев Посад, Пограничная улица
Телефон:
+7 (915) 422-22- Показать номер
Пн-вс: 09:00—21:00
whatsapp telegram vk email

Как избавиться от if else c

Заменить if/else на оператор while/if

И я очень хочу избавиться от if/else и просто использовать if/while строит.

Достаточно ли для языка Тьюринга Полного иметь while/if заявления (для потока управления)?

Как бы я сделал это с if/else строит?

(это не домашняя работа, это из любопытства)

5 ответов

Это несколько неуклюже, но, предполагая, что выполнение любой ветви не меняет условия, вы можете заменить конструкцию if-else на пару whiles: предположим, что оригинал:

Может быть заменено только if s:

Для общей замены if() . else . построить, вы можете кэшировать результат условия:

Таким образом вы избежите проблем с побочными эффектами в cond1 а также exec1 ,

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

Тебе даже не нужно while вам просто нужно уметь сравнивать и ветвиться, другими словами, if а также goto , В следующей программе функции test_normally() а также test_subnormally() эквивалентны:

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

test_subnormally() на самом деле очень близко к тому, что на самом деле делает процессор, после того, как ваш C-источник скомпилирован в машинный код. Вот выход сборки test_normally() из gcc на 64-битном процессоре Intel — вы можете видеть, что между инструкциями по сборке и источником C для другой функции есть почти однозначное соответствие, test_subnormally() :

Компилятор здесь решил поместить части цикла в несколько ином порядке, но кроме этого он читает почти так же, как исходный код C для test_subnormally() , Основная причина у нас есть if , а также while и их друзья в C именно так, что нам не нужно писать код, похожий на этот, но этот уровень простоты и спагетти-подобного кода — это то, что процессоры в конечном итоге выполняют (и это все, что вам нужно для того, чтобы завершено), поэтому у нас есть компилятор, который превращает что-то, что выглядит более понятным и понятным для людей, в тот беспорядок, которым процессоры совершенно довольны.

Как отключить условие if при определенных условиях

Как убить сессию при определенных условиях Как убить сессию при определенных условиях.

imageКак изменить картинку в pictureBox при определенных условиях Подскажите как изменить картинку в пикчербоксе при определённых условиях?

Как сделать кнопку активной только при определенных условиях Добрый день. Есть форма на которой 3 текстбокса и одна кнопка. Я делаю ее Enabled = False. Как.

Qt/WinApi Как установить фокус окну при определенных условиях? Здравствуйте! В общем имею следующую проблему: есть приложение, главное окно скрыто, по комбинации.

Как избавиться от if-else при помощи команд и обработчиков

image

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

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

Рассматриваемый в статье способ — просто один из многих.

Сама по себе конструкция if-else не так уж плоха. Мы просто попали в ситуацию «когда в руках молоток, всё вокруг кажется гвоздями». В основах программирования мы изучаем условные операторы и многим разработчикам не удаётся перерасти их использование.

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

Мы стремимся избегать традиционного условного ветвления

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

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

image

Сложное ветвление, вызывающее головную боль

Кроме уродливого использования if-elseif-else основная проблема заключается в том, что нужно добавлять ветвление для каждой новой причины обновления. Это явное нарушение принципов открытости/закрытости и единственной ответственности.

По сути, каждую ветвь можно преобразовать в собственную команду и соответствующие обработчики.

Давайте посмотрим, как это возможно.

Использование команд и обработчиков для упрощения приложения

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

  1. Тестирование становится намного проще. Не нужно дополнять существующие тесты для учёта новых возможностей. Если команда требует дополнительной обработки, мы создаём ещё один обработчик, который тестируется независимо.
  2. Несколько обработчиков может обрабатывать одну команду. Как вы уже наверно заметили, передача одной команды может вызвать один или несколько обработчиков. Таким образом, можно добавлять новую функциональность, не касаясь старого кода.
  3. Простые классы. Команда — это набор свойств без сеттеров. Ошибиться здесь будет сложно. Аналогично, обработчик — это класс только с одним публичным методом.
  4. Действия контроллера подчиняются шаблону Request-Delegate-Response. Они не содержат никакой бизнес-логики и слоёв хранения данных.

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

Наконец-то код!

Чтобы можно было следить за кодом, позвольте вкратце рассказать, чего мы хотим достичь.

Мы хотим сказать: «Так, должно произойти нечто. Вот значения. Мне не важно, кто этим займётся, просто дайте знать, когда всё будет готово».

Существует три критерия, которые нам нужно удовлетворить:

  1. Команду можно выполнить так, чтобы вызывающий не знал конкретных обработчиков.
  2. Необходимо выполнить каждый обработчик, соответствующий команде.
  3. Новые команды или этапы обработки не должны требовать изменения существующего кода.

Начнём с самого внешнего слоя и дойдём до самого дна

Если смотреть не с точки зрения контроллера, то нам не важно знать конкретные обработчики и даже интерфейсы. Действие должно быть сосредоточено только на данных.

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

image

Обновление конечной точки электронной почты

Примерный смысл кода должен быть вам понятен, хоть это и C# aspnetcore. Если вкратце, это действие контроллера — конечная точка и её реализация.

Я знаю, о чём вы думаете: «а где обработка ошибок?» Не волнуйтесь, вы правы, она должна здесь быть, но ради краткости я вырезал её, чтобы можно было сосредоточиться на концепции выполнения команд.

Контроллер имеет зависимость от CommandDispatcher . Мы доберёмся до этого класса позже. Класс диспетчера имеет единственный метод DispatchAsync(command) . Это пока всё, что вам нужно знать.

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

Каждая «причина обновления» (update reason) требует собственной конечной точки со своей формой данных, то есть отправляемой командой.

На этом этапе для реализации новых функций, например «update username», достаточно создать новую конечную точку и отправить команду.

image

Конечная точка Update username

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

По сути, наша конечная точка уже готова.

Итак, давайте двигаться дальше.

В командах и обработчиках находится вся бизнес-логика

При работе с командами нам нужно заботиться о двух аспектах: неизменяемости и корректности данных.

Это просто старые добрые классы, в них нет ничего сложного. Взгляните на этот ChangeEmailCommand .

Старый добрый класс команды

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

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

Простой обработчик команды, который легко тестировать.

Во-первых, у нас есть интерфейс, который должны реализовать все обработчики команды. Интерфейс важен, когда нам нужно обнаружение динамических типов. Скоро мы к этому вернёмся.

Во-вторых, я создал простой обработчик, знающий, как работать с ChangeEmailCommand . Обобщённый параметр ICommandHandlerAsync сообщает нам «этот обработчик нужно вызывать при передаче команды ‘change email command’».

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

Если вы привыкли к классам «Service», то знаете, какими безумными иногда становятся конструкторы. Такой подход полностью устраняет возможность разбухания конструктора.

Сам диспетчер, невероятно простой и надёжный

Вы уже видели интерфейс диспетчера, он понятен и прост. Но давайте освежим воспоминания.

Публичный интерфейс диспетчера команды

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

Мы хотим сказать: «вот команда, найди все соответствующие ей обработчики и передай команду каждому из них».

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

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

Диспетчер команды с поиском по словарю

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

Сам код в описании не нуждается. Это обычный C#. Важно только его назначение.

Самая важная часть — наличие механизма сопоставления команд с обработчиками. Для этого я использую словарь.

Каждый ключ — это тип команды. Соответствующее ему значение — это список обработчиков, реализующих ICommandHandlerAsync .

При вызове, например, DispatchAsync(ChangeEmailCommand) , диспетчер пытается найти внутри словаря ключ «Type: CommandHandler» и вернуть список зарегистрированных обработчиков.

Затем вызывается каждый обработчик.

Вот и всё. Это довольно просто.

Соединяем это всё с обнаружением динамических типов

На самом деле, на данном этапе ещё ничего не работает.

Нам нужно передать где-нибудь Dictionary диспетчеру команды.

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

Помните наш третий критерий? Новые функции/требования не должны требовать изменения существующего кода.

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

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

У нас снова есть код на C#, который совершенно не относится к делу, но, возможно, покажется кому-то интересным.

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

Обнаружение динамических типов при запуске программы

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

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

Я писал код таким образом намеренно. Естественно, чтобы он был более приятным, нужно было бы провести рефакторинг с извлечением методов. Однако для демонстрации это вполне приемлемо.

Смысл в том, что написав этот код один раз, вы больше не должны будете его касаться. Достаточно написать юнит-тесты, и всё будет в порядке.

На правах рекламы

Эпичные серверы — это VPS на Windows или Linux с мощными процессорами семейства AMD EPYC и очень быстрыми NVMe дисками Intel. Спешите заказать!

Программное обеспечение без конструкции if-else

Не откладывая в долгий ящик скажу: зачастую конструкция if-else — плохой выбор. Её использование приводит к сложным конструкциям, снижает читаемость кода и усложняет рефакторинг.

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

Некоторые живут по правилу: if-else молоток, всё остальное — гвозди.

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

Я продемонстрирую несколько приёмов и шаблонов, которые помогут положить конец этой неприятной практике. Сложность будет возрастать с каждым примером.

1. Совсем лишние блоки else

Это самая распространённая ошибка начинающих разработчиков. Ниже яркий пример того, как вы проигрываете, используя if-else:

Простой if-else

Выражение легко упростить, просто убрав блок else :

Без блока else

Выглядит более профессионально, не так ли?

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

2. Присвоение значений

Если вам нужно присвоить значение переменной на основе некоторого ввода, бросьте эту возню с if-else — существует намного более читаемый подход:

Присвоение значения с использованием if-else

Выглядит ужасно, даже несмотря на простоту. Прежде всего, if-else легко заменяется оператором выбора. Но можно ещё упростить код, удалив else if и else .

/>Оператор if с быстрым return

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

3. Проверка входных условий

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

Скажем, у нас есть вышеописанный метод DetermineGender с требованием, чтобы входное значение равнялось 0 или 1:

Метод без проверки значений

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

Применив граничные операторы метода безопасного программирования, сначала проверяем входные значения и только потом выполняем метод if:

Проверка входных условий с помощью граничных операторов

На этом этапе мы убедились, что основная логика выполняется, только если значения находятся в ожидаемом диапазоне.

Операторы if также заменены на тернарные, так как больше нет смысла возвращать значение “Unknown”.

4. Превращение if-else в словарь — полностью избегаем if-else

Скажем, нужно выполнить некоторую операцию, выбранную на основе некоторого условия, и заранее известно, что позднее нужно будет добавлять ещё операции:

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

Зная, что впоследствии нужно будет добавлять ещё операции, превратим if-else в словарь:

Читаемость выросла, код проще понять.

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

5. Расширение приложений — полностью избегаем if-else

Немного более продвинутый пример

Необходимо уточнить — это более “корпоративный” подход. Нетипичный сценарий “давайте заменим if-else”. Теперь можно продолжить.

If можно полностью заменить объектами.

Довольно часто необходимо расширить какую-то часть приложения. Как начинающий разработчик вы можете просто использовать дополнительный оператор if-else (или else-if).

Рассмотрим пример: нам нужно представить экземпляр Order в виде строки. Во-первых, у нас есть только два типа представления строки — JSON и простой текст. Использование if-else на этом этапе не является проблемой, но мы с лёгкостью можем заменить else if на if , как было показано выше.

Однако этот подход определённо не приемлем, если эту часть приложения нужно будет расширять.

Мало того, что код выше не соответствует принципу открытости/закрытости, его трудно читать и поддерживать в долгосрочной перспективе.

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

Рефакторинг этого беспорядочного куска кода выглядит так:

  1. Извлекаем каждую ветвь в отдельный стратегический класс с общим интерфейсом.
  2. Динамически находим все классы, реализующие общий интерфейс.
  3. На основе входных данных решаем, какую стратегию использовать.

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

Но динамическое расширение приложений — сложная тема.

Я продемонстрировал только часть, заменяющую пример с if-else. Здесь можно увидеть все вовлечённые объекты.

В идеале обнаружение типов и словарь предоставляются снаружи метода PrintOrder. Но в любом случае давайте пробежимся по коду выше:

Ссылка на основную публикацию
Похожее