난 예전에 프로그래머에게 필요한 피드백이라는 글에서 TDD, 리팩토링 등의 기법의 목적은 프로젝트를 더 빨리 완료하는 것이며, clean code니까 빨리 개발할 수 있다는 주장을 펼친 바 있다. 이번엔 이 주장을 한발짝 더 깊게 들어가되, 약간 다른 관점으로 풀어보려고 한다.
린 스타트업과 quick & dirty
에릭 리스의 린스타트업에 보면 이런 스토리가 나온다. 처음에 스타트업을 할 때는 애자일의 주요 기법들을 잘 적용하면서 기술적으로 훌륭한 프로젝트를 했는데 아무도 쓰는 사람이 없어서 망했고, 나중에 다시 창업해서 수많은 버그가 있는 제품을 그냥 출시했는데 흥했더라. 매우 인상적인 이야기다. XP 기법들의 신봉자라면 좀 충격을 받을 수도 있을 것이다. 켄트 벡이 품질은 타협 대상이 아니며, 품질을 타협하면 오히려 프로젝트는 느려진다고 그랬는데 품질이 개판인 프로젝트는 돈을 벌고, 품질이 좋은 프로젝트는 망했다고?
오해를 덜기 위해 명확히 해두자면, 여기서 말하는 품질은 제품의 품질이 아니라 기술적인 품질이다. 그러니까, 에릭 리스의 이야기는 기술적인 품질이 낮은 프로젝트가 성공했다는 것이다. 그래서 그는 나중에 고객 관점에서의 품질을 이야기한다. 고객이 없으면 품질도 없다는 것.
아뭏든, 여기서 우리는 clean code를 위한 노력이 사실은 성공에는 도움이 안되는 게 아니었을까 하는 의심을 품게 된다. 합리적 의심이다. 린 스타트업을 읽고도 그런 생각이 안 들었다면 오히려 그게 더 이상할 것이다.
나도 스타트업은 quick & dirty를 해야 한다고 생각한다. 하지만, 이것이 clean code의 추구에 반하는 이야기는 아니다. 그러니까, 나는 모순처럼 보이는 quick & dirty와 clean code가 모순이 아니라고 주장을 하고 있는 것이다. 심지어, 이 주장은 옳기 때문에 따로 근거도 대지 않고, 이 글의 진짜 주제, 스타트업이 어떻게 quick & dirty를 할 것인가로 넘어갈 것이다. 만약 이 주장에 대한 논증을 보고 싶은 사람이 있다면 맛있는 점심 한 번으로 다 펼쳐보여주겠다.
Quick & Dirty How-to
진짜 주제로 넘어가기 전에 잠시, clean code에 도달하는 가장 훌륭한 방법인 TDD의 사이클을 복습해보자.
- 제대로 동작하는지 확인할 수 있는 테스트를 작성한다.
- 실행 가능하게 만든다. 다른 무엇보다도 중요한 것은 빨리 초록 막대를 보는 것이다. 빨리 초록 막대를 보는 것은 모든 죄를 사해준다. 하지만 아주 잠시동안만이다.
- 올바르게 만든다. 이제 시스템이 작동하므로 직전에 저질렀던 죄악을 수습하자.
린 스타트업은 말하자면 TDD를 조금 더 큰 스케일로 하는 것이다.
- 가설을 수립하고, 가설을 확인할 수 있는 지표를 설정한다.
- 가설을 검증할 수 있는 MVP를 만든다. quick & dirty!
- 가설이 검증되었다면 완성도를 높여간다.
- 가설 검증 결과가 틀린 것으로 나온다면 새로운 가설을 수립한다.
2와 4분의 3 승강장
문제는 바로 2와 4분의 3 승강장 2와 3 사이에 있다. 린 스타트업은 개발자를 위한 책이 아니라 스타트업을 위한 일반적인 방법론이므로, 2와 3 사이에 TDD 사이클의 3번 과정을 따로 시간을 할애해서 적어놓지 않은 것이다. 이게 바로 행간을 읽는다는 것이야. 가설 검증에 성공한 스타트업이 이 2.5 리팩토링을 놓치게 되면 바로 이어 등장하는 추격자들에게 자리를 내주게 된다. 추격자들은 모범 답안이 있으니 빠른 속도로 MVP를 만들 게 아니라, 처음부터 완성품을 보고 달려도 되므로, 완성품에 더 빨리 도달할 수 있다. MVP까지는 quick & dirty로 빠르게 만들 수 있지만, 완성품을 만드는 것은 clean code 없이 속도를 낼 수 없다.
하지만, 설령 개발자가 2와 3 사이에 숨겨진 2.5를 인지한다고 해도 문제는 간단하지 않다. CEO는 quick & dirty로 가설을 검증해냈기 때문에 이미 quick & dirty에 맹신이 생긴 상태이고, 앞으로도 쭉 quick & dirty로 해나가면 된다고 생각한다. 그래서, 계속 같은 속도로 움직일 수 있을 거라고 생각한다. 이런 CEO에게 코드를 정리할 시간이 필요하다고 말하는 것이 먹힐 가능성은 매우 낮다. 설령 말로는 동의해도 이후에 액션들은 2.5 작업을 할 수 있도록 내버려 두지 않는다. proof of concept 이후에도 쭉쭉 뻗어나가는 스타트업과, 경쟁에 뒤쳐지는 스타트업에는 결정적인 차이가 바로 이것이다.
뛰어난 엔지니어가 CEO이거나, 혹은 뛰어난 엔지니어가 어떻게 일하는지를 이해하는 CEO라면 2.5를 이해한다. 하지만 대부분의 CEO는 2.5를 받아들이지 못한다. 고속성장을 하다가 경쟁자에게 따라잡히는 스타트업들은 대체로 CEO가 엔지니어링에 대한 마인드가 부족하다. 국내에도 좋은 사례가 있다. 최근 2~3년 간 스타트업을 가장 떠들썩하게 했던 분야 두 개를 떠올려보라. 그리고, 그 두 개의 선두 업체였던 두 회사의 2년 전과 지금을 비교해보라. 하나는 지속적으로 새로운 성장 동력을 발굴해내며 성장을 가속하고 있지만 다른 하나는 외형적인 성장만 해오다가 그마저도 경쟁사에 따라잡혔다. 물론 차이를 만든 이유는 여러 가지가 있고, 여러가지 분석이 있겠지만, 결정적인 차이는 엔지니어링에 대한 마인드의 차이였다는 것이 내 판단이다.
MVP의 조건
물론 위의 이야기는 어쨋든 다 행복한 스토리다. 내가 창업한 이후 5년 간 직간접으로 관련이 있었던 30여개의 스타트업 중 proof of concept에 성공한 스타트업은 단 둘 뿐이니까 말이다. 그럼 대부분의 스타트업에서는 개발자들이 고민해야 할 clean code는 2.5 단계가 아니라 2단계다. 2단계에선 그럼 무작정 quick & dirty를 하면 되는가? PHP라도 상관 없는가? (그건 물론 안됨) 여기서 quick & dirty를 어떻게 할 것인가를 이야기하기 전에 하나 더 짚고 넘어갈 게 있다. 개발이 아니라 MVP에 대한 조건이다.
MVP는 박병호가 아니고, Minimum Viable Product다. 아니, Minimum Viable Product다. 이게 대부분의 스타트업에서 잘못하는 것 중 하나다. MVP는 충분히 작아야 한다. 이베이의 첫번째 제품은 웹사이트에 중고 물품을 등록하는 기능과 열람하는 기능 두 개 밖에 없었다. 거래는 이메일로 하고 수수료는 우편으로 보내는 원시적인 웹사이트였다. 이런 제품에 버그가 많으면 얼마나 많겠으며 코드가 지저분해봐야 얼마나 지저분하겠는가. MVP가 충분히 작다면 quick & dirty로 매우 빨리 만들어도 그 과정에서 쌓는 기술 부채는 얼마 되지 않는다. 가설 검증이 되면 바로 갚을 수 있다.
하지만 대부분의 스타트업들은 Minimum은 커녕 Maximum을 만들기 위해 노력한다. 뭔가 기능이 없어서 고객이 안 온다고 생각한다. 그래서 점점 비대하게 제품을 확장해나간다. 가설 검증도 안되었는데. 그럼 quick & dirty로 인한 기술 부채는 점점 감당할 수 없을 만큼 쌓여간다. 가설 검증이 된다면 기술 부채는 별 거 아니다. 어차피 돈 벌고 있으니까, 혹은 돈 벌 예정이니까, 뛰어난 개발자 채용하고 시간 들여서 기술 부채를 갚으면 된다. 2.5 단계를 진행하면 되는 거다. 근데, 가설 검증이 안된 상태에서 quick & dirty로 개발한 제품이 점점 커져가면 점점 속도가 느려져서 어느 순간 아무 것도 할 수 없는 상태가 온다. 그리고, 이런 상태가 되면 개발자가 도망가거나, 도망은 안 가도 백기를 들곤 한다. 이렇게 된 스타트업에 헬프하러 간 게 몇 번인지 셀 수 없다.(사실은 셀 수 있다, 여덟 번)
그리고, 설령 어찌어찌 가설 검증이 되더라도, 그 사이 quick & dirty로 만든 제품이 이미 너무 비대하다면, 나중에 뛰어난 개발자를 데려와도 어찌할 수 없는 경우도 생길 수 있다. 2번도 통과했고 2.5번도 할 자세가 충분히 되어 있는데, 2.5를 할 수 없는 상황이 될 수 있는 거다.
그래서, 스타트업은 MVP를 만들 때 극도로 절제해야 한다. 가설-검증의 사이클을 활용하고, 가설의 성공/실패 여부가 가려지지 않았을 때 기능 추가하는 것이 결과적으로 성공 확률을 깎아먹는다는 것을 이해할 필요가 있다. 기술 부채를 통제할 수 있는 선에서 MVP를 만들어야 한다. 기술 부채도 부채이므로 많아지면 파산한다.
Quick & Dirty로 개발하기
그럼 이제부터 본론(?) quick & dirty로 개발하는 방법에 대해 들여다보자.
추상화하지 말 것
좋은 코드는 좋은 추상이다. 그러므로, 개발자는 좋은 추상화를 하기 위해 노력해야 한다. 하지만, 잘못된 추상화만큼 코드를 재앙으로 이끄는 것도 없다. 초기에 좋은 추상을 발견할 시간이 부족하다면, 추상화를 하지 말고 미루라.
Do not extract method
조금 더 구체적으로 말하자면, Do not extract method다. 트위터에도 언젠가 설명 없이 저 문구만 쓴 적이 있는데, 이런 맥락에서 한 말이다. 우선, 함수는 그 자체로 GotoConsideredHarmful을 극복한 것이 아니다. Goto의 문제는 코드를 읽다가 흐름의 점프가 일어난다는 것이며, 함수도 그 문제를 똑같이 갖고 있다. 간혹 이 문제를 네이밍의 문제로 보는 관점도 있는데, 네이밍이 물론 중요한 것은 사실이지만, Goto도 라벨을 붙여서 라벨로 점프하니까 라벨만 잘 붙이면 되는가? 아닐 것이다. 라벨을 잘 붙여도 여전히 Goto는 해로우므로, 함수 역시 이름 잘 붙인다고 그 해로움을 피해가는 것은 아니다.
진짜로 Goto의 해로움을 피해가려면 관련된 변수들의 scope를 잘 정의하는 것이 중요하다. Goto에서 정말 피하기 어려운 것은 전역 변수를 써야 한다는 것이다. 그러니까 코드에서는 물리적으로 다른 공간에 점프를 했는데, 머리 속에는 여전히 여러 변수들의 상태를 담고 있어야 코드를 계속 추적할 수 있기 때문에 문제가 되는 것이다. 함수가 좋은 것은 변수를 지역적으로 제한시키기 쉽다는 것이다. 함수의 이런 장점이 오히려 OOP에 오면서 무분별한 멤버 변수 사용으로 퇴색되는 감이 있는데, OOP를 잘 활용하려면 메서드 역시 변수의 scope를 엄격하게 관리하는 게 중요하다. 그러면 함수는 점프를 한 부분만 따로 이해하는 것이 가능하다. 설령 그 전 코드를 잊어버렸더라도 함수를 이해하고 다시 돌아와서 볼 수 있다. 그런데 만약에 함수나 특히 메서드에서 변수 scope를 제대로 관리하지 않아서 기억해야 하는 상태가 많다면 goto랑 별 다를 바가 없는 것이다.
이런 고민 없이 그냥 extract method를 하게 되면 결국 goto와 같은 문제를 발생시키게 된다. 이렇게 되느니 차라리 1000라인 짜리 메서드가 나중에 리팩토링하기는 더 좋다. 그래서, 좋은 extract method가 떠오르지 않는다면, 그냥 하지 않는 게 더 낫다는 것이다.
상속 계층을 만들지 말 것
마찬가지로, 상속 계층도 되도록 안 만드는 게 좋다. 상속 구조야말로 OOP에서 가장 잘하기 어려운 부분이고, 다형성에 대한 완벽한 이해가 없다면 좋은 상속 구조를 만들어낼 가능성은 거의 없다. 만약 스스로 OOP를 잘한다고 생각하지 않는다면, 상속은 전혀 쓰지 않기를 권한다. 다형성에 대해서는 OOP는 조건문을 줄이는 것을 참조하라. 참고로 나는 저 글에 완전히 동의하지는 않는다. Field Encapsulation도 여전히 OOP의 핵심이며 이건 앞서 Do not extract method에서 했던 이야기와 관련이 있는데 언젠가 다시 한 번 글을 쓸 예정이다.
Cyclomatic Complexity를 늘리지 말 것
자세한 건 구글링을 해보시고, 쉽게 말하면 분기의 depth를 늘리지 않는 것이다. indent가 깊게 안 들어가게 해야 한다고 이해해도 좋다. 다른 건 리팩토링을 하지 않더라도 이것만은 꼭 해놓아야 한다. if문을 줄일 수 있는 다형성을 쓰지 말라고 해놓고 이제와서 if문을 줄이라고? if문을 줄이라는 건 아니다. if문을 flatten하라는 것이다. 예를 들면 GuardClause 같은 것.
public Foo merge (Foo a, Foo b) { Foo result; if (a != null) { if (b != null) { // complicated merge code goes here. } else { result = a; } } else { result = b; } return result; }
위와 같은 코드보다는 아래와 같은 코드가 낫다.
public Foo merge (Foo a, Foo b) { if (a == null) return b; if (b == null) return a; // complicated merge code goes here. }
그리고 조건문도 자세히 보면 중첩 if로 된 것을 풀 수 있는 경우가 많다. 되도록 다 풀어두는 게 좋다. 마찬가지로 SQL을 작성하더라도 FROM 절의 sub query depth가 너무 깊어지지 않도록 하는 게 좋다. 이런 것들은 리팩토링에 큰 에너지를 쓰지 않고 작은 노력으로 큰 효과를 볼 수 있는 것들이다. if가 나타났다고 그걸 다형성으로 대체하려고 하지는 말고, 그냥 그 if들이 중첩되지 않도록 하는 것만 신경 써도 충분하다.
이름 규칙 정하기
quick & dirty는 리팩토링도 대충하는 만큼, 네이밍도 그렇게 열심히 할 필요는 없다. 다만, 네이밍 룰은 잘 정해두는 게 좋다. 특히 각 언어의 코드 관례을 지키지 않으면 나중에 다른 개발자가 왔을 때 실제보다 더 코드가 저평가 받게 된다. 언어의 코드 관례는 존중하도록 하자.
이미지 파일의 이름 규칙도 꽤 영향이 크다. 이 규칙이 잘 정해져 있으면 개발할 때 디렉토리에서 다시 파일명을 확인하지 않아도 된다. 물론 Jetbrains처럼 이미지 파일명까지 추천해주는 IDE를 쓸 수 있는 경우도 있지만, 안 그런 경우도 많으니까.
안 쓰는 코드, 파일 삭제
나중에 레거시가 될 운명인 코드이니만큼, 나중에 이 코드를 만날 개발자들에게 중요한 것 중 하나는 봐야 할 코드량이다. 그런데 쓰지도 않는 코드 때문에 시간을 낭비해야 한다면 얼마나 끔찍할까. 나중에 리팩토링하다가 걸리는 파일들 일일이 다 수정했는데, 알고봤더니 그 중에 80%는 쓰지도 않는 클래스들이었다면? index_1.php, index_2.php 이딴 것들도 다 삭제하도록 하자. 한 눈에 들어오는 파일 개수도 유지보수에 큰 영향을 미친다.
안타까운 것은 이런 일이 대부분 소스 버전 관리를 하지 않기 때문에 일어난다는 것이다. 버전 관리를 하고, 안 쓰는 파일은 빨리빨리 삭제하도록 하자. 실험한 코드들도 다 삭제하는 게 좋다. 안 쓰는 코드를 주석처리한 것도 임시로만 하고, 작업 끝나면 꼭 삭제하자.
주석은 되도록 쓰지 말 것
코드 분석에 도움될 정도의 주석을 쓸 시간이 있거든 리팩토링을 하든가, 구현을 더 빨리하는데 시간을 써라. 대개의 경우 주석은 도움이 안된다. 특히 주석에 누가 언제 이 코드를 작성했는지 따위는 제발 남기지 말도록 하자. 불필요한 라인도 코드 분석을 방해한다.
코드 외의 기술적인 사항은 반드시 문서를 남길 것
레거시를 접할 때 가장 스트레스 받는 사항이 이거다. 소스를 어떻게 빌드하는지, 서버에 어떻게 deploy하는지, 서버에서 무슨 cronjob이 돌고 있는지, 퍼미션은 어떻게 줬는지, 서버 스타트/스톱은 어떻게 시키는지, 서버에 무슨 설정 파일을 건드렸는지 등등. 이런 건 시간이 들더라도 반드시 문서로 남겨야 한다. 이런 정보가 실종되는 바람에 뻔히 돌고 있는 서비스인데 더 이상 업데이트를 할 수 없게 된 회사도 본 적 있다.
적정 기술을 사용할 것
요즘 성능 관련 컨설팅을 좀 하다보니 가장 많이 받는 질문이 이거다. 우리도 분산 DB 기술을 도입해야 하나요? 앞으로도 물어볼 수많은 사람들을 위해 미리 답변하자면, "그냥 MySQL 쓰세요. 그게 정답입니다. 당신이 원하는 건 다 할 수 있어요."
웹을 한다면 생산성이 높고 검증된 프레임웍을 사용하라. 두말할 필요 없이 Rails 아니면 Django가 답이다. Java는 검증되었지만 생산성이 너무 낮고, Node.js는 아직 시간이 더 필요하다. 그 외의 것들은 구글링해도 답이 안 나오는 상황을 자주 겪게 될 것이다. 뭐 그래서 하다가 직접 소스 고쳐가면서 오픈소스 커미터도 되고 좋은 거 아냐? 그래, 좋지, 그 개발자 커리어에는. 근데 그 스타트업에 좋은지는 모르겠다.
마무리
어쨋든 스타트업은 quick & dirty로 달려가야 한다. 기술 부채가 두렵다고 clean code를 극한까지 추구한다면 아무도 쓰지 않는 훌륭한 제품이라는 현실에 직면하게 될 것이다. 스타트업도 투자고, 부채로 레버리지를 한다면 원하는 이익 실현 시점을 앞당길 수 있다. 기술 부채 만드는 것을 두려워해선 안된다. 다만 그 기술 부채를 감당할 수 있는 수준으로, 파산하지 않게 유지하라는 것이다. 그렇게 큰 노력을 기울이지 않고도, 엄청나게 훌륭한 리팩토링을 해내지 않고도 그럭저럭 통제할 수 있는 수준을 유지할 수 있는 방법들이 있다. 물론 또 너무 저런 걸 다들 잘하면 내 밥벌이가 안되겠지. 너무 좋은 코드도, 너무 나쁜 코드도 스타트업에겐 좋지 않다. 적정 기술, 적정 품질을 생각하면서 일하는 엔지니어가 되도록 하자.