Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
intValue = Integer.parseInt(parameter);Object get(); } А теперь посмотрите, как легко добавлять новые типы аргументов в эту структу- ру . Количество изменений минимально, а все изменения логически изолированы . Начнем с добавления нового тестового сценария, проверяющего правильность работы аргументов double : public void testSimpleDoublePresent() throws Exception { Args args = new Args("x##", new String[] {"-x","42.3"}); assertTrue(args.isValid()); assertEquals(1, args.cardinality()); assertTrue(args.has('x')); assertEquals(42.3, args.getDouble('x'), .001); } 272 Аргументы String 273 Чистим код разбора форматной строки и добавляем обнаружение ## для аргу- ментов типа double private void parseSchemaElement(String element) throws ParseException { char elementId = element.charAt(0); String elementTail = element.substring(1); validateSchemaElementId(elementId); if (elementTail. length() == 0) marshalers.put(elementId, new BooleanArgumentMarshaler()); else if (elementTail. equals("*")) marshalers.put(elementId, new StringArgumentMarshaler()); else if (elementTail. equals("#")) marshalers.put(elementId, new IntegerArgumentMarshaler()); else if (elementTail.equals("##")) marshalers.put(elementId, new DoubleArgumentMarshaler()); else throw new ParseException(String.format( "Argument: %c has invalid format: %s.", elementId, elementTail), 0); } Затем пишется класс DoubleArgumentMarshaler private class DoubleArgumentMarshaler implements ArgumentMarshaler { private double doubleValue = 0; public void set(Iterator String parameter = null; try { parameter = currentArgument.next(); doubleValue = Double.parseDouble(parameter); } catch (NoSuchElementException e) { errorCode = ErrorCode.MISSING_DOUBLE; throw new ArgsException(); } catch (NumberFormatException e) { errorParameter = parameter; errorCode = ErrorCode.INVALID_DOUBLE; throw new ArgsException(); } } public Object get() { return doubleValue; } } Для нового типа добавляются новые коды ошибок: private enum ErrorCode { OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT, MISSING_DOUBLE, INVALID_DOUBLE} А еще понадобится функция getDouble : public double getDouble(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); try { 273 274 Глава 14 . Последовательное очищение return am == null ? 0 : (Double) am.get(); } catch (Exception e) { return 0.0; } } И все тесты успешно проходят! Добавление нового типа прошло в целом без- болезненно . Теперь давайте убедимся в том, что обработка ошибок работает правильно . Следующий тестовый сценарий проверяет, что при передаче нераз- бираемой строки с аргументом ## выдается соответствующая ошибка: public void testInvalidDouble() throws Exception { Args args = new Args("x##", new String[] {"-x","Forty two"}); assertFalse(args.isValid()); assertEquals(0, args.cardinality()); assertFalse(args.has('x')); assertEquals(0, args.getInt('x')); assertEquals("Argument -x expects a double but was 'Forty two'.", args.errorMessage()); } --- 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); case MISSING_INTEGER: return String.format("Could not find integer parameter for -%c.", errorArgumentId); case INVALID_DOUBLE: return String.format("Argument -%c expects a double but was '%s'.", errorArgumentId, errorParameter); case MISSING_DOUBLE: return String.format("Could not find double parameter for -%c.", errorArgumentId); } return ""; } Тесты успешно проходят . Следующий тест проверяет, что ошибка с отсутствую- щим аргументом double будет успешно обнаружена: public void testMissingDouble() throws Exception { Args args = new Args("x##", new String[]{"-x"}); assertFalse(args.isValid()); assertEquals(0, args.cardinality()); 274 Аргументы String 275 assertFalse(args.has('x')); assertEquals(0.0, args.getDouble('x'), 0.01); assertEquals("Could not find double parameter for -x.", args.errorMessage()); } Как и ожидалось, все проходит успешно . Этот тест был написан просто для полноты картины . Код исключения некрасив, и в классе Args ему не место . Также в коде иници- ируется исключение ParseException , которое на самом деле нам не принадлежит . Давайте объединим все исключения в один класс ArgsException и переместим его в отдельный модуль . public class ArgsException extends Exception { private char errorArgumentId = '\0'; private String errorParameter = "TILT"; private ErrorCode errorCode = ErrorCode.OK; public ArgsException() {} public ArgsException(String message) {super(message);} public enum ErrorCode { OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT, MISSING_DOUBLE, INVALID_DOUBLE} } --- public class Args { private char errorArgumentId = '\0'; private String errorParameter = "TILT"; private ArgsException.ErrorCode errorCode = ArgsException.ErrorCode.OK; private List public Args(String schema, String[] args) throws ArgsException { this.schema = schema; argsList = Arrays.asList(args); valid = parse(); } private boolean parse() throws ArgsException { if (schema.length() == 0 && argsList.size() == 0) return true; parseSchema(); try { parseArguments(); } catch ( ArgsException e) { } return valid; } 275 276 Глава 14 . Последовательное очищение private boolean parseSchema() throws ArgsException { } private void parseSchemaElement(String element) throws ArgsException { else throw new ArgsException( String.format("Argument: %c has invalid format: %s.", elementId,elementTail)); } private void validateSchemaElementId(char elementId) throws ArgsException { if (!Character.isLetter(elementId)) { throw new ArgsException( "Bad character:" + elementId + "in Args format: " + schema); } } private void parseElement(char argChar) throws ArgsException { if (setArgument(argChar)) argsFound.add(argChar); else { unexpectedArguments.add(argChar); errorCode = ArgsException.ErrorCode.UNEXPECTED_ARGUMENT; valid = false; } } private class StringArgumentMarshaler implements ArgumentMarshaler { private String stringValue = ""; public void set(Iterator try { stringValue = currentArgument.next(); } catch (NoSuchElementException e) { errorCode = ArgsException.ErrorCode.MISSING_STRING; throw new ArgsException(); } } public Object get() { return stringValue; } } private class IntegerArgumentMarshaler implements ArgumentMarshaler { private int intValue = 0; public void set(Iterator String parameter = null; 276 Аргументы String 277 try { parameter = currentArgument.next(); intValue = Integer.parseInt(parameter); } catch (NoSuchElementException e) { errorCode = ArgsException.ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (NumberFormatException e) { errorParameter = parameter; errorCode = ArgsException.ErrorCode.INVALID_INTEGER; throw new ArgsException(); } } public Object get() { return intValue; } } private class DoubleArgumentMarshaler implements ArgumentMarshaler { private double doubleValue = 0; public void set(Iterator String parameter = null; try { parameter = currentArgument.next(); doubleValue = Double.parseDouble(parameter); } catch (NoSuchElementException e) { errorCode = ArgsException.ErrorCode.MISSING_DOUBLE; throw new ArgsException(); } catch (NumberFormatException e) { errorParameter = parameter; errorCode = ArgsException.ErrorCode.INVALID_DOUBLE; throw new ArgsException(); } } public Object get() { return doubleValue; } } } Хорошо — теперь Args выдает единственное исключение ArgsException . Выделение ArgsException в отдельный модуль приведет к тому, что большой объем вспомога- тельного кода обработки ошибок переместится из модуля Args в этот модуль . Это наиболее естественное и очевидное место для размещения этого кода, вдобавок перемещение поможет очистить перерабатываемый модуль Args Итак, нам удалось полностью отделить код исключений и ошибок от модуля Args (листинги 14 .13–14 .16) . Для решения этой задачи понадобилось примерно 30 промежуточных шагов, и после каждого шага проверялось прохождение всех тестов . 277 278 Глава 14 . Последовательное очищение листинг 14 .13 . ArgsTest.java package com.objectmentor.utilities.args; import junit.framework.TestCase; public class ArgsTest extends TestCase { public void testCreateWithNoSchemaOrArguments() throws Exception { Args args = new Args("", new String[0]); assertEquals(0, args.cardinality()); } public void testWithNoSchemaButWithOneArgument() throws Exception { try { new Args("", new String[]{"-x"}); fail(); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.UNEXPECTED_ARGUMENT, e.getErrorCode()); assertEquals('x', e.getErrorArgumentId()); } } public void testWithNoSchemaButWithMultipleArguments() throws Exception { try { new Args("", new String[]{"-x", "-y"}); fail(); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.UNEXPECTED_ARGUMENT, e.getErrorCode()); assertEquals('x', e.getErrorArgumentId()); } } public void testNonLetterSchema() throws Exception { try { new Args("*", new String[]{}); fail("Args constructor should have thrown exception"); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.INVALID_ARGUMENT_NAME, e.getErrorCode()); assertEquals('*', e.getErrorArgumentId()); } } public void testInvalidArgumentFormat() throws Exception { try { new Args("f", new String[]{}); fail("Args constructor should have throws exception"); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.INVALID_FORMAT, e.getErrorCode()); 278 Аргументы String 279 assertEquals('f', e.getErrorArgumentId()); } } public void testSimpleBooleanPresent() throws Exception { Args args = new Args("x", new String[]{"-x"}); assertEquals(1, args.cardinality()); assertEquals(true, args.getBoolean('x')); } public void testSimpleStringPresent() throws Exception { Args args = new Args("x*", new String[]{"-x", "param"}); assertEquals(1, args.cardinality()); assertTrue(args.has('x')); assertEquals("param", args.getString('x')); } public void testMissingStringArgument() throws Exception { try { new Args("x*", new String[]{"-x"}); fail(); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.MISSING_STRING, e.getErrorCode()); assertEquals('x', e.getErrorArgumentId()); } } public void testSpacesInFormat() throws Exception { Args args = new Args("x, y", new String[]{"-xy"}); assertEquals(2, args.cardinality()); assertTrue(args.has('x')); assertTrue(args.has('y')); } public void testSimpleIntPresent() throws Exception { Args args = new Args("x#", new String[]{"-x", "42"}); assertEquals(1, args.cardinality()); assertTrue(args.has('x')); assertEquals(42, args.getInt('x')); } public void testInvalidInteger() throws Exception { try { new Args("x#", new String[]{"-x", "Forty two"}); fail(); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.INVALID_INTEGER, e.getErrorCode()); assertEquals('x', e.getErrorArgumentId()); assertEquals("Forty two", e.getErrorParameter()); } } продолжение 279 280 Глава 14 . Последовательное очищение листинг 14 .13 (продолжение) public void testMissingInteger() throws Exception { try { new Args("x#", new String[]{"-x"}); fail(); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.MISSING_INTEGER, e.getErrorCode()); assertEquals('x', e.getErrorArgumentId()); } } public void testSimpleDoublePresent() throws Exception { Args args = new Args("x##", new String[]{"-x", "42.3"}); assertEquals(1, args.cardinality()); assertTrue(args.has('x')); assertEquals(42.3, args.getDouble('x'), .001); } public void testInvalidDouble() throws Exception { try { new Args("x##", new String[]{"-x", "Forty two"}); fail(); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.INVALID_DOUBLE, e.getErrorCode()); assertEquals('x', e.getErrorArgumentId()); assertEquals("Forty two", e.getErrorParameter()); } } public void testMissingDouble() throws Exception { try { new Args("x##", new String[]{"-x"}); fail(); } catch (ArgsException e) { assertEquals(ArgsException.ErrorCode.MISSING_DOUBLE, e.getErrorCode()); assertEquals('x', e.getErrorArgumentId()); } } } листинг 14 .14 . ArgsExceptionTest.java public class ArgsExceptionTest extends TestCase { public void testUnexpectedMessage() throws Exception { ArgsException e = new ArgsException(ArgsException.ErrorCode.UNEXPECTED_ARGUMENT, 'x', null); assertEquals("Argument -x unexpected.", e.errorMessage()); } public void testMissingStringMessage() throws Exception { ArgsException e = new ArgsException(ArgsException.ErrorCode.MISSING_STRING, 'x', null); 280 Аргументы String 281 assertEquals("Could not find string parameter for -x.", e.errorMessage()); } public void testInvalidIntegerMessage() throws Exception { ArgsException e = new ArgsException(ArgsException.ErrorCode.INVALID_INTEGER, 'x', "Forty two"); assertEquals("Argument -x expects an integer but was 'Forty two'.", e.errorMessage()); } public void testMissingIntegerMessage() throws Exception { ArgsException e = new ArgsException(ArgsException.ErrorCode.MISSING_INTEGER, 'x', null); assertEquals("Could not find integer parameter for -x.", e.errorMessage()); } public void testInvalidDoubleMessage() throws Exception { ArgsException e = new ArgsException(ArgsException.ErrorCode.INVALID_DOUBLE, 'x', "Forty two"); assertEquals("Argument -x expects a double but was 'Forty two'.", e.errorMessage()); } public void testMissingDoubleMessage() throws Exception { ArgsException e = new ArgsException(ArgsException.ErrorCode.MISSING_DOUBLE, 'x', null); assertEquals("Could not find double parameter for -x.", e.errorMessage()); } } листинг 14 .15 . ArgsException.java public class ArgsException extends Exception { private char errorArgumentId = '\0'; private String errorParameter = "TILT"; private ErrorCode errorCode = ErrorCode.OK; public ArgsException() {} public ArgsException(String message) {super(message);} public ArgsException(ErrorCode errorCode) { this.errorCode = errorCode; } public ArgsException(ErrorCode errorCode, String errorParameter) { this.errorCode = errorCode; this.errorParameter = errorParameter; } public ArgsException(ErrorCode errorCode, char errorArgumentId, String errorParameter) { продолжение 281 282 Глава 14 . Последовательное очищение листинг 14 .15 (продолжение) this.errorCode = errorCode; this.errorParameter = errorParameter; this.errorArgumentId = errorArgumentId; } public char getErrorArgumentId() { return errorArgumentId; } public void setErrorArgumentId(char errorArgumentId) { this.errorArgumentId = errorArgumentId; } public String getErrorParameter() { return errorParameter; } public void setErrorParameter(String errorParameter) { this.errorParameter = errorParameter; } public ErrorCode getErrorCode() { return errorCode; } public void setErrorCode(ErrorCode errorCode) { this.errorCode = errorCode; } public String errorMessage() throws Exception { switch (errorCode) { case OK: throw new Exception("TILT: Should not get here."); case UNEXPECTED_ARGUMENT: return String.format("Argument -%c unexpected.", errorArgumentId); 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); case MISSING_INTEGER: return String.format("Could not find integer parameter for -%c.", errorArgumentId); case INVALID_DOUBLE: return String.format("Argument -%c expects a double but was '%s'.", errorArgumentId, errorParameter); case MISSING_DOUBLE: return String.format("Could not find double parameter for -%c.", errorArgumentId); 282 |