Chapter 1

복잡성의 본질

  • 1.1 소프트웨어 복잡성이 문제다
  • 1.2 복잡성의 증상
  • 1.3 복잡성의 원인
  • 1.4 전술적 vs 전략적 프로그래밍

소프트웨어 개발에서 가장 큰 적은 복잡성이야. 프로그래밍 언어가 발전하고 도구가 좋아져도 결국 우리가 만든 시스템을 얼마나 이해할 수 있느냐가 생산성을 결정하거든. John Ousterhout 교수가 스탠포드에서 소프트웨어 설계를 가르치면서 도달한 결론이 이거야 — 복잡성과의 싸움이 소프트웨어 개발의 본질이라는 것. 이 싸움에서 무기는 두 가지인데, 코드 자체를 단순하게 만드는 것과 모듈화를 통해 복잡성을 캡슐화하는 것이지. 이 책은 특히 후자에 집중해. 모듈을 어떻게 설계해야 복잡성이 새어나오지 않는지, 인터페이스를 어떻게 잡아야 하는지를 다뤄. 그리고 설계는 한 번에 끝나는 게 아니라 지속적인 과정이라는 점도 강조하지. 워터폴처럼 처음에 완벽하게 설계하고 구현에 들어가는 건 현실적이지 않아. 코드를 짜보고, 문제를 발견하고, 설계를 개선하는 사이클을 계속 돌려야 해.

**복잡성(complexity)**이 정확히 뭔지부터 정의하자. 감으로 "복잡하다"고 말하지 말고, 구체적으로 뭘 의미하는지 알아야 제대로 싸울 수 있으니까. 저자의 정의는 이래 — 시스템을 이해하고 수정하기 어렵게 만드는 소프트웨어 구조와 관련된 모든 것.

**변경 증폭(Change Amplification)**이 첫 번째 증상이야. 겉보기에 단순한 변경인데 여러 곳을 다 고쳐야 하는 거지. 웹사이트 배경색을 바꾸는데 HTML 파일 20개를 수정해야 한다면, CSS 한 줄이면 끝날 일을 20곳에서 해야 하는 건 설계가 잘못된 거잖아.

두 번째는 **인지적 부하(Cognitive Load)**야. 어떤 작업을 수행하기 위해 개발자가 알아야 하는 지식의 양이 너무 많은 것. API가 복잡하거나 전역 변수 간의 관계를 다 알아야 하는 경우가 여기 해당해. 코드 줄 수가 적다고 복잡성이 낮은 게 아니야. 한 줄이라도 이해하기 어려우면 그게 더 복잡한 거지.

세 번째이자 가장 치명적인 게 **알려지지 않은 미지(Unknown Unknowns)**야. 작업을 완료하기 위해 뭘 알아야 하는지조차 모르는 상황이지. "이 코드 고치면 되겠지" 했는데 저쪽에서 터지는 거. 뭘 모르는지 모르기 때문에 대비할 수가 없어서 가장 위험해.

이 증상들의 근본 원인은 딱 두 가지야. **의존성(Dependencies)**과 모호성(Obscurity). 의존성은 어떤 코드가 다른 코드와 연결되어 있어서 하나를 이해하거나 수정하려면 다른 쪽도 알아야 하는 관계야. 의존성 자체를 완전히 없앨 수는 없어 — 소프트웨어는 결국 모듈들이 협력해야 하니까. 핵심은 의존성을 최소화하고, 남아있는 의존성을 최대한 단순하고 명확하게 만드는 것이지. 모호성은 중요한 정보가 명확하지 않은 거야. 변수 이름이 time인데 밀리초인지 초인지 알 수 없다거나, 두 변수가 항상 같이 수정되어야 하는데 그 관계가 코드에 드러나지 않는 경우. 의존성이 많으면 변경 증폭이 생기고, 모호성이 높으면 인지적 부하와 Unknown Unknowns가 생겨.

여기서 중요한 게, 복잡성은 한 번에 폭발하지 않고 조금씩 점진적으로 쌓인다는 거야. "이 의존성 하나쯤이야", "이 특수 케이스 하나쯤이야" — 이런 태도가 수백 번 반복되면 시스템이 감당 불가능해지지. 물이 서서히 끓는 냄비 속 개구리와 같아. 그래서 저자는 "zero tolerance" 마인드를 제안해. 작은 복잡성도 그냥 넘기지 말자는 거야.

복잡성의 본질을 알았으니, 이제 프로그래밍 태도 이야기를 하자. 저자는 마인드셋을 **전술적 프로그래밍(Tactical Programming)**과 **전략적 프로그래밍(Strategic Programming)**으로 나눠. 전술적 프로그래밍은 "일단 돌아가게 만드는 것"이 최우선인 태도야. 기능이 돌아가면 끝, 일정에 쫓기니까 나중에 리팩터링하자, 빨리 끝내는 게 최고 — 이런 사고방식이지. 문제는 이렇게 코딩하면 단기적으로는 빠르지만 장기적으로 엄청난 대가를 치른다는 거야. 매번 추가하는 작은 hack, 임시 workaround, 깔끔하지 않은 설계가 쌓여서 시스템 전체를 복잡성의 늪으로 빠뜨리거든. 특히 위험한 이유는 즉각적인 보상이 있기 때문이야. 기능이 빨리 완성되니까 뿌듯하고, 관리자도 기뻐하지. 복잡성이 문제를 일으키는 건 몇 달 뒤의 일이니까 지금은 안 느껴져. 전형적인 미래의 나에게 빚 지는 행위야.

반대가 전략적 프로그래밍이야. "돌아가는 코드"만으로는 충분하지 않다는 마인드셋이지. 좋은 설계가 1순위이고, 기능 구현은 그 다음이야. 이 코드가 미래에 읽기 쉬운가, 시스템 복잡성을 줄이는가, 지금 조금 더 시간을 들이면 나중에 크게 절약할 수 있는가를 고민하는 거지. 핵심은 전략적 프로그래밍이 "느린 프로그래밍"이 아니라 투자라는 거야. 저자는 구체적으로 개발 시간의 10~20% 정도를 설계 개선에 투자하라고 해. 거대한 리팩터링이 아니라 일상적인 코딩에서 매번 조금씩 시간을 더 들이는 거야. 기능 구현에 3일 걸린다면 반나절 정도 설계 개선에 쓰고, 버그 수정할 때 원인이 된 나쁜 설계도 같이 고치는 식이지.

실제 사례도 있어. Facebook의 "Move fast and break things" 문화는 초기에 빠른 성장을 가능하게 했지만, 시스템이 커지면서 개발 속도가 급격히 떨어졌고 결국 **"Move fast with stable infrastructure"**로 바꿨지. 반면에 구글이나 VMware는 초기부터 전략적 접근을 취해서 코드베이스가 커져도 상대적으로 생산성을 유지했다고 해.


정리

1장 읽고 기억할 거 세 가지:

  1. 복잡성의 세 가지 증상 — 변경 증폭, 인지적 부하, Unknown Unknowns — 을 기억하자. 특히 Unknown Unknowns가 가장 위험해. 근본 원인은 의존성과 모호성 딱 두 가지야
  2. 복잡성은 점진적으로 쌓여. "이 정도쯤이야" 하면서 넘기는 작은 타협들이 모여서 시스템을 망가뜨리거든. Zero tolerance 마인드로 매번 의식적으로 체크해야 해
  3. 전술적 프로그래밍은 미래의 나에게 빚 지는 행위야. 전략적으로, 개발 시간의 10~20%를 설계 개선에 투자하는 습관이 장기적으로 훨씬 빠른 길이지