14장은 점진적인 개선을 보여주는 명령행 인수 구문분석기에 대한 사례 연구를 다룬다.
프로그램을 짜다 보면 종종 명령행 인수의 구문을 분석할 필요가 생긴다. 편리한 유틸리티가 없다면 main 함수로 넘어오는 문자열 배열을 직접 분석하게 된다. 내 사정에 딱 맞는 유틸리티가 없다면 직접 짜게 되는데, 이렇게 새로 짜게 될 유틸리티를 Args라 칭하며 아래 내용을 읽어보도록 한다.
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');
executeApplication(logging, port, directory);
} catech (ArgsException e) {
System.out.printf("Argument error: %s\\n", e.errorMessage());
}
}
매개변수 두 개로 Args 클래스의 인스턴스를 만들었을 뿐이다.
생성자에서 ArgsException이 발생하지 않는다면 명령행 인수의 구문을 성공적으로 분석했으며 Args 인스턴스에 질의를 던져도 좋다는 말이다.
인수 값을 가져오려면 getBoolean, getInteger, getString과 같은 메서드를 사용한다.
형식 문자열이나 명령행 인수 자체에 문제가 있다면 ArgsException이 발생한다.
구체적인 오류를 알아내려면 예외가 제공하는 errorMessage 메서드를 사용한다.
Args 구현
책에서는 곧바로 구현된 Args 코드를 제시한다. 그중 일부를 살펴보면 비교적 단순한 개념을 구현함에도 불구하고 상당히 긴 코드가 적혀있는데, 그 이유 중 하나는 장황한 언어인 자바를 사용하는 탓이다. 자바는 정적 타입 언어라서 타입 시스템을 만족하려면 많은 단어가 필요하다.
어떻게 짰느냐고?
이렇듯 길고 장황한 코드를 어떻게 저자처럼 깔끔하게 작성할 수 있을까. 깨끗한 코드를 짜려면 먼저 지저분한 코드를 짠 뒤에 정리해야 한다. 작문할 때 초안을 작성하고 끊임없는 수정을 거쳐 최종본을 만들듯이 말이다.
Args: 1차 초안
저자는 같은 Args 클래스에 대해 맨 처음에 짰던 초본 역시 제시했다. 물론 잘 돌아가는 코드이지만 엉망인 상태이다. 함수 이름이나 변수 이름을 선택한 방식, 어설프지만 나름대로 구조가 있다는 사실 등으로 보아 나름 코드를 손보려 애쓴 흔적도 볼 수 있다. 그러나 코드는 조금씩 더 엉망이 되어가기 시작했다.
첫 버전은 Boolean 인수만 지원하는 형태였다. 이 때까지만 해도 나름 괜찮은 코드였다. 그러나 String 인수와 Integer 인수를 추가하면서부터 코드가 엄청나게 지져분해지기 시작했다. 인수 유형 두 개만 더했을 뿐인데 유지 보수가 적당히 수월했던 코드가 버그와 결함이 숨어있을지도 모르는 상당히 의심스러운 코드로 뒤바뀌어 버린 것이다.
그래서 멈췄다
추가할 유형이 적어도 두 개는 더 있었으나 그러면 코드가 훨씬 더 나빠질 것이라는 것이 자명한 상태였다. 결국 저자는 이쯤에서 기능을 더 이상 추가하지 않기로 결정하고 리팩터링을 시작했다.
String 인수 유형과 Integer 인수 유형을 추가한 경험에서 새 인수 유형을 추가하려면 주요 지점 세 곳에 코드를 추가해야 한다는 사실을 깨달을 수 있었다.
- 인수 유형에 해당하는 HashMap을 선택하기 위해 스키마 요소의 구문을 분석한다.
- 명령행 인수에서 인수 유형을 분석해 진짜 유형으로 변환한다.
- get~~ 메서드를 구현해 호출자에게 진짜 유형을 반환한다.
인수 유형은 다양하지만 모두가 유사한 메서드를 제공하므로 클래스 하나가 적합하다 판단한 결과, ArgumentMarshaler라는 개념이 탄생했다.
점진적으로 개선하다
프로그램을 망치는 가장 좋은 방법 중 하나는 개선이라는 이름 아래 구조를 크게 뒤집는 행위다. 어떤 프로그램은 그저 그런 개선에서 결코 회복되지 못한다. 개선 전과 똑같이 프로그램을 돌리기가 아주 어렵기 때문이다.
그래서 저자는 테스트 주도 개발(TDD: Test-Drived Development)이라는 기법을 사용했다. TDD는 언제 어느 때라도 시스템이 돌아가야 한다는 원칙을 따른다. 즉, 시스템을 망가뜨리는 변경을 허용하지 않는다. 어떤 변경을 가하더라도 변경 후에는 변경 전과 똑같이 시스템이 돌아가야 한다는 말이다.
변경 전후에 시스템이 똑같이 돌아간다는 사실을 확인하려면 언제든 실행이 가능한 자동화된 테스트 슈트가 필요하다. 시스템이 해당 테스트를 모두 통과하면 올바로 동작한다고 봐도 좋다.
이후 저자는 테스트 케이스를 통과하는지 끊임없이 확인하며 코드를 조금씩 고쳐나가기 시작한다. 길고 장황한 코드 전문을 모두 보여주며 해당 단계를 상세히 설명하기에 직접 책을 보며 저자의 말을 따라가다 보면 점차 개선되어가고 있는 코드를 볼 수 있게 된다.
결론
그저 돌아가는 코드만으로는 부족하다. 단순히 돌아가는 코드에 만족하는 프로그래머는 전문가 정신이 부족하다. 나쁜 일정은 다시 짜면 된다. 나쁜 요구사항은 다시 정의하면 된다. 나쁜 팀 역학은 복구하면 된다. 하지만 나쁜 코드는 썩어 문드러진다. 점점 무게가 늘어나 팀의 발목을 잡는다.
물론 나쁜 코드도 깨끗한 코드로 개선할 수 있다. 하지만 비용이 엄청나게 많이 든다. 반면 코드를 처음부터 깨끗하게 유지하기란 상대적으로 쉽다.
그러므로 코드는 언제나 최대한 깔끔하고 단순하게 정리하자. 절대로 썩어가게 방치하면 안 된다.
'3-2기 스터디 > 클린코드 독서' 카테고리의 다른 글
[7주차] 17장 정리 (0) | 2022.05.28 |
---|---|
[6주차] 15~16장 정리 (0) | 2022.05.22 |
[4주차] 클린코드 11~13장 정리 (0) | 2022.05.17 |
[3주차] 클린코드 7~10장 정리 (0) | 2022.04.15 |
[2주차] 클린코드 4~6장 정리 (0) | 2022.04.06 |
댓글