[왜 클로저(Clojure)인가?] 3. 단순성이 궁극의 정교함이다.

단순성(Simplicity)과 복잡성(Complexity)


바보는 복잡성을 간과한다. 실용주의자는 복잡성을 견디어 낸다. 어떤 이는 회피한다. 천재는 복잡성을 제거한다. - 알랜 펄리스(Alen Perlis)의 프로그래밍 경구


프로그래밍의 역사는 복잡성과의 사투의 역사이다. 일찍이 다익스트라(Djikstra)는 소프트웨어는 본질적으로 복잡하다고 선언했다. 그는 프로그래밍의 복잡성과 단순성에 대해 평생 역설했다. 프레드 브룩스(Fred brooks)는 ‘은총알(특효약)은 없다(No Silver Bullet)’라는 글에서 아리스토텔레스의 본질과 우연 개념을 빌려와 복잡성을 본질적 복잡성과 우연적 복잡성으로 구분했다. 본질적 복잡성은 문제영역 자체에 내재되어 있는 복잡성이고, 우연적 복잡성은 문제 자체가 아니라 문제를 푸는데 사용되는 도구나 방법에서 기인하는 복잡성을 말한다. 본질적 복잡성은 제거 불능인 반면, 우연적 복잡성은 제거 가능한데 고수준 언어일수록 우연적 복잡성이 적다.


복잡성의 반대는 단순성이다. 단순성의 추구가 곧 복잡성을 제거하는 가장 좋은 방법이다. 클로저는 단순성을 최대한 추구하는 언어이다. 클로저는 기존 언어의 우연적 복잡성이 없으며, 또한 본질적 복잡성의 완화까지도 가능하다.


이것이 어떻게 가능할까? 우연적 복잡성의 제거는 그렇다쳐도 본질적 복잡성의 완화까지 가능하다? 이것을 알기 위해서는 단순성의 본질을 파악하는 것이 중요하다.


단순성이란 도대체 무엇인가?



단순함과 쉬움은 다르다 (Simple is not Easy).


클로저의 창시자인 리치 히키는 프로그래머들이 ‘단순함(Simple)’과 ‘쉬움(Easy)’을 혼동한다고 한다.


‘단순함(Simple)’의 반대말은 ‘복잡함(Complex)’이고, ‘쉬움(Easy)’의 반대말은 ‘어려움(Difficult)’이다. 복잡함과 어려움이 다른 것처럼 단순함과 쉬움은 서로 다르다. 하지만 우리는 종종 쉬움을 단순한 것으로 착각한다.





어원적으로 볼 때 단순함을 의미하는 영어의 simple은 sim-ple로 나누어지는데, 여기서 sim은 라틴어 semen 부사에서 유래한 것으로 once를 의미하고, ple은 라틴어 plicare 동사에서 유래하는데, 이는 영어로는 fold에 해당하며, 접힘, 주름, 줄, 겹을 의미한다.  결국 simple은 ‘한 겹’ ‘한 줄' ‘한 배'를 뜻한다. 비슷하게 duple은 ‘2 배'  triple은 ‘3 배' 를 뜻한다는 것을 떠올려 보면 그 의미가 와 닿을 것이다. 반면 복잡함을 의미하는 complex는 com-plex로 나누어지는데, 마찬가지로 plex는 라틴어 plicare에서 유래해서 fold를, ‘com’은 together, common 을 의미해서, ‘함께 엮여 있는 상태'를 의미한다.


한편 easy는 ease에서 파생한 것으로 ease는 고대 프랑스어 aise에서 유래한 것인데, 이것은 다시 ‘가까운’을 뜻하는 라틴어 adjaces에서 유래했다. 그래서 easy는 ‘가까이에 놓인 것'으로 그것을 얻는데 에너지가 덜 드는 것을 말한다. 반면 difficult는 에너지가 많이 드는 것을 의미한다.


(어원적 분석을 통해 보면 복잡하다는 것은 결국 여러 가지를 같이 엮는 것 (com-plex)을 말하는 것이다. 만약 뭔가가 복잡하게 느껴진다면 그것은 그 안에 여러 가지가 섞여 있기 때문이다. 단순(sim-ple)화한다는 것은 그 여러 가지를 분리해 내서 하나의 것으로 다룰 수 있게 하는 것으로 이해할 수 있다)


