Clean Code

[Clean Code] Chapter 14 점진적 개선

로드태환 2022. 3. 28. 16:47

프로그래밍은 과학보다 공예에 가깝다.

깨끗한 코드를 짜기 위해서는 돌아만 가는 지저분한 코드를 작성한 뒤에 정리해야 한다

 

Args의 간단한 사용.

public static void main(String[] args) {
	try {
		Args arg = new Args("l,p#,d*", args);
		boolean logging = arg.getBoolean('l');
		int port = arg.getInt('p');
		String directory = arg.getString('d');
		executeAppliocation(logging, port, directory);
	} catch (ArgsException e) {
		System.out.printf("Argument error: %s\n", e.errorMessage());
	}
}

Args의 구현

import java.text.ParseException; 
import java.util.*;

public class Args {
  private String schema;
  private String[] args;
  private boolean valid = true;
  private Set<Character> unexpectedArguments = new TreeSet<Character>(); 
  private Map<Character, Boolean> booleanArgs = new HashMap<Character, Boolean>();
  private Map<Character, String> stringArgs = new HashMap<Character, String>(); 
  private Map<Character, Integer> intArgs = new HashMap<Character, Integer>(); 
  private Set<Character> argsFound = new HashSet<Character>();
  private int currentArgument;
  private char errorArgumentId = '\0';
  private String errorParameter = "TILT";
  private ErrorCode errorCode = ErrorCode.OK;
  
