Главная страница
Навигация по странице:

  • ArgumentMarshaler

  • Args.ArgumentMarshaler am

  • public void setString(String s) { stringValue = s; } public String getString() { return stringValue == null "" : stringValue; }

  • private int integerValue;

  • Создание, анализ ирефакторинг


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница28 из 49
    1   ...   24   25   26   27   28   29   30   31   ...   49
    листинг 14 .9 (продолжение)
    private void parseBooleanSchemaElement(String element) {
    char c = element.charAt(0);
    if (Character.isLetter(c)) {
    booleanArgs.put(c, false);
    }
    }
    private boolean parseArguments() {
    for (String arg : args)
    parseArgument(arg);
    return true;
    }
    private void parseArgument(String arg) {
    if (arg.startsWith("-"))
    parseElements(arg);
    }
    private void parseElements(String arg) {
    for (int i = 1; i < arg.length(); i++)
    parseElement(arg.charAt(i));
    }
    private void parseElement(char argChar) {
    if (isBoolean(argChar)) {
    numberOfArguments++;
    setBooleanArg(argChar, true);
    } else unexpectedArguments.add(argChar);
    }
    private void setBooleanArg(char argChar, boolean value) {
    booleanArgs.put(argChar, value);
    }
    private boolean isBoolean(char argChar) {
    return booleanArgs.containsKey(argChar);
    }
    public int cardinality() {
    return numberOfArguments;
    }
    public String usage() {
    if (schema.length() > 0)
    return "-["+schema+"]";
    else return "";
    }
    240

    Args: черновик
    241
    public String errorMessage() {
    if (unexpectedArguments.size() > 0) {
    return unexpectedArgumentMessage();
    } else 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) {
    return booleanArgs.get(arg);
    }
    }
    В этом коде можно найти множество недостатков, однако в целом он не так уж плох . Код компактен и прост, в нем легко разобраться . Тем не менее в этом коде легко прослеживаются зачатки будущего беспорядочного месива . Нетрудно по- нять, как из него выросла вся последующая неразбериха .
    Обратите внимание: в последующей неразберихе добавились всего два новых типа аргументов,
    String и integer
    . Добавление всего двух типов аргументов имело огромные отрицательные последствия для кода . Более или менее понятный код превратился в запутанный клубок, наверняка кишащий множеством ошибок и недочетов .
    Два новых типа аргументов добавлялись последовательно . Сначала я добавил поддержку
    String
    , что привело к следующему результату .
    листинг 14 .10 . Args.java (Boolean и String)
    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 unexpectedArguments = new TreeSet();
    private Map booleanArgs = new HashMap();
    private Map stringArgs =
    продолжение
    241

    242
    Глава 14 . Последовательное очищение
    листинг 14 .10 (продолжение)
    new HashMap();
    private Set argsFound = new HashSet();
    private int currentArgument;
    private char errorArgument = '\0';
    enum ErrorCode {
    OK, MISSING_STRING}
    private ErrorCode errorCode = ErrorCode.OK;
    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;
    parseSchema();
    parseArguments();
    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))
    parseBooleanSchemaElement(elementId);
    else if (isStringSchemaElement(elementTail))
    parseStringSchemaElement(elementId);
    }
    private void validateSchemaElementId(char elementId) throws ParseException {
    if (!Character.isLetter(elementId)) {
    throw new ParseException(
    "Bad character:" + elementId + "in Args format: " + schema, 0);
    }
    }
    242

    Args: черновик
    243
    private void parseStringSchemaElement(char elementId) {
    stringArgs.put(elementId, "");
    }
    private boolean isStringSchemaElement(String elementTail) {
    return elementTail.equals("*");
    }
    private boolean isBooleanSchemaElement(String elementTail) {
    return elementTail.length() == 0;
    }
    private void parseBooleanSchemaElement(char elementId) {
    booleanArgs.put(elementId, false);
    }
    private boolean parseArguments() {
    for (currentArgument = 0; currentArgument < args.length; currentArgument++)
    {
    String arg = args[currentArgument];
    parseArgument(arg);
    }
    return true;
    }
    private void parseArgument(String arg) {
    if (arg.startsWith("-"))
    parseElements(arg);
    }
    private void parseElements(String arg) {
    for (int i = 1; i < arg.length(); i++)
    parseElement(arg.charAt(i));
    }
    private void parseElement(char argChar) {
    if (setArgument(argChar))
    argsFound.add(argChar);
    else {
    unexpectedArguments.add(argChar);
    valid = false;
    }
    }
    private boolean setArgument(char argChar) {
    boolean set = true;
    if (isBoolean(argChar))
    setBooleanArg(argChar, true);
    else if (isString(argChar))
    setStringArg(argChar, "");
    else
    продолжение
    243

    244
    Глава 14 . Последовательное очищение
    листинг 14 .10 (продолжение)
    set = false;
    return set;
    }
    private void setStringArg(char argChar, String s) {
    currentArgument++;
    try {
    stringArgs.put(argChar, args[currentArgument]);
    } catch (ArrayIndexOutOfBoundsException e) {
    valid = false;
    errorArgument = argChar;
    errorCode = ErrorCode.MISSING_STRING;
    }
    }
    private boolean isString(char argChar) {
    return stringArgs.containsKey(argChar);
    }
    private void setBooleanArg(char argChar, boolean value) {
    booleanArgs.put(argChar, value);
    }
    private boolean isBoolean(char argChar) {
    return booleanArgs.containsKey(argChar);
    }
    public int cardinality() {
    return argsFound.size();
    }
    public String usage() {
    if (schema.length() > 0)
    return "-[" + schema + "]";
    else return "";
    }
    public String errorMessage() throws Exception {
    if (unexpectedArguments.size() > 0) {
    return unexpectedArgumentMessage();
    } else switch (errorCode) {
    case MISSING_STRING:
    return String.format("Could not find string parameter for -%c.", errorArgument);
    case OK:
    throw new Exception("TILT: Should not get here.");
    }
    return "";
    }
    244

    Args: черновик
    245
    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) {
    return falseIfNull(booleanArgs.get(arg));
    }
    private boolean falseIfNull(Boolean b) {
    return b == null ? false : b;
    }
    public String getString(char arg) {
    return blankIfNull(stringArgs.get(arg));
    }
    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 я знал, что для каждого типа ар- гументов новый код должен добавляться в трех основных местах . Во-первых, для
    245

    246
    Глава 14 . Последовательное очищение каждого типа аргументов необходимо было обеспечить разбор соответствующего элемента форматной строки, чтобы выбрать объект
    HashMap для этого типа . Затем аргумент соответствующего типа необходимо было разобрать в командной строке и преобразовать к истинному типу . Наконец, для каждого типа аргументов тре- бовался метод getXXX
    , возвращающий значение аргумента с его истинным типом .
    Много разных типов, обладающих сходными методами… Наводит на мысли о классе . Так родилась концепция
    ArgumentMarshaler
    О постепенном усовершенствовании
    Один из верных способов убить программу — вносить глобальные изменения в ее структуру с целью улучшения . Некоторые программы уже никогда не приходят в себя после таких «усовершенствований» . Проблема в том, что код очень трудно заставить работать так же, как он работал до «усовершенствования» .
    Чтобы этого не произошло, я воспользовался методологией разработки через тестирование (TDD) . Одна из центральных доктрин этой методологии гласит, что система должна работать в любой момент в процессе внесения изменений .
    Иначе говоря, при использовании TDD запрещено вносить в систему изменения, нарушающие работоспособность этой системы . С каждым вносимым изменением система должна работать так же, как она работала прежде .
    Для этого был необходим пакет автоматизированных тестов . Запуская их в любой момент времени, я мог бы убедиться в том, что поведение системы осталось не- изменным . Я уже создал пакет модульных и приемочных тестов для класса
    Args
    , пока работал над начальной версией (она же «беспорядочное месиво») . Модуль- ные тесты были написаны на Java и находились под управлением JUnit . Приемоч- ные тесты были оформлены в виде вики-страниц в FitNesse . Я мог запустить эти тесты в любой момент по своему усмотрению, и если они проходили — можно было не сомневаться в том, что система работает именно так, как положено .
    И тогда я занялся внесением множества очень маленьких изменений . Каждое из- менение продвигало структуру системы к концепции
    ArgumentMarshaler
    , но после каждого изменения система продолжала нормально работать . На первом этапе я добавил заготовку
    ArgumentMarshaller в конец месива (листинг 14 .11) .
    листинг 14 .11 . Класс ArgumentMarshaller, присоединенный к Args.java private class ArgumentMarshaler {
    private boolean booleanValue = false;
    public void setBoolean(boolean value) {
    booleanValue = value;
    }
    public boolean getBoolean() {return booleanValue;}
    }
    private class BooleanArgumentMarshaler extends ArgumentMarshaler {
    246

    Args: черновик
    247
    }
    private class StringArgumentMarshaler extends ArgumentMarshaler {
    }
    private class IntegerArgumentMarshaler extends ArgumentMarshaler {
    }
    }
    Понятно, что добавление класса ничего не нарушит . Поэтому я внес самое простейшее из всех возможных изменений — изменил контейнер
    HashMap для логических аргументов так, чтобы при конструировании передавался тип
    Argu- mentMarshaler
    :
    private MapArgumentMarshaler> booleanArgs = new HashMapArgumentMarshaler>();
    Это нарушило работу нескольких команд, которые я быстро исправил .
    private void parseBooleanSchemaElement(char elementId) {
    booleanArgs.put(elementId,
    new BooleanArgumentMarshaler());
    }
    private void setBooleanArg(char argChar, boolean value) {
    booleanArgs.
    get(argChar).setBoolean(value);
    }
    public boolean getBoolean(char arg) {
    return falseIfNull(booleanArgs.get(arg).getBoolean());
    }
    Изменения вносятся в тех местах, о которых я упоминал ранее: методы parse
    , set и get для типа аргумента . К сожалению, при всей незначительности изменений некоторые тесты стали завершаться неудачей . Внимательно присмотревшись к getBoolean
    , вы увидите, что если при вызове метода с 'y'
    аргумента y
    не суще- ствует, вызов booleanArgs.get(‘y’)
    вернет null
    , а функция выдаст исключение
    NullPointerException
    . Функция falseIfNull защищала от подобных ситуаций, но в результате внесенных изменений она перестала работать .
    Стратегия постепенных изменений требовала, чтобы я немедленно наладил ра- боту программы, прежде чем вносить какие-либо дополнительные изменения .
    Действительно, проблема решалась просто: нужно было добавить проверку null
    Но на этот раз проверять нужно было не логическое значение, а
    ArgumentMarshaller
    Сначала я убрал вызов falseIfNull из getBoolean
    . Функция falseIfNull стала бесполезной, поэтому я убрал и саму функцию . Тесты все равно не проходили, поэтому я был уверен, что новых ошибок от этого уже не прибавится .
    public boolean getBoolean(char arg) {
    return booleanArgs.get(arg).getBoolean();
    }
    Затем я разбил функцию getBoolean надвое и разместил
    ArgumentMarshaller в соб- ственной переменной с именем argumentMarshaller
    . Длинное имя мне не понра-
    247

    248
    Глава 14 . Последовательное очищение вилось; во-первых, оно было избыточным, а во-вторых, загромождало функцию .
    Соответственно я сократил его до am
    [N5] .
    public boolean getBoolean(char arg) {
    Args.ArgumentMarshaler
    am = booleanArgs.get(arg);
    return
    am.getBoolean();
    }
    Наконец, я добавил логику проверки null
    :
    public boolean getBoolean(char arg) {
    Args.ArgumentMarshaler am = booleanArgs.get(arg);
    return
    am != null && am.getBoolean();
    }
    Аргументы String
    Добавление поддержки
    String было очень похоже на добавление поддержки
    Boolean
    . Мне предстояло изменить
    HashMap и заставить работать функции parse
    , set и get
    . Полагаю, следующий код понятен без пояснений — если не считать того, что я разместил всю реализацию компоновки аргументов в базовом клссе
    ArgumentMarshaller
    , вместо того чтобы распределять ее по производным классам .
    private MapArgumentMarshaler> stringArgs = new HashMapArgumentMarshaler>();
    private void parseStringSchemaElement(char elementId) {
    stringArgs.put(elementId,
    new StringArgumentMarshaler());
    }
    private void setStringArg(char argChar) throws ArgsException {
    currentArgument++;
    try {
    stringArgs.
    get(argChar).setString(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 ? "" : am.getString();
    }
    private class ArgumentMarshaler {
    private boolean booleanValue = false;
    private String stringValue;
    public void setBoolean(boolean value) {
    booleanValue = value;
    }
    248

    Аргументы String
    249
    public boolean getBoolean() {
    return booleanValue;
    }
    public void setString(String s) {
    stringValue = s;
    }
    public String getString() {
    return stringValue == null ? "" : stringValue;
    }
    }
    И снова изменения вносились последовательно и только так, чтобы тесты по крайней мере хотя бы запускались (даже если и не проходили) . Если работо- способность теста была нарушена, я сначала добивался того, чтобы он работал, и только потом переходил к следующему изменению .
    Вероятно, вы уже поняли, что я собираюсь сделать . Собрав все текущее поведе- ние компоновки аргументов в базовом классе
    ArgumentMarshaler
    , я намерен пере- мещать его вниз в производные классы . Это позволит мне сохранить работоспо- собность программы в ходе постепенного изменения ее структуры .
    Очевидным следующим шагом стало перемещение функциональности аргумента int в
    ArgumentMarshaler
    . И снова все обошлось без сюрпризов:
    private MapArgumentMarshaler> intArgs = new HashMapArgumentMarshaler>();
    private void parseIntegerSchemaElement(char elementId) {
    intArgs.put(elementId,
    new IntegerArgumentMarshaler());
    }
    private void setIntArg(char argChar) throws ArgsException {
    currentArgument++;
    String parameter = null;
    try {
    parameter = args[currentArgument];
    intArgs.
    get(argChar).setInteger(Integer.parseInt(parameter));
    } catch (ArrayIndexOutOfBoundsException e) {
    valid = false;
    errorArgumentId = argChar;
    errorCode = ErrorCode.MISSING_INTEGER;
    throw new ArgsException();
    } catch (NumberFormatException e) {
    valid = false;
    errorArgumentId = argChar;
    errorParameter = parameter;
    errorCode = ErrorCode.INVALID_INTEGER;
    throw new ArgsException();
    }
    }
    public int getInt(char arg) {
    249

    250
    Глава 14 . Последовательное очищение
    Args.ArgumentMarshaler am = intArgs.get(arg);
    return
    am == null ? 0 : am.getInteger();
    }
    private class ArgumentMarshaler {
    private boolean booleanValue = false;
    private String stringValue;
    private int integerValue;
    public void setBoolean(boolean value) {
    booleanValue = value;
    }
    public boolean getBoolean() {
    return booleanValue;
    }
    public void setString(String s) {
    stringValue = s;
    }
    public String getString() {
    return stringValue == null ? "" : stringValue;
    }
    1   ...   24   25   26   27   28   29   30   31   ...   49


    написать администратору сайта