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

}


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

    return false;

  return true;

}

На следующем шаге я перешел на использование ассоциативного массива marshalers в функциях 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++;

  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.  Модульные тесты этого не делали. До этого момента я запускал только модульные тесты[69].

Последнее изменение позволило исключить еще одну точку использования объекта 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 booleanArgs =

  new HashMap();

  private Map stringArgs =

    new HashMap();

  private Map intArgs =

    new HashMap();

  private Map marshalers =

    new HashMap();

...

Далее я проделал аналогичную процедуру для аргументов String и Integer и немного подчистил код:

  private void parseBooleanSchemaElement(char elementId) {

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

  }


  private void parseIntegerSchemaElement(char elementId) {

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

  }