★ 2005년에 쓴 글을 살짝 수정해 올립니다. 최근 <구글 엔지니어는 이렇게 일한다>를 주제로 발표하고 돌아다니며 받은 질문의 A/S를 위한 참고자료입니다.
특히나 주니어 시절의 개발자들은 코드를 만들어내는 데 만족하고, 문제가 생기면 주먹구구식의 코드를 끼워 넣으면서 잘 해결했다고 생각하기 쉽다. 좋은 디자인과 나쁜 디자인의 코드를 모두 보여주어도 왜 하나는 좋고, 다른 하나는 나쁜지 의아해하는 경우도 많이 보았다.
<GoF의 디자인 패턴> 책이 나와 개발자 지망생들의 필독서가 되었지만, 패턴을 제대로 이해하고 응용하는 사람의 비중은 여전히 작은 듯하다. 패턴을 공부한 사람에게 실제로 적용해본 패턴을 물어보면, 싱글톤 정도만 답하는 경우가 적지 않다. 어떻게 개선해야 할까?
패턴을 이해하려면 설계를 바라보는 눈을 키워야 한다. GoF의 패턴은 많은 시행착오를 거쳐 추상화된 개념들이기 때문에, 그런 시행착오를 겪어보지 않은 사람에게는 잘 와닿지 않을 수 있다. 막연히 '좋아 보이는군' 정도일 뿐이지, 안에 담긴 깊은 의미는 인식하지 못한다. 이런 사람은 패턴을 응용하는 과정에서 선구자가 겪은 시행착오를 다시 겪는 실수를 범하기도 한다. 나도 그러했다.
즉, GoF의 고도로 추상화된 패턴들은 설계를 볼 줄 아는 눈이 없는 사람에겐 내포한 의미를 모두 말해주지 못한다. GoF의 23가지 패턴을 처음 접했을 때, 쉽게 이해되고 와닿는 패턴은 자신이 비슷한 경험과 고민을 해봤던 것들이었음을 상기해보자. 경험과 고민, 특히 시행착오는 관련된 문제에 대한 설계를 바라보는 눈을 향상시킨다. 다시 말해, 우리의 눈은 패턴으로부터 더 많은 것을 읽어낼 수 있게 된다.
이러한 눈의 능력은 어떻게 발전시킬 수 있을까?
앞서 언급했듯이 경험과 고민, 특히 시행착오가 눈의 능력을 키우는 열쇠다. 그렇다면 어떻게 하면 보다 많은 경험, 고민, 시행착오를 해볼 수 있을까? 나는 이 세 가지를 동시에 수행할 수 있는 방법을 제안하려 한다. '의도적으로 시행착오를 겪는다고?' 그렇다. 나는 이 방식에 '적극적 시행착오 수련법'이라는 이름을 붙여보았다.
적극적 시행착오는 일반적인 개발 방식에 비해 많은 경험, 고민, 시행착오를 동반한다. 그리고 자동적으로 자신의 능력에 맞춘 수련이 가능하다는 특징도 있다.
핵심 개념은 자신이 만들어 이미 잘 동작하는 코드와 설계를 의도적으로 시행착오화 하는 것이다. 시행착오라 하면 두 가지 선행조건이 만족되어야 한다.
1. 현재의 단점/잘못된 점을 발견했다.
2. 지금보다 더 나은 코드/설계를 찾아냈다.
'적극적'이란 단어는 위의 선행조건들을 의도적으로 만족시키려 노력해야 함을 의미한다. 내 경험으로는 잘못을 찾기보다는 더 나은 설계를 찾으려 노력하는 편이 더 효과적이다. '이런 잘못이 있어서 이렇게 고쳤다'보다야 '이것도 잘 동작하지만 이 방식이 더 좋다'가 더 고무적이지 않은가?
알고리즘화하여 정리하면 아래와 같다.
[적극적 시행착오 수련법 알고리즘]-----------------------------------------------
1. 동작하는 코드를 짠다.
2. 더 좋은 구조를 찾고자 심사숙고한다.
3. 더 좋은 구조가 떠오르는가?
A. 그렇다.
a. 더 좋다고 생각하는 구조를 적용한다. (리팩터링)
b. 적용해보니 정말 더 좋은가?
i. 그렇다.
- Goto 2
ii. 아니다.
- 원래 구조로 복원한다.
- Goto 2
B. 아니다.
- Exit
--------------------------------------------------------------------------------
보다시피 더 나은 구조를 찾지 못할 때까지 반복된다.
이 수련법의 가장 큰 장점은 설계가 단계적으로 개선된다는 것이다. 아주 나쁜 구조에서 아주 좋은 구조로 단번에 개선되지 않고, 여러 단계에 걸쳐 각 단계에서의 장단점을 고민해보게 된다. 이렇게 다양한 레밸의 설계를 고민해본 사람은 누군가의 코드만 보아도 어느 정도 수준에 올라서 있는지 쉽게, 보다 정확히 추측해낼 수 있다. 뿐만 아니라 프로젝트에서 요구하는 일정이나 범용성 등에 따라 어느 정도로 추상화하여 설계하고 구현할지 결정하는 데 많은 도움이 된다.
이제 두 가지 문제가 남는다. '더 좋은 구조라고 판단하는 기준'과 '더 좋은 구조를 찾는 방법'이다.
GoF의 패턴들은 상당히 추상화되어 있고, 또 적용할 수 있는 곳이 한정되어 있다. 따라서 이는 설계의 좋고 나쁨을 판단할 수 있는 범용 잣대로서는 좋지 않다.
나는 이 수련에 가장 적합한 판단 지침으로 GRASP 패턴, 리팩터링에서 말하는 나쁜 냄새(bad smell), 테스트 용이성(testability), 정적 분석 도구 활용을 추천한다.
GRASP 패턴은 General Responsibility Assignment Software Pattern의 약자로, 이름에서부터 자신이 'General'한 'Pattern'임을 표방하고 있다. 이 패턴은 비록 객체지향 설계에 초점을 두고 있지만, 모듈 사이의 역할 할당이라는 측면에서 대부분의 패턴이 타 프로그래밍 방법론에도 충분히 유효하다.
GRASP의 각 패턴들을 살펴보면 지극히 기본적인 설계 지침임을 알 수 있다. 설계에 능숙하지 못한 사람이라도 각 항목을 차근히 되새기며 자신이 만들어 놓은 소프트웨어의 구조를 살펴보면, 의외로 개선이 필요한 부분이 어렵지 않게 눈에 들어올 것이다.
나쁜 냄새에 대해서는 <리팩터링> 책의 '3장. 코드에서 나는 악취'에서 다양한 예를 찾아볼 수 있다. 악취가 나는 코드는 적절히 리팩터링하면 더 좋은 코드로 거듭날 수 있다. 이때 IDE에서 지원하는 리팩터링 기능을 이용하면 부작용 걱정 없이 빠르게 새로운 설계를 시험해보고 되돌리고를 반복해볼 수 있다. 이 글에서 말하는 시행착오를 '적극적'으로 해보는 데는 IDE의 리팩터링 기능이 가장 큰 선물이 아닐까 생각한다.
테스트 용이성은 '산출물이 의도한 대로 동작하는지 검증하는 테스트 케이스를 얼마나 쉽게 만들 수 있는가'를 말한다. 리팩터링이나 테스트 주도 개발 쪽에서 항상 말하듯이, 코드를 수정하려면 테스트가 충분히 갖춰져 있어야 한다. 한편, IDE의 리팩터링 기능을 이용하면 테스트 코드까지 한꺼번에 수정해준다. 자신의 IDE가 어떤 리팩터링을 지원하는지 꼼꼼히 살펴보고 적극적으로 활용하길 바란다.
설계의 좋고 나쁨을 판단할 때 정적 분석 도구 활용도 적극 추천한다. 많이 쓰이는 프로그래밍 언어라면 다양한 정적 분석 도구가 시장에 나와 있다. 정적 분석들은 코드에서 잠재적 결함과 나쁜 냄새 등을 지적해주며, 모듈 사이의 커플링이나 복잡성까지 검토해 경고해주기도 한다. 정적 분석 도구는 누구나 활용할 수 있는 훌륭한 '코드 리뷰어'인 샘이다. IDE에 플러그인됨은 물론이고 지속적 통합(CI) 인프라에도 쉽게 통합할 수 있어서 팀 단위로 활용하면 코드 리뷰의 효율도 크게 개선된다.
내가 사용하는 언어를 지원하는 정적 분석 도구를 찾고 싶다면 다음 검색어로 구글링해보자.
best static analysis tools for [언어]
나는 대학교 텀 프로젝트 때 보통 1~2주 안에 기능 대부분을 완성하고, 추가로 2~3주 정도는 코드를 개선하곤 했다. 수련 목적보다는 설계가 재미있어서 본능적으로 그리 하였다. 이 글은 그때의 경험이 내 개발 역량 개선에 큰 도움이 되었기에 따로 정리한 것이다.