Условные операторы Python — плохой код

Текстовая версия выступления на онлайн-конференции US PyCon 2020 в формате видео-выступления Элая Сивджея с сопровождением в виде презентации. Материал посвящен чрезмерному использованию дублирующихся и вложенных условных выражений, а также вариантам рефакторинга такого кода через абстракцию более высокого уровня. Почти полный (без некоторых отвлеченных замечаний) перевод с английского и часть слайдов.

Источник: If Statements are a Code Smell

  • Введение
  • Понятие об операторе if statement
  • Недостатки оператора if
  • Виды плохого кода и варианты рефакторинга
    • Составные условия
    • Вложенные условные выражения
    • Дубликаты условных выражений
  • Кейс с рефакторингом кода
    • Минимально жизнеспособный продукт
    • Расширение функционала
    • Абстракция более высокого порядка
    • Расширение функционала
  • О чем следует помнить
  • Заключение
  • Дополнительные материалы

Введение

Хочу сказать спасибо организаторам от Python и всем сотрудникам Python Software Foundation, потому что подготовить конференцию — это много тяжелой работы. Было трудно, но вы смогли перенести PyCon из Питтсбурга в сеть.

Я по-настоящему ценю этот шанс поделиться своими знаниями с сообществом и вообще со всеми. Спасибо, что подключились послушать мои жалобы по поводу условного оператора (if statement). Будет смешно. Меня зовут Элай Сивджей (Aly Sivji), меня можно найти в твиттере.

Еще я участвую в организации группы пользователей Python в Чикаго, одной из самых больших в мире. У нас примерно 6 000 пользователей. Каждый месяц мы проводим 4-6 мероприятий. Обычно я бы позвал вас поучаствовать, но сейчас нам пришлось отменить все личные встречи до тех пор, пока крупные мероприятия не станет безопасны.

Понятие об операторе if statement

слайды с выступления про рефакторинг операторов if в Python

Данное выступление посвящено оператору if statement как показателю плохого кода (code smell). Начну с того, что не собираюсь ругать ничей код. Просто хочу поделиться опытом, благодаря которому могу использовать схему написания немного более понятного и подходящего для тестов кода.

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

слайды с выступления про рефакторинг операторов if в Python

Обычно, когда мы запускаем программу, она исполняется сверху вниз построчно. Когда она добирается до условного оператора, то, если условие выполняется (то есть, оно дает ответ True), находящийся под ним блок кода исполняется. Если условие не выполняется (оно дает ответ False), то этот блок кода пропускается, и программа продолжает исполнение, как будто его вообще не существует.

Давайте перейдем к конкретике. Вот немножко питона.


# ... code
if today == user.birthday:
    print("Happy Birthday!")
# code ...

Наш код проверяет будет ли переменная today равна дню рождения пользователя. Если условие выполняется, мы выводим (print) сообщение с поздравлением. Если нет, то пропускаем его и продолжаем исполнение программы, как будто этого кода не существует.

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

слайды с выступления про рефакторинг операторов if в Python

Недостатки оператора if

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

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

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

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

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

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

слайды с выступления про рефакторинг операторов if в Python

Такой подход к программированию ИНОГДА показывает, что где-то сделан неверный выбор. Подчеркну, что плохой код не всегда говорит о наличии проблемы. Но если есть трудности с пониманием, то мы можем упростить эту логику.

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

Виды плохого кода и варианты рефакторинга

Составные условия

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

Если в наше выражение с оператором if заложено одно условие, то его сравнительно легко прочитать.


# ... code
if today == user.birthday:
    print("Happy Birthday!")
# code ...

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


# ... code
if today.month == 6 and today.day == 21:
    print("First day of summer")
# code ...

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


# ... code
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
    print("Leap year")
# code ...

Предлагаю переделать условия в булевую переменную (boolean variable) или функцию.

Вернемся к условному выражению с двумя компонентами.


# ... code
if today.month == 6 and today.day == 21:
    print("First day of summer")
# code ...

Рефакторинг кода может заключаться в вынесении условия в отдельную переменную с понятным описательным названием. Затем мы используем эту переменную в выражении с оператором if:


# ... code
first_day_of_summer = today.month == 6 and today.day == 21
if first_day_of_summer:
    print("First day of summer")
# code ...

Эту переменную можно использовать и в том более сложном условном выражении, и еще много-много раз.

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


# ... code

def leap_year(year):
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

if leap_year(year):
    print("Leap year")

# code ...

Вложенные условия выражения

