KoderKoder.ai
가격엔터프라이즈교육투자자용
로그인시작하기

제품

가격엔터프라이즈투자자용

리소스

문의하기지원교육블로그

법적 고지

개인정보 처리방침이용 약관보안허용 사용 정책악용 신고

소셜

LinkedInTwitter
Koder.ai
언어

© 2026 Koder.ai. All rights reserved.

홈›블로그›리치 히키와 클로저: 단순성, 불변성, 더 나은 기본값
2025년 6월 27일·7분

리치 히키와 클로저: 단순성, 불변성, 더 나은 기본값

리치 히키의 클로저 철학—단순성, 불변성, 더 나은 기본값—을 쉽게 풀어 설명합니다. 복잡한 시스템을 더 차분하고 안전하게 만드는 실용적 교훈.

리치 히키와 클로저: 단순성, 불변성, 더 나은 기본값

왜 현실 프로젝트에서 복잡성이 이기는가

소프트웨어는 한 번에 복잡해지지 않습니다. 마감일을 맞추기 위한 빠른 캐시 하나, 복사를 피하려는 공유 가변 객체 하나, "이건 예외야"라는 이유로 규칙을 깨는 예외 하나씩—이러한 "합리적인" 결정들이 쌓여 어느 순간 변경이 위험하게 느껴지고, 버그 재현이 어려워지며, 기능 추가가 만들기보다 오래 걸리는 시스템이 됩니다.

복잡성이 이기는 이유는 단기적 편안함을 제공하기 때문입니다. 새 의존성을 연결하는 것이 기존 것을 단순화하는 것보다 빠를 때가 많습니다. 상태를 패치하는 것이 상태가 다섯 서비스에 분산된 이유를 묻는 것보다 쉽습니다. 시스템이 문서보다 빨리 커질 때 관습과 조직 내 지식에 의존하기도 쉽습니다.

이 글의 목적(그리고 한계)

이 글은 클로저 튜토리얼이 아니며, 클로저를 몰라도 충분히 가치가 있습니다. 목표는 리치 히키의 작업과 자주 연관되는 실용적 아이디어를 빌려와—언어와 상관없이 매일의 엔지니어링 결정에 적용할 수 있는—교훈을 제공하는 것입니다.

기본값이 생각보다 중요한 이유

대부분의 복잡성은 의도적으로 작성한 코드에서 나오지 않습니다. 도구가 기본으로 쉽게 만들어주는 것에서 옵니다. 기본값이 "모든 곳에서 가변 객체"라면 숨은 결합이 생깁니다. "상태가 메모리에만 존재"라는 기본이 있으면 디버깅과 추적이 어려워집니다. 기본값은 습관을 만들고, 습관은 시스템을 만듭니다.

우리는 세 가지 주제에 집중합니다:

  • 단순성: 기능이 적다는 뜻이 아니라 움직이는 부품과 특수 케이스가 적다는 뜻입니다.
  • 불변성: 변경되지 않는 값으로 데이터를 다루어 추론을 쉽게 합니다.
  • 더 나은 기본값: 안전하고 예측 가능한 선택이 가장 쉬운 선택이 되게 합니다.

이 아이디어들이 도메인에서 복잡성을 제거하지는 않지만, 소프트웨어가 복잡성을 증폭시키는 것을 막을 수 있습니다.

리치 히키와 클로저가 해결하려 한 문제들

리치 히키는 클로저를 만든 것으로 잘 알려진 소프트웨어 개발자이자 설계자이며, 흔한 프로그래밍 관행에 도전하는 강연으로 유명합니다. 그의 초점은 유행을 쫓는 것이 아니라 시스템이 변경하기 어렵고, 추론하기 어렵고, 성장하면 신뢰하기 어려워지는 반복적 원인을 탐구하는 데 있습니다.

클로저란 무엇인가(하이레벨, 전문 용어 최소화)

클로저는 JVM(자바 런타임)이나 자바스크립트 같은 잘 알려진 플랫폼에서 실행되는 현대적 프로그래밍 언어입니다. 기존 생태계를 활용하면서 특정 스타일을 권장하도록 설계됐습니다: 정보를 평범한 데이터로 표현하고, 변경되지 않는 값을 선호하며, “무슨 일이 일어났는가”를 “화면에 무엇을 보여줄지”와 분리합니다.

클로저는 숨은 부작용에서 멀어지게 하고, 더 명확한 구성 블록으로 유도하는 언어라고 생각할 수 있습니다.

클로저가 줄이려 했던 문제들

클로저는 작은 스크립트를 더 짧게 만들기 위해 만들어진 것이 아닙니다. 반복적으로 발생하는 프로젝트 고통을 겨냥했습니다:

  • 공유 상태에서 오는 증가하는 복잡성: 시스템의 많은 부분이 같은 데이터를 수정할 수 있으면 버그는 타이밍 의존적이 되어 재현하기 힘들어집니다.
  • 데이터와 행위의 긴밀한 결합: 정보가 객체나 클래스 안에 잠겨 있으면 재사용이 어렵고 변경의 파급이 커집니다.
  • 동시성 골칫거리: 백그라운드 작업, 큐, 병렬 작업이 늘어나면 “누가 언제 무엇을 바꿨나?”가 매일의 문제가 됩니다.