  private enum ErrorCode {
    OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT}
    
  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(); 
    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)) 
      parseBooleanSchemaElement(elementId);
    else if (isStringSchemaElement(elementTail)) 
      parseStringSchemaElement(elementId);
    else if (isIntegerSchemaElement(elementTail)) 
      parseIntegerSchemaElement(elementId);
    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 void parseBooleanSchemaElement(char elementId) { 
    booleanArgs.put(elementId, false);
  }
  
  private void parseIntegerSchemaElement(char elementId) { 
    intArgs.put(elementId, 0);
  }
  
  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 boolean isIntegerSchemaElement(String elementTail) { 
    return elementTail.equals("#");
  }
  
  private boolean parseArguments() throws ArgsException {
    for (currentArgument = 0; currentArgument < args.length; currentArgument++) {
      String arg = args[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 { 
    if (isBooleanArg(argChar))
      setBooleanArg(argChar, true); 
    else if (isStringArg(argChar))
      setStringArg(argChar); 
    else if (isIntArg(argChar))
      setIntArg(argChar); 
    else
      return false;
    
    return true; 
  }
  
  private boolean isIntArg(char argChar) {
    return intArgs.containsKey(argChar);
  }
  
  private void setIntArg(char argChar) throws ArgsException { 
    currentArgument++;
    String parameter = null;
    try {
      parameter = args[currentArgument];
      intArgs.put(argChar, new Integer(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();
    } 
  }
  
  private void setStringArg(char argChar) throws ArgsException { 
    currentArgument++;
    try {
      stringArgs.put(argChar, args[currentArgument]); 
    } catch (ArrayIndexOutOfBoundsException e) {
      valid = false;
      errorArgumentId = argChar;
      errorCode = ErrorCode.MISSING_STRING; 
      throw new ArgsException();
    } 
  }
  
  private boolean isStringArg(char argChar) { 
    return stringArgs.containsKey(argChar);
  }
  
  private void setBooleanArg(char argChar, boolean value) { 
    booleanArgs.put(argChar, value);
  }
  
  private boolean isBooleanArg(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 { 
    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);
    }
    return ""; 
  }
  
  private String unexpectedArgumentMessage() {
    StringBuffer message = new StringBuffer("Argument(s) -"); 
    for (char c : unexpectedArguments) {
      message.append(c); 
    }
    message.append(" unexpected.");
    
    return message.toString(); 
  }
  
  private boolean falseIfNull(Boolean b) { 
    return b != null && b;
  }
  
  private int zeroIfNull(Integer i) { 
    return i == null ? 0 : i;
  }
  
  private String blankIfNull(String s) { 
    return s == null ? "" : s;
  }
  
  public String getString(char arg) { 
    return blankIfNull(stringArgs.get(arg));
  }
  
  public int getInt(char arg) {
    return zeroIfNull(intArgs.get(arg));
  }
  
  public boolean getBoolean(char arg) { 
    return falseIfNull(booleanArgs.get(arg));
  }
  
  public boolean has(char arg) { 
    return argsFound.contains(arg);
  }
  
  public boolean isValid() { 
    return valid;
  }
  
  private class ArgsException extends Exception {
  } 
}

위 코드는 Boolean 인수만 지원하던 꽤 괜찮은 코드에 String과 Int 형 인수 유형을 추가하면서 엉망이 된 경우이다.

수 많은 인스턴스 변수 개수, 'TILT' 같은 의미 모를 문자열, try catch catch 블록 등이 코드의 가독성을 떨어뜨린다.

 

추가할 유형이 더 있다면 여기서 멈추고 리펙터링을 해야한다. 위의 코드는 새 인수를 추가할 때 세곳에 코드를 추가해야 한다. 

  1. 인수 유형에 따른 해시맵을 선택하기 위해 스키마를 분석하는 부분
  2. 명령행 인수에서 인수 유형을 분석해 진짜 유형으로 변환하는 부분
  3. getXX 메서드를 구현해 호출자에게 진짜 유형을 반환하는 부분

인수 유형은 다양하지만 모두 유사한 메서드를 제공하므로 하나의 클래스로 분리해 관리하는게 적합하다고 판단되어 ArgumentMarshaler 라는 개념이 나왔다.

 개선이라는 이름아래 프로그램의 구조를 크게 뒤집으면 개선 전과 똑같이 프로그램을 돌리기 어려워 망치기 딱 좋다. 

그래서 책에서는 TDD를 사용했다. 덕분에 변경 후에도 시스템이 전과 똑같이 의도한대로 돌아간다는 것이 확실해졌다.

 

우선 추상 클래스인 ArgumentMarshaler를 만들고 이의 파생 클래스를 만들어 코드를 분리하고 각 파생클래스에서 세부 구현을 하도록 하여 전체 시스템이 조금 더 일반적으로 작동할 수 있게 하였다. 

 ArgumentMarshaler클래스에 set, get을 만들고 이를 각 유형별 파생클래스에서 구현한다. 이러면 각 인수 유형마다 만든 해시맵을 하나만 만들수 있고 parse 메서드도 인라인으로 작성이 가능하다.

package com.road3144.lec4th;
import java.text.ParseException;
import java.util.*;

public class Args {
	private String schema;
	private String[] args;
	private boolean valid = true;
	private Set<Character> unexpectedArguments = new TreeSet<Character>();
    //ArgumentMarshaler 맵 하나만 만듬
	private Map<Character, ArgumentMarshaler> marshalers = new HashMap<Character, ArgumentMarshaler>();
	private Set<Character> argsFound = new HashSet<Character>();
	private int currentArgument;
	private char errorArgumentId = '\0';
	private String errorParameter = "TILT";
	private ErrorCode errorCode = ErrorCode.OK;

	private enum ErrorCode {
		OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT}

	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();
		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))
        	// parseXXX 대신 인라인 코드로
			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("#");
	}

	private boolean parseArguments() throws ArgsException {
		for (currentArgument = 0; currentArgument < args.length; currentArgument++) {
			String arg = args[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(argChar);
			else if (m instanceof StringArgumentMarshaler)
				setStringArg(argChar);
			else if (m instanceof IntegerArgumentMarshaler)
				setIntArg(argChar);
			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) {
			valid = false;
			errorArgumentId = argChar;
			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);
			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 (ClassCastException e) {
			return 0;
		}
	}

	public boolean has(char arg) {
		return argsFound.contains(arg);
	}

	public boolean isValid() {
		return valid;
	}

	private class ArgsException extends Exception {
	}


	private abstract class ArgumentMarshaler {
		public abstract void set(String s);
		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에 일일히 유형을 확인하는 부분은 보기 싫고 모든 setXX메서드도 흉하다. 

 

즉, setXXXArg 을 각 파생 클래스로 내려야 한다는 뜻이다. setIntArg는 인자가 두개 나와 지저분 해지는데 이는 args 배열을 list 로 변환후 Iterator를 전달하면 해결된다. boolean에는 Iterator가 필요 없지만 추상 메서드로 만들기 위해 Iterator로 받는다.

이렇게 하면 setXXXArg의 책임이 XXXArgumentMarshaler의 set 함수로 전가 되었기 때문에 setArgument 메서드에서 일일이 if else문으로 유형을 검사할 필요 없이 try {m.set(currentArgument);} 로 간단히 구현 가능하다. 

 

여기에 더해 ArgumentMarshaler를 인터페이스로 수정한다.

이렇게 하면 새로운 유형인 Double을 추가 하기가 매우 수월해 진다.

스키마 구문분석 코드에 double 유형을 뜻하는 '##'을 감지하는 코드를 추가하고 DoubleArgumentMarshaler 클래스를 추가한 뒤 ErrCode와 getDouble 메서드만 추가해 주면 된다. 추가 되는 부분 이외는 바꿀 필요가 없어졌다.

 

여기다 예외 코드만 분리하면 완벽하다. 예외 코드는 아주 흉할 뿐만 아니라 Args 클래스에 속하지도 않고 ParseException을 던지는데 이 예외도 Args 클래스에 속하지 않는다. 예외코드는 정상 로직에 혼란을 주므로 반드시 분리하자

package com.road3144.lec4th;

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) {
		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);
		}
		return "";
	}

	public enum ErrorCode {
		OK, INVALID_FORMAT, UNEXPECTED_ARGUMENT, INVALID_ARGUMENT_NAME, MISSING_STRING,
		MISSING_INTEGER, INVALID_INTEGER,
		MISSING_DOUBLE, INVALID_DOUBLE
	}
}

