직관과 프로그래밍
직관과 프로그래밍
컴퓨터 소프트웨어라는 것은 수학적으로 잘 짜인 논리 집합체이다. 그래서 프로그래밍이라는 작업은 그 어떤 작업보다 논리적인 작업이다. 그래서 사람들은 그 프로그램을 작성하는 프로그래머들이 프로그램을 작성할 때는 언제나 논리적으로 생각할 것이라고 간주해 버린다. 하지만 정말 논리적인 생각만으로 프로그램을 작성할 수 있을까.
크게 봤을 때 프로그래밍은 당연히 논리적인 작업이 맞다. 알고리즘을 설계한다. 그리고 설계한 알고리즘을 프로그래밍 언어로 바꾸어서 실제 키보드를 두르려 코드를 써낸다. 그 코드를 컴파일 해서 실행파일을 만들어 내는 과정은 논리적 사고와 절차 외에 다른 요소가 끼어들 틈이 없어 보인다.
하지만 위에서 우리가 생각하지 않은 것이 있다. 일반적으로 우리가 생각하고 작성하는 알고리즘과 코드는 처음 알고리즘의 설계할 때의 목표를 달성하지 못한다는 것이다. 아무리 세심하게 주의를 기울여 설계한 알고리즘 혹은 작성한 코드라도 열에 다섯 여섯은 오작동하거나 예상과 다르게 동작한다. 그래서 개발자들은 디버깅을 해야 한다.
디버깅은 크게 두 가지로 나눌 수 있다. 명확히 오류가 보이는 경우를 디버깅하는 것. 그리고 도대체 원인이 뭔지 모르는 경우를 디버깅하는 것이다. 명확히 보이는 오류를 디버깅하는 경우는 잘 알려진 절차를 따르고 도구를 사용해 디버깅한다. 처음부터 끝까지 논리적인 사고로 문제가 되는 코드를 분석하고 수정해서 버그를 없앤다. 역시 논리적 사고 외에 다른 요소가 끼어들 틈이 없다. 문제는 두 번째 경우이다. 아무리 분석적으로 뜯어보고 논리적으로 생각해보고 여러 가지 툴을 사용해도 답이 보이지 않는다. 개발자들은 여기서 직관이라는 것을 끄집어낸다.
직관. 논리의 집합체인 프로그램을 직관을 이용해 만든다고 하면 언뜻 어울리지 않는다는 생각이 든다. "앞의 내용이 이렇고 뒤의 내용이 이러하니 여기는 이렇게 고쳐야해!" 하는 식의 사고가 아니라, "음.. 그냥 여기에 이걸 넣어 볼까?" 혹은 "혹시 이거 아니야?" 하는 식의 사고방식. 즉, 막말로 때려 맞추기 혹은 찍기의 사고방식인 것이다.
그렇다면 실제 프로그래밍 과정에서 직관이 차지하는 비율은 얼마나 될까? 내가 정확히 설문조사를 해본적은 없지만 주변의 개발자들과 많은 대화를 하면서 추정해 보건데 거의 절반에 가까운 비율이 아닐까 싶다. 이것은 나의 경험에 비추어도 거의 같은 비율이 나온다. 특히 프로그램을 최초 작성할 때 보다, 앞서 말한 디버깅 과정에서 직관이 차지하는 비율은 더 높아진다.
얼마 전에 동료 개발자 한명이 나에게 도움을 요청했던 적이 있다. 정확한 코드의 내용은 기억이 나지 않는데 대충 아래와 같은 모양이었다.
if(VERIFY(somefunc(a,b,c))){ // 어떤작업 }
이 코드가 debug 모드에서는 if 문 블록 안에 들어가 어떤 작업을 수행한다. 하지만, release 모드에서는 if 문을 그냥 통과해 버린다. 즉, 블록에 들어가지 않는다는 것이었다. 프로그램 코드나 로직이 잘못된 것도 아니고 컴파일 모드에 따라서 동작이 다르다는 것에 그 개발자는 원인을 알 수 없어 계속 짜증만 내고 있는 상태였다. 물론 도움을 요청받은 나도 원인을 알 수 없었다. 그때 불현듯 떠오르는 생각 하나가 있었다.
일반적으로 gcc의 경우 컴파일러 최적화 옵션을 높게 주면 소스코드 안에서 많은 부분을 자체적으로 수정한다. 그러다 보면 개발자가 의도하지 않은 오동작을 일으키는 경우가 가끔 생긴다. 이번 경우는 gcc가 아니라 vc를 이용한 개발이었다 하지만 기본 원리는 같을 것이란 생각이 들었다. 그래서 빌드 옵션에서 "컴파일러 최적화 안함"을 선택하고 다시 빌드해 보라고 했다. 그렇게 빌드하고 테스트하니 의도한대로 동작하였다.
위 이야기에서 내가 갑자기 떠올린 컴파일러 최적화 옵션을 통한 디버깅은 직관적 사고일까 논리적 사고일까. 사람들마다 해석의 차이가 있을 수 있겠지만 나는 직관이라고 생각한다. 'gcc에서 컴파일로 최적화 옵션에 따라 동작이 바뀌는 현상이 있으니 이번 것도 그럴 것이다.' 라는 사고 과정은 논리적이지 않느냐 라고 반문하는 하는 사람도 있을 것이다. 그렇다 그 과정은 논리적인 사고 과정이다. 하지만 그 생각을 떠올린 것 자체는 그야말로 직관에 의존한 것이다. 왜냐하면 컴파일러 최적화 옵션이라는 것을 떠 올리기까지 과정에서 그 생각이 도출되는 연역적 근거가 없기 때문이다.
다른 예를 하나 더 들어 보겠다. 역시 내가 예전에 어떤 프로그램을 만들면서 겪었던 일이다. 중복실행을 막아 놓은 데몬을 만들었던 적이 있다. 그리고 그 데몬에 맞물려 돌아가는 클라이언트 프로그램도 작성하였다. 문제는 데몬이 떠 있으면 클라이언트 프로그램이 실행이 되지 않는 것이다. '도대체 어디가 잘못된 것인가' 하고 디버거 등으로 소스코드를 한 줄씩 실행해보고 커널 시스템 콜 호출을 캡춰해서 추적해보는 등 별짓을 다 해봐도 답이 나오지 않았다.
그때 혹시나 싶어서 클라이언트 프로그램을 먼저 동작시키고 데몬을 띄워 보았다. 정상적인 프로그램 동작 상황이라면 데몬이 클라이언트보다 먼저 떠 있어야 했다. 그래서 당연히 데몬을 띄워 놓은 상태에서 데몬과 클라이언트를 테스트 했었다. 그런데 반대로 하면 어떻게 되나 싶어서 데몬을 나중에 띄워 본 것이다. 결과는 역시 데몬이 뜨지 않았다.
순간 머릿속을 지나가는 것이 있었다. 그것은 데몬과 클라이언트에 중복실행을 방지하기 위해 넣어놓은 뮤택스 관련 코드의 내용이었다. 데몬을 작성하면서 먼저 만들어 놓은 중복실행 방지 코드를 그대로 클라이언트에 넣은 것이 화근이었다. 데몬에서 잘 동작하였으므로 그 동작이 검증되었다고 생각하고 아무 생각 없이 클라이언트 코드에 긁어 넣은 것이다. 하지만 문제는 그 코드 안에서 뮤택스 이름을 수정하지 않은 것이었다. 그대로 긁어 붙이기만 하고 두 프로그램의 뮤택스 이름을 다르게 해야 한다는 사실을 잊은 것이다. 그래서 두개의 프로그램이 모두 뮤택스 이름이 같게 된 것이다. 결국 하나가 떠 있으면 다른 하나가 동작하지 않은 것이다. 서너 시간 동안 이리저리 디버깅해 가며 나를 괴롭혔던 문제였지만 달랑 문자열 하나 바꾸는 것으로 해결되었다.
위 경우에도 역시 "프로그램 실행순서를 바꿔 보면?" 하는 직관에 가까운 의문이 먼저 떠 올랐다. 그리고 그렇게 했을 때 데몬이 뜨지 않을 것을 보고 "아! 뮤택스" 하며 문제를 한 번에 찾았다. 그것 역시 논리적인 사고라기 보다는 직관에 가까운 사고라고 볼 수 있다. 마찬가지로 연역적 근거 없기 때문이다. 혹시나 싶어 프로그램을 반대로 실행해 보고 다른 디버깅 수단 없이 그냥 동작의 결과만 보았다. 그리고 뮤택스가 문제가 아닐까 하고 떠올린 것이기 때문이다. 원인은 뮤택스 이름이라는 결과에 도달하기 까지 논리적 사고 절차는 없었다.
내가 뮤택스의 이름을 고쳐서 프로그램의 버그를 수정한 것이 논리적 사고의 결과라면 나는 이것저것 다른 헛된 시도를 하지 않았을 것이다. 그저 처음부터 버그를 잡기까지 전 과정에 걸쳐 일관되고 효율적인 절차와 수단으로 디버깅을 했을 것이다. 하지만 그것이 직관의 결과이기 때문에 다른 과정을 건너뛰고 바로 뮤택스 부분에 관심을 둘 수 있게 된 것이다.
결과적으로 뮤택스 이름을 수정해서 문제를 해결 했지만, 사실 뮤택스가 원인이 아닐 수도 있었다. 나는 충분히 실패를 할 수도 있었다. 내가 논리적인 사고를 거쳐서 뮤택스의 이름이 잘못되었다는 결론에 도달했다면 문제의 원인은 무조건 뮤택스의 이름이어야 한다. 하지만 어떻게 생각하면 내가 운이 좋기 때문에 직관적 사고를 통해 뮤택스의 이름이 원인인 것을 알아낸 것이다. 그것이 원인이 아니었다면 나는 또 다른 시도를 하며 그 뒤로도 몇 시간을 더 보냈을 것이다.
그렇다면 직관은 단순히 그냥 운인 것인가? 운이 좋은 사람은 직관적 사고를 통해서 더 쉽게 원인을 찾을 수 있고 결과적으로 디버깅을 포함한 프로그래밍 전체 작업을 운이 나쁜 사람보다 더 잘 해낼 수 있는 것인가? 결론부터 말하자면 경험이 비슷한 두 개발자가 같은 상황이라면 운이 좋은 사람이 문제를 더 잘 해결할 수 있다. 하지만 경험에 차이가 있다면 운은 경험을 이기지 못한다.
앞서 두 개의 예를 보면 나는 분명 직관적 사고를 통해서 문제를 해결 했다. 하지만 그 직관적 사고를 이끌어 내기 위해서 나는 여러 가지 많은 시도를 했다. 그리고 그 과정에서 나는 나의 경험속의 비슷한 예를 끊임없이 검색한다. 물론 그 검색과정은 의식적으로 이뤄지는 것이 아니다. 나도 모르게 이뤄지는 것이고 그 검색의 결과는 나에게 직관이라는 수단으로 전달되는 것이다.
실제로 경험 많은 개발자와 경험이 적은 개발자가 동일한 문제를 해결하는 것을 보면 작업의 순서는 비슷하다. 디버거를 이용해서 문제가 되는 부분을 찾고 눈에 보이는 몇 가지 코드를 이리저리 고쳐 본다. 하지만 결국 문제는 보통 경험 많은 개발자를 통해서 이루어진다. 경험이 적은 개발자는 경험이 많은 개발자가 어떻게 문제를 해결했는지 보고 들으며 그것을 자신의 경험으로 쌓는다.
프로그램을 만들면서 생기는 여러 가지 문제를 해결하기 위해서 이것저것 해보는 과정을 속된 말로 삽질이라고 표현한다. 위에서 경험이 많은 개발자와 경험이 적은 개발자는 둘 다 삽질을 했다. 하지만 경험이 많은 개발자는 자신의 경험을 통해서 삽질을 빨리 끝냈고, 경험이 적은 개발자는 끝내 삽질을 끝내지 못했다. 다만 경험을 많은 개발자로 부터 이런 문제는 어떤 식으로 해결하는지를 배웠다. 이제 그 개발자는 다음에 비슷한 문제를 접하게 되면 지금의 경험을 바탕으로 문제를 해결할 수 있을 것이다.
프로그래밍은 논리적인 작업이다. 그러면서 동시에 논리적인 작업이 아니다. 어쩌면 세상의 그 어떤 작업보다도 직관에 크게 의존하는 작업일지도 모른다. 하지만 그 직관은 태어나면서 부터 주어지거나 운이 아니다. 그 직관은 곧 경험이다. 경험은 많은 삽질을 통해 얻어진다. 그래서 이제 막 프로그래미에 입문하거나 개발자의 길을 걸으려는 후배들에게 말을 하고 싶다.
"삽질을 두려워하지 말라. 오늘의 삽질은 내일의 무기가 될 것이다."
댓글
세상사의 90%가 삽질입니다.
고차원적인 사고는 10%도 안됩니다.
근데 대개는 내가 왜 이 삽질을 해야 하냐고들 생각하죠.
( 인생자체가 삽질인데 말입니다. )
언제인지부터
언제인지부터 논리적으로 프로그래밍을 한다는게 무슨 뜻인지가 와 닿지 않더군요.
프로그래밍이 아니라 어떤 문제를 해결할 때 일단 머리 속에 해결법이 떠오르고 나서, 그 해결 과정들을 논리적으로 검증하는게 아닌가하는 생각이 들었습니다. 글을 쓰는 것도 마찬가지인데 제가 지금 적고 있는 글 또한 머리 속에서 생각나는데로 쓰면서, 쓴 문장들이 논리적으로 연결되어 있는지 검증하는 것이지 지금 글을 논리적으로 쓰고 있는 건지 잘 모르겠더군요. 아니 논리적으로 쓰고 있다는 말 자체가 무슨 의미인지 감이 잘 안옵니다 ;;
프로그래밍의 경우도 문제를 보고 직관적으로 하나의 해결책을 떠올리고 그 해결책을 논리적으로 검증해보는게 아닌가하는 의심이 들더군요. 알고리즘을 떠올리는 과정을 논리적으로 재구성해보려고 해도 도저히 가능하지가 않은 것 같습니다. 문제를 보면 열심히 생각하게 되긴 하는데 그 사고과정이 논리적인지 모르겠습니다. 그냥 생각이 흐르는데로 이것저것 떠올리게 되는 것 같아요 ;;;단 일단 해결책이 떠오르고 나면 그게 논리적으로 맞는지 확인하는 절차를 알게 모르게 자동적으로 하는 건 분명한듯 합니다.
이런 생각을 가장 많이 했던게, 논리학 수업 들으면서 증명 문제 풀 때, 논리학 정리나 기본 규칙들을 연속적으로 적용하는 것보다 일단 푸는 방법을 미리 떠 올리고 이후에 어떤 규칙을 쓸까 생각했던 때였었습니다. 그리고 수학교육과 친구가 프로그래밍을 하면서 잘 안된다고 물어볼 때마다 분명히 생각하는 건 논리적인데, 함수를 수학 함수로 생각해서 그 함수가 상태를 바꾸는 것을 처음엔 전혀 이해 못 하는 걸 보고도 그랬구요.
그래서 저도 프로그래밍을 잘 하는 건, 어떤 문제를 봤을 때 적합한 대안들을 직관적으로 잘 떠올리는 것이 아닌가 하는 생각을 늘 합니다. 삽질을 많이 해 본다는 건 되든 안되든 새로운 해결책을 찾아 시험해 보는 것이니 좋은 훈련일 거구요. 물론 선천적으로 직관이 좋은 사람들은 분명히 있습니다. "이렇게 하면 될거 같은데"라고 생각하면 거의 맞아 떨어지는 사람들이 분명 있죠 ;;
경험적 프로그래밍...
첫번째 예시로 들어주신 내용은 직관이라기 보다는 경험에서 우러나오는 추론이 아니였을까 생각합니다.
사실 직관이라는 것이 경험에서 나오는 것이니 같은 말이라고 불러도 상관없지만 '최적화'에의한
코드변화를 경험해보지않은 사람은 모르기 때문이죠.
그것을 경험해보았기에 다른 컴파일러일지라도 그렇게 될 것이란 직관력이 생긴것이 아닌가 조심스럽게
생각해봅니다.
두번째 예시의 경우도 결과적으로는 경험이라는 굴레를 벗어날 수는 없을 것 같습니다.
프로그램이 실행되지 않는다는건 큰 실수가 있기 때문이지만 그것이 다른 프로그램에 영향을 받는
다는 것을 찾아내는 것도 경험이 있어야 가능하겠죠. 이후에 비슷한 문제가 생길 경우 아마 뮤텍스의
이름부터 찾게 될지도 모르는 일이죠. :-)
이 두가지의 경험은 저도 해본적이 있습니다.
저도 보통 두가지정도가 기억납니다. 둘다 네트워크에 관련된 일이였는데 한가지는 제가 미처 생각치도
못한 포트를 닫은 후 일정시간(Default로 45초로 알고 있죠.)이 경과해야지만 해당 포트를 다시 사용
할 수 있다는 점이었습니다. 이건 공부가 게을러서 발생한 일이지만 결국 setopt인가하는 함수로
해결을 했던 기억이나네요. 만일 이런 일이 네트워크를 막 개발하기 시작할때 발생했다면 아마 어떻게
해야할지 난감해할 수도 있었겠지만 다행히 다른 장비에서 사용하던 소스가 있어서 그 소스에서 힌트를
얻어서 해결했던 기억이나네요.
두번째의 경우는 조금 독특한 경험이었는데 디버깅을 위해서 printf문을 넣으면 소켓통신이 이상없이
잘되는데 디버깅을 끝내고 printf를 주석처리하면 데이터전달이 안된다는 문제가 있었습니다.
이런 문제를 겪고있던사람이 어떻게 하느냐고 문의를 해와서 문제가 무엇인지 생각을 해봤습니다.
맞는 방법인지는 모르겠지만 예전에 UNIX에 대해서 공부할 때 출력을 위한 버퍼를 따로 두고 그것이
다 차면 한꺼번에 출력(화면또는 파일)을 한다는 글귀를 본적이 있었죠.
그래서 혹시나 이 방법이 통신용 버퍼에도 영향을 미치는 것은 아닐까 생각해서 나름대로 프로그램을
작성해서 실험을 해봤습니다. 결국 그전까지는 알지못했던 사실이 바로 나오더군요.
그래서 어차피 printf는 화면에 바로 출력하기위한 방법이니 버퍼를 강제로 비우게하는 fflush (stdout);을
써보았죠. 결과적으로 제 생각이 맞았는지 별무리없이 실행이 가능했고 이사실을 문의했던 사람에게
알려주자 문제가 해결되었다는 결과를 통지 받았죠.
두번째 문제에서는 직관이라고 생각되는 부분이기도 합니다만 저 부분도 만일 UNIX의 파일 시스템에
대한 경험이 없었다면 그러한 생각을 하기 어려웠을 것이아닌가 생각합니다.
경험은 많은 시도와 실패 그리고 성공을 해보아야만 생기는 것이기에 우리 사회에서도 많은 경험을
한 개발자가 더욱 많이 있었으면 하는 생각을 가져봅니다. (현실상에서는 그들의 연봉이 너무 높아서
꺼려하는 회사들도 많아지겠지만요... -.-;)
------------------------------
좋은 하루 되세요.
------------------------------
좋은 하루 되세요.
경험이죠!
제목은 직관과 프로그래밍이라고 썼지만, 저 역시도 강조하고 싶었던건 바로 경험이었습니다.
삽질이라고 표현한 많은 시행착오적 경험이 없다면 개발자에게는 직관역시 생기지 않는다는 생각에 위 글을 쓰게 된 것이지요.
codebank 님이 겪었던 문제들 역시 저도 겪었던 문제였는데..
개발자들이 겪는 문제는 다들 비슷한가 봅니다..^^;
-----
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
경험과 지식의 결합이 직관이 되겠지요 ^^
하지만 직관은 좀 뭐랄까. 가설이라는 느낌아닌가요 ^^;; 혹시 이럴수도 있지않을까? 이렇게 가정해보자... 하고 디버깅 시작하는거죠. 예전에 읽은 도서중에서도...사람에 따라 다른 개발능력에 대해서 말하면서 어떤 사람은 얘기를 듣자마자 이런식으로 디자인 및 설계를 하는게 좋지않겠느냐고 하는게 마치 그 과정이 번개를 스치듯이 머릿속에서 만들어지는것 같았다. 라고 하더군요.
맞는 말 같습니다. 많이 생각해왔고, 익숙했던 문제라면 끝까지 읽기도 전에 머릿속에서 연상이 되어집니다. 나빌레라님이 언급하신 두번째 문제의 경우에도 '아...중복실행 막아놓은 부분이 서버,클라이언트 둘다 동일하겠구나...' 싶더군요 ^^; 그렇게 가설을 세운뒤에 그것을 증명하는 방법은 나빌레라님의 방식도 괜찮지만,(2개의 실행순서를 뒤바꾸는...) 디버거로 프로그램을 로딩하면 뮤텍스로 막아놓은 부분에서(아마 커널 Critical Section이라고...따로 있었던것 같습니다. 하도 오래전 일이라 가물가물하네요;;) 실행이 되지않고 끝날 겁니다. 그럼 그부분을 코드를 봐야겠지요 ^^;
다만...전혀 쌩뚱맞은 상황이라면...정말 헤딩에 헤딩을 거듭하면서 가설을 아주 많이 세워야겠지요 ㅎㅎ 이른바 '맨땅에 헤딩' 되겠습니다 -ㅁ-;;;
------------------------------------------
Let`s Smart Move!!
http://kalstein.tistory.com/
------------------------------------------
Let`s Smart Move!!
http://kalstein.tistory.com/
감성적 debugging...이라고 부르기도 했었는데. ^^;;
그래서 직관에 의한 debugging을 저희는 감성적 debugging이라고도 불렀습니다. ^^;;
운은 절대 경험을 이길수 없고,
오늘의 삽질은 내일의 무기가 된다...
크게 공감하고 갑니다. ^^ 좋은 하루 되세요
감성적 디버깅. 멋진
감성적 디버깅.
멋진 말이군요.^^;
----------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
Blink 라는 책을 읽은 적이 있습니다.
직관에 의한 판단/결정에 대한 책입니다.
사람들은 자신이 논리적으로 판단을 하는 것이 바람직하고 자신이 그렇다고 생각하지만,
실제의 사고 과정은 (대부분) 논리적이 아니라 직관적이라는 것이죠.
전문가들의 경우 딱 보면 알지만, 왜 그런지 설명 하는 것은 오히려 힘들어 하거나 오랜 시간이 걸리는 경우가 많다는 것입니다.
논리적 사고를 판단의 근거로 삼기 보다는, 판단 후에 설명/검증을 위해서 하게되는 경우가 더 일반적이라는 얘기죠. (보기에는 논리적 사고의 결과로 판단을 내린 것 같아 보일지라도...)
제가 기억하기로는 대충 그런 내용이었습니다.
---
"경험"과 그로 얻어지는 "직관"에 대한 님의 생각을 지지해 주는 책인 것 같아서 몇자 적어봅니다.
물론, 제 생각도 비슷합니다.
http://hj-lee.github.io/
뒤늦게
뒤늦게 읽었네요.
좋은 글 감사합니다.
大逆戰
大逆戰
댓글 달기