클로저의 기본값은 더 적은 움직이는 부품을 향해 밀어 넣습니다: 안정적인 자료구조, 명시적 업데이트, 조정을 안전하게 만드는 도구들.

클로저를 도입하지 않아도 유용한 이유

가치는 언어 전환에만 국한되지 않습니다. 히키의 핵심 아이디어—불필요한 상호 의존을 제거해 단순화하고, 데이터를 지속 가능한 사실로 다루며, 가변 상태를 최소화하라—는 자바, 파이썬, 자바스크립트 등 어디에서나 시스템을 개선할 수 있습니다.

단순성: ‘쉬움’이 아니라 움직이는 부품이 적음

리치 히키는 단순(simple) 과 쉬움(easy) 을 분명히 구분합니다—그리고 많은 프로젝트가 이 경계를 알아차리지 못하고 넘습니다.

단순함과 쉬움의 차이(일상적 예시)

쉬움(easy) 은 지금 느끼는 편의성에 관한 것입니다. 단순함(simple) 은 부품의 수와 그들이 얼마나 밀접하게 얽혀 있는가에 관한 것입니다.

  • 인스턴트 라면은 쉽습니다. 기본 스튜는 단순할 수 있습니다: 몇 가지 재료, 한 냄비, 숨은 요소 없음.
  • 버튼 60개짜리 리모컨은 한 기능을 ‘쉽게’ 만들 수 있지만 단순하지는 않습니다. 다섯 개의 명확한 버튼을 가진 리모컨이 더 단순합니다.

소프트웨어에서 “쉽다”는 종종 “오늘 타이핑하기 빠르다”는 의미이고, “단순하다”는 “다음 달에 고장내기 어렵다”는 의미입니다.

‘지금의 쉬움’이 미래의 복잡성을 만드는 방식

팀들은 즉각적인 마찰을 줄이는 지름길을 선택하는 경향이 있고, 이는 보이지 않는 구조를 추가합니다:

  • "플래그 하나만 추가하자." 이제 모든 기능이 그 플래그를 고려해야 합니다.
  • "계산된 값을 저장해서 시간을 절약하자." 이제 여러 경로에서 일관성을 유지해야 합니다.
  • "UI에서 패치하자." 이제 동일한 비즈니스 규칙이 여러 곳에 존재합니다.

각 선택은 속도로 느껴질 수 있지만, 움직이는 부품, 특수 케이스, 교차 의존성의 수를 늘립니다. 이게 시스템이 극적인 실수 없이도 취약해지는 방식입니다.

속도는 단순성과 동일하지 않습니다

빠른 배포는 훌륭할 수 있지만, 단순화 없이 빠르게 진행하면 보통 미래의 빚을 지는 것입니다. 그 이자는 재현하기 힘든 버그, 지체되는 온보딩, "신중한 조정"을 요구하는 변경으로 나타납니다.

우연한 복잡성 체크리스트

설계나 PR을 검토할 때 다음 질문을 하세요:

  • 새로운 모드, 플래그, 설정 분기가 도입되었는가?
  • 일관성을 유지해야 하는 캐시나 중복 데이터를 도입했는가?
  • 하나의 동작을 위해 여러 모듈을 함께 변경해야 하는가?
  • 규칙이 여러 곳에 중복 구현되어 있는가?
  • 새 팀원이 별도 설명 없이 이 동작을 예측할 수 있는가?

상태(state): 보이지 않는 복잡성 증폭기

“상태”는 시스템에서 변할 수 있는 모든 것입니다: 사용자 장바구니, 계정 잔액, 현재 설정, 워크플로 단계 등. 문제는 변화 자체가 아니라 각 변화가 불일치의 새로운 기회를 만든다는 점입니다.

사람들이 “상태가 버그를 만든다”고 말할 때 보통 의미하는 것은: 동일한 정보가 서로 다른 시간(또는 장소)에 다를 수 있으면, 코드가 끊임없이 "지금 어느 버전이 진짜인가?"라는 질문에 답해야 한다는 것입니다. 그 질문을 잘못 대답하면 무작위처럼 보이는 오류가 발생합니다.

가변성(mutability): 눈앞에서 바뀌는 변화

가변성은 객체가 제자리에서 편집된다는 뜻입니다: "같은" 것이 시간이 지나며 달라집니다. 효율적으로 보일지 모르지만 추론을 어렵게 만듭니다. 왜냐하면 잠깐 전에 본 것을 더 이상 신뢰할 수 없기 때문입니다.

비유로는 공유 스프레드시트나 문서를 들 수 있습니다. 여러 사람이 동시에 같은 셀을 편집할 수 있다면 이해가 즉시 무효화될 수 있습니다: 합계가 바뀌고, 수식이 깨지고, 행이 사라질 수 있습니다. 누군가 악의적으로 하지 않더라도 공유 가능성과 편집 가능성이 혼란을 만듭니다.

소프트웨어 상태도 마찬가지입니다. 두 부분이 같은 가변 값을 읽으면 한 부분이 아무런 경고 없이 그것을 바꾸고 다른 부분은 오래된 가정으로 계속 진행할 수 있습니다.

