git이 주는 착각
개발자에게 git은 안전망이다. 뭘 잘못해도 되돌릴 수 있다. 커밋을 되감고, 브랜치를 지우고, 아예 처음부터 다시 시작할 수도 있다. 이 경험이 반복되다 보면 은연중에 이런 감각이 생긴다. "실수해도 괜찮아, 돌리면 되니까."
코드 레벨에서는 대체로 맞는 말이다. 변수명을 잘못 지었으면 바꾸면 된다. 함수를 잘못 쪼갰으면 합치면 된다. 컴포넌트 구조가 마음에 안 들면 리팩토링하면 된다. 비용이 들긴 하지만 불가능하지는 않다.
문제는, 모든 결정이 그런 게 아니라는 거다.
문의 종류가 다르다
제프 베조스가 아마존의 의사결정을 두 가지로 나눈 게 있다. 되돌릴 수 있는 결정 (two-way door) 과 되돌릴 수 없는 결정 (one-way door) .
two-way door는 문을 열고 지나갔다가, 별로면 다시 돌아오면 된다. 변수명, UI 색상, 로그 메시지 같은 것들. 이런 결정에 오래 고민하는 건 시간 낭비다. 빨리 결정하고, 결과를 보고, 필요하면 바꾸면 된다.
one-way door는 지나가면 돌아올 수 없는 문이다. 돌아오려면 문을 부수고 다시 만들어야 한다. 비용이 엄청나거나, 아예 불가능하거나.
소프트웨어에도 one-way door는 곳곳에 있다.
데이터베이스 스키마. 한 번 프로덕션에 올라가고 데이터가 쌓이기 시작하면, 스키마를 바꾸는 건 살아있는 환자의 뼈를 교체하는 수술이다. 할 수는 있지만, 안 해도 되는 상황을 만드는 게 훨씬 낫다.
공개 API. 외부에 노출한 API는 다른 사람의 코드가 의존하기 시작한다. 내 코드는 내가 고칠 수 있지만, 남의 코드를 고칠 수는 없다. 한 번 공개한 인터페이스는 사실상 영구적이다.
프레임워크 선택. React로 짠 프로젝트를 Vue로 바꾸는 건 이론적으로는 가능하지만, 현실적으로는 새로 짜는 것과 같다. 프레임워크는 코드 전체에 스며들기 때문에, 교체 비용이 프로젝트 규모에 비례해서 커진다.
팀 구조. 콘웨이 법칙이 말하듯, 시스템 구조는 조직 구조를 따라간다. 팀을 마이크로서비스에 맞춰 쪼개 놓으면, 다시 모놀리스로 돌아가고 싶어도 조직이 이미 그 구조 위에서 돌아가고 있다.
이런 결정들은 시간이 갈수록 되돌리는 비용이 기하급수적으로 늘어난다. 초기에는 가볍지만, 일단 그 위에 다른 것들이 쌓이기 시작하면 빼내는 게 불가능에 가까워진다.
그래서 프로그래밍의 근본 주제가 "변경"이다
여기서 한 발 뒤로 물러나서 보면, 흥미로운 게 보인다. 소프트웨어 공학에서 "좋은 설계"라고 부르는 원칙들을 쭉 나열해보자. 캡슐화, 인터페이스, 의존성 역전, 관심사 분리, 느슨한 결합. 이름은 다 다르지만, 결국 하나의 문장으로 귀결된다.
"나중에 바꿀 수 있게 만들어라."
캡슐화는 내부를 숨겨서, 내부가 바뀌어도 외부에 영향이 없게 한다. 인터페이스는 구현을 추상화해서, 구현을 교체할 수 있게 한다. 의존성 역전은 구체적인 것에 의존하지 말고 추상적인 것에 의존해서, 구체적인 것이 바뀌어도 괜찮게 한다. 관심사 분리는 서로 다른 이유로 바뀌는 것들을 떨어뜨려서, 한쪽의 변경이 다른 쪽으로 전파되지 않게 한다.
전부 "변경"에 대한 이야기다. 변경이 쉬운 코드가 좋은 코드이고, 변경이 어려운 코드가 나쁜 코드다. 왜 이게 그토록 중요한 기준이 되었을까?
되돌릴 수 없는 결정의 고통을 알기 때문이다.
한 번 내린 결정을 바꿀 수 없을 때의 비용이 얼마나 큰지, 소프트웨어 역사가 반복적으로 증명해왔다. 그래서 공학은 반대 방향으로 움직인다. 가능한 한 많은 결정을 "되돌릴 수 있는 결정"으로 만들려고 한다. 구현을 인터페이스 뒤에 숨기면, 프레임워크 교체라는 one-way door가 "어댑터 교체"라는 two-way door로 바뀔 수 있다. 관심사를 분리하면, 전체를 다시 짜야 하는 상황이 한 모듈만 교체하는 상황으로 바뀔 수 있다.
좋은 아키텍처는 one-way door를 two-way door로 바꾸는 기술이다.
되돌릴 수 없는 것들 앞에서
하지만 아무리 잘 설계해도 one-way door를 완전히 없앨 수는 없다. 어떤 결정은 본질적으로 비가역적이다.
그리고 이건 소프트웨어만의 문제가 아니다. 시간은 한 방향으로만 흐른다. 어제 한 말은 취소할 수 없고, 떠나보낸 사람은 돌아오지 않을 수 있고, 지나간 시간은 되돌릴 수 없다. 비가역성은 우리가 사는 세계의 근본 속성이다.
물리학에서 엔트로피가 증가하는 방향이 시간의 방향이듯, 소프트웨어에서도 결정이 쌓이는 방향이 프로젝트의 방향이다. 각각의 결정은 가능성의 공간을 좁힌다. React를 선택하면 Vue의 가능성은 닫힌다. PostgreSQL을 선택하면 MongoDB의 가능성은 닫힌다. 이건 좋고 나쁨의 문제가 아니라 선택의 본질이다. 무언가를 고른다는 건 다른 무언가를 포기한다는 뜻이니까.
속도를 조절하는 감각
결국 중요한 건 결정의 성격을 구분하는 감각이다.
되돌릴 수 있는 결정 앞에서 너무 오래 고민하면 시간을 낭비한다. 변수명을 30분 동안 고민하는 건 비효율이다. 빨리 정하고, 나중에 더 좋은 이름이 떠오르면 바꾸면 된다.
되돌릴 수 없는 결정 앞에서 너무 빨리 움직이면 나중에 값을 치른다. 데이터 모델을 대충 잡고 시작하면, 6개월 뒤에 마이그레이션 지옥을 만날 수 있다.
이 감각은 경험에서 온다. 어떤 결정이 one-way door인지를 미리 알려면, 그 문을 열고 지나가본 적이 있어야 한다. 아니면 지나가본 사람의 이야기를 들어야 한다. 시니어 개발자가 가진 가치는 코드를 빨리 짜는 능력이 아니라, 어떤 결정 앞에서 속도를 줄여야 하는지를 아는 감각이다.
git은 코드를 되돌려주지만, 시간은 되돌려주지 않는다. 되돌릴 수 없는 결정을 알아보는 눈이, 좋은 설계의 시작점이다.