Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
261 parseSchema(); try { parseArguments(); } catch (ArgsException e) { } return valid; } private boolean parseSchema() throws ParseException { for (String element : schema.split(",")) { if (element.length() > 0) { String trimmedElement = element.trim(); parseSchemaElement(trimmedElement); } } return true; } 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); } } private void validateSchemaElementId(char elementId) throws ParseException { if (!Character.isLetter(elementId)) { throw new ParseException( "Bad character:" + elementId + "in Args format: " + schema, 0); } } private boolean isStringSchemaElement(String elementTail) { return elementTail.equals("*"); } private boolean isBooleanSchemaElement(String elementTail) { return elementTail.length() == 0; } private boolean isIntegerSchemaElement(String elementTail) { return elementTail.equals("#"); } продолжение 261 262 Глава 14 . Последовательное очищение листинг 14 .12 (продолжение) private boolean parseArguments() throws ArgsException { for (currentArgument=0; currentArgument parseArgument(arg); } return true; } private void parseArgument(String arg) throws ArgsException { if (arg.startsWith("-")) parseElements(arg); } private void parseElements(String arg) throws ArgsException { for (int i = 1; i < arg.length(); i++) parseElement(arg.charAt(i)); } private void parseElement(char argChar) throws ArgsException { if (setArgument(argChar)) argsFound.add(argChar); else { unexpectedArguments.add(argChar); errorCode = ErrorCode.UNEXPECTED_ARGUMENT; valid = false; } } 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++; String parameter = null; try { 262 Аргументы String 263 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(); } } private void setBooleanArg(ArgumentMarshaler m) { try { m.set("true"); } catch (ArgsException e) { } } public int cardinality() { return argsFound.size(); } public String usage() { if (schema.length() > 0) return "-[" + schema + "]"; else return ""; } public String errorMessage() throws Exception { switch (errorCode) { case OK: throw new Exception("TILT: Should not get here."); case UNEXPECTED_ARGUMENT: return unexpectedArgumentMessage(); case MISSING_STRING: return String.format("Could not find string parameter for -%c.", errorArgumentId); case INVALID_INTEGER: return String.format("Argument -%c expects an integer but was '%s'.", errorArgumentId, errorParameter); продолжение 263 264 Глава 14 . Последовательное очищение листинг 14 .12 (продолжение) case MISSING_INTEGER: return String.format("Could not find integer parameter for -%c.", errorArgumentId); } return ""; } private String unexpectedArgumentMessage() { StringBuffer message = new StringBuffer("Argument(s) -"); for (char c : unexpectedArguments) { message.append(c); } message.append(" unexpected."); return message.toString(); } 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; } public String getString(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); try { return am == null ? "" : (String) am.get(); } catch (ClassCastException e) { return ""; } } public int getInt(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); try { return am == null ? 0 : (Integer) am.get(); } catch (Exception e) { return 0; } } public boolean has(char arg) { return argsFound.contains(arg); } public boolean isValid() { return valid; } 264 Аргументы String 265 private class ArgsException extends Exception { } private abstract class ArgumentMarshaler { public abstract void set(String s) throws ArgsException; 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 { private int intValue = 0; public void set(String s) throws ArgsException { try { intValue = Integer.parseInt(s); } catch (NumberFormatException e) { throw new ArgsException(); } } public Object get() { return intValue; } } } Вроде бы проделана большая работа, а результат не впечатляет . Структура кода немного улучшилась, но в начале листинга по-прежнему объявляются много- численные переменные; в setArgument осталась кошмарная конструкция проверки типа; функции set выглядят просто ужасно . Я уже не говорю об обработке оши- бок… Нам еще предстоит большая работа . Прежде всего хотелось бы избавиться от конструкции выбора в setArgument [G23] . В идеале она должна быть заменена единственным вызовом ArgumentMarshaler.set Это означает, что код setIntArg , setStringArg и setBooleanArg должен быть пере- 265 266 Глава 14 . Последовательное очищение мещен в соответствующие классы, производные от ArgumentMarshaler . Однако при этом возникает одна проблема . Внимательно присмотревшись к функции setIntArg , можно заметить, что в ней используются две переменные экземпляров: args и currentArg . Чтобы переместить setIntArg в BooleanArgumentMarshaler , мне придется передать args и currentArgs в аргументах при вызове . Решение получается «грязным» [F1] . Я бы предпочел передать один аргумент вместо двух . К счастью, у проблемы существует простое решение: мы можем преобразовать массив args в list и передать Iterator функ- циям set . Следующее преобразование было проведено за десять шагов, с обяза- тельным выполнением всех тестов после каждого шага . Здесь я приведу только конечный результат, но вы легко сможете опознать большинство промежуточных шагов по этому листингу . public class Args { private String schema; private String[] args; private boolean valid = true; private Set private Map private Set private Iterator private char errorArgumentId = '\0'; private String errorParameter = "TILT"; private ErrorCode errorCode = ErrorCode.OK; private List private enum ErrorCode { OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT} public Args(String schema, String[] args) throws ParseException { this.schema = schema; argsList = Arrays.asList(args); valid = parse(); } private boolean parse() throws ParseException { if (schema.length() == 0 && argsList.size() == 0) return true; parseSchema(); try { parseArguments(); } catch (ArgsException e) { } return valid; } --- private boolean parseArguments() throws ArgsException { for (currentArgument = argsList.iterator(); currentArgument.hasNext();) { String arg = currentArgument. next(); 266 Аргументы String 267 parseArgument(arg); } return true; } --- private void setIntArg(ArgumentMarshaler m) throws ArgsException { String parameter = null; try { parameter = currentArgument. next(); m.set(parameter); } catch ( NoSuchElementException 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 { try { m.set(currentArgument. next()); } catch ( NoSuchElementException e) { errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); } } Все изменения были простыми и не нарушали работы тестов . Теперь можно за- няться перемещением функций в соответствующие производные классы . Начнем с внесения изменений в setArgument : private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == null) return false; 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; } 267 268 Глава 14 . Последовательное очищение Это изменение важно, потому что мы хотим полностью устранить цепочку if - else . Для этого из нее необходимо вывести состояние ошибки . Теперь можно переходить к перемещению функций set . Функция setBooleanArg тривиальна, поэтому начнем с нее . Наша задача — изменить функцию setBool- eanArg так, чтобы она просто передавала управление BooleanArgumentMarshaler private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == null) return false; try { if (m instanceof BooleanArgumentMarshaler) setBooleanArg(m, currentArgument); else if (m instanceof StringArgumentMarshaler) setStringArg(m); else if (m instanceof IntegerArgumentMarshaler) setIntArg(m); } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } return true; } --- private void setBooleanArg(ArgumentMarshaler m, Iterator throws ArgsException { try { m.set("true"); catch (ArgsException e) { } } Но ведь мы только что перенесли обработку исключения в функцию? Ситуация с включением того, что вы намереваетесь вскоре исключить, весьма часто встре- чается при переработке кода . Малый размер шагов и необходимость прохождения тестов означает, что вам придется часто перемещать туда-сюда фрагменты кода . Переработка кода напоминает кубик Рубика: чтобы добиться большой цели, необходимо выполнить множество мелких операций . Каждая операция делает возможной следующую . Зачем передавать итератор, если setBooleanArg он не нужен? Потому что он ну- жен setIntArg и setStringArg ! И если я хочу организовать доступ ко всем трем функциям через абстрактный метод в ArgumentMarshaller , мне не обойтись без его передачи setBooleanArg Итак, функция setBooleanArg стала бесполезной . Если бы в ArgumentMarshaler при- сутствовала функция set , то мы могли бы вызвать ее напрямую . Значит, нужно создать такую функцию! Первым шагом станет включение нового абстрактного метода в ArgumentMarshaler 268 Аргументы String 269 private abstract class ArgumentMarshaler { public abstract void set(Iterator throws ArgsException; public abstract void set(String s) throws ArgsException; public abstract Object get(); } Конечно, это нарушает работу всех производных классов, поэтому мы добавим реализацию нового метода в каждый из них . private class BooleanArgumentMarshaler extends ArgumentMarshaler { private boolean booleanValue = false; public void set(Iterator booleanValue = true; } public void set(String s) { booleanValue = true; } public Object get() { return booleanValue; } } private class StringArgumentMarshaler extends ArgumentMarshaler { private String stringValue = ""; public void set(Iterator } public void set(String s) { stringValue = s; } public Object get() { return stringValue; } } private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intValue = 0; public void set(Iterator } public void set(String s) throws ArgsException { try { intValue = Integer.parseInt(s); } catch (NumberFormatException e) { throw new ArgsException(); } } 269 270 Глава 14 . Последовательное очищение public Object get() { return intValue; } } А теперь setBooleanArg можно удалить! private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == null) return false; try { if (m instanceof BooleanArgumentMarshaler) m.set(currentArgument); else if (m instanceof StringArgumentMarshaler) setStringArg(m); else if (m instanceof IntegerArgumentMarshaler) setIntArg(m); } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } return true; } Все тесты проходят, а функция set размещается в BooleanArgumentMarshaler ! Теперь можно сделать то же самое для String and Integer private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == null) return false; try { if (m instanceof BooleanArgumentMarshaler) m.set(currentArgument); else if (m instanceof StringArgumentMarshaler) m.set(currentArgument); else if (m instanceof IntegerArgumentMarshaler) m.set(currentArgument); } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } return true; } --- private class StringArgumentMarshaler extends ArgumentMarshaler { private String stringValue = ""; public void set(Iterator 270 Аргументы String 271 try { stringValue = currentArgument.next(); } catch (NoSuchElementException e) { errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); } } public void set(String s) { } public Object get() { return stringValue; } } private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intValue = 0; public void set(Iterator String parameter = null; try { parameter = currentArgument.next(); set(parameter); } catch (NoSuchElementException e) { errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (ArgsException e) { errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw e; } } public void set(String s) throws ArgsException { try { intValue = Integer.parseInt(s); } catch (NumberFormatException e) { throw new ArgsException(); } } public Object get() { return intValue; } } А теперь завершающий штрих: убираем цепочку if - else ! private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == null) return false; try { 271 272 Глава 14 . Последовательное очищение m.set(currentArgument); return true; } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } } Избавляемся от лишних функций в IntegerArgumentMarshaler и слегка чистим код: private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intValue = 0 public void set(Iterator String parameter = null; try { parameter = currentArgument.next(); |