디버깅이 고통스러운 이유

가변 상태는 디버깅을 고고학처럼 만듭니다. 버그 리포트가 "데이터가 10:14:03에 잘못 변경되었다"고 알려주지는 않습니다. 단지 잘못된 숫자, 예상치 못한 상태, 가끔만 실패하는 요청 같은 최종 결과만 보입니다.

상태는 시간에 따라 변하기 때문에 가장 중요한 질문은: **어떤 편집들의 연속이 여기로 이끌었는가?**입니다. 그 히스토리를 재구성할 수 없다면 동작은 예측 불가능해집니다:

  • 동일한 동작이 타이밍에 따라 다른 결과를 낳습니다.
  • 고친 것이 로컬에서는 동작하지만 프로덕션에서는 동작하지 않습니다.
  • 로깅을 추가하면 타이밍이 바뀌어 버그가 사라집니다.

이것이 히키가 상태를 복잡성 승수로 보는 이유입니다: 데이터가 공유되고 가변적일 때 가능한 상호작용의 수는 우리가 그것들을 정리할 수 있는 능력보다 빠르게 증가합니다.

컴퓨터 과학 전문 용어 없이 설명한 불변성

불변성이란 단순히 생성된 뒤 변경되지 않는 데이터를 의미합니다. 기존 정보를 제자리에서 편집하는 대신, 업데이트를 반영한 새 정보를 만듭니다.

영수증을 떠올려 보세요: 한 번 인쇄된 영수증에서 항목을 지우고 합계를 다시 쓰지 않습니다. 무언가 바뀌면 정정된 영수증을 발행합니다. 이전 영수증은 여전히 존재하고 새 영수증은 명확히 "최신 버전"입니다.

왜 불변성이 놀라움을 줄이나요

데이터를 조용히 수정할 수 없게 되면 누군가가 뒤에서 몰래 편집할까 걱정할 필요가 줄어듭니다. 그로 인해 일상적 추론이 훨씬 쉬워집니다:

  • 값을 가지고 있으면 그 값은 유지된다고 신뢰할 수 있습니다.
  • 버그 재현이 쉬워집니다(같은 입력은 같은 출력을 만듭니다).
  • 시스템의 다른 부분이 데이터를 공유할 때 아무도 실수로 다른 사람을 망칠 수 없습니다.

이것이 히키가 단순성에 대해 말하는 큰 부분입니다: 숨은 부작용이 적을수록 추적해야 할 정신적 분기(branch)가 줄어듭니다.

“새 버전 만들기” vs “제자리 편집”

새 버전 생성을 낭비로 느낄 수 있지만 대안과 비교해 보세요. 제자리 편집은 "누가, 언제, 무엇을 바꿨나?"를 묻도록 만듭니다. 불변 데이터에서는 변경이 명시적입니다: 새 버전이 존재하고 이전 것은 디버깅, 감사, 롤백을 위해 남아 있습니다.

클로저는 업데이트를 기존 값을 변이시키는 대신 새 값을 만드는 것으로 다루는 것을 자연스럽게 합니다.

솔직히 말해야 할 트레이드오프

불변성은 공짜가 아닙니다. 더 많은 객체를 할당할 수 있고 "그냥 그것만 업데이트하면 돼"에 익숙한 팀은 적응 기간이 필요합니다. 다행히 현대 구현은 내부적으로 구조를 공유해 메모리 비용을 줄이는 경우가 많고, 보상은 보통 설명하기 어려운 사고가 적은 더 차분한 시스템입니다.

데이터가 변하지 않으면 동시성이 쉬워진다

소스 코드를 소유하세요
소스 코드를 내보내 검토·리팩터링하고 경계에서 불변성을 강제하세요.
코드 내보내기

동시성은 단지 "많은 일들이 동시에 일어나는 것"입니다. 수천 요청을 처리하는 웹 앱, 영수증을 생성하면서 잔액을 업데이트하는 결제 시스템, 백그라운드 동기화를 하는 모바일 앱 등은 모두 동시성 상황입니다.

문제는 많은 작업이 같은 데이터를 건드릴 때 발생합니다.

공유된 변경 가능한 데이터가 레이스 컨디션을 만드는 이유

두 작업자가 같은 값을 읽고 수정할 수 있으면 최종 결과는 타이밍에 따라 달라집니다. 이것이 레이스 컨디션입니다: 시스템이 바쁠 때 나타나고 재현이 어렵습니다.

예시: 두 요청이 주문 합계를 업데이트하려 합니다.

  1. 요청 A가 total = 100을 읽음
  2. 요청 B가 total = 100을 읽음
  3. A가 20을 더해 120을 씀
  4. B가 10을 더해 110을 씀

아무것도 충돌하진 않았지만 업데이트가 하나 손실되었습니다. 부하가 걸리면 이런 타이밍 창이 더 흔해집니다.

전통적 해결책—락, 동기화 블록, 신중한 순서 지정—은 작동하지만 모든 사람이 조정해야 해서 비용이 듭니다. 조정은 처리량을 낮추고 코드베이스가 커질수록 취약해집니다.