Еще одна схема программирования, усложняющая чтение кода — это вложенные выражения с оператором if (nested if statements). Они включают в себя серию подчиненных выражений, которые приобретают форму наконечника стрелы.

слайды с выступления про рефакторинг операторов if в Python

Какие проблемы создает такой код.

Во-первых, в таком коде высокая цикломатическая сложность (cyclomatic complexity). Это такая метрика для количества отдельных ветвей кода. Чем выше цикломатическая сложность, тем код сложнее для понимания и еще сложнее для тестов.

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

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

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

Давайте пройдемся по коду, который создает эту панель. В нем используется библиотека requests, которая соединяется с API. Мы получаем ответ (response), и если он 200, то мы получим данные со станции. Затем мы запустим цикл for loop по всем доступным станциям и найдем ту, которая нужна именно нам.

Далее, если количество велосипедов меньше или равно заданному пороговому значению (здесь это будет 3), то мы вернем сообщение о том, что нужно поспешить ("Hurry up! Bikes are almost out!"). Если же велосипедов больше порогового значения, то мы возвращаем другое сообщение о том, что их много и торопиться не нужно ("No need to rush just yet.")


resp = requests.get("https://api.divvybikes.com")
if resp.status_code == HTTPStatus.OK:
    station_data = resp.json()["data"]["stations"]
    for station in station_data:
        if station["station_id"] == STATION_ID:
            if station["num_bikes_available"] <= 3:
                return "Hurry up! Bikes are almost out!"
            else:
                return "No need to rush just yet."
    else:
        resp.raise_for_status()

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

слайды с выступления про рефакторинг операторов if в Python

Есть много способов, с помощью которых можно выровнять вложенный код. Основные из них следующие:
- всегда добавлять строку о возвращаемом значении как можно раньше;
- заменить условное выражение контрольным оператором (guard clause);
- заменить проверку на положительный ответ проверкой на отрицательный ответ;
- дополнительная информация

Итак, что можно сделать с этой программой. Сначала мы добавляем контрольного оператора (Guard Clause), который проверяет код статуса на случай, если ответ будет не 200. Это дает возможность объединить логику завершения программы в этом случае, а остальной код не будет иметь посторонних элементов.

слайды с выступления про рефакторинг операторов if в Python

Также обратите внимание, что в новом варианте полностью удален блок else, потому что он не приносит никакой пользы.

слайды с выступления про рефакторинг операторов if в Python

Дубликаты условных выражений

Еще один вид плохого кода, который мы сегодня обсудим — дубли выражений с оператором if, набрызганные по всему коду.

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

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

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

Кейс с рефакторингом кода

Минимально жизнеспособный продукт

Давайте изучим пример с дублированием условных выражений из чикагской базы Python. У нас есть бот по имени "busy beaver" (занятой бобер) для освещения активности пользователей гитхаба. Одна из его функций — создавать ежедневные резюме активности в специальном канале для зарегистрированных пользователей:

слайды с выступления про рефакторинг операторов if в Python

Как мы можем реализовать этот функционал. Можно создать цепочку условный выражений для реализации любой задачи. Например:
- получение данных из API
- классификация событий по видам
- подсчет статистики событий
- генерация текста резюме

слайды с выступления про рефакторинг операторов if в Python

Чтобы создать минимально жизнеспособный продукт (Minimal Viable Product), мы будем отслеживать два вида событий. Нас интересуют все коммиты пользователя и все репозитории (будем отталкиваться от количества звезд).

Начнем с получения данных через API гитхаба, функция get_github_events. Мы определим нужные заголовки и возвратим JSON. Вполне обычный вариант для использования requests:

слайды с выступления про рефакторинг операторов if в Python

Далее мы извлекаем интересующие нас события из JSON. Делаем цикл итерации по всем событиям, которые содержатся в полученном от гитхаба словаре. В конце функции мы возвращаем итоговый вариант словаря classified_events. Его ключом будет вид события, а значением — список всех событий нужного вида:

слайды с выступления про рефакторинг операторов if в Python

Наконец, мы создаем текст резюме по всем нужным нам событиям. Сначала мы делаем заголовок, затем пройдемся по всем парам ключ-значение нашего словаря и под конец обновим текст в зависимости от вида события. В итоге функция возвращает текст резюме:

слайды с выступления про рефакторинг операторов if в Python

Функция perform объединяет данную логику и выполняет задачу, поставленную перед программой:

слайды с выступления про рефакторинг операторов if в Python

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

