Кент Бек - Экстремальное программирование
Если попробовать реализовать эту стратегию на практике, поначалу она покажется вам странной. Мы берем первый тестовый случай. Мы говорим: Если нам надо только лишь обеспечить срабатывание этого теста, тогда нам потребуется всего один объект с двумя методами. Мы создаем объект, добавляем в него два необходимых метода и считаем дело сделанным: весь наш дизайн – это один объект. Но только на минуту.
После этого мы берем второй тестовый случай. Мы можем попытаться решить задачу, используя то, что есть у нас на руках, однако вместо этого, возможно, будет удобнее преобразовать существующий объект, разбив его на два разных объекта. В этом случае для обеспечения срабатывания тестового случая необходимо заменить один из полученных объектов. Поэтому прежде, чем продолжать работу, мы выполняем реструктуризацию нашей программы, затем мы проверяем, срабатывает ли наш первый тестовый случай, затем мы добиваемся срабатывания второго тестового случая.
После пары дней работы в таком режиме система становится достаточно большой, и мы уже можем представить себе две группы разработчиков, которые могут работать над ней, не наступая при этом постоянно друг другу на пятки. И тогда мы пускаем в дело две пары программистов, которые занимаются реализацией тестовых случаев параллельно друг с другом и периодически (через каждые несколько часов) интегрируют вносимые ими изменения. Еще один или два дня, и система разрастается настолько, что мы можем обеспечить работой всю команду. Постепенно все члены команды начинают работать в описанном стиле.
Время от времени у команды будет возникать ощущение, что перед ними простирается невозделанная целина. Возможно, они обнаруживают существенное отклонение реальности от предварительных оценок. А может быть, их желудки завязываются узлом каждый раз, когда они приходят к выводу, что некоторая часть системы требует полной переработки.
В любом случае, кто-то просит тайм-аут. Команда собирается вместе на целый день и плотно занимается реструктуризацией системы как единого целого, используя при этом комбинацию карт CRC, набросков и переработки кода. Не каждую переработку можно выполнить за пару минут. Если вы обнаружили, что построили запутанную иерархию наследования классов, возможно, вам потребуется месяц на то, чтобы распутать ее. Однако у вас в запасе нет лишнего месяца. Вы обязаны реализовать все истории, запланированные для данной текущей итерации.
Когда вы сталкиваетесь с необходимостью крупномасштабной переработки кода, вы должны действовать небольшими шажками (вновь постепенное изменение). Вы работаете над некоторым тестовым случаем и видите возможность на один маленький шажок приблизиться к вашей большой крупномасштабной цели. Сделайте этот маленький шажок. Переместите метод сюда, переменную – туда. Постепенно крупномасштабное изменение станет не таким уж и крупномасштабным. После постепенной поэтапной эволюции вы сможете завершить переработку за пару минут.
В свое время я столкнулся с необходимостью крупномасштабной переработки кода, когда работал над системой управления страховыми контрактами. Тогда я попробовал выполнить ее небольшими шажками. У нас была иерархия, показанная на рис. 10.
Рис. 10. Проект и продукт обладают паралельными подклассами
Этот дизайн нарушает правило Once And Only Once (OAOO) объектно-ориентированного дизайна (код должен присутствовать в системе один раз и только один раз). Чтобы исправить ситуацию, мы начали работать над формированием дизайна, показанного на рис. 11.
Рис. 11. Контракт ссылается на класс Function (функция), но не имеет подклассов
В течение года, пока мы работали над этой системой, мы сделали множество небольших шажков в направлении желаемого дизайна. Мы перекладывали обязанности подклассов класса Contract (контракт) либо на подклассы Function (функция), либо на подклассы Product (продукт), В самом конце работы над заказом мы не смогли полностью избавиться от подклассов Contract, однако они стали существенно менее содержательными, чем в начале, и было очевидно, что мы держим курс на отказ от их использования. Все это время мы продолжали добавлять в систему новую функциональность.
Вот так. Именно так осуществляется экстремальный дизайн. В рамках ХР проектирование – это не рисование огромного количества схем и затем реализация системы в точном соответствии с этими схемами. В ХР проектирование напоминает ориентирование автомобиля в нужном направлении в то время, как вы едете по шоссе. История об управлении автомобилем подсказывает нам совершенно иной стиль проектирования – вы заводите машину, начинаете движение, а затем поправляете руль чутьчуть влево, затем чуть-чуть вправо, затем опять обратно влево.
Что является самым простым?Таким образом, лучшим является самый простой дизайн, который обеспечивает срабатывание всех тестовых случаев. Чтобы сделать это определение эффективным, необходимо объяснить, что именно мы подразумеваем, когда говорим самый простой.
Может быть, самый простой дизайн – это дизайн с наименьшим количеством классов? Но если в системе мало классов, значит используемые объекты становятся настолько большими, что их неудобно использовать.
Может быть, самый простой дизайн – это дизайн с наименьшим количеством методов? Но это приведет к формированию слишком крупных методов, а следовательно – к дублированию кода. Может быть, самый простой дизайн – это дизайн с наименьшим количеством строк кода? Но тогда мы будем стремиться сжать программу только ради сжатия и, кроме того, нам потребуется слишком много общаться между собой.
Когда я говорю самый простой дизайн, я имею ввиду следующие четыре ограничения в порядке приоритета.
1. Система (как ее код, так и соответствующие тесты) должна выражать собой все, что вы хотите сообщить о ней всем остальным участникам проекта.
2. Система не должна содержать дублирующегося кода (1 и 2 пункты вместе составляют собой правило Once and Only Once).
3. Система должна состоять из наименьшего возможного количества классов.
4. Система должна содержать в себе наименьшее возможное количество методов.
Цель проектирования системы – это, во-первых, выразить намерения программистов и, во-вторых, обеспечить место для размещения логики работы системы. Представленные здесь ограничения обеспечивают обрамление, в рамках которого необходимо удовлетворить двум этим условиям.
Если вы смотрите на дизайн как на среду обмена информацией, значит, вы должны создать объекты или методы для каждой важной используемой вами концепции. Вы должны выбрать имена классов и методов так, чтобы их было удобно использовать совместно.
Разрабатывая код, ограничивайте себя так, чтобы создаваемый вами код было бы удобно использовать для коммуникации, после этого вы должны удалить из системы весь дублирующийся код. Для меня это самая сложная часть проектирования. Дело в том, что вначале надо обнаружить дублирующийся код, а затем найти способ избавиться от дублирования. Для того чтобы избавиться от дублирования, как правило, приходится создавать множество мелких объектов и множество мелких методов, потому что в противном случае неизбежно возникнет дублирование кода.
Однако вы создаете новые объекты и новые методы не просто для собственного удовольствия. Если вы обнаруживаете класс, который ничего не делает и ни о чем не информирует, или метод, который ничего не делает и ни о чем не информирует, вы должны уничтожить их.
Еще один способ взглянуть на этот процесс – это провести аналогию со стиранием. У вас есть система, для которой срабатывают все тестовые случаи. Вы удаляете из нее все, что не имеет определенной цели – либо коммуникационной цели, либо вычислительной цели. То, что остается, – это самый простой дизайн, который, скорее всего, сработает.
Как это может работать?Традиционная стратегия сокращения с течением времени затрат на разработку программного обеспечения заключается в том, чтобы снизить вероятность переработки и затраты, связанные с переработкой. ХР предлагает действовать с точностью до наоборот. Вместо того чтобы снижать частоту переработки, ХР наслаждается переработкой. День без переработки – это день без солнечного света. Но как это может обходиться дешевле?
Ответ состоит в том, что риск – это деньги точно так же, как и время – это деньги. Если сегодня вы включаете в проект некоторую возможность и используете ее завтра, вы выигрываете, так как вы платите меньше за то, что включили эту возможность именно сегодня, а не завтра. Однако в главе 3, посвященной экономике разработки программного обеспечения, было показано, что эта оценка не является полной. Если сопутствующая этому неопределенность достаточно велика, ценность сценария, в котором вы просто ждете, может оказаться настолько большой, что вам становится выгодно просто подождать.