이제 Args 클래스가 던지는 예외는 ArgsException 뿐이다. ArgsException을 독자적으로 만들면 자잘한 오류 지원 코드를 여기로 옮길 수있어 코드가 훨씬 깨끗해 진다. 

package com.road3144.lec4th;
import com.road3144.lec4th.argumentMarshaler.ArgumentMarshaler;

import java.text.ParseException;
import java.util.*;

public class Args {
	private String schema;
	private Map<Character, ArgumentMarshaler> marshalers = new HashMap<Character, ArgumentMarshaler>();
	private Set<Character> argsFound = new HashSet<Character>();
	private Iterator<String> currentArgument;
	private List<String> 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("##"))
			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);
	}

}
public interface ArgumentMarshaler {
	public abstract void set(Iterator<String> currentArgument) throws ArgsException;
	public abstract Object get();
}

public class StringArgumentMarshaler implements ArgumentMarshaler {
	private String stringValue = "";
	@Override
	public void set(Iterator<String> currentArgument) throws ArgsException {
		try {
			stringValue = currentArgument.next();
		} catch (NoSuchElementException e) {
			ArgsException.ErrorCode errorCode = ArgsException.ErrorCode.MISSING_STRING;
			throw new ArgsException();
		}
	}

	@Override
	public Object get() {
		return stringValue;
	}
}

public class BooleanArgumentMarshaler implements ArgumentMarshaler{
	private boolean booleanValue = false;
	@Override
	public void set(Iterator<String> currentArgument) throws ArgsException {
		booleanValue = true;
	}

	@Override
	public Object get() {
		return booleanValue;
	}
}

public class IntegerArgumentMarshaler implements ArgumentMarshaler{
	private int intValue = 0;
	@Override
	public void set(Iterator<String> currentArgument) throws ArgsException {
		String parameter = null;
		try {
			parameter = currentArgument.next();
			set(parameter);
		} catch (NoSuchElementException e) {
			ArgsException.ErrorCode errorCode = ArgsException.ErrorCode.MISSING_INTEGER;
			throw new ArgsException();
		} catch (ArgsException e) {
			e.setErrorParameter(parameter);
			e.setErrorCode(ArgsException.ErrorCode.INVALID_INTEGER);
			throw e;
		}
	}

	public void set(String s) throws ArgsException {
		try {
			intValue = Integer.parseInt(s);
		} catch (NumberFormatException e) {
			throw new ArgsException();
		}
	}

	@Override
	public Object get() {
		return intValue;
	}
}

결론 

돌아가는 코드만으로 부족하다. 나쁜 코드는 그대로 두면 썩어 문드러져 심각하게 개발 프로젝트에 악역향을 끼친다. 오래된 코드의 의존성을 찾아 깨려면 오래걸리고 고되지만 오늘 오전에 짠 코드를 오후에 고치는 것은 쉽다. 

언제나 최대한 깔끔하고 단순하게 코드를 정리해 놓자.