1. 개요 (Introduction)
- 신뢰성의 전제 조건: 에츠허르 다이크스트라(Edsger W. Dijkstra)의 말처럼, 소프트웨어 시스템의 신뢰성을 확보하기 위한 핵심 전제 조건은 **단순성(Simplicity)**임.
- 강연의 목적: 소프트웨어 개발 과정에서 무심코 혼용하는 ’단순함(Simple)’과 ’쉬움(Easy)’을 명확히 구분하고, 복잡성을 유발하는 요소를 파악하여 지속 가능한 코드를 작성할 수 있는 도구와 논리를 제공함.
2. 단순함(Simple)과 쉬움(Easy)의 정의 및 어원적 분석
가. 단순함 (Simple)
- 어원: ‘sim’(하나) + ‘plex’(겹치다, 엮다)의 합성어로, **“단 한 장의 줄을 엮음(unentangled)”**을 의미함.
- 반대말: 복잡함(Complex) - ‘com’(함께) + ‘plex’(엮다)가 합쳐져 여러 줄이 서로 얽혀 있고 꼬여 있는 상태를 뜻함.
- 본질:
- 하나의 역할, 하나의 임무, 하나의 명확한 개념만을 다룸.
- 기능의 개수(Cardinality)가 적은 것이 아니라, 서로 다른 개념이 뒤섞이지 않은 상태를 의미함.
- 객관적인 지표로 측정이 가능함 (개념 간의 연결점이나 얽힘이 있는지를 직접 눈으로 확인 가능).
나. 쉬움 (Easy)
- 어원: 라틴어 ‘adjacent’(가까이 누워 있음, 인접함)에서 유래하여 **“물리적 또는 인지적으로 가까운 상태”**를 뜻함.
- 반대말: 어려움(Hard) - 멀리 떨어져 있거나 많은 힘을 요구하는 상태를 뜻함.
- 본질:
- 접근성(Proximity): 하드드라이브에 즉시 설치하여 바로 실행할 수 있는 도구처럼 손닿기 쉬운 곳에 있는 상태.
- 친숙함(Familiarity): 자신의 기존 지식, 언어, 경험 범위 내에 있어서 별도의 학습 없이 이해할 수 있는 상태 (예: 독일어를 모르는 사람에게 독일어는 어려운 언어이지만, 독일인에게는 쉬운 언어인 것과 같음).
- 상대성(Relativity): 주체에 따라 ’쉬움’의 기준이 완전히 달라짐.
다. 비교 요약
| 구분 | 단순함 (Simple) | 쉬움 (Easy) |
|---|---|---|
| 개념 | 얽히지 않고 독립적인 상태 (Unentangled) | 인지적/물리적으로 가까운 상태 (Familiar, Near) |
| 성격 | 객관적 (시스템 구조적 특징) | 상대적 (사용자의 경험과 지식 수준에 의존) |
| 관점 | 결과물(Artifact)의 품질 향상에 초점 | 개발자 개인의 단기적인 편리함에 초점 |
3. 도구(Tool) 중심적 사고의 한계와 결과물(Artifact)의 중요성
가. 도구 중심의 ’쉬움’에 매몰된 현실
- 프로그래머는 코드를 작성하는 도중 “세미콜론이 없고 타이핑 수가 적어 편리하다”는 이유로 도구의 ’편의성’에 과도하게 집중함.
- 고용주(경영진) 역시 대체 가능한 인력을 쉽게 구하기 위해 기존 개발자에게 가장 익숙하고 흔한(Easy) 도구를 고집하는 경향이 있음.
나. 비즈니스 가치는 결과물(Artifact)에서 발생
- 사용자는 소스코드의 미학이나 작성 과정의 편리함에 관심이 없음.
- 가치는 시스템이 실제로 구동되며 변경 사항이 발생할 때 오류 없이 유연하게 대처할 수 있는 결과물의 속성에서 나옴.
- 따라서 도구는 그것을 사용해 만들어낸 최종 결과물의 구조가 얼마나 단순한지를 기준으로 평가받아야 함.
4. 인지 능력의 한계와 복잡성의 누적
가. 인간 정신 능력의 한계
- 제럴드 제이 서스먼(Gerald Jay Sussman) 교수의 지적처럼, 완벽히 이해하지 못하는 대규모 시스템을 신뢰성 있게 유지하는 것은 불가능함.
- 인간이 한 번에 머릿속에 담고 저글링할 수 있는 개념의 개수는 지극히 제한적임.
- 서로 엮여 있는(Complect) 요소가 늘어날수록 조합 폭발이 일어나며 시스템 분석에 드는 뇌의 연산 부담이 급격히 증가함.
나. ’Complect’의 독성
- Complect (엮다, 땋다): 둘 이상의 개념을 묶거나 휘감아 분리할 수 없게 만드는 행위를 뜻함.
- 시스템 안의 요소들이 서로를 직접 호출하지 않더라도 상호 의존적인 상태 정보를 공유하게 되면 실질적으로 강하게 엮이게 됨.
- 변수(Variable)의 남용이나 전역 상태 관리는 하나의 값과 시간을 엮어버려, 분석 시점마다 다른 결과를 발생시키는 독성과 같은 역할을 함.
5. ’가드레일 프로그래밍’과 장기적 생산성의 관계
가. 가드레일 프로그래밍(Guardrail Programming)의 오류
- 컴파일러의 타입 체커와 단위 테스트 프레임워크가 모든 오류를 잡아줄 것이라 믿고 무작정 코드를 수정하는 방식.
- 가드레일(테스트)은 차가 도로 밖으로 굴러떨어지는 참사를 방지할 뿐, 차가 올바른 목적지로 가고 있는지 방향을 제시하지는 못함.
- 테스트 통과 여부에만 의존하여 내재된 복잡성을 방치하면 추론(Reasoning)이 불가능한 시스템이 됨.
나. 개발 속도 그래프 분석 (단순함 vs 쉬움)
- 쉬움(Easy)만 추구할 경우: 초반에는 학습 비용이 적어 전력 질주(Sprint)가 가능하나, 시간이 지날수록 우발적 복잡성(Accidental Complexity)이 누적되어 생산성이 지수 함수적으로 급락함. 매 스프린트마다 기존 코드의 재작업에 시간을 소모하게 됨.
- 단순함(Simple)을 추구할 경우: 개발 초기에 개념을 분리하고 단순화하는 설계 과정이 요구되므로 시작은 상대적으로 느릴 수 있음. 하지만 장기적으로는 속도 저하 없이 지속적으로 기능을 추가할 수 있는 우상향 곡선을 그림.
6. 구성 요소의 단순성 대비표 (Simple vs. Complex Taxonomy)
시스템을 구축할 때 사용하는 여러 도구와 개념을 얽힘의 유무를 기준으로 분류할 수 있음.
| 복잡함 (Complex / Complect) | 단순함 (Simple) | 단순화의 원리 / 대체안 |
|---|---|---|
| 상태 (State), 객체 (Object) | 값 (Value) | 변하지 않는 불변 데이터(Immutable Data) 활용 |
| 메서드 (Method) | 함수 (Function), 네임스페이스 | 입력값에 대해 항상 동일한 결과를 반환하는 순수 함수 사용 |
| 변수 (Variable) | 참조 (Reference) | 값과 시간을 합성하여 다루는 정교한 참조 모델 적용 |
| 상속 (Inheritance) | 다형성 (Polymorphism) | 클래스 계층 구조 대신 프로토콜, 타입 클래스 등을 통한 다형성 구현 |
| 문법 (Syntax) | 데이터 (Data) | 고정된 언어 문법 구조보다 유연한 자료구조(Map, Set)를 활용 |
| 명령형 루프 (Loop), Fold | Set 함수 (Set Functions) | 흐름 순서에 제약을 주지 않는 방식으로 대량의 데이터 처리 |
| 액터 (Actor) | 큐 (Queue) | 행위자와 작업을 결합하지 않고 메시지 큐를 통한 전송 분리 |
| ORM | 선언형 데이터 조작 (SQL, Datalog) | 클래스와 관계형 테이블을 무리하게 매핑하지 않고 원시 데이터로 조회 |
| 조건문 (Conditionals) | 규칙 (Rules) | 비즈니스 로직 곳곳에 조건문을 심지 않고 규칙 시스템으로 분리 |
7. 단순함을 설계하는 구체적 방법론 (5W1H 분리 원칙)
개념적 복잡성을 추상화할 때, 시스템의 책임을 육하원칙에 따라 쪼개어 독립적인 모듈로 구현해야 함.
- 무엇을 (What) - 명령어와 목적 정의
- 수행하려는 행동을 정의함. 아주 작고 세분화된 인터페이스나 함수 규격을 생성함.
- 이때 ‘어떻게(How)’ 처리할지에 대한 구체적 제어 방식을 미리 엮지 않는 것이 핵심임.
- 누가 (Who) - 데이터와 엔티티 주입
- 특정 구체 클래스를 내부에서 직접 생성(Hardcode)하지 않고 하위 컴포넌트나 매개변수 형태로 외부에서 주입(Injection)받아 사용함.
- 어떻게 (How) - 개방된 다형성 구현
- 실제 행위를 실행하는 코드는 가능한 한 독립적인 외딴섬처럼 구현함.
- 런타임에 유연하게 결합할 수 있는 다형성 구조(Polymorphism)를 채택하고, 분기문(Switch) 등으로 구현체를 묶지 않음.
- 언제 & 어디서 (When & Where) - 결합도 제어
- 컴포넌트 A가 컴포넌트 B를 직접 호출하는 방식은 ’언제 실행되는가’와 ’어디서 실행되는가’를 동시에 묶어버림.
- 이를 해결하기 위해 두 컴포넌트 사이에 **큐(Queue)**를 도입하여 실행 시점과 위치를 분리함.
- 왜 (Why) - 규칙과 정책의 외부화
- 비즈니스 의사 결정이나 제약 사항은 소스코드 내부에 조건문 형태로 분산시키지 않고, 선언형 규칙 엔진(Rule System)이나 별도의 정책 관리 레이어로 추출함.
8. 결론 (Conclusion)
- 단순함은 선택의 문제: 우리가 작성한 시스템이 복잡한 원인은 도구나 환경의 탓이 아닌, 단순함을 우선시하지 않은 설계자의 선택에 기인함.
- 감성적 ‘쉬움’ 극복하기: “익숙해서 편하다”는 개인적이고 상대적인 감각에 의존하여 도구를 고르기보다, 다소 낯설더라도 결과물을 물리적으로 단순화하는 도구(불변 데이터 구조, 순수 함수, 프로토콜 등)를 채택하는 태도가 요구됨.
- 목표 지향적 태도: 줄의 개수가 늘어나더라도(기능이 많아지더라도) 꼬이지 않고 평행하게 뻗어 있는 단순한 줄들의 집합이, 몇 줄 안 되더라도 단단히 뒤엉켜 있는 매듭보다 훨씬 안전하고 변경하기 용이함. 복잡한 시스템의 실타래를 푸는 주체는 바로 개발자 자신임을 인지해야 함.