Дисциплина, которая может заставить писать только тот код, который вам нужен

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

В первом посте я начал с истории Джека, ростовщика, который искал более эффективные способы расчета процентов по своим займам.

Это выглядит так:

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

Сможете ли вы построить что-нибудь, что может сделать эти расчеты за меня?

От 0 до 2000 долларов = нет процентов

2001–5000 долларов = 9 центов за доллар

От 5001 до 10000 долларов = 14 центов за доллар

$ 10001 + = 21 цент за доллар

После первого поста Джек получил продукт, который может рассчитывать проценты в размере 0,09 доллара на каждый доллар сверх 2000 долларов до бесконечности. Это была первая граница проблемы. После второго поста Джек получил продукт, который может начислять проценты по 0,14 доллара на каждый доллар сверх 5000 долларов.

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

Как и раньше, есть репозиторий, где вы можете увидеть одну фиксацию за один тестовый прогон. Этот репозиторий показывает шаги красного / зеленого / рефакторинга в отдельной фиксации.

  • У коммита есть значок 🔴, когда он представляет собой красный шаг.
  • Фиксация отмечена зеленым ✅, когда она представляет собой зеленый шаг.
  • У коммита есть значок 🔨, когда он представляет этап рефакторинга.

Давай начнем.

Джек пока доволен результатом. Нет ничего лучше этого… не так ли?

В последнем посте вы узнали, как написать код для расчета 0,14 доллара процентов для сумм кредита выше 5000 долларов. Вы можете следовать той же схеме, чтобы создать код для процентов на суммы ссуды выше 10000 долларов.

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

Затем вы можете создать тест на первый доллар в следующем диапазоне.

Вы ожидаете, что существующий алгоритм по 0,14 доллара за каждый доллар будет по-прежнему действовать. Таким образом, тест не проходит с осмысленным сообщением.

Теперь выполните те же шаги, что и в предыдущем посте. Выделите диапазон для суммы ссуды более 5000 долларов в предыдущем условии и разработайте процент для сумм ссуды выше 10000 долларов в новом условии:

Результатом является новое состояние с большим количеством дублированного кода. Кроме того, тест не проходит, потому что вам не хватает правил для расчета процентов в размере 0,09 доллара сверх 2000 долларов и 0,14 доллара сверх 5000:

После того, как вы скопируете расчет для сумм ссуды выше 5000 долларов, сообщение об ошибке изменится:

После того, как вы скопируете расчет для сумм кредита выше 2000 долларов, тест пройдет:

Вот как выглядит код после удаления всех магических чисел:

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

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

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

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

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

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

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

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

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

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

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

У вас все еще есть дубликаты, но они выглядят лучше, чем раньше. Есть одна функция для обработки 2000 долларов, одна функция для обработки 5000 долларов и еще одна функция для обработки 10000 долларов.

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

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

Кроме того, внутренние переменные и аргументы для всех функций имеют разные имена. Сделаем их такими же.

Теперь вы можете видеть, что все функции принимают одно и то же:

  • Сумма кредита.
  • Сумма, представляющая конец диапазона.
  • Сумма, представляющая проценты за доллар.
  • Сумма, представляющая предыдущие проценты за доллар.

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

Остальные аргументы разные:

  1. Код вызывает функции с разными значениями аргументов «конец диапазона», «процент на доллар» и «предыдущий процент на доллар» в зависимости от того, какой расчет выполняется.
  2. Функции для расчета процентов для каждого диапазона используют в качестве аргументов Connascence of Position вместо Connascence of Name. Это плохой запах кода.

Чтобы исправить неприятный запах кода и выяснить, почему код вызывает функции с разными аргументами, вы можете применить СУХОЙ подход. Создайте один литерал объекта, представляющий изменяющиеся аргументы, затем повторно используйте их. Начать можно с ряда расчетов для сумм кредита выше 2000 долларов:

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

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

Вы можете видеть коммиты, которые приводят к такому результату.

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

Удалим это дублирование:

Теперь внимательно посмотрите на код:

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

Вы можете сбросить правое условие первого условия. Если вы это сделаете, вы исправите дублирование:

Вы можете сделать то же самое с остальной частью кода:

Вот результат:

Приведенный выше код ясно показывает, как алгоритм рассчитывает проценты, если «сумма ссуды» превышает 2000, 5000 или 10000 долларов.

И вот умопомрачительный момент:

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

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

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

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

Разработка через тестирование позволяет понять проблему и создать дополнительную ценность.

Вот и все! Вот последний код:

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

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

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

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

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

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

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

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

Он выбрал профессионального программиста.

См. также Вы не знаете TDD.

Спасибо за прочтение. Если у вас есть отзывы, напишите мне в Twitter, Facebook или Github.

Благодарим Джея Базузи, Иэна Тинсли и Кали за их полезный вклад в этот пост.