private String blankIfNull(String s) {
return s == null ? "" : s;
}
public boolean has(char arg) {
return argsFound.contains(arg);
}
public boolean isValid() {
return valid;
}
}
Ситуация явно выходит из-под контроля. Код все еще не ужасен, но путаница очевидно растет. Это уже клубок, хотя и не беспорядочное месиво. А чтобы месиво забродило и стало подниматься, хватило простого добавления целочисленных аргументов.
На этом я остановился
Мне предстояло добавить еще два типа аргументов. Было совершенно очевидно, что с ними все станет намного хуже. Если бы я с упорством бульдозера пошел вперед, скорее всего, мне удалось бы заставить программу работать, но разобраться в получившемся коде не удалось бы уже никому. Если я хотел, чтобы с моим кодом можно было работать, спасать положение нужно было именно сейчас.
Итак, я прекратил добавлять в программу новые возможности и взялся за переработку. После добавления типов String и integer я знал, что для каждого типа аргументов новый код должен добавляться в трех основных местах. Во-первых, для каждого типа аргументов необходимо было обеспечить разбор соответствующего элемента форматной строки, чтобы выбрать объект HashMap для этого типа. Затем аргумент соответствующего типа необходимо было разобрать в командной строке и преобразовать к истинному типу. Наконец, для каждого типа аргументов требовался метод getXXX, возвращающий значение аргумента с его истинным типом.
Много разных типов, обладающих сходными методами… Наводит на мысли о классе. Так родилась концепция ArgumentMarshaler.
О постепенном усовершенствовании
Один из верных способов убить программу — вносить глобальные изменения в ее структуру с целью улучшения. Некоторые программы уже никогда не приходят в себя после таких «усовершенствований». Проблема в том, что код очень трудно заставить работать так же, как он работал до «усовершенствования».
Чтобы этого не произошло, я воспользовался методологией разработки через тестирование (TDD). Одна из центральных доктрин этой методологии гласит, что система должна работать в любой момент в процессе внесения изменений. Иначе говоря, при использовании TDD запрещено вносить в систему изменения, нарушающие работоспособность этой системы. С каждым вносимым изменением система должна работать так же, как она работала прежде.
Для этого был необходим пакет автоматизированных тестов. Запуская их в любой момент времени, я мог бы убедиться в том, что поведение системы осталось неизменным. Я уже создал пакет модульных и приемочных тестов для класса Args, пока работал над начальной версией (она же «беспорядочное месиво»). Модульные тесты были написаны на Java и находились под управлением JUnit. Приемочные тесты были оформлены в виде вики-страниц в FitNesse. Я мог запустить эти тесты в любой момент по своему усмотрению, и если они проходили — можно было не сомневаться в том, что система работает именно так, как положено.
И тогда я занялся внесением множества очень маленьких изменений. Каждое изменение продвигало структуру системы к концепции ArgumentMarshaler, но после каждого изменения система продолжала нормально работать. На первом этапе я добавил заготовку ArgumentMarshaller в конец месива (листинг 14.11).
private class ArgumentMarshaler {
private boolean booleanValue = false;
public void setBoolean(boolean value) {
booleanValue = value;
}
public boolean getBoolean() {return booleanValue;}
}
private class BooleanArgumentMarshaler extends ArgumentMarshaler {
}
private class StringArgumentMarshaler extends ArgumentMarshaler {
}
private class IntegerArgumentMarshaler extends ArgumentMarshaler {
}
}
Понятно, что добавление класса ничего не нарушит. Поэтому я внес самое простейшее из всех возможных изменений — изменил контейнер HashMap для логических аргументов так, чтобы при конструировании передавался тип ArgumentMarshaler:
private Map
new HashMap
Это нарушило работу нескольких команд, которые я быстро исправил.
...
private void parseBooleanSchemaElement(char elementId) {
booleanArgs.put(elementId, new BooleanArgumentMarshaler());
}
..
private void setBooleanArg(char argChar, boolean value) {