메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

리팩토링(3) - 단순함의 미학

한빛미디어

|

2005-07-15

|

by HANBIT

12,896

저자: 임백준
출처: 임백준의 소프트웨어 산책(2005, 한빛미디어) 중 제3장 리팩토링



* 리팩토링(1) - 과거와 대결하는 프로그래머의 무기
* 리팩토링(2) - 복잡성에 대한 두려움

이데올로기나 정치적 입장을 떠나서 인간으로서의 칼 마르크스(Karl Marx)의 생애를 보면 대단하다는 생각을 하지 않을 수 없다. 경제학, 정치학, 사회학, 철학 등에 두루 걸친 그의 영향은 20세기를 수놓았던 사회주의의 물결이 잠잠해진 지금도 적지 않게 남아있지만 필자가 특히 흥미로웠던 것은 말년의 그가 근심을 잊기 위해서 대수학(algebra)에 몰두했었다는 사실이다. 뒤늦게 손댄 수학이었음에도 불구하고 만만하지 않은 수준을 보여주었다고 하니 능력이 뛰어난 사람에겐 전문적인 교육이나 나이의 장벽이 별 의미가 없는 것처럼 보인다.

그가 한때 ‘인식(認識)의 시(詩)’라는 칭송까지 받은 <자본>을 저술할 때 거대하고 복잡한 자본주의를 분석하기 위해서 사용한 방법은 “추상과 구체의 변증법”이라고 알려진 방법이었다. 피상적으로만 모습을 드러내는 현실은 이면에서 복잡하게 얽힌 채 전개되고 있는 본질적이고 궁극적인 움직임을 드러내지 않는다. 그러기는커녕 본질적인 움직임을 종종 숨기고 감춘다. 이와 같이 왜곡되고 뒤틀린 현실을 정확하게 바라보기 위해서 마르크스는 현실을 잘게 쪼개서 각각의 대상을 본질적으로 이해한 다음 쪼개진 대상을 결합하면서 전체를 이해해 나가는 방법을 구사했다. 어떻게 보면 알고리즘에서 말하는 병합 정렬(merge sorting)을 닮은 이 방법은 다음과 같은 두 개의 단계로 이루어졌다.

1. 현실을 구성하는 대상을 하나씩 잘게 쪼개서 한 눈에 파악할 수 있는 작은 대상으로 재구성 한다. 즉, 구체적이고 전체적인 현실에서 추상적이고 개별적인 밑바닥으로 내려간다.

2. 파편화된 작은 대상의 본질을 파악한다. 정체가 파악된 대상들 사이에 존재하는 관련성을 연구하면서 본래의 현실 전체에 이를 때까지 하나씩 결합해 나간다. 즉, 개별적인 추상에서 전체적인 구체로 상승해 올라간다.

현실을 구성하는 최종 단위는 작기 때문에 그 의미가 한 눈에 들어온다. 하지만 그 의미가 전체 현실 속에서 어떤 위치를 갖는지 알 수 없기 때문에 ‘추상적’이다. 예를 들어서 자동차를 볼트와 너트 수준까지 분해했을 때, 작은 나사 한 개가 제공하는 기능 자체는 한 눈에 이해가 되지만 그것이 완성된 자동차 내부에서 어떤 위치에 있는지, 어떤 역할을 하는지는 알 수 없기 때문에 추상적이다.

그에 비해서 완성된 자동차의 모습은 그것이 최종적으로 어떤 모습을 갖추고 있는지, 성능과 기능은 어떤 수준인지를 분명하게 보여주기 때문에 ‘구체적’이다. 그렇지만 자동차를 각각의 구성부분까지 세밀하게 분석하기 전에는 그것이 어떤 원리로 움직이는지, 어떤 관리를 필요로 하는지, 문제가 생겼을 때 어디가 어떻게 고장 난 것인지 알기 어렵다. 다시 말해서 하나의 완성된 전체로서 주어진 자동차는 구체적이지만 그것이 분석되기 전에는 막연한 구체일 뿐이다. 그리하여 구체를 부정하고 추상의 영역으로 내려갔다가 다시 추상을 부정하고 구체로 되돌아오는 변증법을 거쳤을 때 막연한 구체는 비로소 파악된 구체가 된다. 이미 눈치 챘겠지만 프로그래밍에도 이와 동일한 방법론이 존재한다. 유명한 알고리즘 방법론인 ‘분할 점령(divide and conquer)’이 바로 그것이다.