불변성이 조정을 최소화하는 방법

불변 데이터에서는 값이 제자리에서 편집되지 않고, 대신 변경을 나타내는 새 값이 만들어집니다.

이 한 가지 변화가 많은 문제 범주를 제거합니다:

  • 읽는 쪽은 읽는 도중 값이 바뀔까 걱정할 필요가 없습니다.
  • 쓰는 쪽은 같은 메모리를 두고 싸우지 않고 새 버전을 만듭니다.
  • 시스템은 최신 버전을 공개하는 안전한 방법(종종 단순하고 잘 검증된 프리미티브)을 선택할 수 있습니다.

결과: 부하가 걸렸을 때 예측 가능한 동작

불변성이 동시성을 공짜로 만들지는 않습니다—어떤 버전이 현재인지 결정하는 규칙은 여전히 필요합니다. 그러나 데이터 자체가 움직이는 표적이 아니므로 동시 프로그램은 훨씬 더 예측 가능합니다. 트래픽이 급증하거나 백그라운드 작업이 쌓여도 타이밍 의존적 실패를 볼 가능성이 줄어듭니다.

실제로 '더 나은 기본값'이 의미하는 것

“더 나은 기본값”은 안전한 선택이 자동으로 발생하고, 추가 위험을 감수하려면 명시적으로 선택해야 한다는 뜻입니다.

작은 것처럼 보이지만 기본값은 월요일 아침 개발자가 무엇을 쓰는지, 금요일 오후 리뷰어가 무엇을 수락하는지, 새 팀원이 처음 코드베이스에서 배우는 것을 조용히 가르칩니다.

위험을 줄이는 기본값

“더 나은 기본값”은 모든 결정을 대신하는 것이 아닙니다. 흔한 경로를 덜 오류가 나기 쉬운 방식으로 만드는 것입니다.

예:

  • 경계에서의 불변 데이터: "그것을 변경"하는 대신 새 버전을 만듭니다. 이렇게 하면 실수로 다른 부분에 영향을 주기 어려워집니다.
  • 순수 함수(style) 보통 사용: 함수는 입력을 받아 출력을 반환하고, 숨은 전역 상태를 변경하거나 의존하지 않습니다. 이는 동작을 예측하고 테스트하기 쉽게 만듭니다.
  • 명시적 상태 변경: 뭔가가 반드시 변경되어야 한다면 명확하고 잘 정의된 메커니즘을 통해 일어나게 합니다(코드 어디에서나 막무가내로 수정하지 않음).

이들은 복잡성을 없애지 않지만 확산을 막습니다.

기본값이 팀과 코드 리뷰를 어떻게 바꾸는가

팀은 문서를 따르기보다 코드가 "원하"는 방식을 따릅니다.

공유 상태를 변이하는 것이 쉬우면 그것이 표준 지름길이 되고, 리뷰어들은 안전성에 대해 토론하게 됩니다: "여기서 이게 안전한가?" 반면 불변성과 순수 함수가 기본이면 위험한 동작이 더 눈에 띄어 리뷰어는 논리와 정확성에 집중할 수 있습니다.

즉, 더 나은 기본값은 건강한 기준선을 만듭니다: 대부분의 변경이 일관되게 보이고, 특이한 패턴은 질문하기 쉬울 만큼 명확해집니다.

유지보수와 온보딩

장기적인 유지보수는 기존 코드를 안전하게 읽고 변경하는 것이 대부분입니다.

더 나은 기본값은 새 팀원이 적은 숨은 규칙(예: "이 함수가 저 글로벌 맵을 은밀히 수정하니 조심하라")으로 인해 고생하지 않게 합니다. 시스템은 추론하기 쉬워져 향후 기능, 수정, 리팩터링의 비용을 낮춥니다.

사실(facts)과 뷰(views)를 분리하기: 시간, 기록, 추적성

히키의 강연에서 유용한 사고 전환은 사실(무슨 일이 일어났는가)과 뷰(우리가 현재 믿고 있는 것)를 분리하는 것입니다. 대부분의 시스템은 최신 값만 저장해 어제는 덮어버리므로 시간이 사라집니다.

사실은 추가만, 뷰는 파생

사실은 불변의 기록입니다: "주문 #4821이 10:14에 접수되었다", "결제 성공", "주소가 변경되었다" 등. 이런 것들은 편집되지 않고 현실이 바뀔 때마다 사실을 추가합니다.

뷰는 앱이 지금 필요로 하는 것: "현재 배송 주소는?" 또는 "고객의 잔액은?" 같은 것입니다. 뷰는 사실들로부터 다시 계산되거나 캐시되거나 인덱싱되거나 물리화될 수 있습니다.

기록을 보관하면 얻는 것들

사실을 유지하면 다음을 얻습니다:

  • 감사 가능성: 현재 값이 왜 그런지 설명할 수 있습니다.
  • 디버깅: 연속을 재생해 언제 엇나갔는지 찾을 수 있습니다.
  • 추적성: "누가, 언제, 무엇을 바꿨고 이전 값은 무엇이었나?"가 수사학이 아니라 데이터 질문이 됩니다.

접근 가능한 예: 덮어쓰기 vs 추가

