
출발은 좋았으나 확장성이 부족했던 모듈을 샘플로 하여 분석, 개선, 정리하는 과정을 통해 점진적으로 코드를 개선해본다. 여기서는 main 함수로 넘어오는 문자열을 분석하는 유틸리티를 Args를 구현한다.
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');
executeApplication(logging, port, directory);
} catch(ArgsException e) {
System.out.printf("Argument error: %s\n", e.errorMessage());
}
}
돌아는 가지만, 상당히 길고 복잡하다. 인수 유형이 늘어날수록 코드가 지저분해지고 복잡해진다.
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 int numberOfArguments = 0;
public Args(String schema, String[] args) throws ParseException {
this.schema = schema;
this.args = args;
valid = parse();
}
public boolean isValid() {
return valid;
}
private boolean parse() throws ParseException {
if(schema.length() == 0 && args.length == 0)
return true;
parseSchema();
parseArguments();
return unexpectedArguments.size() == 0;
}
private boolean parseSchema() throws ParseException {
for(String element : schema.split(",")) {
parseSchemaElement(element);
}
return true;
}
private void parseSchemaElement(String element) throws ParseException {
if(element.length() == 1) {
parseBooleanSchemaElement(element);
}
}
private void parseBooleanSchemaElement(char elementId) {
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(ing 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.containKey(argChar);
}
public int cardinality() {
return numberOfArguments;
}
public String usage() {
if(schema.length)( > 0)
return "-["+schema+"]";
else
return "";
}
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);
}
}
위 코드는 크게 세 가지 기능을 제공한다.
인수 유형은 다양하지만 모두 유사한 메서드를 제공하므로 한 클래스에 담는것이 적합하다. 그래서 ArgumentMarshaler를 생성하였다.
private class ArgumentMarshaler {
private boolean booleanVlaue = false;
public void setBoolean(boolean value) {
booleanVlaue = value;
}
public boolean getBoolean() {return booleanValue;}
}
private class BooleanArgumentMarshaler extends ArgumentMarshaler {
}
private class StringArgumentMarshaler extends ArgumentMarshaler {
}
private class IntegerArgumentMarshaler extends ArgumentMarshaler {
}
private Map<Character, ArgumentMarshaler> booleanArgs = new HashMap<Character, ArgumentMarshaler>();
...
private void parseBooleanSchemaElement(char elementId) {
booleanArgs.put(elementId, new BooleanArgumentMarshaler());
}
..
private void setBooleanArg(char argChar, boolean value) {
booleanArgs.get(argChar).setBoolean(value);
}
...
private boolean getBoolean(char arg) {
return falseIfNumm(booleanArgs.get(arg).getBoolean());
}
public boolean getBoolean(char arg) {
Args.ArgumentMarshaler am = booleanArgs.get(arg);
return am != null && am.getBoolean();
}
private abstract class ArgumentMarshaler {
protected boolean booleanValue = false;
private String stringValue;
private int integerValue;
public void setBoolean(boolean value) {
booleanValue = value;
}
public boolean getBoolean() {
return booleanValue;
}
public abstract void set(String s);
}
추상 set 함수는 String 인수를 받아들이나 BooleanArgumentMarshaler는 인수를 사용하지 않는다. 여기서 인수를 정의한 이유는 StringArgumentMarshaler와 IntegerArgumentMarshaler에서 필요하기 때문이다.
private class BooleanArgumentMarshaler extends ArgumentMarshaler {
public void set(String s) {
booleanValue = true;
}
}
private void setBooleanArg(char argChar, boolean value) {
booleanArgs.get(argChar).set("true");
}
public boolean getBoolean(char arg) {
Args.ArgumentMarshaler am = booleanArgs.get(arg);
return am != null && (Boolean)am.get();
}
private abstract class ArgumentMarshaler {
...
public Object get() {
return null;
}
}
private abstract class ArgumentMarshaler {
protected boolean booleanValue = false;
...
public abstract Object get();
}
private class BooleanArgumentMarshaler extends ArgumentMarshaler {
public void set(String s) {
booleanValue = true;
}
public Object get() {
return booleanValue;
}
}
그저 돌아가는 코드만으로는 부족하다. 돌아가는 코드가 심하게 망가지는 사례는 흔하다. 단순히 돌아가는 코드에 만족하지 말고 처음부터 코드를 최대한 깔끔하고 단순하게 정리하자.