이 장은 원칙 설명이 아니라 사례 연구다. 명령행 인수를 구문분석하는 Args 유틸리티를 놓고, 지저분한 초안에서 깨끗한 코드까지 실제 개선 과정을 처음부터 끝까지 보여준다. 결론부터 말하면 — 깨끗한 코드는 처음부터 깨끗하게 짜서 나오는 게 아니라, 돌아가는 코드를 끊임없이 다듬어서 나온다.
초안은 원래 지저분하다
1차 초안의 Args는 Boolean 인수만 지원할 때는 그럭저럭 봐줄 만했다. String과 Integer 유형이 추가되자 인스턴스 변수가 늘고, 타입별 분기가 흩어지고, 코드는 통제를 벗어나기 시작했다. 여기서 중요한 건 저자가 그 시점을 인지하고 기능 추가를 멈췄다는 것이다.
프로그래밍은 과학보다 공예에 가깝다. 처음부터 완성된 구조를 뽑아내는 사람은 없다. 문제는 초안을 짜는 것이 아니라, 돌아간다는 이유로 초안을 방치하는 것이다.
판단 기준: “타입을 하나 더 추가하려는데 손대야 할 곳이 여러 군데다” — 이 느낌이 정리를 시작할 신호다. 함정: 신호를 무시하고 기능을 계속 얹으면, 분기는 기하급수로 늘고 어느 순간 리팩터링 비용이 재작성 비용을 넘어선다.
점진적으로 개선한다
개선은 리팩터링의 규율을 따른다.
- 한 번에 하나씩. 시스템을 뒤엎는 큰 수술 대신, 언제나 실행 가능한 상태를 유지하며 아주 작은 변경을 반복한다.
- 테스트가 안전망이다. 변경 전후로 테스트 스위트가 항상 통과해야 한다. 테스트 없이 하는 구조 변경은 개선이 아니라 도박이다.
- 패턴을 향해 이동한다. Args에서는 타입별로 흩어져 있던 분기를
ArgumentMarshaler인터페이스로 모으고, Boolean/String/Integer 각각의 구현 클래스로 분리했다.
스텝 플레이어: Args가 다듬어지는 네 단계
아래는 Args의 핵심(스키마 파싱과 인수 설정)만 40줄 이내로 압축한 교육용 축약판이다. 타입별 분기가 흩어진 초안에서 출발해, 새 타입 추가가 클래스 하나로 끝나는 구조까지 네 걸음으로 이동한다. 각 단계 사이에서 테스트는 계속 초록불이라는 점이 이 서사의 전제다.
public class Args { private Map<Character, Boolean> booleanArgs = new HashMap<>(); private Map<Character, String> stringArgs = new HashMap<>(); private Map<Character, Integer> intArgs = new HashMap<>(); private void parseSchemaElement(char id, String tail) { if (tail.isEmpty()) booleanArgs.put(id, false); // 타입별 분기가 else if (tail.equals("*")) stringArgs.put(id, ""); // 여기저기 else if (tail.equals("#")) intArgs.put(id, 0); // 흩어진다 } private void setArgument(char id, Iterator<String> args) { if (booleanArgs.containsKey(id)) booleanArgs.put(id, true); // set 로직도 else if (stringArgs.containsKey(id)) stringArgs.put(id, args.next()); // 또 분기 else if (intArgs.containsKey(id)) intArgs.put(id, Integer.parseInt(args.next())); }}
판단 기준: 각 걸음은 “돌아가는 상태 → 조금 더 나은, 여전히 돌아가는 상태”다. 자료구조 통합(2단계)과 분기 제거(3단계)를 한 번에 하지 않고 나눈 이유가 여기 있다 — 작게 쪼갤수록 실수의 원인이 방금 그 변경으로 좁혀진다. 함정: 3단계를 건너뛰고 곧장 레지스트리로 점프하고 싶어지지만, ArgumentMarshaler가 아직 상태를 담는 껍데기일 때 레지스트리를 얹으면 다형성 없는 반쪽짜리가 된다. 순서가 발판이다.
타입 추가가 두려운 구조에서, 타입 추가가 사소한 구조로 — 마지막 단계에서 Double을 더하는 데 든 것은 클래스 하나와 레지스트리 한 줄이 전부다. 이것이 이 장 전체가 증명하는 변화다.
정리
- 돌아가는 코드와 깨끗한 코드는 다르다. 전자에서 멈추면 코드는 반드시 썩는다.
- 나빠지기 시작하는 순간을 감지하면 기능 추가를 멈추고 정리부터 한다. 나중은 결코 오지 않는다.
- 개선은 한 번에 하나씩, 발판이 되는 순서로. 자료구조 통합 → 다형성 도입 → 레지스트리의 순서가 그 예다.
- 점진적 개선의 조건은 테스트다. 테스트가 있으면 구조 변경이 일상이 되고, 없으면 모험이 된다.
다음장으로 15장