레코드를 덮어쓰는 것은 스프레드시트 셀을 업데이트하는 것과 같습니다: 최신 값만 보입니다.

추가만 하는 로그는 수첩의 거래 내역과 같습니다: 각 항목이 사실이고, "현재 잔액"은 항목들로 계산된 뷰입니다.

모든 시스템이 전체 이벤트 소싱을 필요로 하진 않음

전체 이벤트 소싱 아키텍처를 채택할 필요는 없습니다. 많은 팀이 작은 규모로 시작합니다: 중요한 변경에 대해 append-only 감사 테이블을 유지하거나, 고위험 워크플로우의 변경 이벤트만 기록하거나, 스냅샷과 짧은 히스토리를 보관하는 식입니다. 핵심은 습관입니다: 사실을 오래 보관하고, 현재 상태는 편리한 투영(projection)으로 취급하세요.

데이터 우선: 정보를 지속 가능하고 유연하게 만들기

단순한 접근을 프로토타입으로
다음 리팩터 아이디어를 채팅으로 실행 가능한 프로토타입으로 만드세요.
프로토타입 만들기

히키의 실용적 아이디어 중 하나는 데이터 우선입니다: 시스템 정보를 평범한 값(사실)으로 다루고, 행위는 그 값들에 대해 실행하는 것으로 생각하세요.

데이터는 지속적입니다. 명확하고 자족적인 정보를 저장하면 나중에 재해석하고, 서비스 간에 이동시키고, 재인덱싱하고, 감사하고, 새로운 기능에 공급할 수 있습니다. 반면 행위는 덜 지속적입니다—코드는 바뀌고 가정이 바뀌며 의존성이 바뀝니다.

값(values) 대 행동(actions) (전문 용어 없이)

  • 데이터(값): "무엇이 사실인가?" 고객 이메일, 주문 합계, 타임스탬프, 상태 등.
  • 행동(동작): "무엇을 할 것인가?" 검증, 할인 계산, 알림 전송, 상태 해석 등.

이 둘을 섞으면 시스템이 끈적거려집니다: 데이터를 재사용하려면 그것을 만든 행동까지 끌고 가야 합니다.

결합도 감소, 재사용성 증가

사실과 행동을 분리하면 컴포넌트들이 데이터 형태에는 동의하지만 공유 코드 경로에 동의할 필요는 없습니다.

리포팅 작업, 지원 툴, 청구 서비스는 동일한 주문 데이터를 소비하면서 각자 로직을 적용할 수 있습니다. 저장된 표현에 로직을 박아넣으면 모든 소비자가 그 내장 로직에 의존하게 되고 변경이 위험해집니다.

예: 깨끗한 데이터 저장 vs 저장소 안의 미니 프로그램

깨끗한 데이터(진화하기 쉬움):

{
  "type": "discount",
  "code": "WELCOME10",
  "percent": 10,
  "valid_until": "2026-01-31"
}

저장소 안의 미니 프로그램(진화하기 어려움):

{
  "type": "discount",
  "rule": "if (customer.orders == 0) return total * 0.9; else return total;"
}

두 번째 버전은 유연해 보이지만 데이터 레이어에 복잡성을 밀어넣습니다: 안전한 평가기, 버전 관리 규칙, 보안 경계, 디버깅 도구, 규칙 언어가 바뀔 때 마이그레이션 계획 등이 필요합니다.

왜 이렇게 하면 시스템을 진화시키기 쉬운가

저장된 정보가 단순하고 명시적이면 동작을 바꿀 때 역사(history)를 다시 쓰지 않아도 됩니다. 오래된 레코드는 계속 읽을 수 있고, 새 서비스를 추가할 때 레거시 실행 규칙을 "이해"할 필요가 없습니다. 새 UI 뷰, 가격 전략, 분석 등을 도입할 때 새로운 코드를 쓰면 되고 데이터의 의미를 변조할 필요가 없습니다.

복잡한 시스템에 아이디어 적용하기(재작성 없이)

대부분의 엔터프라이즈 시스템은 한 모듈이 나빠서 실패하지 않습니다. 모든 것이 서로 연결되어 있기 때문에 실패합니다.

주의할 실패 모드들

긴밀한 결합은 "작은" 변경이 몇 주간의 재테스트를 촉발하는 방식으로 나타납니다. 한 서비스에 필드를 추가하면 세 downstream 소비자가 깨집니다. 공유 DB 스키마는 조정 병목이 됩니다. 단일 가변 캐시나 싱글턴 설정 객체가 코드베이스의 절반의 의존성이 되는 경우가 생깁니다.

공유된 동일한 변경 가능한 것을 여러 부분이 다루면 그 폭발 반경은 자연스럽게 커집니다. 팀은 더 많은 프로세스, 규칙, 핸드오프를 추가하여 배달을 더욱 느리게 만드는 경향이 있습니다.

더 단순한 경계로 폭발 반경 줄이기

