본문으로 건너뛰기

영상

Simple Made Easy - 소프트웨어 설계에서 단순함과 쉬움의 구분

youtube.com ↗

Clojure의 창시자 Rich Hickey의 명강연 'Simple Made Easy'를 바탕으로, 소프트웨어 개발에서 혼동하기 쉬운 '단순함(Simple)'과 '쉬움(Easy)'의 개념을 재정의하고, 복잡성을 제어하여 장기적인 신뢰성을 확보하는 아키텍처 설계 원칙을 제시합니다

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)

  • 단순함은 선택의 문제: 우리가 작성한 시스템이 복잡한 원인은 도구나 환경의 탓이 아닌, 단순함을 우선시하지 않은 설계자의 선택에 기인함.
  • 감성적 ‘쉬움’ 극복하기: “익숙해서 편하다”는 개인적이고 상대적인 감각에 의존하여 도구를 고르기보다, 다소 낯설더라도 결과물을 물리적으로 단순화하는 도구(불변 데이터 구조, 순수 함수, 프로토콜 등)를 채택하는 태도가 요구됨.
  • 목표 지향적 태도: 줄의 개수가 늘어나더라도(기능이 많아지더라도) 꼬이지 않고 평행하게 뻗어 있는 단순한 줄들의 집합이, 몇 줄 안 되더라도 단단히 뒤엉켜 있는 매듭보다 훨씬 안전하고 변경하기 용이함. 복잡한 시스템의 실타래를 푸는 주체는 바로 개발자 자신임을 인지해야 함.