커니건과 파이크는 ‘좋은’ 프로그램의 특징을 네 가지로 압축했다. 그 네 가지 속성이란 프로그램을 짧고 관리하게 편하게 만들어 주는 단순성(simplicity), 이해하기 쉽게 만들어 주는 명확성(clarity), 새로운 상황에 잘 적응하고 확장이 가능하도록 해주는 일반성(generality), 그리고 사람이 해야 하는 수고를 덜어주는 자동화(automation)였다. 프로그래머라면 누구든지 이와 같은 프로그램의 속성을 구현하기 위해서 노력할 필요가 있는데 “추상과 구체의 변증법”은 그런 노력의 과정에서 반드시 필요한 프로그래밍의 방법론에 해당한다.

객체지향 프로그래밍보다 범위가 넓은 보편적인 프로그래밍의 관점에서 생각했을 때 리팩토링이란 이러한 네 가지 속성에 반대되는 모습을 가지고 있는 프로그램을 만났을 때 프로그래머가 취해야 하는 조치를 의미한다고 볼 수 있다. 리팩토링은 일반적으로 객체의 설계와 관련된 맥락에서 이야기되는 것이 보통이지만 객체와 상관없는 순수한 알고리즘도 리팩토링의 대상이 될 수 있다. 그 예로 얼마 전에 필자는 회사에서 소프트웨어의 새로운 버전이 출시되기 직전에 다른 사람의 코드에서 논리적 오류를 발견한 적이 있었다. 빠져 있는 논리를 채워 넣기 위해서 하던 일을 모두 중단하고 그와 나란히 앉아서 급히 페어 프로그래밍(pair programming)을 수행했다.

전체적인 논리의 완결성을 고려하느라 코딩은 서둘러서 할 수밖에 없었는데, 시간이 매우 급박했기 때문에 생각이 떠오르는 대로 키보드를 두드리며 전진해 나갔다. 그러다가 어느 지점에서 특정한 조건이 충족되면 주어진 (자바 언어에서 ArrayList로 선언된) 리스트의 내용을 모두 비워야 하는 순간이 있었다. 필자를 포함한 두 사람은 급히 다음과 같은 코드를 작성했다.


    for (int index = 0; index < list.size(); index++) {
        list.remove(index);
    }   


필요한 내용을 모두 구현한 다음에 테스트를 수행했는데, 결과가 원하는 대로 나오는 것 같더니, 어느 순간 엉뚱한 결과가 나오기도 했다. 그래서 두 사람은 디버거(debugger)를 돌리면서 변수의 상태를 하나씩 검사하다가 위의 루프에서 오류를 발견하게 되었다. 위의 루프가 담고 있는 오류를 발견하지 못한 사람은 잠깐 생각해보기 바란다.

리스트에 4개의 객체가 담겨 있다고 하자. 이 때 원래의 의도는 루프의 본문이 네 번 수행되면서 안에 담긴 내용을 모두 제거하는 것이지만 실제로는 루프가 한 번 돌 때마다 size가 1씩 줄어들기 때문에 루프의 본문은 두 번만 돌고 빠져나오게 되어 있다. 루프가 수행된 결과는 비어있는 리스트가 아니라 두 개의 객체가 담겨 있는 리스트였기 때문에 오동작이 발생한 것이었다. 버그를 발견한 두 사람은 서로 마주보고 웃으면서 루프의 내용을 다음과 같이 고쳤다.


    for (int index = list.size() - 1; index >= 0; index--) {
        list.remove(index);
    }


프로그램은 정상적으로 동작했고 모든 것이 해결되었다. 하지만 두 사람이 급하게 작성한 코드를 제3자에게 검토(review)해 달라고 부탁했을 때 제3의 프로그래머는 위의 루프가 도대체 왜 필요하냐고 말하더니, 코드를 이렇게 고쳤다.


    list = new ArrayList();