언어를 바꾸거나 모든 것을 재작성하지 않고도 히키의 아이디어를 적용할 수 있습니다:

  • 경계에서 불변 데이터를 선호하세요. 메시지, 이벤트, API 입력을 편집하지 않는 사실로 취급하세요. 변경이 필요하면 새 버전을 만드세요.
  • 상태를 가장자리로 이동시키세요. 핵심 로직은 입력 데이터 → 출력 데이터 같은 순수 변환으로 유지하세요. 데이터베이스, 캐시, UI가 "현재 상태"를 다루게 하세요.
  • 가변 구조 공유를 멈추세요. 두 모듈이 같은 객체를 쓰기만 한다면 그들은 결합되어 있습니다. 변이하려는 참조가 아니라 값을 전달하세요.

데이터가 발밑에서 바뀌지 않으면 "어떻게 이 상태가 되었지?"라는 디버깅 대신 코드가 무엇을 하는지 더 많이 추론할 수 있습니다.

팀 간의 ‘더 나은 기본값’

기본값은 일관성이 스며드는 곳입니다: 각 팀이 각자의 타임스탬프 형식, 오류 모양, 재시도 정책, 동시성 접근을 발명합니다.

더 나은 기본값은 버전 있는 이벤트 스키마, 표준 불변 DTO, 쓰기의 명확한 소유권, 직렬화/검증/추적을 위한 소수의 권장 라이브러리처럼 보입니다. 결과는 놀라운 통합과 일회성 수정이 줄어드는 것입니다.

기존 코드베이스에서 점진적 도입

변경이 이미 일어나는 곳에서 시작하세요:

  1. 기존 모듈을 감싸는 API를 만들어 불변 데이터 입력/출력을 수용하세요.
  2. 변동이 잦은 워크플로 하나를 이벤트 스타일의 "추가 전용" 레코드로 변환하세요.
  3. 공유 가변 캐시를 내구성 있는 사실로부터 재계산 가능한 뷰로 교체하세요.

이 접근법은 시스템을 계속 가동하면서 신뢰성과 팀 조정을 개선하고 범위를 작게 유지해 마무리할 수 있게 합니다.

플랫폼과 도구가 ‘더 나은 기본값’을 강화하는 방법

리뷰 빌드를 공유하세요
커스텀 도메인을 사용해 빌드를 팀원과 공유하고 빠르게 리뷰·반복하세요.
도메인 추가

일상 워크플로가 빠르고 저위험 반복을 지원하면 이러한 아이디어를 적용하기 쉽습니다. 예를 들어, Koder.ai(웹, 백엔드, 모바일 앱용 채팅 기반 바이브 코딩 플랫폼)에서 두 가지 기능은 “더 나은 기본값” 마인드셋과 직접 연결됩니다:

  • **계획 모드(Planning mode)**는 구현 전에 경계와 데이터 모양을 명시하게 장려합니다—대개 단순한 데이터 흐름과 우연한 결합의 차이를 만드는 요소입니다.
  • 스냅샷과 롤백은 변경을 더 안전하게 배포하게 합니다. "쉬운" 지름길이 복잡성 폭발로 이어질 때 빠르게 되돌릴 수 있습니다.

스택이 React + Go + PostgreSQL(또는 모바일은 Flutter)이더라도 핵심 요점은 같습니다: 매일 사용하는 도구가 조용히 기본 작업 방식을 가르칩니다. 추적 가능성, 롤백, 명시적 계획을 일상화하는 도구를 선택하면 순간의 "그냥 패치하자" 압박을 줄일 수 있습니다.

트레이드오프, 한계, 그리고 이데올로기 피하기

단순성과 불변성은 강력한 기본값이지 도덕 규범이 아닙니다. 시스템이 커질 때 예기치 않은 변경을 줄여주지만 실제 프로젝트에는 예산, 마감, 제약이 있고 때로는 변이성이 적절한 도구입니다.

변이성이 허용되는 경우

변이성은 다음과 같은 경우 실용적일 수 있습니다:

  • 성능 핫스팟(치밀한 루프, 고처리량 파싱, 그래픽, 수치 작업)
  • 범위가 통제되는 곳: 함수 내부 지역 변수, 좁은 인터페이스 뒤의 비공개 캐시, 단일 스레드 컴포넌트

핵심은 격리입니다. "가변인 것"이 밖으로 유출되지 않으면 코드베이스 전반에 복잡성이 퍼지지 않습니다.

소유권과 인터페이스로 복잡성 경계 짓기

대부분 함수형 스타일에서도 명확한 소유권이 필요합니다:

  • 한 모듈이 상태를 소유하고 작은 API를 노출합니다.
  • 데이터는 경계를 넘을 때 평범한 값으로 전달됩니다(비밀 행동을 가진 살아있는 객체가 아님).
  • 부작용은 가장자리로 밀어냅니다(입출력, DB, 시간).

이것이 클로저의 데이터와 명시적 경계에 대한 편향이 도움이 되는 점이지만, 이 규율은 언어가 아니라 아키텍처적입니다.

클로저가 해결하지 못할 것

어떤 언어도 잘못된 요구사항, 불명확한 도메인 모델, 또는 무엇이 "완료"인지 합의하지 못하는 팀을 고치지 못합니다. 불변성이 혼란스러운 워크플로를 이해하기 쉽게 만들지 못하고, “함수형” 코드도 잘못된 비즈니스 규칙을 더 깔끔하게 담을 뿐입니다.