그런데 단순함과 쉬움을 구분하는 것이 왜 중요할까?


쉬움이 단순함과 다르다면, 쉽다고 해서 반드시 단순한 것이 아닌 것이고, 이는 곧 쉽지만 복잡한 것이 있다는 것이다. 쉽지만 복잡한 것이 있는데, 우리가 쉬움을 단순함과 같은 것으로 혼동한다면 쉬움 속에 있는 복잡함을 간과하게 되기 때문이다. 쉬움 속에 숨어있는 복잡성을 알아채기 위해서는 쉬움과 단순함을 분간할 수 있어야 한다.


리치 히키는 쉬움과 단순함이 개발 속도에 차이를 준다는 점을 강조한다. 프로젝트를 시작할 때 쉬움은 초기에 빠르게 시작하게 해준다. 하지만 프로젝트가 진행되면서 쉬움 속에 숨어있는 복잡성들이 서로 점점 얽히고설키면서 거대한 괴물이 되어 개발을 방해한다. 반면 단순함은 초기에는 느리지만 프로젝트가 진행되면서 점점 개발 자체가 쉬워지면서 진행 속도는 점점 빨라진다.



[Simple Made Easy] by Rich Hickey 에서 발췌



쉬움 속에 있는 복잡성? 복잡한데 어떻게 쉬울 수 있을까? 이것을 이해하기 위해서는 인간이 복잡성을 제어하는 2가지 방식에 대한 이해가 필요하다.



복잡성 제어 : 복잡성의 극복인가 복잡성의 제거인가?


산이 있다. 험준한 산이다. 산 너머로 가는 방법은 2가지다. 하나는 산을 넘는 것이고, 다른 하나는... 산을 없애는 것이다. 전자의 방식에서는 뒤에 따르는 모든 이가 매번 계속 산을 넘어야 하지만, 후자의 방식에서는 뒤에 따르는 이는 넘지 않고 그냥 평지를 걸어가면 된다.


마찬가지로 복잡성을 제어하는 방법은 2 가지다. 하나는 객관적이면서도 단순한  방식이다. 사물의 복잡성을 제거하여 단순하게 만드는 것이다 (산을 없애는 것). 다른 하나는 주관적 방식인데 복잡하다. 인간이 훈련을 통해 어려운 것을 쉬운 것으로 만들어 내는 것이 그것이다 (등산을 하듯이). 이것은 사물의 복잡성은 그대로 둔 채 그것을 통제할 수 있을 정도로 우리 자신을 복잡하게 만드는 것이다. 훈련을 통해 우리 뇌가 그만큼 복잡해지면 필수다양성의 법칙에 따라 그 복잡성은 제어가능하게 되고 우리는 이제 그것이 쉽다고 느끼게 된다. 필수다양성의 법칙은 사이버네틱스(Cybernetics)의 제 1법칙으로 불리기도 하는데, 한 시스템이 안정화되기 위해서는 그것을 제어하는 시스템이 제어되는 시스템보다 상태의 수가 더 많아야 한다, 즉 더 복잡해야 한다는 것이다.


복잡성을 제어하는 방법중 주관적 방식의 예로 저글링을 생각해 보자. 3개의 공을 저글링하는 것은 어떤 사람에게는 쉬운 일이지만, 저글링을 아예 못하는 사람에게는 어렵다. 하지만 이 3개의 공을 저글링하는 사람도 10개의 공을 저글링하는 것은 어렵다. 3개의 공을 저글링하기 위해서, 혹은 10개의 공을 저글링하기 위해서 우리는 훈련을 한다. 훈련을 하는 동안 저글링의 복잡도는 결코 줄어들지 않는다. 공의 수는 그대로다. 다만 손을 부산하게 움직이게 하는 우리 뇌의 신경세포들이 발달하게 된다. 즉 복잡해진다. 손의 움직임을 담당하는 부위의 뇌세포가 많아지고, 더 많은 시냅스들이 연결되고, 미엘린이 두터워지고, 뇌혈류가 증가한다. 이렇게 뇌가 점점 더 복잡해지면서 그 복잡도가 저글링의 복잡도를 넘어섰을 때 드디어 저글링은 쉬워진다. 하지만 공의 갯수는 그대로 3개 혹은 10개다. 저글링의 복잡도는 변하지 않았다.(필수다양성의 법칙에 의하면 공이 공중에서 계속 저글링되는 것이 제어되는 시스템이라면 그것을 제어하는 시스템인 사람의 손과 두뇌가 좀 더 상태의 수가 많아야 즉 복잡해야 된다는 것이다.)


