섣부른 추상화가 만악의 근원이다 Premature abstraction is the roots of all evil
씹마초의 시대가 가고 씹게이의 시대가 오다
오래 전, 컴퓨팅 자원이 극도로 부족하여 성능 최적화 능력이 곧 실력이던 씹마초 진짜 프로그래머 전성기가 있었다. 그 때 크누스옹이 썼던 논문에 등장하는 문구이다
"섣부른 최적화가 만악의 근원이다"
Premature optimization is the root of all evil [1]
성능 최적화한다면서 온갖 똥꼬쇼로 코드 베이스에 테러하는 개씹마초 개발자들이 판을 치던 시절, 크누스옹이 한마디 한 것이다.
23년 현재 씹마초의 시대는 가고 씹게이의 시대가 도래했다. 인텔 무어의 법칙이니 엔당 황의 법칙이니 하면서 하드웨어 가격이 개같이 싸졌다. 오늘날의 프로그래머들은 과거만큼 최적화에 신경쓰지 않는다. 그런 걸 신경이나 썼다면 파이썬 같은 환경 오염 언어[2]를 쓰겠나? 감히 GC나 스마트 포인터 따위로 신성한 메모리에 불질을 하겠는가? 성능이 부족할 땐 Cache보다는 Cash를 써서 하드웨어를 더 사는 시대가 와 버렸다.
이제 섣부른 최적화를 하는 사람은 대학에서 C만 쓰다가 입사해서 어리버리 까는 쌩신입이나, 여전히 성능딸이 일상다반사인 분야에서 온 사람들 말고는 거의 없다. 개발 업계 대부분의 문화 자체가 성능보다는 유지보수성, 변경가능성, 개발 생산성 등에 초점을 맞추게 되었다. 그렇지 않다면 아직도 웹을 C로 짜고 있었으리라.
그렇게 "섣부른 최적화"의 위협은 최소한 업계의 대부분에서 역사의 뒤안길로 사라졌다.
생산성의 시대, 그리고 추상화Abstraction 혹은 제약Constraints
요즘 프로그래머들은 빠른 요구 사항 수용, 유연성, 유지보수성 등 - 궁극적으로는 생산성을 위해 아름다운 구조와 가독성에 집착하는 경우가 많다. 이것 자체는 문제가 되지 않는다. 점점 더 많은 비즈니스가 소프트웨어로 재편되고 있는[3] 21세기 현재, 이러한 패러다임 변화[4]는 사실 필연적이다.
하지만 생산성을 달성하려는 수단이 잘못되었다. 보통 좋은 설계나 구조를 표현하는 추상화abstraction는 궁극적으로는 "어떠한 코드의 작성을 금지하는 제약constraint"으로 구현된다.
그런데 일반적으로 제약 혹은 규제, 규칙은 애초에 유연성이나 변경 가능성을 위한 도구가 아니다.
제약은 오히려 자유를 제한하는 도구이다.
제약constraint은 기본적으로 자유를 제한한다
규제가 혁신을 가로 막고 있다[5]
문화 규제/검열이 자유로운 사고와 창의적인 컨텐츠 개발의 발목을 잡는다[6] [7]
과도한 회사 내규와 결재라인이 직원의 창의성을 저해하고, 변화에 빠르고 유연하게 대응할 수 없게 한다[8]
당신은 위 말에 동의하는가? 어떤 분야에서건 규제나 제약은 기본적으로 무언가를 할 수 없게 막는 규칙이다. 이러한 규칙이 아예 없는 것도, 너무 과도한 것도 모두 문제가 된다.
- 제약이 전혀 없는 과도한 자유의 말로는 산업혁명 시기 영국의 아동 노동 착취 같은 것이다. 또한 경제 대공황이 악화된 것도 시장 개입 없는 무분별한 자유방임주의 탓이라는 의견이 많다.
- 물론 시장 가격을 무시하는 과도한 규제를 적용했던 공산주의 국가들은 20세기에 예외 없이 다 망했고, 21세기 현재 거의 모든 경제대국은 자본주의 체계를 따르고 있다. (마오쩌둥 사망 후의 중국도 이름만 사회주의, 공산주의지 실상은 자본주의에 가깝다)
프로그래밍에서 제약이란?
그래서 그 제약이란 정확히 무엇인가?
"제약"은 내가 만든 말이니 여기서 명확히 정의하고 가자
정의: 소프트웨어에서 제약은 미래에 작성될 수 있는 특정한 코드를 금지하는 무언가 이다.
프로그래밍에서 특정 코드의 작성을 막는 것은 정말로 다양하다.
- 정적 타입 언어의 타입, GC에 의한 메모리 관리, 함수형 언어에서 assign을 어렵게 하는 등 언어 피처
- 레거시 코드, 테스트 코드 등 일반적인 코드도 제약이다
- 추상화나 설계도 충실히 따른다면 제약이라 할 수 있다
어떤 제약은 강제적이지만, 어떤 제약은 그렇지 않다. 즉 제약의 성격에 따라 강한 제약/약한 제약으로 나눌 수 있다. 코드가 제약을 준수하지 않으면 아예 실행을 거부하기도 하지만, 문제가 되는 코드가 실행되지 않으면 강제하지 않는 경우도 있다.
- 정적 타이핑은 강한 제약이고 동적 타이핑은 약한 제약이다.
- 테스트나 assertion은 반드시 준수하면 강한 제약이지만, 실행하지 않을 수도 있다.
- 문서로 표현된 정책이나 설계는 사람 없이는 검증도 불가능하고, 강제할 수도 없다.
- 레거시의 경우 레거시 코드의 제약이 새로 작성하는 코드 또한 제약하게 된다.
어디서부터 과도한 제약인가?
프로그래머들은 이미 다양한 제약 속에서 프로그래밍을 하고 있다. 매니지드 언어는 프로그래머가 메모리를 직접 관리하는 것을 막는다. 타입 시스템은 문자열과 숫자를 더하는 등 말이 안 되는 연산을 막는다.
아무리 제약이 자유를 제한한다고 해도, 당장 웹사이트 짜야 되는데 동적할당된 메모리의 해제를 신경 쓰고 싶지는 않을 것이다. 그렇다면 프로그램에서는 어디까지가 적절한 제약이고, 어디까지가 과도한 제약인가?
이 질문은 사실 모든 경우에 들어 맞는 답이 없다. 프로그램이 해결하려는 문제마다 적정한 제약의 수준이 다르기 때문이다. 인공심장에 들어가는 스케쥴러와, 에러로 강종되도 걍 다시 켜면 되는 내 블로그 서버에 동일한 기준을 적용할 수는 없다.
어떻게 보면 현재 씹게이 전성 시대의 프로그래밍을 간단히 다음 한 줄로 요약할 수 있다.
어떤 추상화/제약이 문제를 해결하기에 알맞는가?
언제 제약이 문제가 되는가?
사실 제약이 과도해서 문제라기보단, 잘못된 제약이 새 코드 작성의 발목을 잡을 때가 문제다. 당장 어떤 코드가 필요한지 뻔히 보이는데 과거에 짠 무언가가 이를 막는 것이다.
이럴 때는 제약이 강제적일수록, 제약을 만드는데 들어간 노력과 자원이 많으면 많을수록 문제가 커진다. 제약이 너무 강제적이면 이를 무시하는 새 코드를 아예 작성할 수가 없고, 반드시 제약을 변경해야만 한다.
만드는데 자원이 많이 들어간 제약을 변경해야 한다면 일단 그 자체로 낭비다. 그리고 매몰비용으로 인해 합리적이지 못하고 비이성적인 결정을 할 수가 있다. 이성이 아닌 감정의 문제가 되고, 권력이나 서열 문제가 끼어들면 뭔가 바꾸는 것이 정말 힘들어질 수 있다.
기존의 제약/추상화가 쓰레기 같을 때, 그래도 변경을 할 수 있으면 그나마 낫다. 어떤 이유로든 기존의 제약을 변경 할 수 없으면 결국 이를 피해가는 hack을 써서 이후의 코드를 짜야만 한다. 그러면 문제는 더 심각해진다. 이전 코드가 그래도 일관성 있는 쓰레기였다면, 이제는 일관성조차 없는 잡탕 찌개 음식물 쓰레기가 되어 버린다.
제약을/추상화를 어떻게 써야 하는가
일단은 추상화 = 제약이 항상 좋은 건 아니라는 점을 이야기하고 싶었다. 여기서는 그냥 지금 생각 나는 것만 적어 본다.
사실 어떻게 써야 하는지는 뻔하다.
문제에 알맞는 추상화/제약을 적절히 사용하면 된다
- 문제를 잘 알아야 한다 - 섣부른 추상화의 재앙은 결국 너무 이른 시점에 규칙을 정하는데서 발생한다. 문제를 잘 모르는 개발 초기에는 제약을 덜 걸고, 문제를 잘 알게 되고 실제 유저로부터 피드백을 받은 후에야 강한 제약을 도입하라.
- 추상화를 신중하게 도입해야 한다: 추상화는 제약이다. 모든 추상화가 좋은 것은 아니다. 사실 대부분의 추상화는 쓸모가 없다. 오직 문제에 맞는 추상화만이 도움이 된다.
변경 불가능한 강한 제약은 가능한 쓰지 않는다. 다음은 몇가지 예시다
- "Composition over Inheritance"는 유명한 말이다
- 상속은 매우 매우 강한 제약이다. 무분별한 상속이 얼마나 인생을 힘들게 하는지는 다들 알 것이다.
- 자유로움과 유연성을 원한다면 클래스, 레코드 타입을 쓰지 않는 것이 좋다
- 클래스나 레코드 타입은 프로그램의 이름 공간을 없애면서 데이터의 묶음에 뭔갈 추가하거나 빼기 힘들게 고정하는 피처다. 이는 결국 유연성과 자유를 줄이는 제약이 된다.
- 갓바처럼 클래스만 써야 하는 븅신 언어는 어쩔 수 없지만, 자바스크립트(object)나 파이썬(dict) 같은 언어에서는 데이터를 연관시키는 것이 자유로운 자료구조를 쓰는 것이 낫다.
- 구체적인 타입을 나중에 걸 수 있는 언어를 쓴다
- 당연하지만 타입은 제약이다. 프로그래머의 실수를 줄여줄 수 있지만 유연성의 측면에서는 없느니만 못할 때도 있다.
- 타입 또한 필요하지만 필요하지 않을 때도 있다. 그러면 필요할 때만 타입을 붙일 수 있는 언어를 쓰면 된다.
- TDD, PBT[9] 같은 건 프로그램이 어느 정도 성숙하거나 문제를 잘 알 때만 써라
- TDD나 PBT의 테스트 또한 타입과 비슷하다. 프로그래머의 실수를 줄이고, 문제가 잘 변하지 않을 때는 많은 도움이 된다.
- 그런데 문제나 혹은 문제에 대한 인식이 미친 듯이 변할 때는.. TDD는 그냥 시간낭비가 된다. 왜냐면 뭐가 바뀔 때마다 테스트를 전부 갈아 치워야 하기 때문이다[10]. 그럴 때 한 가지 방법은: 예제 데이터와 실행 코드는 적되 assert는 쓰지 않는다. 실행 결과를 눈으로 확인만 하되 코드로 못박아두지는 않는 것이다. 왜냐 하면 곧 바뀔 거니까..
- 아예 초기에는 테스트를 하지 않고, 실제 유저로부터의 피드백을 받은 뒤 가정이 깨져서 변경이 필요한 부분에만 테스트를 한다는 방법도 있다. 좋아하는 사람은 좋아하는 김포프님..의 방법론[11]이다 - 나도 나쁘지 않다고 생각한다.
다만 유연성과 자유를 증대시키는 결정에 프로그래머의 실수를 유발하는 trade-off가 있다는 것은 기억해두어야 한다.
유연성을 증대시키는 추상화를 적용한다
- 어떤 추상화는 시간의 시련을 버텨낸다. 대표적인 예로 unix 철학이 있다.
- 모듈은 한 가지 일만 한다 / 모듈이 의존할 수 있는 표준화된 입출력 형식(문자열)을 정의한다 / 그리고 모듈을 서로 엮을 수 있는 수단(pipe 등)을 제공한다.
- 이러한 unix의 추상화는 믿을 수 없을 만큼 성공했고, 50년이 지난 지금도 대다수의 서버에서 쓰여지고 있다.
- 그 외에도 성공적인 추상화/제약 기법들이 있다.
- 약간 다른 용어를 쓰지만, 비슷한 이야기를 하는 발표가 있다. Simple made Easy, 스크립트와 슬라이드
- 사실 위 발표는 Clojure 창시자인 Rich Hickey힛키옹의 발표다. 발표를 이해하는 것도 좋지만 그냥 Clojure를 배우고 써보는 게 더 좋다. 나랑 같이 근로저 하자 나부터 시작했어
- 변경을 버텨내는 추상화 기법에 대해 설명하는 책으로 SICP의 저자가 쓴 책이 있다. 프로그램의 유연성과 additive programming을 정의하며 직구를 던지는 대담한 책이다: Software Design for Flexibility - scheme을 쓰는데다 그냥 개같이 어려운 책이라서 추천하기 약간 망설여지지만, 이 정도로 대놓고 유연성을 언급하는 책은 본 적이 없다(책이 상당히 어렵기 때문에 번역서와 함께 보는 것을 추천한다).
주의해야 하는 것은, 그 아무리 좋은 제약과 추상화라도 문제를 해결하지 못하면 그게 바로 잘못된 추상화이고, 결국에는 나를 옥죄는 족쇄가 될 것이라는 점이다. 이는 솔루션(추상화)에 대해 아무리 연구해도 절대 해결할 수 없는 부분이다. 결국 해결하려는 문제를 제대로 이해해야만 문제에 맞는 올바른 추상화와 제약을 적용할 수 있다.
맺음말
성능 최적화의 시대가 가고, 생산성 향상과 추상화의 시대가 왔다.
그런데 프로그래머들은 방향만 다르지 하는 짓은 옛날과 똑같은 뇌절에 고통받고 있다.
섣불리 최적화하다 피를 보던 시대가 지나고 나니
이제는 섣불리 추상화하다 제 손으로 뚝배기를 터뜨리고 있다
이 꼴들을 보면서, 나는 이렇게 주장하고 싶다.
섣부른 추상화가 만악의 근원이다.
Premature abstraction is the roots of all evil.
우리 모두 섣부른 추상화로 섣부른 제약을 걸지 않도록 항상 조심하고 또 조심하자.
Your Programming Language is Making Climate Change Worse!, 논문 ↩︎
https://a16z.com/2011/08/20/why-software-is-eating-the-world/ ↩︎
저 유명한 데이터 중심 어플리케이션 설계 책의 1장 첫 줄이 하는 말이다: "오늘날의 소프트웨어는 계산보단 데이터에 집중한다". 그러면서 믿을 수 있고reliable 확장가능하고scalable 유지보수maintainable 가능한 제품을 만드는 것이 오늘날 소프트웨어의 목표라고 한다. 나는 웃기려고 마초 게이 드립을 쳤지만 이런 패러다임 변화의 원인을 진지하게 고찰하고 싶다면 책의 1장을 읽어보는 게 좋다. ↩︎
https://velog.io/@skynet/회사에서-TDD-쓰려다-실패한-후기 TDD 도입에 실패, 개발 초기에 별로라는 글 ↩︎
https://youtu.be/gs1qM1TF5zA 시간이 없다면 첫 댓글을 보면 된다. ㅋ ↩︎