교리화 피하기: 위험을 줄이는 가장 작은 변경부터 시작

프로덕션에 이미 올라와 있다면 이 아이디어들을 전부 혹은 전무로 취급하지 마세요. 위험을 낮추는 가장 작은 움직임을 찾으세요:

  • 모듈 경계에서 공유 가변 구조를 불변 데이터로 교체하세요.
  • 감사 가능성이 필요한 곳에 이벤트 로그나 추가 전용 기록을 추가하세요.
  • 레거시 상태를 좁은 인터페이스 뒤로 래핑해 퍼지는 것을 막으세요.

목표는 순수성이 아니라 변경당 놀라는 일의 수를 줄이는 것입니다.

더 단순한 소프트웨어로 나아가기 위한 실용 체크리스트

이것은 언어, 프레임워크, 팀 구조를 바꾸지 않고도 적용할 수 있는 스프린트 단위 체크리스트입니다.

다음 스프린트에 시도할 3–5가지

  1. 경계의 데이터 모양을 기본적으로 불변으로 만들기. 요청/응답 객체, 이벤트, 메시지를 한 번 생성하고 수정하지 않는 값으로 다루세요. 변경이 필요하면 새 버전을 만드세요.

  2. 워크플로 중간에는 순수 함수를 선호하세요. 하나의 워크플로(예: 가격 계산, 권한, 체크아웃)를 골라 핵심을 읽기-입력 → 출력 데이터로 바꾸세요—숨은 읽기/쓰기 금지.

  3. 상태를 더 적고 명확한 장소로 이동시키세요. 개념당 단일 출처(source of truth)를 정하세요(고객 상태, 기능 플래그, 재고 등). 여러 모듈이 자체 복사본을 유지한다면 명시적 동기화 전략을 세우세요.

  4. 핵심 사실에 대한 추가 전용 로그를 추가하세요. 한 도메인 영역에 대해 “무슨 일이 일어났나”를 내구성 있는 이벤트로 기록하세요(현재 상태를 동시에 저장해도 됩니다).

  5. API에서 더 안전한 기본값 정의. 기본값은 놀라운 동작을 최소화해야 합니다: 명시적 시간대, 명시적 널 처리, 명시적 재시도, 명시적 정렬 보장 등.

설계 리뷰 질문(이 문구를 그대로 쓰세요)

  • 여기서 가변 부분은 무엇이고 누가 그것을 변경할 수 있는가?
  • 이것을 필드 덮어쓰기가 아니라 “사실 + 파생 뷰”로 모델링할 수 있는가?
  • 동시 요청 두 개가 동시에 실행되면 무엇이 깨지는가?
  • 우리가 의존하는 기본값(시간, 순서, 재시도, 캐싱)은 무엇이며 문서화되어 있는가?
  • 이 문제를 세 달 뒤에 디버그하려면 무엇이 필요할까?

히키 강연에서 다시 살펴볼 주제

단순성 vs 쉬움, 상태 관리, 값 지향 설계, 불변성, 그리고 시간이 있으면 디버깅과 운영이 어떻게 쉬워지는지에 관한 자료를 찾아보세요.

단순성은 덧붙이는 기능이 아니라 작은, 반복 가능한 선택에서 연습하는 전략입니다.

자주 묻는 질문

실제 프로젝트에서 왜 복잡성이 결국 ‘이기는’가요?

복잡성은 작은, 지역적으로 타당한 결정들(추가 플래그, 캐시, 예외 처리, 공유 헬퍼 등)이 쌓이면서 발생합니다.

좋은 신호는 “작은 변경”이 여러 모듈이나 서비스에 걸친 동시 수정이 필요하거나, 리뷰어가 안전성을 판단하기 위해 부족한 문서 대신 조직 내 구전 지식에 의존해야 할 때입니다.

소프트웨어에서 “단순(simple)”과 “쉬움(easy)”의 차이는 무엇인가요?

지름길은 오늘의 마찰을 줄이는 데 최적화됩니다(출시 속도). 그러나 비용은 미래로 밀려납니다: 디버깅 시간, 조정 오버헤드, 변경 리스크 등.

설계/PR 리뷰에서 유용한 습관은: “이 변경이 어떤 새로운 움직이는 부분이나 특수 케이스를 도입하고, 누가 그것들을 유지할 것인가?”라고 묻는 것입니다.

프로그래밍 언어나 프레임워크의 기본값이 어떻게 우연한(혹은 의도치 않은) 복잡성을 만들죠?

기본값은 압박 상황에서 엔지니어들이 무엇을 하게 될지를 형성합니다. 변경이 기본값이라면 공유 상태가 널리 퍼집니다. 메모리 중심의 기본값이면 추적 가능성이 사라집니다.

안전한 경로가 가장 쉬운 경로가 되게 하세요: 경계에서의 불변 데이터, 명시적 시간대/널 처리/재시도, 명확한 상태 소유권 등으로 기본값을 개선할 수 있습니다.

왜 상태(state)를 ‘복잡성의 승수’라고 말하나요?