복잡성을 제어하는 방법중 객관적 방식의 예로는 천동설과 지동설을 들 수 있다. 천동설과 지동설은 모두 똑같이 행성의 운동들에 대해 설명할 수 있는 이론 체계다. 다만 지동설이 더 단순하다. 천동설의 경우 지구와 태양 이외의 모든 다른 행성들은 부전원을 돌아야 한다. 하지만 지동설에서는 달만 지구를 공전하는 부전원만 그리면 된다. 이렇게 지동설은 천체의 운동을 보다 더 단순하게 드러내 준다. 그리고 이렇게 객관적 방식으로 얻게 된 단순성은 그 자체로 모두의 것이 된다.




단순성은 객관적인 것이고, 쉬움은 주관적인 것이다. 단순성은 사물 자체에 내재되어 있는 것이지만, 쉬움은 주관적인 경험에서 비롯되는 것이고 따라서 상대적이다. 단순성은 복잡성에 숨겨진 사물이나 사태의 본질에 더 다가서기 위해 노력해야 얻을 수 있는 것이고 한 번 얻으면 모두가 누릴 수 있지만, 쉬움은 사물이나 사태의 복잡성만큼 인간이 각자의  훈련을 통해 스스로가 복잡해져야 도달할 수 있다. 매번 개개의 인간이. 이 무슨 낭비인가?


클로저는 복잡성을 제어하기 위해 프로그래머들에게 어떤 훈련(TDD)을 요구하지 않는다. 클로저 언어의 사용 자체가 상당한 복잡성을 제거해 준다.


“저는 '일을 올바르게 한다는 것’(doing the right thing)이 단지 관례나 훈련에 의해 되는 것이 아니라, 저절로 자연스럽게 되게 하고 싶었어요.” - 리치 히키. simple talk 의 geek of the week 인터뷰에서



친숙함은 복잡성을 숨긴다 (Familarity Hides Complexity)


‘쉬움(Easy)’이 감성을 동반하면 ‘친숙함(Familarity)’으로 된다. ‘쉬움(Easy)’과 ‘단순함(Simple)’의 구분이 중요하게 되는 또 하나의 지점이다. 천동설은 매일 해가 뜨는 것을 보는 우리에게 얼마나 친숙했던 것인가? ‘지구가 돈다니! 말이 돼나? 이런 불편하고 허무맹랑한 소리가 있나!’ 이처럼 친숙함은 사물과 현상의 복잡성을 가리고 진실을 덮는다. 우리는 진실에 다가서기 위해 ‘친숙한 것과 멀어지기’를 일부러 의식적으로 행해야 하는 것이다.


프로그래머에게 있어 쉬움이 친숙함으로 될 때 복잡성이 보이지 않게 된다. 지난 회에서 제시했던 딘 왐플러의 ‘단어 세기(word cound)’ 알고리즘을 구현한 자바 코드를 보자. 경력이 많은 자바 프로그래머들에게 그 코드는 매우 친숙하고 자연스럽다. 반면 클로저 코드는 매우 낯설고, 심지어 이게 과연 코드인가 싶기도 하다. 그에게는 자바 코드의 (프로그래밍을 처음 하는 사람에게는 보이는) 복잡성은 보이지 않는다. 한편 스칼라 코드는 자바와 유사한 점이 많아 좀 덜 낯설 것이다 (이것이 클로저보다 스칼라가 자바 프로그래머들에게 인기있는 이유중 하나이다).


