Более того, при внесении этого изменения я обнаружил неприметную ошибку. Исходный код не закрывал тег HR косой чертой, как того требует стандарт XHTML (иначе говоря, он выдавал
Разделение уровней абстракции — одна из самых важных и одновременно самых сложных в реализации функций рефакторинга. В качестве примера возьмем следующий код — мою первую попытку разделения уровней абстракции в методе HruleWidget.render.
public String render() throws Exception
{
HtmlTag hr = new HtmlTag("hr");
if (size > 0) {
hr.addAttribute("size", ""+(size+1));
}
return hr.html();
}
На этой стадии я стремился к тому, чтобы создать необходимое разделение, и обеспечить прохождение тестов. Мне удалось легко добиться этой цели, но в созданной функции по-прежнему смешивались разные уровни абстракции — на этот раз конструирование тега HR и интерпретация/форматирование переменной size. Таким образом, при разбиении функции по уровням абстракции иногда обнаруживаются новые уровни, скрытые прежней структурой.
G35: Храните конфигурационные данные на высоких уровнях
Если в программе имеется константа, определяющая значение по умолчанию или параметр конфигурации, и эта константа известна на высоких уровнях абстракции, — не прячьте ее в низкоуровневой функции. Передайте ее в аргументе низкоуровневой функции, вызываемой из функции высокого уровня. Рассмотрим пример из FitNesse.
public static void main(String[] args) throws Exception
{
Arguments arguments = parseCommandLine(args);
...
}
public class Arguments
{
public static final String DEFAULT_PATH = ".";
public static final String DEFAULT_ROOT = "FitNesseRoot";
public static final int DEFAULT_PORT = 80;
public static final int DEFAULT_VERSION_DAYS = 14;
...
}
Аргументы командной строки разбираются в самой первой исполняемой строке FitNesse. Значения аргументов по умолчанию задаются в начале класса Argument. Читателю не приходится спускаться на нижние уровни системы за командами следующего вида:
if (arguments.port == 0) // 80 по умолчанию
Конфигурационные константы находятся на очень высоком уровне. Если потребуется, их можно легко изменить. Их значения передаются на более низкие уровни иерархии другим компонентам приложения. Значения этих констант не принадлежат нижним уровням приложения.
G36: Избегайте транзитивных обращений
В общем случае модуль не должен обладать слишком полной информацией о тех компонентах, с которыми он взаимодействует. Точнее, если A взаимодействует с B, а B взаимодействует с C, то модули, использующие A, не должны знать о C (то есть нежелательны конструкции вида a.getB().getC().doSomething();). Иногда это называется «законом Деметры». Прагматичные программисты используют термин «умеренный код» [PRAG, с. 138].
В любом случае все сводится к тому, что модули должны обладать информацией только о тех модулях, с которыми они непосредственно взаимодействуют, а не располагать навигационной картой всей системы.
Если в нескольких модулях используется та или иная форма команды a.getB().getC(), то в дальнейшем вам будет трудно изменить архитектуру системы, вставив между B и C промежуточный компонент Q. Придется найти каждое вхождение a.getB().getC() и преобразовать его в a.getB().getQ().getC(). Так образуются жесткие, закостеневшие архитектуры. Слишком многие модули располагают слишком подробной информацией о системе.
Весь необходимый сервис должен предоставляться компонентами, с которыми напрямую взаимодействует модуль. Не заставляйте пользователя странствовать по графу объектов системы в поисках нужного метода. Проблема должна решаться простыми вызовами вида
myCollaborator.doSomething()
Java
J1: Используйте обобщенные директивы импорта
Если вы используете два и более класса из пакета, импортируйте весь пакет командой
import package.*;
Длинные списки импорта пугают читателя кода. Начало модуля не должно загромождаться 80-строчным списком директив импорта. Список импорта должен быть точной и лаконичной конструкцией, показывающей, с какими пакетами мы собираемся работать.