좋은 소프트웨어가 변화에도 형태를 유지하는 이유를 ‘표면장력’에 비유해, 무결성·제약·타입 시스템·불변식·경계가 어떻게 불가능한 상태를 제거하고 시스템을 일관되게 만드는지 살펴본다.
물에 손가락을 눌러 넣으면, 물이 다시 밀어냅니다. 보이지 않는 그 저항, 즉 표면장력은 방해를 받아도 액체가 하나로 유지되게 합니다.
좋은 소프트웨어에도 비슷한 것이 있습니다. 어떤 시스템은 바꿔도 전체가 잘 붙어 있지만, 어떤 시스템은 아주 살짝만 건드려도 새어 나옵니다. 그 차이는 무결성(integrity)에 있습니다. 즉, 시스템이 부수효과를 관리하면서도 자기 형태를 잃지 않는 방식 말입니다.
저는 이상하리만큼 고요하게 느껴지는 코드베이스를 본 적이 있습니다. 가능한 모든 상태가 실제 의미를 갖고, 자의적인 것들이 비집고 들어갈 틈이 없던 곳이죠. 반대로, 말이 안 되는 상태가 존재하도록 허용하는 코드베이스도 있었습니다. 거기서부터는 페인트 아래의 균열처럼 엔트로피가 조용히 퍼져 나갑니다.
타입 시스템, 불변식(invariant), 경계(boundary)는 의미를 명시적으로 만들기 위해 존재합니다. 그것들은 어디서 시작하고 어디서 끝나는지—무엇이 허용되고 무엇이 허용되지 않는지—를 정의합니다. 그 구조가 없으면 로직은 물러집니다. 가정이 번지고, 결국 시스템은 자기 모호함의 무게 아래에서 접혀 버립니다.
시스템이 온전하게 남는 것은 구조가 일관성을 강제할 때입니다. 분명한 경계, 정직한 인터페이스, 일관된 언어. 각각이 고유한 중력을 더하고, 함께 모여 ‘붙어 있는 세계’를 만듭니다. 안정성은 선언되는 것이 아니라, 작고 일관된 힘들이 합쳐진 결과로 나타납니다.
제약 기반 설계(constraint-driven design)는 그 중력을 눈에 보이게 만듭니다. 소프트웨어에서 이런 법칙들은 이름을 갖습니다: 순수성(purity), 불변성(immutability), 멱등성(idempotence), 투명성(transparency), 합성 가능성(composability). 이것들은 시스템을 궤도에 올려놓는 물리 법칙입니다.
순수 함수는 같은 입력에 대해 언제나 같은 출력을 돌려주며, 숨은 부수효과가 없습니다. 불변 데이터는 생성 이후 바뀔 수 없고, 오직 변환될 뿐입니다. 멱등 연산은 몇 번 적용하든 같은 결과를 만듭니다. 이런 것들은 학술적 연습이 아니라—불가능한 일이 일어나지 않게 막는 물리 법칙입니다.
하지만 부주의한 변경 하나가 단계를 건너뛰면, 세계는 찢어집니다.
사용자 데이터를 가져오는 UI를 생각해 봅시다. 장력이 없다면, 이런 식으로 새기 쉽습니다:
struct UserProfile {
loading: bool,
error: Option<String>,
data: Option<User>,
}
loading이 false이고, error는 Some이며, data도 Some일 때는 무엇을 의미할까요? 이 타입은 말이 안 되는 상태를 허용합니다. 그래서 곳곳에 방어 코드를 쓰게 됩니다. 모든 렌더링은 어떤 조합이 진짜인지 추측해야 하죠.
이제 형태를 부여해 봅시다:
enum UserProfile {
Loading,
Failed(String),
Loaded(User),
}
불가능한 상태가 사라집니다. 로딩 중이면서 실패일 수는 없습니다. 패턴 매칭은 유효한 모든 경우를—그리고 그 경우들만—처리하도록 강제합니다. 타입 시스템이 막(membrane)이 되어 형태를 유지시키는 겁니다.
형태가 잘 잡힌 시스템에서는, 넌센스가 애초에 존재할 수 없습니다. 프로그램의 우주에 그런 것이 들어갈 자리가 없기 때문입니다. 불가능한 것에 대비해 방어하지 않습니다. 불가능한 것에 문법 자체가 없는 세계를 설계합니다.
그 법칙이 분명할수록 표면장력은 저절로 생깁니다. 리팩터링이 바깥으로 파문을 내지 않을 때, 변화가 부러지지 않고 휘어질 때, 경계가 의미를 보존할 만큼만 적당히 밀어낼 때—그때 우리는 장력을 느낍니다.
좋은 패턴과 추상화는 막처럼 행동합니다. 움직임을 억누르는 것이 아니라, 움직임을 안내합니다. 그것들은 물 표면이 잔물결을 붙잡듯 부수효과를 가둡니다—흘림 없는 움직임, 붕괴 없는 에너지. 파싱, 타이핑, 합성: 이것들이 바로, 방해를 받아도 시스템이 온전함을 유지하게 하는 운동 법칙입니다.
“일단 나중에 문제가 생기면 그때 처리하자”라는 오래된 공학적 본능이 있습니다. 하지만 일관적인 시스템에서는, 애초에 그런 일은 일어날 수 없습니다. 오직 유효한 상태들 사이로만 이동할 수 있고, 그 제약이야말로 움직임을 가능하게 만드는 조건입니다.
하지만 장력에도 한계는 있습니다. 너무 강하면 물은 얼음이 됩니다—결함 없고, 움직이지 않으며, 생기가 없습니다. 소프트웨어도 같은 방식으로 얼어붙을 수 있습니다. 너무 경직되어 흐르는 법을 잊어버리는 거죠. 균형은 질서와 변화 사이, 붙잡는 것과 놓아주는 것 사이 어딘가에 있습니다.
최고의 시스템은 그곳에 삽니다. 구조가 자유와 만나는 섬세한 균형 위에. 그리고 아마도 그 지점에서 해커와 화가의 세계는 조용히 만날지도 모릅니다. 각자가 자신의 매체를 다듬어, 형태와 움직임이 하나가 될 때까지.
그 정밀한 균형이야말로 코드의 진정한 예술입니다.
추가 읽을거리:
Rich Hickey의 Simple Made Easy — 단순함이 편의가 아니라 ‘분리’에서 온다는 점을 설명하는 고전적인 강연.