필자는 속으로 깜짝 놀랐다. 아무리 시간에 쫓겨서 다른 생각을 할 여유가 없었다고 한들, 이렇게 한 줄로 쓸 코드를 놓고 루프를 돌리고 있었다는 한심한 생각에 잠깐 괴로웠다. 평상시에 ‘단순성’을 그토록 강조했건만 정작 급박한 상황에 몰렸을 때는 단순성을 배반한 자신의 모습을 바라보는 것이 마치 신실하게 예수를 따르다가 위험에 빠졌을 때 예수를 부정한 베드로의 심정과 비슷했을 것이다. 어쨌든 이 짧은 경험은 코드리뷰(code review)의 중요성과 함께 리팩토링의 생활화가 소프트웨어의 질을 어떻게 향상시키는지에 대한 좋은 깨달음을 주었다.

앞에서 이야기했던 ‘객체의 소나기’에 대한 비판이며 지금 이야기 하는 ‘추상과 구체의 변증법’ 그리고 ‘리팩토링의 생활화’는 모두 하나의 공통점을 가지고 있다. 그들은 모두 ‘단순함의 미학’을 추구하고 있다는 것이 공통점이다. 우주의 법칙이 그렇듯이 살아있는 소프트웨어는 항상 복잡성, 즉 엔트로피가 상승하는 방향으로 운동하는 속성을 가지고 있다. 사용자의 요구조건이 늘어날수록, 프로그래머의 수가 증가할수록, 소프트웨어의 생명주기가 길어질수록, 소프트웨어의 엔트로피는 상승한다. 아무리 깔끔하고 단정하게 설계된 소프트웨어라고 하더라도 이와 같은 우주의 법칙을 거스를 도리는 없다. 커니건과 파이크가 지적한 네 가지 속성을 아무리 철저하게 구현한 프로그램이라고 해도 몇 사람의 프로그래머가 거쳐 가고 나면 깔끔하던 코드는 불어터진 스파게티처럼 보기 흉한 밀가루 반죽이 되어 버린다.

소프트웨어는 이렇게 본질적으로 복잡해지려는 속성을 가지고 있기 때문에 프로그래머들은 의도적으로 ‘단순함의 미학’을 추구해야 한다. 프로그래머들은 기하급수적으로 증가하는 엔트로피의 열기에 휩쓸리지 않기 위해서 쉴 새 없이 ‘단순함’의 아름다움을 기억하고 큰 소리로 노래해야 한다. ‘상속’, ‘캡슐화’, ‘추상화’와 같은 객체 지향의 초식은 유용하지만 오직 그들이 ‘단순함의 미학’에 복무하는 한에 있어서만 유용하다. 그 미학의 경계를 벗어날 때 그들은 치기에 사로잡힌 프로그래머의 자기 과시로 전락해 버린다. 복잡하게 얽히고설킨 소프트웨어를 잘게 쪼개서 분석하는 변증법도 여기에서는 결국 ‘단순함의 미학’을 증명하기 위한 방법론일 뿐이다.

소프트웨어의 세계에서는 오직 ‘단순한’ 것만이 아름답다. 이 점을 분명히 염두에 두게 되었다면 이제 리팩토링에 대해서 말할 수 있다. 리팩토링은, 복잡해지려고 몸부림치는 소프트웨어를 억눌러서 오히려 단순해지는 방향으로 끌고 가는 냉정한 ‘초식’이다. 소프트웨어 코드를 화가가 붓질을 하듯이 천천히 그려나갔던 그레이엄도, 추상과 구체의 변증법을 펼쳤던 마르크스도, ‘객체의 소나기’ 앞에서 질려버릴 수밖에 없었던 평범한 프로그래머도, 결국은 모두 ‘복잡성’과의 전쟁을 하고 있었던 것이다. 그 전쟁은 프로그래머의 숙명이다. 그래서 리팩토링은 영원히 이길 수 없지만 그렇다고 포기할 수도 없는 힘겨운 전쟁에 나서는 병사의 무기이다.
TAG :
댓글 입력
자료실

최근 본 상품0