Чистый код. Создание, анализ и рефакторинг — страница 50 из 94

 marshalers =

    new HashMap();

  private Set argsFound = new HashSet();

  private Iterator currentArgument;

  private List argsList;


  public Args(String schema, String[] args) throws ArgsException {

    this.schema = schema;

    argsList = Arrays.asList(args);

    parse();

  }


  private void parse() throws ArgsException {

    parseSchema();

    parseArguments();

  }


  private boolean parseSchema() throws ArgsException {

    for (String element : schema.split(",")) {

      if (element.length() > 0) {

        parseSchemaElement(element.trim());

      }

    }

    return true;

  }


  private void parseSchemaElement(String element) throws ArgsException {

    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("##"))


Листинг 14.16 (продолжение)

      marshalers.put(elementId, new DoubleArgumentMarshaler());

    else

      throw new ArgsException(ArgsException.ErrorCode.INVALID_FORMAT,

                              elementId, elementTail);

  }


  private void validateSchemaElementId(char elementId) throws ArgsException {

    if (!Character.isLetter(elementId)) {

      throw new ArgsException(ArgsException.ErrorCode.INVALID_ARGUMENT_NAME,

                              elementId, null);

    }

  }


  private void parseArguments() throws ArgsException {

    for (currentArgument = argsList.iterator(); currentArgument.hasNext();) {

      String arg = currentArgument.next();

      parseArgument(arg);

    }

  }


  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 {

      throw new ArgsException(ArgsException.ErrorCode.UNEXPECTED_ARGUMENT,

                              argChar, null);

    }

  }


  private boolean setArgument(char argChar) throws ArgsException {

    ArgumentMarshaler m = marshalers.get(argChar);

    if (m == null)

      return false;

    try {

      m.set(currentArgument);

      return true;

    } catch (ArgsException e) {

      e.setErrorArgumentId(argChar);

      throw e;

    }

  }

  public int cardinality() {

    return argsFound.size();

  }

  public String usage() {

    if (schema.length() > 0)

      return "-[" + schema + "]";

    else

      return "";

  }

  public boolean getBoolean(char arg) {

    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) {

    ArgumentMarshaler am = marshalers.get(arg);

    try {

      return am == null ? "" : (String) am.get();

    } catch (ClassCastException e) {

      return "";

    }

  }


  public int getInt(char arg) {

    ArgumentMarshaler am = marshalers.get(arg);

    try {

      return am == null ? 0 : (Integer) am.get();

    } catch (Exception e) {

      return 0;

    }

  }


  public double getDouble(char arg) {

    ArgumentMarshaler am = marshalers.get(arg);

    try {

      return am == null ? 0 : (Double) am.get();

    } catch (Exception e) {

      return 0.0;

    }

  }


  public boolean has(char arg) {

    return argsFound.contains(arg);

  }

}

Основные изменения в классе Args свелись к удалениям. Большая часть кода ушла из Args в ArgsException. Хорошо. Мы также переместили все разновидности ArgumentMarshaller в отдельные файлы. Еще лучше!

Одним из важнейших аспектов хорошей программной архитектуры является логическое разбиение кода — создание подходящих мест для размещения разных кодовых блоков. Разделение ответственности заметно упрощает понимание и сопровождение кода.

Обратите внимание на метод errorMessage класса ArgsException. Очевидно, размещение форматирования сообщения об ошибках нарушает принцип единой ответственности. Класс Args должен заниматься обработкой аргументов, а не форматом сообщений об ошибках. Но насколько логично размещать код форматирования сообщений в ArgsException?

Откровенно говоря, это компромиссное решение. Пользователям, которым не нравится, что сообщения об ошибках поставляет класс ArgsException, придется написать собственную реализацию.

К этому моменту мы уже вплотную подошли к окончательному решению, приведенному в начале этой главы. Завершающие преобразования остаются читателю для самостоятельных упражнений.