Прекратите использовать оператор If-else

Original author: Nicklas Millard
  • Translation
image

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

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

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

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

Для тех, кто все еще находится в неведении, вот очень краткое введение.

Вы увеличите сложность с любым новым условным требованием, реализованным с помощью If-else.

Применяя шаблон состояния, вы просто изменяете поведение объектов, используя специализированные объекты состояния вместо операторов If-else.

Прошли те дни, когда код выглядел примерно так, как показано ниже.

image

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

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

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

Даже лучше, это сделает вашу кодовую базу более SOLID, за исключением буквы ”D".

«Хорошо, я поверил, что if-else — это зло, теперь покажите мне, как избежать беспорядочного ветвящегося кода»

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

Давайте создадим очень простой класс Booking, который имеет несколько состояний. Он также будет иметь два паблик метода: Accept() и Cancel().

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

image

Рефакторинг ветвящейся логики из нашего кода представляет собой трехэтапный процесс:

  1. Создать абстрактный класс базового состояния
  2. Реализовать каждое состояние как отдельный класс, наследующийся от базового состояния
  3. Пусть класс Booking имеет приватный или внутренний метод, который принимает класс базового состояния в качестве параметра

Реализация


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

image

Обратите внимание, что этот класс также имеет два метода, Accept и Cancel — хотя здесь они помечены как внутренние.

Кроме того, базовое состояние имеет ”специальный" метод EnterState(Booking booking). Он вызывается всякий раз, когда объекту бронирования присваивается новое состояние.

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

image

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

Наконец, сам класс бронирования.

image

Посмотрим, класс бронирования — это просто делегирование полномочий осуществления принятия и отклонения?

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

Как бороться с новыми условиями


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

Это очень просто. Вам больше не придется иметь дело с громоздким оператором if-else.

Как сохранить объект состояния в базе данных


Объект состояния не имеет значения при сохранении объекта, например, в базе данных SQL или NoSQL. Важно только знать состояние объекта и то, как оно должно быть отображено в столбце.
Вы можете сопоставить состояние с читаемым именем, перечислением или целым числом. Все, что вам удобно, пока у вас есть какой-то способ преобразовать сохраненное значение обратно в объект состояния.

Но ты все еще используешь «if»


Да, он очень важен. Особенно при использовании в качестве проверочных точек. Именно сочетание If-else является основной причиной головных болей поддержки кода.

Это же куча дополнительных классов


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

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

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя платные онлайн-курсы SkillFactory:



Читать еще


SkillFactory
Онлайн-школа Data Science и разработки

