Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
public void setInteger(int i) { integerValue = i; } public int getInteger() { return integerValue; } } Переместив всю логику компоновки аргументов в ArgumentMarshaler , я занялся перемещением функциональности в производные классы . На первом этапе я должен был переместить функцию setBoolean в BooleanArgumentMarshaller и по- заботиться о том, чтобы она правильно вызывалась . Для этого был создан аб- страктный метод set private abstract class ArgumentMarshaler { protected boolean booleanValue = false; private String stringValue; private int integerValue; public void setBoolean(boolean value) { booleanValue = value; } public boolean getBoolean() { 250 Аргументы String 251 return booleanValue; } public void setString(String s) { stringValue = s; } public String getString() { return stringValue == null ? "" : stringValue; } public void setInteger(int i) { integerValue = i; } public int getInteger() { return integerValue; } public abstract void set(String s); } Затем метод set был реализован в BooleanArgumentMarshaller private class BooleanArgumentMarshaler extends ArgumentMarshaler { public void set(String s) { booleanValue = true; } } Наконец, вызов setBoolean был заменен вызовом set private void setBooleanArg(char argChar, boolean value) { booleanArgs.get(argChar). set("true"); } Все тесты прошли успешно . Так как изменения привели к перемещению set в BooleanArgumentMarshaler , я удалил метод setBoolean из базового класса ArgumentMarshaler Обратите внимание: абстрактная функция set получает аргумент String , но реализация в классе BooleanArgumentMarshaller его не использует . Я доба- вил этот аргумент, потому что знал, что он будет использоваться классами StringArgumentMarshaller и IntegerArgumentMarshaller На следующем шаге я решил разместить метод get в BooleanArgumentMarshaler Подобные размещения get всегда выглядят уродливо, потому что фактически возвращается тип Object , который в данном случае приходится преобразовывать в Boolean public boolean getBoolean(char arg) { Args.ArgumentMarshaler am = booleanArgs.get(arg); return am != null && ( Boolean)am.get(); } 251 252 Глава 14 . Последовательное очищение Просто для того, чтобы программа компилировалась, я добавил в ArgumentMar- shaler функцию get private abstract class ArgumentMarshaler { public Object get() { return null; } } Программа компилировалась, а тесты, разумеется, не проходили . Чтобы тесты снова заработали, достаточно объявить метод get абстрактным и реализовать его в BooleanAgumentMarshaler private abstract class ArgumentMarshaler { protected boolean booleanValue = false; public abstract Object get(); } private class BooleanArgumentMarshaler extends ArgumentMarshaler { public void set(String s) { booleanValue = true; } public Object get() { return booleanValue; } } Итак, тесты снова проходят успешно . Теперь оба метода get и set размещаются в BooleanArgumentMarshaler ! Это позволило мне удалить старую функцию get- Boolean из ArgumentMarshaler , переместить защищенную переменную booleanValue в BooleanArgumentMarshaler и объявить ее приватной . Аналогичные изменения были внесены для типа String . Я реализовал методы set и get , удалил ненужные функции и переместил переменные . private void setStringArg(char argChar) throws ArgsException { currentArgument++; try { stringArgs.get(argChar). set(args[currentArgument]); } catch (ArrayIndexOutOfBoundsException e) { valid = false; errorArgumentId = argChar; errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); } } public String getString(char arg) { Args.ArgumentMarshaler am = stringArgs.get(arg); return am == null ? "" : ( String) am.get(); } 252 Аргументы String 253 private abstract class ArgumentMarshaler { private int integerValue; public void setInteger(int i) { integerValue = i; } public int getInteger() { return integerValue; } public abstract void set(String s); public abstract Object get(); } private class BooleanArgumentMarshaler extends ArgumentMarshaler { private boolean booleanValue = false; public void set(String s) { booleanValue = true; } public Object get() { return booleanValue; } } private class StringArgumentMarshaler extends ArgumentMarshaler { private String stringValue = ""; public void set(String s) { stringValue = s; } public Object get() { return stringValue; } } private class IntegerArgumentMarshaler extends ArgumentMarshaler { public void set(String s) { } public Object get() { return null; } } } 253 254 Глава 14 . Последовательное очищение Осталось лишь повторить этот процесс для integer . На этот раз задача немного усложняется: целые числа необходимо разбирать, а в ходе разбора возможны исключения . Но внешний вид кода улучшается тем, что вся концепция Number- FormatException скрыта в классе IntegerArgumentMarshaler private boolean isIntArg(char argChar) {return intArgs.containsKey(argChar);} private void setIntArg(char argChar) throws ArgsException { currentArgument++; String parameter = null; try { parameter = args[currentArgument]; intArgs.get(argChar). set(parameter); } catch (ArrayIndexOutOfBoundsException e) { valid = false; errorArgumentId = argChar; errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch ( ArgsException e) { valid = false; errorArgumentId = argChar; errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw e; } } private void setBooleanArg(char argChar) { try { booleanArgs.get(argChar).set("true"); } catch (ArgsException e) { } } public int getInt(char arg) { Args.ArgumentMarshaler am = intArgs.get(arg); return am == null ? 0 : ( Integer) am.get(); } private abstract class ArgumentMarshaler { public abstract void set(String s) throws ArgsException; public abstract Object get(); } private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intValue = 0; public void set(String s) throws ArgsException { try { intValue = Integer.parseInt(s); } catch (NumberFormatException e) { 254 Аргументы String 255 throw new ArgsException(); } } public Object get() { return intValue; } } Конечно, тесты по-прежнему проходили . Далее я избавился от трех разновид- ностей Map в начале алгоритма, отчего система стала намного более универсаль- ной . Впрочем, я не мог их просто удалить, поскольку это нарушило бы работу системы . Вместо этого я добавил новый объект Map для ArgumentMarshaler , а затем последовательно изменял методы, чтобы они использовали этот объект вместо трех исходных . public class Args { private Map private Map private Map private Map new HashMap private void parseBooleanSchemaElement(char elementId) { ArgumentMarshaler m = new BooleanArgumentMarshaler(); booleanArgs.put(elementId, m); marshalers.put(elementId, m); } private void parseIntegerSchemaElement(char elementId) { ArgumentMarshaler m = new IntegerArgumentMarshaler(); intArgs.put(elementId, m); marshalers.put(elementId, m); } private void parseStringSchemaElement(char elementId) { ArgumentMarshaler m = new StringArgumentMarshaler(); stringArgs.put(elementId, m); marshalers.put(elementId, m); } Разумеется, тесты проходили успешно . Далее я привел метод isBooleanArg : private boolean isBooleanArg(char argChar) { return booleanArgs.containsKey(argChar); } к следующему виду: private boolean isBooleanArg(char argChar) { ArgumentMarshaler m = marshalers.get(argChar); 255 256 Глава 14 . Последовательное очищение return m instanceof BooleanArgumentMarshaler; } Тесты по-прежнему проходят . Я внес аналогичные изменения в isIntArg и is- StringArg private boolean isIntArg(char argChar) { ArgumentMarshaler m = marshalers.get(argChar); return m instanceof IntegerArgumentMarshaler; } private boolean isStringArg(char argChar) { ArgumentMarshaler m = marshalers.get(argChar); return m instanceof StringArgumentMarshaler; } Тесты проходят . Я удалил все повторяющиеся вызовы marshalers.get : private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (isBooleanArg( m)) setBooleanArg(argChar); else if (isStringArg( m)) setStringArg(argChar); else if (isIntArg( m)) setIntArg(argChar); else return false; return true; } private boolean isIntArg( ArgumentMarshaler m) { return m instanceof IntegerArgumentMarshaler; } private boolean isStringArg( ArgumentMarshaler m) { return m instanceof StringArgumentMarshaler; } private boolean isBooleanArg( ArgumentMarshaler m) { return m instanceof BooleanArgumentMarshaler; } Причин для существования трех методов isxxxArg не осталось . Я оформил их в виде встроенного кода: private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if ( m instanceof BooleanArgumentMarshaler) setBooleanArg(argChar); else if ( m instanceof StringArgumentMarshaler) setStringArg(argChar); else if ( m instanceof IntegerArgumentMarshaler) setIntArg(argChar); else 256 Аргументы String 257 return false; return true; } На следующем шаге я перешел на использование ассоциативного массива mar- shalers в функциях set , отказываясь от использования трех старых контейнеров . Преобразование началось с Boolean : private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m instanceof BooleanArgumentMarshaler) setBooleanArg( m); else if (m instanceof StringArgumentMarshaler) setStringArg(argChar); else if (m instanceof IntegerArgumentMarshaler) setIntArg(argChar); else return false; return true; } private void setBooleanArg( ArgumentMarshaler m) { try { m.set(“true”); // было: booleanArgs.get(argChar).set(«true»); } catch (ArgsException e) { } } Тесты проходили успешно, и я сделал то же самое для типов String и Integer . Это позволило мне интегрировать часть некрасивого кода обработки исключений в функцию setArgument private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); try { if (m instanceof BooleanArgumentMarshaler) setBooleanArg(m); else if (m instanceof StringArgumentMarshaler) setStringArg( m); else if (m instanceof IntegerArgumentMarshaler) setIntArg( m); else return false; } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } return true; } private void setIntArg( ArgumentMarshaler m) throws ArgsException { currentArgument++; 257 258 Глава 14 . Последовательное очищение String parameter = null; try { parameter = args[currentArgument]; m.set(parameter); } catch (ArrayIndexOutOfBoundsException e) { errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (ArgsException e) { errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw e; } } private void setStringArg( ArgumentMarshaler m) throws ArgsException { currentArgument++; try { m.set(args[currentArgument]); } catch (ArrayIndexOutOfBoundsException e) { errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); } } Я вплотную подошел к удалению трех старых объектов Map . Прежде всего было необходимо привести функцию getBoolean : public boolean getBoolean(char arg) { Args.ArgumentMarshaler am = booleanArgs.get(arg); return am != null && (Boolean) am.get(); } к следующему виду: public boolean getBoolean(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); boolean b = false; try { b = am != null && (Boolean) am.get(); } catch (ClassCastException e) { b = false; } return b; } Возможно, последнее изменение вас удивило . Почему я вдруг решил обраба- тывать ClassCastException ? Дело в том, что наряду с набором модульных тестов у меня был отдельный набор приемочных тестов, написанных для FitNesse . Ока- залось, что тесты FitNesse проверяли, что при вызове getBoolean для аргумента с типом, отличным от Boolean , возвращается false . Модульные тесты этого не делали . До этого момента я запускал только модульные тесты 1 1 Чтобы предотвратить подобные сюрпризы в будущем, я добавил новый модульный тест, который запускал все тесты FitNesse . 258 Аргументы String 259 Последнее изменение позволило исключить еще одну точку использования объ- екта Map для типа Boolean : private void parseBooleanSchemaElement(char elementId) { ArgumentMarshaler m = new BooleanArgumentMarshaler(); booleanArgs.put(elementId, m); marshalers.put(elementId, m); } Теперь объект Map для типа Boolean можно было удалить: public class Args { private Map private Map private Map private Map Далее я проделал аналогичную процедуру для аргументов String и Integer и не- много подчистил код: private void parseBooleanSchemaElement(char elementId) { marshalers.put(elementId, new BooleanArgumentMarshaler()); } private void parseIntegerSchemaElement(char elementId) { marshalers.put(elementId, new IntegerArgumentMarshaler()); } private void parseStringSchemaElement(char elementId) { marshalers.put(elementId, new StringArgumentMarshaler()); } public String getString(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); try { return am == null ? "" : (String) am.get(); } catch (ClassCastException e) { return ""; } } public class Args { private Map private Map private Map 259 260 Глава 14 . Последовательное очищение Затем я подставил в parseSchemaElement код трех методов parse , сократившихся до одной команды: private void parseSchemaElement(String element) throws ParseException { char elementId = element.charAt(0); String elementTail = element.substring(1); validateSchemaElementId(elementId); if (isBooleanSchemaElement(elementTail)) marshalers.put(elementId, new BooleanArgumentMarshaler()); else if (isStringSchemaElement(elementTail)) marshalers.put(elementId, new StringArgumentMarshaler()); else if (isIntegerSchemaElement(elementTail)) { marshalers.put(elementId, new IntegerArgumentMarshaler()); } else { throw new ParseException(String.format( "Argument: %c has invalid format: %s.", elementId, elementTail), 0); } } Давайте взглянем на общую картину . В листинге 14 .12 представлена текущая форма класса Args листинг 14 .12 . Args.java (после первой переработки) package com.objectmentor.utilities.getopts; import java.text.ParseException; import java.util.*; public class Args { private String schema; private String[] args; private boolean valid = true; private Set private Map private Set private int currentArgument; private char errorArgumentId = '\0'; private String errorParameter = "TILT"; private ErrorCode errorCode = ErrorCode.OK; private enum ErrorCode { OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT} public Args(String schema, String[] args) throws ParseException { this.schema = schema; this.args = args; valid = parse(); } private boolean parse() throws ParseException { if (schema.length() == 0 && args.length == 0) return true; 260 |