Array<String> arrString = new Array<String>();


자바 프로그래머는 위 코드에서 거북함을 느끼지 못한다. ‘Array<String>’가 2번 씩 중복되고 있는데도. 어느 초심자가 ‘왜 Array<String> 2 번 씩 나와야 되냐’고 묻는다면 자바 프로그래머는 그 당연한 질문에 자세히 설명해 줄 수 있을 것이다. 하지만 그 질문에 그렇게 답하고 말면 그는 더 배우지 못한다. 그 질문을 더 파고 들어야 가야 한다. 그래야 복잡성을 만나게 된다. 복잡성은 전문가에게는 당연한(친숙한, 자연스러운) 것과 초심자에게는 의문인 것 사이에 숨어있다.


Point *point = new Point(1, 2);


반면 C++ 프로그래머들에게 위 코드는 아주 자연스럽지만 자바 프로그래머들에게는 이상하다. 포인터. 그것이 왜 필요하냐고 물으면 C++ 프로그래머들은 자세히 알려줄 수 있을 것이다. 하지만 복잡성은 바로 거기에 바로 그 의문에 숨어있다.


그러나 필자 자신이 그랬듯이 C++을 하는 동안에는 포인터가 야기하는 복잡성에 대해 인지하기가 힘들다. 세계안에서는 세계의 한계를 알 수 없다. 필자는 자바를 알게 되면서 자바가 만들어 내는 생산성의 향상을 체험하면서 포인터가 주는 생산성의 저하에 대해 뼈저리게 느끼게 되었다. 한가지 예로 C++ 프로그래머는 자바에는 없는 소멸자에서 포인터에 대한 온갖 복잡성을 처리해 주어야 한다! 만일 이 소멸자들이 스레드와 관련이 생긴다면 그 복잡성은 기하급수적으로 증가하게 된다.


산을 오르는 기술을 매번 가르치는 것보다 산을 없애는 것이 낫듯이, 포인터가 야기하는 복잡성은 포인터를 다루는 현란한 기술들을 가르침으로서 극복하기 보다, 언어에서 포인터 자체를 없앰으로서 복잡성 자체를 제거하는 것이 더 좋았던 것이다.


자바는 어떤가? 파이썬과 루비는? 그리고 당신이 사용하는 그 밖의 다른 언어는? 그 언어 자체가 갖고 있는 복잡성에 대해 인지하고 있는가? OOP와 디자인 패턴은 과연 단순한 것일까? 다음의 질문에 복잡성이 숨어있다.



  • 데이타가 변하는가? 즉 변수를 지원하는가?
  • 오브젝트가 있는가? 즉 상태를 지니는 객체가 있는가?
  • 메소드를 사용하는가? 함수를 사용하는가?
  • 상속을 지원하는가?
  • 타입(Type)을 지원하는가?
  • 액터(Actor)를 지원하는가?
  • ORM을 해야 하는가?



클로저는 프로그래밍 언어가 내포할 수 있는 복잡성을 그 바닥까지 파헤친다. 적어도 현재까지는. 클로저는 우리가 알고 있는 근본 개념속에 숨어있는 복잡성을 제거한다. 시간(time), 상태(state), 변화(change), 과정(process) 그리고 값(Value 혹은 데이타)! 클로저가 본질적 복잡성의 완화를 가능하게 한 그 원천은 이러한 근본 개념들에서 복잡성을 제거한 후 근본 개념 자체를 새로 정립한 기반위에서 언어를 설계하였기 때문이다.


이후 연재 기사에서 클로저의 본질적 복잡성 완화에 대해 더 자세하게 다루게 될 것이다. 만일 이 내용이 궁금해서 좀 더 자세하게 먼저 알고 싶다면 다음 리치 히키의 강연을 보기 바란다. (각각 프리젠테이션 pdf 자료가 있다)




(리습동호회의 오종빈님의 블로그에서 위의 리치 히키 강연에 대한 리뷰를 볼 수 있다)


자! 이제 익숙하고 친숙한 것과 작별하고 오시라. 클로저 세계로의 불편하지만 흥미진진한 여행을 떠날 시간이다!