Comments 49

    +20

    Вроде первоначальный вариант был прекрасен. Зачем вы всё испортили и обмазали всё паттерном?)

      0

      Потому что нельзя так просто написать "void main(){}".
      Это не модно За такое не платят.

        +8

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

        +19

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

          +1
          Я ещё слышал что-то насчёт goto…
          +30
          Простите, я один вижу что стало хуже?
            +14
            Автор похоже узнал про существование FSM. Когда автор узнает про map/reduce будет новая статья про отказ от циклов :)
              –1
              кажется уже было…
                +1
                А концепция, на которой работает SQL, просто повергнет его в экстаз.
                +10
                Вариант на if-else выглядит наиболее понятным
                  –1

                  tldr: используйте для состояний классы, а не пеленки if-else

                    +11
                    Наверное, просто пример выбран неудачно. Мои мысли при взгляде на исходный код, с if-else:
                    здесь закодирована кака-то бизнес-логика, вложенные условия, кривое форматирование, жызнь боль


                    При взгляде на простыню без if но с классами:
                    что тут воще происходит?
                      +3

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


                      Технически, каждый if — это +1 к сложности кода. Одна функция с 4 условиями многократно сложнее для поддержки и понимания, чем 4 функции каждая с 1 условием.
                      Вдобавок, условие внутри именованной сущности как класс или функция, немедленно говорит о некоторых нарушениях принципа единой ответственности.
                      Важная ремарка: критически относиться следует к условиям которые меняют логику сущности, а не её внутренней реализации.
                      Использование условий следует изолировать в отдельную именованную сущность для сохранения сложности кода, вариации поведения выделять в родственные сущности за общим интерфейсом, который определен вызывающим кодом.


                      // Плохой пример. Избегайте условий глубоко в бизнес-логике.
                      void do_action(bool isWrite ){ 
                        if(isWrite){
                          do_read();
                        } else {
                          do_write()
                        }
                      }
                      /////////////
                      // Допустимо использовать условия, как часть внутренней реализации, но не рекомендуется. Имеет смысл смотреть в функциональные парадигмы и подходы. 
                      int do_search( std::vector<int> collection ){ 
                        for(auto i : collection){
                          if( is_value_good_for_you(i) ){
                             return i;
                          }
                        }
                      }
                      /////////////
                      // Предпочтительное использование условий, вне бизнес-логики, для предварительной настройки взаимосвязей между сущностями.
                      handler_t* do_handler_configuration( config_t config ) {
                        if(config.use_A){
                           return new a_handler_t();
                        }
                        return new general_handler_t();
                      }
                      
                        –1
                        Я бы не был так категоричен в суждениях.
                        Возьмите на старте любой проект, для него идеально куча if-ов в бизнес логике, так как непонятно что делать и как, да и постоянно какие-то задачи уровня вкрутить акцию для новых пользователей, сделать интеграцию с этими (ой не подошли, давай с другими), собрать какую-то статистику, и прочее. В куче if проще разобраться, чем помнить все связи классов и как они менялись за последнее время. Важна гибкость.

                        Когда же проект устоялся, то да тут надо рефакторить и выносить части в классы, модули и накручивать другие абстракции. Тут уже важна поддерживаемость.
                          +2

                          Согласен, здравый смысл в первую очередь.


                          Но кучей if-ы становятся уже после трёх в одной функции, потому что цикломатическая сложность приближается к 10. А бизнес-логика меняется так часто и неопределенно, что где 3 условия — там и 4, 5, 6, а потом еще и с force-флагом откуда-нибудь из глобальной области видимости.


                          Оверинжиниринг на 100500 утилитных классов связанных на внешнем слое — не совсем правильный подход к запуску нового продукта на неизведанном рынке. Но и намеренно макаронить код, оправдывая "Ща зарелизим, потом рефакторинг" — не выход. Как показывает практика — выделенный этап рефакторинга для устранения макарон из условий очень тяжело продать в продакт менеджмент, ибо новых фич не обещает, а гипотетическая поддерживаемость через год менее важна, чем 100 новых фич, каждая со своим условием, чтобы обратно совместимо.


                          И ростки плохой архитектуры начинают загнивать. А ростки хорошей — всегда можно вырастить в нужную сторону.

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

                            Менеждменту продать разработку супер архитектуры тоже тяжело — потому что дохода нет, а деньги теряем каждый день. А потом при кардинальном изменении бизнес требований опять все сначала в плане бумажной работы и рефакторинг.

                            Я не против хорошей архитектуры, я против преждевременной оптимизации архитектуры.
                              0

                              Целиком и полностью разделяю ваше мнение. Преждевременная оптимизация — время и деньги на ветер.

                                0

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


                                Микросервисы — ни разу не панацея и в большинстве случаев приведет только к замедлению разработки и усложнению поддержки.

                                  0

                                  Микросервисы уменьшают сложность путём чёткого очерчивания границ модулей, резко увеличивая сложность их нарушения. В монолите нам, например, обычно не составит труда к запросу на получение юзера из таблицы аутентификационных данных подтянуть ещё и данные о непрочитанных сообщениях. Но запрос получится сложным, можно и налажать легко, например inner join использовать вместо left join и получить что пароль невереный, хотя просто нет непрочитанных сообщений. А в микросервисах это будет два простых запроса, налажать в которых значительно сложнее.

                                    0
                                    Это все делается прекрасно и в монолите — просто поделите на модули. А чтобы джунам было неповадно нарушать границы, есть code review и всякие автоматизированные тула аля JArchitect / ArchUnit (для Java). Если же нет нормального code review и статического анализа, то и микросервисы выйдут такими, что поддерживать это будет нереально.

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

                                    Можно говорить о том, что должны существовать умные дядьки, правильно ограничивающие границы и т.п. Но в Java это уже все проходили, причем неоднократно — куча идей, проектов, стандартов и мечтаний о том, что архитекторы нарисуют диаграммы классов, а джуны закодят. И все это выливается все равно в монструозные и неподдерживаемые системы.

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

                                      Это если язык поддерживает модули более-менее, то ещё можно как-то. А если вся надежда только на код-ревью, то разделить процессы надёжней. Ну и продать бизнесу идею перехода на микросервисы может оказаться проще чем идею "а давайте всё перепишем, а то границы модулей нарушены". А так "под шумок" и те же CI/CD, и автотесты, и тулзы можно внедрить. В общем каша из топора :)


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

                                0
                                уже после трёх в одной функции, потому что цикломатическая сложность приближается к 10

                                Эм, цикломатическая сложность цепочки вызовов из 3 виртуальных функций, каждая из которых на 2 класса, тоже приближается к 10.
                                'if (cond) else' можно заменить на 'jmp if_table[(int)cond]', что по цикломатической сложности ничем не отличается от вызова виртуальной функции 'call object.virtual_table[function_index]'.

                              +1
                              Простите, а где вы Java увидели? Ни в статье, ни в вашем примере кода ее нет.
                              –2

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


                              Пример функции без else if
                              function shmunction(data) {
                                if(notValid(data)) return false
                                if(specialData(data)) return transformed(data)
                                return data
                              }
                                +1

                                Что только не придумают когда нету сум-типов.

                                  0

                                  А зачем тут сум-типы? Конкретно в этой задаче достаточно перечисления...

                                    0

                                    Потому что внутренние поля при разных состоянихя разные. Типа CancelationReason в одном случае и TransactionId в другом. И даже если одинаковые внутренее данные получаем больше на уровне типов, т.к. функция PrepareRoom должна принмать только AcceptedBooking а не BaseBooking внутри которого enum BookingState.
                                    Может я чуть отклонился от терминологии в посте, но мысль надеюсь понятна.

                                  +1
                                  Какая-то странная реализация алгебраических типов и паттерн-матчинга через наследование.
                                    0
                                    Интересно, что автор статьи:
                                    Nicklas Millard works at one of the Big4 Consulting companies in Denmark as a Senior Technology Consultant. He’s primarily taking the role as lead developer and solution architect on client projects.
                                    He’s been developing software for commercial clients and government institutions such as the Ministry of Defence, Ministry of Education, Ministry of Environment and Food of Denmark, The National Police, Danish Agency for Labour Market and Recruitment, and Ørstad.
                                      +2
                                      Вы совместили 2 паттерна «Стратегия» и «Состояние», и получилось это не лучшим способом.
                                      Я понял направление статьи сам некоторые вещи решаю так, но делаю немного иначе так как ваши примеры нарушают некоторые принципы SOLID + «Просто добавить новый класс» в такой реализации не выйдет. Почему не выйдет? Что Вы будете делать когда в каком-то состоянии 2 из 3 методов реализовать просто нельзя? NotImplementedException? null — нарушаем LSP.
                                      Что Вы будете делать когда в каком-то состоянии 2 из 3 методов должны иметь ту же реализацию что и в другом стейте?

                                      Я рекомендую сильнее дробить реализации этих паттенов, то-есть брать не целый Агрегат и описывать его состояния такими классами, а реализовывать стратегию\состояние с одним методом (Handle, Execute, Run, MoveNext, etc). В случае «Стратегии» — просто что-то делаем, «Состояние» — что то делаем и возвращаем новое состояние.

                                      При таком дроблении у вас будет на каждый метод (Enter, Cancel, Accept) свои пачки разных реализаций, где-то 2, где-то 3, где-то 5, а какой-то класс выше уже будет всем этим рулить. Хотя и тут уже не все тривиально выглядит :), ведь все равно кто то будет через if-else выбирать реализацию

                                      Updated:
                                      Жалко что статья — перевод, автор не обсудит мои мысли со мной
                                        0
                                        Это наверное дискуссионный вопрос, но вроде бы «стратегия» и так является частью «состояния». Ну то есть стратегия определяет семейство похожих алгоритмов с одинаковым интерфейсом, состояние — это стратегия применительно к бизнес логике + данные о непосредственно выбранном варианте.
                                        Стратегия вообще универсальная и используется по факту и в «команде» и в «итераторе» (частный случай — алгоритм перебора коллекции)

                                        Но в остальном согласен.
                                        Ну то есть все с if-else нормально, но состояние — хороший способ их декомпозировать.
                                        Просто тут примеры выглядят неудачными
                                        +1
                                        Жалко что статья — перевод, автор не обсудит мои мысли со мной

                                        Надо привлекать англоязычную аудиторию на хабр. И обсуждать с ними на инглыше.
                                        Потому что пока там тишина и мертвые с косами стоят :)

                                          +2

                                          Да там нормально комментов, чего: https://medium.com/p/f4d2323e6e4/responses/show
                                          И самые заплюсованные, конечно, "нет, спасибо".


                                          ЗЫ:
                                          Привлекать аудиторию, конечно, надо, но перетягивать из оригинала — нелепо.
                                          Там есть возможность комментирования и связи с автором — хочется что-то сказать — значит надо идти туда.
                                          Перетягивать надо оригинальными (перевод русскоязычной — тоже оригинал) статьями на английском, что многие тут уже делают.
                                          Вот если б кто написал ответную статью, кто-то перевёл её — тогда можно было бы скинуть автору, мол, приходи, комментируй к нам.

                                            +1

                                            Примерная расшифровка треда в ответе на первый заплюсованный комментарий
                                            К: комментатор, А: автор


                                            К: Заголовок желтоват, и делать заключение, что if..else плохое заменяя не очень распространённым подходом — неправильно

                                            -


                                            А: Заголовок может и желтоват, но это действительно моё мнение, я не использую конструкцию if..else в проде уже два года

                                            -


                                            К: Серьёзно? Два года? Да ты не программируешь вообще!

                                            -


                                            А: Не, ну, я пишу тыщи IFов. Но if..else? Не очень много. Конструкция if..else редко необходима, часто это признак плохо структурированного и плохо расширяемого кода.

                                            -


                                            Такие дела. Из чего у меня напрашивается самый очевидный вывод — автор предпочитает early-return, предложенный (и заминусованный) даже тут где-то в комментариях.


                                            ЗЫ: Ну и странный же тут MD форматтер.

                                            0
                                            Мне кажется сами условия — это неотъемлемая часть сложности предметной области, и вопрос лишь как ими управлять.
                                            С самими if — else все нормально, их можно и нужно использовать.
                                            Просто нужно понимать ограничения и сложности.
                                            Но тут есть несколько разных проблем — большое количество условий и дублирование условных операторов на разных уровнях.
                                            Первая проблема — большие каскады else if плохо читаемы. Их нужно просто декомпозировать по правильным модулям и сводить к плоским случаям в духе «либо выполнил операцию, либо вернул false ранним выходом». Ну и про нейминг не забывать.
                                            Вторая проблема — множественные проверки, связанные с flow.
                                            Их характерная особенность — когда в нескольких методах встречаются похожие наборы ифов или даже switch.
                                            В этом случае да — речь скорее всего идет о том, что нужно использовать полиморфизм и состояния.

                                            В остальных случаях все с ифами нормально. А если у вас нет хороших идей по организации кода — даже свич на 1000 строк неплохой архитектурный паттерн.
                                              0
                                              Первым был if-then-else. Сейчас конечные автоматы. Ждём третий этап — схемы и движки BPM. :)
                                                +2

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


                                                Я в таких случаях использую таблицы решений (decision tables). Это наглядный метод табличного описания алгоритмов, который описываются большим количеством логических операторов if и switch.


                                                Я ними познакомился в 2000 году во время работы над проектом, который реализовывался по методологии Хэтли-Пирбхаи. С тех пор я регулярно использую их как для написания нового кода, так и для анализа существующего кода.


                                                Это очень давно известная методика. Хорошее описание можно найти в книге издательства "Мир" 1976 года: Э.Хамби "Программирование таблиц решений".


                                                Методология Хэтли-Пирбхаи, одной из составных частей которых является использование таблиц решений, описана в книге Hatley & Pirbhai "Strategies for Real Time System Specification" https://www.amazon.co.uk/gp/product/0932633110 Есть более свежее издание: https://www.amazon.co.uk/gp/product/0932633412

                                                  +2
                                                  Если у автора стояла цель показать пример антипаттерна, то у него это получилось.

                                                  Я искренне честно прочитал эту статью два раза, чтобы исключить свою предвзятость. Рассуждения автора мягко говоря перекликаются с книгой Elegant Objects. Возможно такие «паттерны» берутся от слабого понимания промышленного кода и требований к нему. Ведь ни один не приводит примеры такого реально работающего кода в больших проектах на миллион (или в этом случае «на миллиард») строчек. У автора прекрасно получилось обмазать простой до глухого щелчка код абстракциями и паттернами, растянуть его примерно в 10 раз. Вы поймите меня правильно, этот высосанный непонятно из какого места пример очень пахнет оверхедом, вот и все, это такой же «запах» в коде как дублирование или глупые комментарии.
                                                  Порой я думаю, что такие статьи пишутся ради статьи.
                                                    –1
                                                    Немного оффтоп, но вот странно: у меня в коде
                                                    for(int i=0;i<10;i+=1){
                                                     if(condition1){do_stuff_1;}
                                                     if(condition2){do_stuff_2;}
                                                     if(condition3){do_stuff_3;}
                                                    }
                                                    

                                                    не проверяются условия condition1 и condition2, а когда я убираю условие condition3, то эти условия проверяются.
                                                    Поставил в первые два условия булевую переменную bool
                                                    for(int i=0;i<10;i+=1){
                                                     if(condition1){do_stuff_1; bool=true;}
                                                     if(condition2){do_stuff_2; bool=true;}
                                                     if(condition3 && !bool){do_stuff_3;}
                                                     }
                                                    

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

                                                      +1

                                                      Звучит как бред. Скорее всего всё у вас проверяется, просто блок do_stuff_3 отменяет то, что делалось в do_stuff_1 и do_stuff_2.


                                                      Возможно, эти условия достаточно поменять местами. А может, как раз нужен критикуемый тут if-else. Впрочем, ваш вариант с флагом тоже нормальный, если только не называть переменную bool.

                                                        –1
                                                        просто блок do_stuff_3 отменяет то, что делалось в do_stuff_1 и do_stuff_2

                                                        Да, действительно.
                                                        В итоге у меня
                                                         if(condition1 && !bool){do_stuff_1; bool=true;}
                                                         if(condition2 && !bool){do_stuff_2; bool=true;}
                                                         if(condition3 && !bool){do_stuff_3; bool=true;}
                                                         if(condition4 && !bool){do_stuff_4; bool=true;}
                                                        

                                                        но использование флага выглядит как костыль.
                                                        Может можно, как в статье, создать класс с двумя методами Accept и Cancel
                                                        (далее создать два отдельных класса для двух состояний)?
                                                          +1

                                                          Самое время начать использовать цепочки if — else if — else if...

                                                            0
                                                            Ладно, попробую)). Просто хотелось разобраться в коде из статьи, но на более простом примере. Можно ли создать класс, например, myBool. Тогда проверка условий должна, наверное, выполняться методами класса, т.е.
                                                            myBool.accept
                                                            myBool.cancel

                                                              0

                                                              Вы пытаетесь использовать ООП для решения несуществующей проблемы.


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

                                                                0
                                                                Спасибо
                                                      0
                                                      Кто-то прочитал книжку про паттерны и теперь не знает куда же ещё можно их запихать.
                                                        0

                                                        Кажется, только меня одного смутило то, насколько бездарно переведена статья. Я тут недавно, может, тут не принято критиковать форму. Но только сама подача материала мотивирует не читать дальше, но продолжать использовать if-else.

                                                          –1

                                                          Извините меня за грубость, конечно, но это онанизм.
                                                          Это сроде малолеткам, которые только начали осваивать язык, и пытаются всячески выпендриться перед своими сверстниками, заявляя: "А смотрите, как Я МОГУ!"


                                                          • Конечные автоматы?


                                                          • Не, не слышали!


                                                          • Плодим новые сущности без сильной надобности?


                                                          • Ну и фиг с ним!


                                                          • Код будет выполняться медленнее?


                                                          • Да кого это заботит!


                                                          • Отладка всего этого дела превращается в ад?


                                                          • Да ты просто вот эту крутую IDE не осилил, кекеке!



                                                          Отвратительно до боли.

                                                            0
                                                            Боюсь показаться очевидным, но когда нужен if-else нужно использовать if-else, а когда нужны состояния нужно использовать состояния, не нужно использовать одно вместо другого.

                                                            Приведенный пример if-else ужасен, идея с состояниями имеет право на жизнь но реализация здесь точно так же ужасна

                                                            Only users with full accounts can post comments. Log in, please.