[플밍노트] 실험 주도 프로그래밍

신입 프로그래머들이 자주 사용하는 기법 중에 하나로 실험 주도 개발이 있다. 정식 용어는 아니고 그들이 프로그래밍 하는 과정이 일종에 물리 법칙에 관한 실험을 하는 것과 유사해서 나는 그렇게 부른다. 잘 사용한다면 빠르게 결과물을 도출할 수 있는 방법이지만 잘못 사용된다면 아주 위험할 수 있는 방법이기에 간단하게 그 방식에 관한 생각을 정리해 본다.

실험 주도 프로그래밍

int BadFunc()
{
#ifdef PLATFORM_A
    some code
#else
    some other code
#endif

    return 1;
}

위와 같은 코드에서 PLATFORM_A 환경에서만 크래시가 발생한다. 어떻게 해야 할까? 이때 우리가 대처할 수 있는 방법은 크게 두 가지 방법이 있다. 1) 덤프 파일을 분석해서 정확하게 왜 크래시가 발생했는지를 알아낸다. 2) 위 코드가 정확하게 어떤 동작을 하는 코드인지 이해하고 무엇이 잘못되었는지 생각해서 잘못된 원인을 찾아낸다. 그런데 여기에 이 두가지 방법이 아닌 전혀 새로운 방식의 접근 방법이 있다. 바로 실험 주도 프로그래밍이다.

실험 주도 프로그래밍은 물리적 현상을 추론해 내는데 사용하는 가설, 검증 방식의 실험과 유사하게 코드를 고쳐 나가는 것을 말한다. 전체적인 시나리오는 이렇다. 1) 가설을 세운다. 2) 실험을 한다. 3) 결과를 관찰한다. 아주 심플하다. 그러면 간단한 가설을 세워보자. PLATFORM_A에서만 크래시가 나니 some code의 문제일 수 있다. some code를 주석처리 한 다음 컴파일을 해본다. 이제 결과를 관찰한다. 잘 동작한다면 가설이 맞은 것이니 주석을 풀고 그 코드 내부에 대한 추가적인 세부 가설을 세우고 실험을 진행한다. 가설이 맞지 않다면 주석을 풀고 return 1 부분에 관한 가설을 세우고 다시 테스트를 한다.

이 방식은 관찰된 현상에 따라서 가설만 잘 세운다면 본질적인 구조를 모르고도 굉장히 빠르게 문제를 수정할 수 있다는 장점이 있다. 그래서 대체로 다른 이들이 작성한 코드를 유지보수해야 하는 신입 프로그래머들이 많이 사용하는 것 같다.

가설, 검증 구조가 가지는 함정

앞선 실험 주도 프로그래밍의 가설, 검증 방식의 함정은 잘 동작하는 결과를 가졌을 때 발견된다. 예를들어 보자. 앞선 코드에서 가설, 검증 과정을 통해서 return 뒤의 1을 0으로 바꾸니 크래시가 발생하지 않고 잘 동작했다고 생각해보자. 그러면 이제 거기서 1을 0으로 바꾸고 끝을 내도 되는 것일까? 만약 끝을 내도 된다고 생각한다면 다음 우스개에 나오는 과학자와 비슷한 오류를 범하는 꼴이 된다.

옛날에 한 과학자가 있었다. 파리에 관한 연구를 하던 과학자는 한날 박수를 치니 파리가 날아가는 것을 보고는 파리에게도 분명 귀가 있을 거라는 생각을 한다. 그리고 그 귀가 어디에 달려 있을까를 알아내기로 했다. 과학자는 가설을 세운다. 처음에는 파리의 귀가 다리에 붙어 있을 거라고 생각했다. 그래서 다리를 뜯었다. 그리고는 옆에서 박수 소리를 들려줬다. 그러니 파리가 날아서 도망을 갔다. 다리에 귀가 있다면 박수 소리를 못 들어 도망 가지 못했을테니 과학자는 다리는 아니라고 생각한다. 다음은 날개에 귀가 있다는 가설을 세운다. 그리고는 날개를 뜯고 실험을 했다. 그러자 이번에는 박수를 쳐도 파리가 도망 가지 않고 가만히 있었다. 과학자는 당당하게 실험 결과를 토대로 파리의 귀는 날개에 달려 있다는 논문을 쓴다.