Расширение функционала

Представим, что все в Чикаго полюбили нашего бота, поэтому мы расширяем функционал. Начнем с добавления новых видов событий.

Изменения в функции perform:

слайды с выступления про рефакторинг операторов if в Python

Изменения в функции extract_events_of_interest для извлечения событий из полученного словаря. Добавляем еще одно условное выражение:

слайды с выступления про рефакторинг операторов if в Python

В функции generate_summary_from_events для создания текста резюме мы добавляем условное выражение с логикой для запросов на принятие изменений (pull requests). Эта функция уже начинает становиться сложнее:

слайды с выступления про рефакторинг операторов if в Python

Это было легко. Наверное. Мы видим, как легко начать утрачивать контроль над кодом. При этом на данный момент мы отслеживаем всего три события.

слайды с выступления про рефакторинг операторов if в Python

А как же тесты? Давайте сравним тесты для первого варианта программы и тесты для расширенного варианта. Нам нужно написать тест для проверки функционала для запросов на принятие изменений в гитхабе, поэтому мы модифицируем уже написанные тесты:

слайды с выступления про рефакторинг операторов if в Python

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

Абстракция более высокого порядка

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

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

слайды с выступления про рефакторинг операторов if в Python

Примерно в то же время я начал книгу "Чистый код" (Clean Code) Роберта Мартина (Robert Martin). В ней много прекрасных советов по поводу того, как можно писать более качественный код. В ней рассматривается ряд хороших практик.

Среди прочего, книга рекомендует проводить рефакторинг кода с условными выражениями, используя полиморфные классы (polymorphic classes). Да, мне тоже не очень понятно.

Давайте немного отвлечемся и поговорим про объектно-ориентированное программирование (object-oriented programming) в Python. Это парадигма, построенная на объектах. Мы пытаемся моделировать вещи из окружающего мера в виде объектов. Когда мы структурируем свое решение, то в том числе создаем коллекцию взаимосвязанных объектов, которые контактируют друг с другом.

В объектно-ориентированном программировании (ООП) объекты объединяют в себе используемые определенным элементом данные и разрешенные для этого элемента действия.

В этой парадигме мы можем мыслить на более высоком уровне абстракции, создавая объекты с предопределенным содержанием (данные) и предопределенными умениями (поведение).

Это позволяет нам совершать действия с этими объектами для выполнения поставленной задачи. Такой подход отличается от последовательно-линейной системы.

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

слайды с выступления про рефакторинг операторов if в Python

Четыре главных принципа ООП:
- инкапсуляция, т.е. скрытие внутренней структуры (encapsulation),
- абстракция (abstraction),
- полиморфизм, т.е. способность объектов определять свое поведение в зависимости от вида получаемых данных (polymorphism),
- наследование (inheritance).

слайды с выступления про рефакторинг операторов if в Python

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

Абстракция позволяет нам скрывать внутреннюю реализацию конкретной программы в абстракции более высокого порядка. Также она помогает объектам контактировать друг с другом с помощью вызова открытых методов (public methods), скрывает сложность программы и изолирует сферу внесения изменений в код.

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

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

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

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

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

Я сделал диаграмму, подсмотренную в книге Мартина Фаулера (Martin Fowler) про рефакторинг кода. На ней видно, как можно преобразовать логику условных выражений в иерархию классов или объектов:

слайды с выступления про рефакторинг операторов if в Python

Рассмотрим полиморфизм подробнее на конкретном примере. Допустим у нас есть родительский класс (parent class) Animal:

слайды с выступления про рефакторинг операторов if в Python

В нем реализован интерфейс для метода speak(). В разных дочерних объектах он инициирует разное поведение в зависимости от типа объекта. В базовом классе этот интерфейс на самом деле ничего не делает и в случае вызова выдает ошибку об отсутствии реализации (NotImplementedError):


class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError

Когда мы создаем дочерний класс для кота и подать голос, то он исполняет свою реализацию, которая заменяет исходную версию метода speak() и говорит meow:


class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError


class Cat(Animal):
    def speak(self):
        return "Meow!"

Собака же с помощью этого метода говорит woof:


class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError


class Dog(Animal):
    def speak(self):
        return "Woof!"

Это и есть реализация той самой объектной взаимосвязи в Python. По этой системе мы можем создавать классы животных и дальше, и каждое из них будет говорить что-то свое, заменяя заимствованный в родительском классе метод speak() индивидуальной реализацией.

