Книга: Экстремальное программирование. Разработка через тестирование
Назад: 4. Данные должны быть закрытыми
Дальше: 6. Равенство для всех, вторая серия

5. Поговорим о франках

$5 + 1 °CHF = $10, если курс обмена 2:1
$5 * 2 = $10
Сделать переменную amount закрытым (private) членом
Побочные эффекты в классе Dollar?Округление денежных величин?
equals()
hashCode()
Равенство значению null
Равенство объектов
5 CHF * 2 = 1 °CHF

 

Можем ли мы приступить к реализации первого, самого интересного теста в данном списке? Мне все еще кажется, что это будет слишком большой шаг. Я не представляю себе, как можно написать этот тест за один маленький шажок. Мне кажется, что вначале необходимо создать объект наподобие Dollar, который соответствовал бы не долларам, а франкам. Пусть это будет объект с названием Franc. Для начала объект Franc может функционировать в точности как объект Dollar – если у нас будет такой объект, нам будет проще размышлять о реализации теста, связанного со смешанным сложением двух разных валют.
А если объект Franc работает так же, как объект Dollar, значит, мы можем просто скопировать и слегка отредактировать тест для объекта Dollar:

 

public void testFrancMultiplication() {
Franc five = new Franc(5);
assertEquals(new Franc(10), five.times(2));
assertEquals(new Franc(15), five.times(3));
}

 

(Хорошо, что в главе 4 мы упростили тест для Dollar. Благодаря этому работа по редактированию теста существенно упростилась. Похоже, в данной книге дела идут довольно гладко, однако я не могут гарантировать, что в будущем все будет так же хорошо.)
Теперь нам надо получить зеленую полоску. Какой способ будет самым простым? Проще всего скопировать код класса Dollar и заменить Dollar на Franc.
Стоп. Подождите-ка. Я уже вижу, как некоторые наиболее ярые сторонники правильных подходов начинают морщиться и плеваться. Повторное использование кода путем его дублирования через буфер обмена? Пренебрежение абстракцией? А как же все эти разговоры об основополагающих принципах ООП и чистом дизайне?
Если вам не по себе, глубоко вдохните через нос, досчитайте до трех и медленно выдохните через рот. Вам лучше? Теперь вспомните, что наш цикл состоит из пяти этапов. Иногда последовательное выполнение всех этапов занимает всего несколько секунд, однако в любом случае мы обязательно выполняем каждый из них:
1. Написать тест.
2. Добиться его безошибочной компиляции.
3. Запустить тест и убедиться, что он потерпел неудачу.
4. Добиться успешного выполнения теста.
5. Устранить дублирование.
На разных этапах решаются разные задачи, преследуются разные цели. То, что совершенно недопустимо для одного из этапов, может быть вполне приемлемым для другого этапа. Однако в целом методика TDD работает только в случае, если ни один из этапов не упущен. Если вы пропустите хотя бы одно звено, развалится вся цепочка.
Первые три фазы цикла разработки TDD должны выполняться как можно быстрее. Определяющая характеристика этих этапов – скорость. На этих этапах в жертву скорости можно принести очень многое, в том числе чистоту дизайна. Честно говоря, сейчас я несколько волнуюсь. Я только что разрешил вам забыть о принципах хорошего дизайна. Представляю, как вы приходите к своим коллегам, подчиненным и во всеуслышание объявляете: «Кент сказал, что все эти разговоры про хороший дизайн – полная ерунда!» Остановитесь. Цикл еще не закончен. Четырехногий уродец из благородного семейства пятиногих стульев вечно падает. Первые четыре шага нашего цикла не работают без пятого. Хороший дизайн в подходящее время! Сначала сделаем, чтобы код заработал, потом сделаем, чтобы код был правильным (make it run, make it right).
Теперь мне стало легче. Теперь я уверен, что до тех пор, пока вы не избавитесь от дублирования, вы не покажете свой код никому за исключением своего партнера по паре. На чем мы остановились? Ах, да. Забываем о принципах хорошего дизайна в угоду скорости (мы будем заниматься искуплением этого греха на протяжении нескольких следующих глав).

 

Franc
class Franc {
private int amount;
Franc(int amount) {
this.amount = amount;
}

 

Franc times(int multiplier) {
return new Franc(amount * multiplier);
}

 

public boolean equals(Object object) {
Franc franc = (Franc) object;
return amount == franc.amount;
}
}
$5 + 1 °CHF = $10, если курс обмена 2:1
$5 * 2 = $10
Сделать переменную amount закрытым (private) членом
Побочные эффекты в классе Dollar?
Округление денежных величин?
equals()
hashCode()
Равенство значению null
Равенство объектов
5 CHF * 2 = 1 °CHF
Дублирование Dollar/Franc
Общие операции equals()
Общие операции times()

 

Чтобы запустить код, нам не потребовалось прикладывать каких-либо усилий, поэтому мы смогли «перепрыгнуть» через этап «добиться безошибочной компиляции кода» (Make it compile).
Зато теперь в нашем коде полно повторяющихся фрагментов. Прежде чем приступить к разработке следующего теста, мы должны избавиться от дублирования. Думаю, что следует начать с обобщения метода equals(). Однако об этом в следующей главе. На текущий момент мы можем вычеркнуть из нашего списка еще один пункт, однако вместе с этим нам придется добавить в него два дополнительных пункта.
В данной главе мы
• решили отказаться от создания слишком большого теста и вместо этого создали маленький, чтобы обеспечить быстрый прогресс;
• создали код теста путем бесстыдного копирования и редактирования;
• хуже того, добились успешного выполнения теста путем копирования и редактирования разработанного ранее кода;
• дали себе обещание ни в коем случае не уходить домой до тех пор, пока не устраним дублирование.
Назад: 4. Данные должны быть закрытыми
Дальше: 6. Равенство для всех, вторая серия