Contents
14장. 점진적인 개선
이 장은 점진적인 개선을 보여주는 사례 연구다. 우선, 출발은 좋았으나 확장성이 부족했던 모듈을 소개한다. 그런 다음, 모듈을 개선하고 정리하는 단계를 살펴본다.
14.1 Args 구현
목록 14-2는 Args 클래스다. 아주 주의해서 읽어보기 바란다. 스타일과 구조에 신경을 썼으므로 흉내 낼 가치가 있다고 믿는다.
...
일단 진정하기 바란다. 필자는 위 프로그램을 처음부터 저렇게 구현하지 않았다. 더욱 중요하게는 여러분이 깨끗하고 우아한 프로그램을 한 방에 뚝딱 내놓으리라 기대하지 않는다. 지난 수십 여 년 동안 쌓아온 경험에서 얻은 교훈이라면, 프로그래밍은 과학보다 공예에 가깝다는 사실이다. 클린 코드를 짜려면 먼저 지저분한 코드를 짠 뒤에 정리해야 한다는 의미이다.
처음 듣는 이야기가 아니리라 생각한다. 초등학교 시절 선생님들도 (대개 허사였지만) 작문할 때 초안부터 쓰라고 하셨다. 먼저 1차 초안을 쓰고, 그 초안을 고쳐서 2차 초안을 만들고, 계속 고쳐서 최종안을 만들라고 말씀하셨다. 깔끔한 작품을 내놓으려면 단계적으로 개선해야 한다고 가르치려 애쓰셨다. 대다수 신참 프로그래머는 (대다수 초등학생과 마찬가지로) 이 충고를 충실히 따르지 않는다. 그들은 무조건 돌아가는 프로그램을 목표로 잡는다. 일단 프로그램이 '돌아가면' 다음 업무로 넘어간다. '돌아가는' 프로그램은 그 상태가 어떻든 그대로 버려둔다. 경험이 풍부한 전문 프로그래머라면 이런 행동이 전문가로서 자살 행위라는 사실을 잘 안다.
14.2 Args: 1차 초안
목록 14-8은 필자가 맨 처음 짰던 Args 클래스다. 코드는 '돌아가지만' 엉망이다.
...
이처럼 지저분한 코드를 보고서 처음 든 생각이 "저자가 그냥 버려두지 않아서 진짜 다행이야!"이길 바란다. 만약 그렇다면 자신이 대충 짜서 남겨둔 코드를 남들이 어떻게 느낄지 생각하기 바란다.
처음부터 지저분한 코드를 짜려는 생각은 없었다. 실제로도 코드를 어느 정도 손보려고 애썼다. 함수 이름이나 변수 이름을 선택한 방식, 어설프지만 나름대로 구조가 있다는 사실 등이 필자 노력의 증거다. 하지만 어느 순간 프로그램은 필자의 손을 벗어났다.
코드는 조금씩 엉망이 되어갔다. 첫 버전은 이만큼 엉망이지 않았다. 예를 들어, 목록 14-9는 Boolean 인수만 지원하던 초기 버전이다.
...
위 코드도 불평할 거리가 많겠지마 나름대로 괜찮은 코드다. 간결하고 단순하며 이해하기도 쉽다. 하지만 코드를 잘 살펴보면 나중에 엉망으로 변해갈 씨앗이 보인다. 코드가 점차로 지저분해진 이유가 분명히 드러난다.
나중 코드는 위 코드에 String과 Integer라는 인수 유형 두 개만 추가했을 뿐이라는 사실에 주목한다. 인수 유형 두 개만 더했을 뿐인데 코드가 엄청나게 지저분해졌다. 유지와 보수가 상당히 수월했던 코드가 버그와 결함이 숨어있을지도 모른다는 상당히 의심스러운 코드로 뒤바뀌어버렸다. 필자는 두 인수 유형을 단계적으로 추가했다. 먼저 String 인수 유형을 추가해 다음 코드를 얻었다.
...
보다시피 코드는 통제를 벗어나기 시작했다. 아직은 끔찍한 수준이 아니지만 확실히 분위기가 수상쩍다. 여기저기 눈에 거슬리지만 아직은 엉망이라 부르기 어렵다. 여기다 Integer 인수 유형을 추가하니 코드는 완전히 엉망이 되어 버렸다.
그래서 멈췄다
추가할 인수 유형이 적어도 두 개는 더 있었는데 그러면 코드가 훨씬 더 나빠지리라는 사실이 자명했다. 계속 밀어붙이면 프로그램은 어떻게든 완성하겠지만 그랬다가는 너무 커서 손대기 어려운 골칫거리가 생겨날 참이었다. 코드 구조를 유지 보수하기 좋은 상태로 만들려면 지금이 적기라고 판단했다.
그래서 필자는 기능을 더 이상 추가하지 않기로 결정하고 리팩토링을 시작했다. String 인수 유형과 Integer 인수 유형을 추가한 경험에서 필자는 새 인수 유형을 추가하려면 주요 지점 세 곳에 코드를 추가해야 한다는 사실을 이미 알았다. 첫째, 인수 유형에 해당하는 HashMap을 선택하기 위해 스키마 요소의 구문을 분석한다. 둘째, 명령행 인수에서 인수 유형을 분석해 진짜 유형으로 변환한다. 셋째, getXXX 메소드를 구현해 호출자에게 진짜 유형을 반환한다.
인수 유형은 다양하지만 모두가 유사한 메소드를 제공하므로 하나의 클래스가 적합하다 판단했다. 그래서 ArgumentMarshaler라는 개념이 탄생했다.
점진적으로 개선하다
프로그래밍을 망치는 가장 좋은 방법 중 하나가 개선이라는 이름 아래 구조를 크게 뒤집는 행위다. 어떤 프로그램은 그저 그런 '개선'에서 결코 회복하지 못한다. '개선' 전과 똑같이 프로그램을 돌리기가 아주 어렵기 때문이다.
그래서 필자는 테스트 주도 개발(TDD) 이라는 기법을 사용했다. TDD는 언제 어느 때라도 시스템이 돌아가야 한다는 원칙을 따른다. 다시 말해, TDD는 시스템을 망가뜨리는 변경을 허용하지 않는다. 변경을 가한 후에도 시스템이 변경 전과 똑같이 돌아가야 한다는 소리다.
변경 전후에 시스템이 똑같이 돌아간다는 사실을 확인하려면 언제든 실행이 가능한 자동화된 테스트 수트가 필요하다. 앞서 Args 클래스를 구현하는 동안에 이미 단위 테스트 수트와 인수 테스트를 만들어 놓았었다. 단위 테스트 수트는 JUnit 프레임워크에서 자바로 작성했다. 인수 테스트는 FitNess에서 위키 페이지로 작성했다. 두 테스트 모두 언제든 실행이 가능했으며, 시스템이 두 테스트를 모두 통과하면 올바로 동작한다고 봐도 좋았다.
그래서 필자는 시스템에 자잘한 변경을 가하기 시작했다. 코드를 변경할 때마다 시스템 구조는 조금씩 ArgumentMarshaler 개념에 가까워졌다. 또한 변경 후에도 시스템은 변경 전과 다름없이 돌아갔다.
...
소프트웨어 설계는 분할만 잘해도 품질이 크게 높아진다. 적절한 장소를 만들어서 코드만 분리해도 설계가 좋아진다. 관심사를 분리하면 코드를 이해하고 보수하기 훨씬 더 쉬워진다.
...
결론
그저 돌아가는 코드만으로는 부족하다. 돌아가는 코드가 심하게 망가지는 사례는 흔하다. 단순히 돌아가는 코드에 만족하는 프로그래머는 전문가 정신이 부족하다. 설계와 구조를 개선할 시간이 없다고 변명할지 모르지만 필자로서는 동의하기 어렵다. 나쁜 코드보다 더 오랫동안 더 심각하게 개발 프로젝트에 악영향을 미치는 요인도 없다. 나쁜 일정은 다시 짜면 된다. 나쁜 요구사항은 다시 정의하면 된다. 나쁜 팀 역학은 복구하면 된다. 하지만 나쁜 코드는 썩어 문드러진다. 점점 무게가 늘어나 팀의 발목을 잡는다. 속력이 점점 느려지다 못하 기어가는 팀도 많이 보았다. 너무 서두르다가 이후로 영원히 자신들의 운명을 지배할 악성 코드라는 굴레를 짊어진다.
물론 나쁜 코드도 클린 코드로 개선할 수 있다. 하지만 비용이 엄청나게 많이 든다. 코드가 썩어가며 모듈은 서로서로 얽히고 설켜 뒤엉키고 숨겨진 의존성이 수도 없이 생긴다. 오래된 의존성을 찾아내어 깨려면 상당한 시간과 인내심이 필요하다. 반면 처음부터 코드를 깨끗하게 유지하기란 상대적으로 쉽다. 아침에 엉망으로 만든 코드를 오후에 정리하기는 어렵지 않다. 더욱이 5분 전에 엉망으로 만든 코드는 지금 당장 정리하기 아주 쉽다.
그러므로 코드는 언제나 최대한 깔끔하고 단순하게 정리하자. 절대로 썩어가게 방치하면 안된다.