Теперь можно вернуться к нашему кейсу и приступить к замене условных выражений экземплярами полиморфизма. Когда мы создавали первый вариант единой логики, то занимались именно этим, желая приготовить что-то для своих пользователей.

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

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

слайды с выступления про рефакторинг операторов if в Python

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

слайды с выступления про рефакторинг операторов if в Python

Пройдемся по этапам рефакторинга условных выражений с помощью двух полиморфных классов.

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

слайды с выступления про рефакторинг операторов if в Python

Верхний блок пытается найти события, отвечающие условиям типа "событие":

слайды с выступления про рефакторинг операторов if в Python

Нижний блок пытается генерировать резюме для каждого из этих видов событий:

слайды с выступления про рефакторинг операторов if в Python

Теперь попробуем написать базовый класс EventList ("список событий"), включающий в себя определенные данные и действия. С точки зрения данных (data) мы хотим отслеживать все события, которые попадают в список. Что касается поведения (behavior), мы хотим получить возможность узнать размер списка и добавлять с конца (append) новые элементы.

слайды с выступления про рефакторинг операторов if в Python

Далее, у нас есть функция matches_event, которая возвращает True или False для события, которое отвечает или не отвечает условиям типа "событие". Еще есть функция generate_summary_text, которая генерирует текст резюме для определенного типа "событий":

слайды с выступления про рефакторинг операторов if в Python

Для этих функций в базовом классе, приведенном выше, предусмотрен базовый интерфейс, который выводит ошибку отсутствия реализации.

Теперь мы переходим к замене условных блоков дочерним классом Commits, который соотносится с этими двумя функциями для событий типа push в гитхабе.

слайды с выступления про рефакторинг операторов if в Python

Теперь посмотрим на функционал для извлечения событий по количеству звезд репозитория, который изначально был в функции extract_events_of_interest

слайды с выступления про рефакторинг операторов if в Python

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

слайды с выступления про рефакторинг операторов if в Python

Наконец, последний функционал был предусмотрен для событий типа "запрос на принятие изменений в гитхабе". Он был реализован в функции extract_events_of_interest и дополняющей ее функции generate_summary_from_events для написания текста к таким событиям:

слайды с выступления про рефакторинг операторов if в Python

Здесь мы тоже хотим создать дочерний класс под названием PullRequestsOpened с методом matches_event для поиска подходящих событий и методом generate_summary_text для подготовки текста:

слайды с выступления про рефакторинг операторов if в Python

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

слайды с выступления про рефакторинг операторов if в Python

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

слайды с выступления про рефакторинг операторов if в Python

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

слайды с выступления про рефакторинг операторов if в Python

Мы завершим программу новой версией функции perform, которая отвечает за исполнение этой абстракции более высокого порядка:

слайды с выступления про рефакторинг операторов if в Python

Расширение функционала

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

Создаем класс под названием IssuesOpened, он будет дочерним для класса EventList. В этом классе будет функция matches_event, выявляющая все события со статусом opened ("открыт"). Еще будет функция generate_summary_text для создания нужного нам текста:

слайды с выступления про рефакторинг операторов if в Python

Теперь мы просто вернемся к объединяющему данные и действия классу GitHubUserEvents и обновим список событий:

слайды с выступления про рефакторинг операторов if в Python

С виду несложно.

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

слайды с выступления про рефакторинг операторов if в Python

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

Так что, думаю, новая реализация немного лучше.

О чем следует помнить

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

Далее, можно прикинуть, насколько часто предполагается менять код. Если такие планы есть, то лучше уделить время рефакторингу кода, чтобы вывести его на более высокий уровень абстракции. В этом смысле мне нравится следовать правилу трех этапов, тоже взятому из книги Мартина Фаулера "Refactoring":

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

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

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

Вспомним дзен Python:

слайды с выступления про рефакторинг операторов if в Python

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

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

Но выбирать неверное направление абстракции тоже неправильно. Лучше уж копировать и вставлять дублирующийся код. На эту тему есть отличное выступление Санди Метца (Sandi Metz) с конференции по Ruby.

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

Заключение

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

Мы рассмотрели процесс рефакторинга дублирующихся условных выражений в полиморфные классы, состоящий из пяти этапов:
1. Определить дублирующиеся условные блоки.
2. Определить функционал каждого блока кода.
3. Создать базовый класс для моделирования.
4. Вывести функционал условных блоков в дочерние классы.
5. Обновить код для реализации процесса.

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

Дополнительные материалы

Ссылки

Книги

Видео

Материал для выступления