출발은 좋았으나 확장성이 부족했던 모듈을 소개하고 그 모듈을 개선하며 정리하는 단계로 살펴볼 것이다.
1. Args
프로그램을 짜다 보면 종종 명령행 인수의 구문을 분석할 필요가 생긴다.
편리한 유틸리티가 없다면 main 함수로 넘어오는 문자열 배열을 직접 분석하게 된다.
새로 짤 유틸리티를 Args라 부를 것이고 사용법은 간단하다.
Args 생성자에 인수 문자열과 형식 문자열을 넘겨 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');
executeAppliocation(logging, port, directory);
} catch (ArgsException e) {
System.out.printf("Argument error: %s\n", e.errorMessage());
}
}
매개변수 두 개로 Args 클래스의 인스턴스를 만들었다.
- 첫째 매개변수는 형식 또는 스키마를 지정하는데, "l(부울), p(정수)#, d(문자열)*"는 명령행 인수 세 개를 정의한다.
- 둘째 매개변수는 main으로 넘어온 명령행 인수 배열 자체이다.
생성자에서 ArgsException이 발생하지 않았다면
명령행 인수의 구문을 성공적으로 분석했으며 Args 인스턴스에 질의를 던져도 좋다는 말이다.
1) Args 구현
import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;
public class Args {
private Map<Character, ArgumentMarshaler> marshalers;
private Set<Character> argsFound;
private ListIterator<String> currentArgument;
public Args(String schema, String[] args) throws ArgsException {
marshalers = new HashMap<Character, ArgumentMarshaler>();
argsFound = new HashSet<Character>();
parseSchema(schema);
parseArgumentStrings(Arrays.asList(args));
}
private void parseSchema(String schema) throws ArgsException {
for (String element : schema.split(","))
if (element.length() > 0)
parseSchemaElement(element.trim());
}
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 if (elementTail.equals("[*]"))
marshalers.put(elementId, new StringArrayArgumentMarshaler());
else
throw new ArgsException(INVALID_ARGUMENT_FORMAT, elementId, elementTail);
}
private void validateSchemaElementId(char elementId) throws ArgsException {
if (!Character.isLetter(elementId))
throw new ArgsException(INVALID_ARGUMENT_NAME, elementId, null);
}
private void parseArgumentStrings(List<String> argsList) throws ArgsException {
for (currentArgument = argsList.listIterator(); currentArgument.hasNext();) {
String argString = currentArgument.next();
if (argString.startsWith("-")) {
parseArgumentCharacters(argString.substring(1));
} else {
currentArgument.previous();
break;
}
}
}
private void parseArgumentCharacters(String argChars) throws ArgsException {
for (int i = 0; i < argChars.length(); i++)
parseArgumentCharacter(argChars.charAt(i));
}
private void parseArgumentCharacter(char argChar) throws ArgsException {
ArgumentMarshaler m = marshalers.get(argChar);
if (m == null) {
throw new ArgsException(UNEXPECTED_ARGUMENT, argChar, null);
} else {
argsFound.add(argChar);
try {
m.set(currentArgument);
} catch (ArgsException e) {
e.setErrorArgumentId(argChar);
throw e;
}
}
}
public boolean has(char arg) {
return argsFound.contains(arg);
}
public int nextArgument() {
return currentArgument.nextIndex();
}
public boolean getBoolean(char arg) {
return BooleanArgumentMarshaler.getValue(marshalers.get(arg));
}
public String getString(char arg) {
return StringArgumentMarshaler.getValue(marshalers.get(arg));
}
public int getInt(char arg) {
return IntegerArgumentMarshaler.getValue(marshalers.get(arg));
}
public double getDouble(char arg) {
return DoubleArgumentMarshaler.getValue(marshalers.get(arg));
}
public String[] getStringArray(char arg) {
return StringArrayArgumentMarshaler.getValue(marshalers.get(arg));
}
}
위 코드는 위에서 아래로 읽히고 전반적으로 깔끔한 구조에 잘 짜인 프로그램으로 여겨진다.
예를 들어 날짜 인수나 복소수 인수 등 새로운 인수 유형을 추가하는 방법이 명백하다.
2) 그렇다면 어떻게 이런 코드를 짰을까?
위 프로그램은 처음부터 저렇게 구현하지 않았다.
깨끗한 코드를 짜려면 먼저 지저분한 코드를 짠 뒤에 정리해야 한다.
그저 돌아가는 코드만으로는 부족하고 돌아가는 코드가 심하게 망가지는 사례는 흔하다.
단순히 돌아가는 코드에 만족하는 프로그래머는 전문가 정신이 부족하다.
나쁜 코드보다 더 오랫동안 더 심각하게 개발 프로젝트에 악영향을 미치는 요인도 없다.
나쁜 코드를 깨끗한 코드로 개선하려면 비용이 엄청나게 많이 든다.
오래된 의존성을 찾아내 깨려면 상당한 시간과 인내심이 필요하다.
반면 처음부터 코드를 깨끗하게 유지하기란 상대적으로 쉽다.
아침에 엉망으로 만든 코드를 오후에 정리하기는 어렵지 않다.
더욱이 5분 전에 엉망으로 만든 코드는 지금 당장 정리하기 아주 쉽다.
그러므로 코드는 언제나 최대한 깔끔하고 단순하게 정리해야 한다.
'Prodo 독서 리뷰' 카테고리의 다른 글
[Clean Code] 17장 냄새와 휴리스틱 (0) | 2021.03.27 |
---|---|
[Clean Code] 15장 JUnit 들여다보기 (0) | 2021.03.26 |
[Clean Code] 12장 창발성 (0) | 2021.03.26 |
[Clean Code] 11장 시스템 (0) | 2021.03.26 |
[Clean Code] 9장 단위 테스트 (0) | 2021.03.26 |