시스템 사고
Source: Hacker News
소프트웨어 개발에 대한 두 가지 사고 방식
- 진화적(점진적) 개발 – 작게 시작하고, 시간이 지남에 따라 기능을 추가하며, 시스템이 자연스럽게 성장하도록 한다.
- 사전 대규모(공학) 설계 – 코드를 작성하기 전에 모든 복잡성을 포괄하는 사양을 작성한다.
요약하면, 이는 기업가가 스타트업을 구축하는 방식과 우리가 현대 고층 건물을 건설하는 방식의 차이와 같다: 진화 vs. 공학.
실제 사례
저는 한 번 > 3,000개의 활성 시스템을 보유한 대기업에서 일한 적이 있습니다. 이 시스템들은 수십 개의 사업 부문과 모든 내부 부서를 아우르고 있었습니다.
- 이 환경은 ~50 년에 걸쳐 진화해 왔습니다.
- 다양한 기술 스택, 셀 수 없이 많은 공급업체, 그리고 레거시와 최신 애플리케이션이 뒤섞인 형태였습니다.
- 하나의 “것”으로 바라보면 불안정한 카드 집과도 같았습니다.
시스템 수가 적고 규모가 큰 경우는 어떨까요?
- 데이터, 보안, 운영, 품질, 접근성의 불일치가 크게 줄어들 것입니다.
- 최신, 고대, 잘 작동하는, 거의 작동하지 않는 시스템이 뒤섞인 상황이 보다 일관된 플랫폼으로 대체될 것입니다.
- 전체 복잡도는 현재 수준의 ≈ 10 % 수준으로 감소할 수 있으며, 절반 수준에 그치지 않습니다.
예상되는 이점
- 성능 및 신뢰성 향상
- 변화에 대한 회복력 강화
- 비용 절감 및 인력 감소
- 현재 존재하는 많은 “불편한” 문제들의 제거
The Core Issue: Dependencies
| 항목 | Evolutionary Approach | Big‑Up‑Front Design |
|---|---|---|
| Assumption | 의존성을 처음에는 무시하고 나중에 해결할 수 있다. | 모든 의존성을 사전에 식별하고 설계에 반영해야 한다. |
| Pros | 빠른 시작, 즉각적인 진행, 조정 필요성 감소. | 아키텍처가 1일 차부터 일관성을 유지하고, 나중에 숨겨진 문제가 적다. |
| Cons | 숨겨진 의존성이 나중에 수정 비용이 크게 늘고, “해킹”이 누적돼 복잡도가 급증한다. | 많은 조정, 회의, 그리고 팀 간 초기 마찰이 필요하다. |
수천 개의 독립적인 블롭이 있다면 각각을 “빠르게 구현”할 수 있습니다. 실제로는 매우 적은 구성 요소만이 진정으로 독립적이기 때문에, 시스템이 확장될수록 진화적 접근 방식은 위험합니다.
왜 초기 대규모 설계가 정체되는가
- 지식 부족 – 기반(기술 스택, 프레임워크, 라이브러리)이 빠르게 진화하여 보편적으로 받아들여지는 모범 사례가 거의 없습니다.
- 짧은 경력 경로 – 대부분의 애플리케이션 프로그래머는 ≤ 5 년의 깊은 경험을 가지고 있으며, “20년 이상” 베테랑 엔지니어는 드뭅니다.
- 복잡성 위협 – 초보 개발자들은 대규모 상호 의존 설계를 다루기에 준비가 부족하다고 느끼는 경우가 많습니다.
개인 선호도 & 현실 점검
진화형 프로젝트
장점
- 더 재미있고 회의가 적으며 즉시 실무에 착수할 수 있음.
- 초기 조정이 덜 필요해 “그냥 일에 들어갈 수 있음”.
단점
- 코드베이스가 커질수록 탈선 위험이 증가함.
- 시스템이 의도대로 동작하지 않을 때 말기에 패닉이 발생함.
- 문제를 해결하기보다 오히려 문제를 추가했다는 장기적인 불만족이 생김.
대규모 사전 설계
장점
- 시작은 느리지만 전체 개발 흐름이 매끄러움.
- 세부 사항, 재사용성, 아키텍처 일관성에 신중히 신경 쓸 수 있음.
- 장기적으로 스트레스를 감소시킴; 올바르게 할 시간이 있음.
단점
- 마찰감이 있음: 회의가 많고, 조정이 무겁고, 초기 진행이 느림.
Note: 대규모 사전 프로젝트가 잘못된 제품을 만들 위험은 종종 과장됩니다. 요구사항이 잘 이해된 성숙한 비즈니스 영역에서는 견고한 사양이 실제로 위험을 줄일 수 있습니다.
핵심 요약
- 진화적 개발은 문제 영역이 유동적인 소규모, 빠르게 움직이는 이니셔티브에 적합합니다.
- 대규모 사전 엔지니어링은 도메인이 안정적이고 숨겨진 의존성 비용이 높으며 조직이 초기 조정을 감당할 수 있을 때 빛을 발합니다.
올바른 접근 방식을 선택하는 것은 문제의 성격, 팀의 성숙도, 그리고 장기 기술 부채에 대한 허용 범위에 달려 있습니다.
원본 작업 리팩토링 및 결함 해결
중간 어딘가에 균형 잡힌 경로가 있어야 하지만, 수십 년이 지나도 그에 대한 공식적인 버전을 찾지 못했습니다.
우선 의존성을 살펴보고, 왜 일시적으로 무시할 수 있는지 설명할 수 있습니다. 다음 릴리스를 진행하면서도 막연하고 장기적인 설계를 염두에 둘 수 있습니다. 새로운, 예상치 못한 의존성이 나타나면 설계를 그에 맞게 리팩토링하면 됩니다. 계속해서 생각을 바꾸면서, 진화하는 작업이 견고한 거대한 설계에 수렴하도록 노력합니다.
- 빠르게 시작하고, 속도를 늦췄다가 다시 가속하고, 이를 반복합니다.
- 목표는 결국 “모두를 지배하는” 단일하고 포괄적인 시스템을 만드는 것이며, 이를 달성하는 데 시간이 걸리더라도 괜찮습니다.
Iteration Size Matters
각 iteration의 규모는 매우 중요합니다:
- 매우 작은 iteration은 눈을 가리고 앞을 헤매는 경우가 많습니다.
- 더 긴 iteration(눈을 가리지 않을 때)은 더 효과적입니다.
iteration은 반드시 균일할 필요는 없지만, 각 iteration이 끝난 뒤에는 멈춰서 상황을 점검해야 합니다.
- 코딩 속도가 빨라질수록 정리(cleanup) 작업이 더 많이 필요합니다.
- 정리를 미루면 문제가 기하급수적으로 커집니다.
- 검증 없이 앞만 달리면 작업 환경이 결국 정체된 늪으로 변해 멈추게 됩니다.
이 원칙은 소프트웨어 시스템 구축부터 레스토랑에서 요리까지 모든 것에 적용됩니다. 속도는 언제나 트레이드‑오프입니다.
Balancing Evolution and Engineering
- Evolution은 엔지니어링에 빠져들지 않게 도와주지만, 엔지니어링은 제품이 제 역할을 하도록 보장합니다.
- 엔지니어링은 느리지만, 통제 없이 급격히 진행되는 것은 훨씬 더 느립니다.
- Evolution은 역동적이지만 혼란스럽습니다; 잘못된 경로를 택했을 때 이를 인식하고 되돌아가는 것이 필요하고, 이를 인정하기가 어려울 수 있습니다.
대부분의 시스템에 대해:
- Engineered parts – 신중하게 설계하고 검증해야 하는 구성 요소.
- Evolved parts – 자연스럽게 변화하도록 허용할 수 있는 부분.
진화 경로가 무작위일수록 버리고 다시 만드는 일이 많아집니다. 흔들림은 언제나 비용이 많이 듭니다. 자연은 수백만 종을 통해 이를 극복하지만, 우리는 보통 하나의 개발 프로젝트만 진행합니다—따라서 위험이 더 크고 과정이 덜 편리합니다.