Выбрать главу

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

На данном уровне тестирование будет обсуждаться в разделе "Программа, которую легко тестировать", тестирование на более высоком уровне – в разделе "Безжалостное тестирование"), но мнение г-на Фаулера о тщательном регрессионном тестировании является ключом к надежной реорганизации.

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

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

Другие разделы, относящиеся к данной теме:

• Мой исходный текст съел кот Мурзик

• Энтропия в программах

• Суп из камней и сварившиеся лягушки

• Пороки дублирования

• Ортогональность

• Программирование в расчете на стечение обстоятельств

• Программа, которую легко тестировать

• Безжалостное тестирование

Упражнения

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

if (state==TEXAS) {

rate=TX.RATE;

amt=base * TX_RATE;

calc=2*basis(amt) + extra(amt)*1.05;

}

else if ((state==OHIO) || (state==MAINE)) {

rate=(state==OHIO) ? OH_RATE : MN_RATE;

amt=base*rate;

calc=2*basis(amt) + extra(amt)*1.05;

if (state==OHIO)

   points = 2;

}

else {

rate=1;

amt=base;

calc=2*basis(amt) + extra(amt)*1.05;

}

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

public class Shape {

public static final int SQUARE = 1;

public static final int CIRCLE = 2;

public static final int RIGHTTRIANGLE = 3;

private int shapeType;

private double size;

public Shape(int shapeType, double size) {

   this.shapeType = shapeType;

   this.size = size;

}

//… другие методы…

public double area() {

 switch (shapeType) {

  case SQUARE: return size*size;

  case CIRCLE: return Math.PI*size*size/4.0;

  case RIGHT TRIANGLE: return size*size/2.0;

}

return 0;

}

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

public class Window {

  public Window(int width, int height) {…}

  public void setSize(int width, int height) {…}

  public boolean overiaps(Window w) {…}

  public int getArea() {…}

34

Программа, которую легко тестировать

Термин "программная интегральная схема" является метафорой, брошенной в ходе дискуссии о многократном использовании и компонентно-ориентированной разработке [39]. Идея заключается в том, что программные компоненты должны объединяться так же, как это происходит с чипами интегральной схемы. Этот подход срабатывает только в том случае, если известно, что используемые компоненты надежны.

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

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

Модульное тестирование

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

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

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

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

Тестирование в рамках контракта

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

Что это означает на практике? Рассмотрим подпрограмму извлечения квадратного корня, с которой мы впервые встретились в разделе "ППК и аварийное завершение работы программы". Ее контракт довольно прост:

require:

   argument >=0

ensure:

  abs((result*result)–argument) < epsilon

Он указывает на моменты, нуждающиеся в проверке:

• Передать отрицательный аргумент и удостовериться в том, что он отклонен