사실 위의 우스개는 과학이 가지는 본질적인 문제점을 풍자한 것이다. 요즘 발표되는 많은 논문들이 사실은 위와 다름이 없다. 어느 날은 비타민 과다 복용이 좋다고 했다가 어느 날은 과다 복용은 위험하다고 한다. 어느 날은 운동이 건강에 좋다고 했다가, 어느 날은 운동하다 죽는 사람이 더 많다고 한다. 과학이 이러니 프로그래밍도 이렇게 하면 되는 것일까? 하지만 거기엔 엄청난 차이가 있다. 가장 큰 차이는 과학이 탐구하는 대상인 자연, 인간, 우주 같은 것들은 본질적으로 우리가 만든 것이 아니다. 그러니 우리는 그 원리를 알 수 없기 때문에 귀납적 추론을 할 수 밖에 없다. 반대로 프로그래밍은 아주 사소한 약속 하나부터 모두 우리가 정한 것이다. 그러니 이 세계는 본질적으로는 귀납적으로 추리할 필요가 없다. 모두 우리가 만든 약속을 쌓아 올려서 만든 것이니 당연히 그 모든 것을 연역적인 방식으로 설명할 수 있어야 하는 것이다.

앞선 return으로 돌아가 보자. 1을 0으로 바꾼 결과 정상 동작하는 것을 관찰했다면 그 다음은 무엇을 해야 할까? 왜 1이면 동작하지 않고, 0이면 동작하는지에 대한 설명을 할 수 있어야 한다. 만약 그걸 설명하지 못한다면 원인을 찾은 것도 아니고 코드를 이해한 것도 아니다. 이 단계에서 완전한 설명이 가능하고 그것을 다른 프로그래머에게 이야기를 했을 때에 똑같이 동의를 한다면 그것은 어느 정도 올바른 해결책을 찾은 것으로 간주해도 될 것이다.

완전한 이해

신입 프로그래머들이 실험 주도 프로그래밍의 함정에 빠지는 것은 그들이 이해해야 하는 어떤 대상에 대해서 완전한 이해를 하지 않기 때문인 것 같다. 완전한 이해의 대상은 기존에 작성된 코드일 수도 있고, 특정 프로그래밍 언어일 수도 있고, 특정 라이브러리나 프레임워크일 수도 있고, 특정 운영체제일 수도 있다. 심지어는 특정 표준에 대한 스펙일 수도 있다. 그런 대상에 대한 이해가 부족하기 때문에 어느 지점에서 문제가 발생한다는 것을 관찰했을 때에도 느낌표가 없고 계속 물음표만 존재하는 것이다.

그렇다면 많은 신입 프로그래머들이 실력 향상을 원한다고 이야기를 하면서도 정작 중요한 문제 해결에 필요한 완전한 이해를 하지 않는 것일까? 아마도 그 과정이 시간이 오래 걸리고 고통스럽기 때문일 것이다. 따분하고 지루한 공부가 필요하다는 의미다. 하지만 안타깝게도 현실 세계에서 내가 관찰한 대부분의 경우는 공부가 부족한 경우가 많았다. 일단 대체로 절대적으로 공부하는 시간이 부족하다. 앞선 이해의 대상 중 어떤 것이든 간단하게라도 살펴보기 위해서는 상당히 많은 시간이 필요하다. 하지만 많은 신입 프로그래머들은 그런 연속된 시간을 투자하는 것을 시간 낭비라고 생각한다. 심지어는 퇴근 후에 컴퓨터를 본다는 것은 뭔가 손해보는 일이라고 생각하기까지 한다. 다음으론 검색의 지나친 발달로 인한 폐해를 들 수 있다. 대체로 거의 모든 문제에 대해서 구글 검색을 하면 결과가 나오기 때문이다. 그러니 복잡하고 원론적인 이해 보다는 단편적인 현상에 대한 답만 알고 넘어가려는 경향이 강하다. 그렇게 빈약하게 이해를 하더라도 문제가 해결되니 말이다.

검색으로 얻는 파편적인 지식으로는 한계가 있다. 잘 쓰여진 좋은 책을 통해서 한 점이 아닌 전체적인 선 모양이 어떤지를 알 수 있는 공부를 해야 한다. 또 일주일에 한두시간 공부하는 것으로도 한계가 있다. 아마 환갑 잔치 때가 되서야 목표에 도달할 수 있을 것이다. 훨씬 더 많은 시간을 투자해야 한다. 물론 취미로 프로그래밍을 하거나 직업의 영속성이 크게 중요하지 않은 사람들에겐 이런 조언이 별로 중요하지 않을 수 있다. 하지만 반대로 해자를 구축하고 장기적으로 살아 남아야 하는 사람이라면 그래야 하지 않을까라는 생각을 해본다.

여기서는 제자리에 머물기 위해서라면 아주 빨리 달려야 해.
만약 다른 곳에 가려면 지금보다 두 배는 빨리 뛰어야 하지.

— 거울 나라의 앨리스