상태는 시간이 흐르며 변할 수 있는 모든 것입니다. 문제는 변화가 불일치의 기회를 만든다는 점입니다: 두 컴포넌트가 서로 다른 “현재” 값을 가질 수 있습니다.

버그는 ‘어느 버전의 데이터를 기반으로 동작했나?’라는 질문이 되며, 이 때문에 타이밍 의존적 동작(로컬에서는 동작하지만 프로덕션에서는 불안정) 같은 현상이 나타납니다.

불변성(immutability)은 실무에서 무슨 뜻인가요?

불변성은 값을 생성한 뒤 수정하지 않는다는 뜻입니다. 값을 제자리에서 편집하지 않고, 업데이트를 반영한 새 값을 만듭니다.

실용적으로 유용한 점:

  • 값을 가진 쪽은 그 값이 사용 중에 바뀌지 않는다고 신뢰할 수 있습니다.
  • 버그 재현이 쉬워집니다(입력이 안정적이므로).
  • 스레드/모듈 간 데이터 공유가 안전해집니다.
변이(mutation)는 언제 허용되거나 오히려 바람직한가요?

항상 그런 것은 아닙니다. 변이성(mutatability)은 제한될 때 매우 유용합니다:

  • 함수 내부의 지역 변수
  • 성능 핫스팟(치밀한 루프, 파싱, 수치 연산)
  • 좁은 인터페이스 뒤의 비공개 캐시

핵심 규칙은: 가변 구조가 경계를 넘어 누수되지 않도록 하세요. 여러 곳이 읽고 쓸 수 있게 되면 복잡성이 퍼집니다.

불변성이 부하가 많은 상황에서 동시성 문제에 어떻게 도움이 되나요?

레이스 컨디션은 보통 공유된 가변 데이터를 여러 작업자가 읽고 쓴 결과로 발생합니다.

불변성은 작성자가 새 버전을 만들게 함으로써 조정 표면적을 줄입니다. 여전히 어떤 버전이 ‘현재’인지 발행하는 규칙은 필요하지만, 데이터 자체가 이동 표적이 되는 문제는 사라집니다.

‘사실(facts)’과 ‘뷰(views)’를 분리한다는 건 무슨 의미이고, 점진적으로 어떻게 적용하나요?

사실(facts)은 일단 추가(append-only)되는 불변 기록이고, 뷰(views)는 그 사실들로부터 파생되는 현재 상태입니다.

점진적으로 적용하는 방법:

  • 중요한 변경에 대해 감사(audit) 테이블을 추가하세요.
  • 위험이 높은 워크플로우에 변경 이벤트를 기록하세요.
  • 스냅샷과 짧은 히스토리 창을 보관해 재생/디버깅에 활용하세요.
‘데이터 우선(data-first)’ 설계란 무엇이며, 어떻게 결합도를 줄이나요?

데이터를 평범하고 명확한 값으로 저장하고, 그 데이터에 대해 동작(behavior)을 수행하세요. 저장소에 실행 가능한 규칙을 넣지 마세요.

이 방식의 장점:

  • 코드가 바뀌어도 오래된 기록을 읽을 수 있습니다.
  • 새로운 소비자는 동일한 데이터 구조를 재사용할 수 있습니다.
  • 로직을 바꾸려면 기록을 재작성할 필요 없이 새 코드를 도입하면 됩니다.
다음 스프린트에 복잡성을 줄이기 위해 시도할 첫 3가지 구체적 변경은 무엇인가요?

자주 변경되는 하나의 워크플로를 골라 다음 세 가지를 적용하세요:

  1. 경계 데이터 불변화: API 입력/출력, 메시지, 이벤트를 ‘한번 생성하고 수정하지 않음’으로 처리하세요.
  2. 핵심을 순수 변환(pure transformations)으로 리팩터링하세요: 입력 데이터 → 출력 데이터. I/O와 부작용은 경계로 밀어내세요.
  3. 공유 가변 구조 줄이기: 상태당 단일 소유자, 좁은 인터페이스로 노출하세요.

성공 측정: 불안정한 버그 감소, 변경당 폭발 반경 축소, 릴리스에서의 ‘신중한 조정’ 감소.

목차
왜 현실 프로젝트에서 복잡성이 이기는가리치 히키와 클로저가 해결하려 한 문제들단순성: ‘쉬움’이 아니라 움직이는 부품이 적음상태(state): 보이지 않는 복잡성 증폭기컴퓨터 과학 전문 용어 없이 설명한 불변성데이터가 변하지 않으면 동시성이 쉬워진다실제로 '더 나은 기본값'이 의미하는 것사실(facts)과 뷰(views)를 분리하기: 시간, 기록, 추적성데이터 우선: 정보를 지속 가능하고 유연하게 만들기복잡한 시스템에 아이디어 적용하기(재작성 없이)플랫폼과 도구가 ‘더 나은 기본값’을 강화하는 방법트레이드오프, 한계, 그리고 이데올로기 피하기더 단순한 소프트웨어로 나아가기 위한 실용 체크리스트자주 묻는 질문
공유
Koder.ai
Koder로 나만의 앱을 만들어 보세요 지금!

Koder의 힘을 이해하는 가장 좋은 방법은 직접 체험하는 것입니다.

무료로 시작데모 예약