Eternity's Chit-Chat

aeternum.egloos.com



진화적인 설계-1.우리는 실패하고 있다 2부 Evolutionary Design

부채는 쌓이고
요구사항 변경이 외부 품질의 적이라면 잘 못 된 메타포는 내부 품질의 적이다. 다른 성숙한 공학 분야에 비해 역사가 짧은 소프트웨어 개발 커뮤니티는 건축학이나 제조업과 같은 다른 공학 분야로부터 다양한 메타포를 차용해 왔다. 건축가가 설계 도면을 완성하면 하도급자에게 전달하여 건물을 짓도록 하는 소위 건축 메타포는 소프트웨어 개발 프로세스의 다양한 측면에 악영향을 미쳤다. 이것은 앞서 살펴 본 ‘요구사항 변경은 악’이라는 사상과도 연결되는데 건축이 시작된 후에 건물의 설계를 변경하는 것은 비용이 많이 들거나 혹은 불가능에 가깝기 때문이다.

건축 메타포의 가장 큰 단점은 설계와 구현을 완전히 분리된 단계로 다루도록 강요한 점이다. 설계와 구현의 이분법적인 구분은 모든 설계가 완전히 완료된 후에 코드를 구현하는 것이 올바른 방법이라는 소프트웨어 개발 커뮤니티의 오랜 미신을 낳고 말았다. 이와 같이 구현 전에 모든 설계를 확정하는 방식을 ‘과도한 사전 설계(Big Design Up-Front)’라고 부른다.

과도한 사전 설계의 단점은 코드의 구현 과정에서 얻게 되는 피드백을 통해 설계를 개선할 수 있는 여지를 원천적으로 차단한다는 점이다. 설계에 부합하는 제품을 제공하는 것이 목적인 제조업이나, 설계 도면에 어그러짐 없이 건물을 건설하는 것이 목적인 건축학과 달리 소프트웨어는 구체적인 피드백과 배움을 통해 설계를 성장시켜 나가야 하는 분야이다.

프로젝트에 합류한 지 일주일 만에 설계한 모듈을 6개월이 지난 후에 다시 설계한다면 시간이 흐른 후에 설계한 모듈이 더 높은 품질을 지닐 확률은 100%에 가깝다. 소프트웨어 설계는 시간의 흐름 속에서 얻어진 지식을 토양으로 최적의 설계를 배양하는 배움의 과정이다. 그리고 설계가 현재의 요구사항을 만족하는 지에 대한 가장 확실한 피드백 받을 수 있는 유일한 방법은 해당 설계를 구현 해 보는 것이다.

공학 메타포로 인해 오염된 개발 프로세스는 구현을 통한 피드백을 원천적으로 차단한 채 과도한 사전 설계에 순응하는 코드를 생산할 것을 요구한다. 그리고 강력한 제한 조치에도 불구하고 요구사항은 결국에는 변경되고 만다. 아직 구현하지 않은 기능의 요구사항에 대한 변경은 사전 설계를 순수한 낭비 요소로 만든다. 설상가상으로 앞서 살펴 본 것처럼 이미 구현이 완료된 기능에 대한 요구사항 변경은 더 큰 비용을 초래한다.

요구사항 변경에 지친 설계자(또는 개발자)들이 취할 수 있는 최후의 방법은 요구사항이 변경되리라 예상되는 모든 항목들을 미리 설계에 반영하는 것이다. 예상되는 모든 변경에 대비해서 유연성을 추가해 놓는다면 차후에 예상된 변경이 발생할 경우 변경 곡선의 기울기를 완만하게 낮출 수 있으리라는 기대 때문이다.

그러나 소프트웨어 개발 영역이라고 해서 머피의 법칙을 피할 수는 없다. 변경이 발생하리라는 기대로 추가한 유연성은 변경이 발생하지 않을 경우 코드에 불필요한 복잡도를 추가하는 계륵으로 전락하고 말 것이다. 유연성이 필요하지 않은 부분에 추가된 유연성은 코드를 이해하기 어렵게 만든다. 현재 사용되지 않지만 언제가 사용될 것이라는 희망에 추가된 코드는 실제로 사용되기 전까지는 코드에 부채를 쌓을 뿐이다. 부채는 언젠가는 갚아야 할 빚이다. 그러나 대부분의 경우 예측은 빗나가고 해결되지 않은 부채는 이자에 이자를 거듭하며 소프트웨어의 유지보수성을 갉아 먹고 만다.

결과적으로 모든 사람이 코드를 수정해야 한다는 사실은 인식하고 있지만 아무도 코드를 책임지려고 하지 않게 된다. 시간이 지날수록 소프트웨어는 녹슬어 가고 내부 품질 속성 그래프는 바닥을 친 채 상승하려는 어떠한 의지도 보여주지 못 하게 된다. 소프트웨어 개발과 관련된 가정을 송두리째 뒤집어야 한다는 절박함이 소프트웨어 개발 커뮤니티에 서서히 퍼져 나가고 있었다.

변경을 수용하라
요구사항 분석 단계에서 요구사항 오류를 수정하는 비용에 비해 유지보수 단계에서 요구사항 오류를 수정할 경우 100배에서 최대 200배 이상의 비용이 소요 된다는 가정 이면에는 요구사항 변경이 오류라는 사상이 깔려 있다. 결론을 말하면 요구사항 변경은 오류가 아니다. 요구사항 변경은 사용자가 우리의 소프트웨어에 대해 더 잘 알게 되었다는 환영의 신호다. 요구사항 변경은 개발자로서 우리가 개발할 소프트웨어 대해 더 잘 알게 되었다는 긍정의 신호다.

따라서 요구사항 변경을 제한하는 전통적인 요구사항 관리 방식은 근본적으로 요구사항 변경은 오류라는 잘못된 가정에 기인하고 있다. 7%라는 비참한 통계치를 수직 상승시키기 위해서는 요구사항 변경을 수용할 수 있는 민첩한 개발 프로세스가 필요하다.

앞에서 살펴 본 바와 같이 개발 과정을 둔하게 만드는 원인은 결정을 내릴 수 있는 정보의 양이 부족한 상태에서 불안정한 결정을 내리고 그것을 변경하지 않으려고 하기 때문이다. 요구사항이 명확하지 않음에도 불구하고 요구사항 베이스라인을 확정한다. 요구사항이 변경될 수도 있는 상황에서 미리 설계를 고정시키고 불필요할 수도 있는 유연성을 추가하기 위해 코드 복잡도를 높인다. 이러한 문제의 원인은 요구사항 변경이란 나쁜 것이라는 가정에서 출발하기 때문이다.

해결방법은 간단하다. Kent Beck의 말처럼 변경을 수용하라(Embrace the Change). 변경이란 필연적인 것이며 우리가 무언가 유용한 것을 좀 더 배웠다는 증거다. 필요한 것은 요구사항 변경을 제한하는 프로세스가 아니라 요구사항 변경을 촉진시키고 수용할 수 있는 프로세스다. 그리고 그 중심에 ‘진화적 설계’가 위치하고 있다.

진화적 설계(Evolutionary Design)
요구사항 변경과 과도한 사전 설계의 근본적인 원인은 명확한 오늘에 충실하기 보다는 불확실한 내일에 대비하려는 인간의 본성 때문이다. 인간은 제어할 수 없는 것을 제어하고 있다고 착각하는 경향이 있다. 발생할 수 있는 경우의 수는 신에게 맡겨두고 우리는 오늘의 주사위에 충실할 필요가 있다.

진화적 설계의 모토는 내일이 아니라 오늘에 충실하자는 것이다. 오늘을 위해 요구사항을 수집하고 오늘을 위해 설계하라. 적어도 소프트웨어를 개발할 때는 ‘오늘 할 일을 내일로 미루지 말라’는 벤자민 프랭클린의 명언은 잠시 잊어버려도 좋다. 만약 오늘 주어진 정보가 충분하지 않다면 내일로 미루어라. 오늘은 오늘 주어진 정보만으로 가능한 작업을 하라.

진화적 설계는 현재를 위한 설계다. 오늘 구현에 필요한 요구사항에 대해서만 고객과 커뮤니케이션하라. 내일 구현할 요구사항은 간단한 목록 정도로만 정리한 채 책상 한 편으로 치워 두도록 하라. 언제 변경될 지, 언제 구현할 지 모르는 요구사항을 정리하는데 너무 많은 시간을 소모하지 말라. 오늘의 요구사항을 만족시킬 정도의 설계만 하라. 오늘의 요구사항이 필요로 하지 않는 유연성은 추가하지 말라. 그 유연성은 결코 필요하지 않을 지도 모르기 때문이다. 불필요한 유연성은 불필요한 복잡도의 원인이다.

진화적 설계는 피드백 메커니즘을 통해 진행된다. 피드백의 주기가 짧으면 짧을수록 오늘을 위해 작업하고 있는 지 여부를 좀 더 확실하게 알 수 있다. 요구사항에 대한 고객의 피드백 주기를 줄이는 방법은 고객에게 좀 더 짧은 주기로 소프트웨어를 배포하여 확인을 받는 것이다. 소프트웨어를 점진적(incremental)으로 개발함으로써 불명확한 채로 잊혀지는 요구사항의 양을 줄일 수 있다.

설계에 대한 피드백 주기를 줄이는 방법은 개발주기를 여러 개의 반복iteration으로 나누고 각 반복에서 구현해야 하는 부분에 대해서만 설계하고 곧장 구현에 돌입하는 것이다. 그리고 더 효과적이라고 알려진 방법은 설계 후에 구현하는 것이 아니라 구현 후에 설계하는 것이다. 현재 이와 같은 기법은 ‘리팩토링(Refactoring)’이라는 이름으로 널리 알려져 있다.

진화적 설계를 위한 프랙티스와 원칙
지난 10년 간 소프트웨어 개발 커뮤니티에 가장 큰 영향을 미친 프랙티스를 뽑으라고 한다면 주저할 것도 없이 테스트 주도 개발(Test-Driven Development, TDD )을 선택할 것이다. TDD의 기본 흐름은 테스트를 작성하고, 테스트를 통과하는 가장 간단한 코드를 작성한 후(이 시간 동안에는 중복이 있어도 무방하다), 리팩토링을 통해 중복을 제거하는 것이다. TDD의 결과는 “작동하는 깔끔한 코드(clean code that works)” 다.

여러분의 도구 상자 안에 TDD와 리팩토링이 들어 있다면 이러한 접근 방식을 가능한 모든 경우에 사용할 것을 권한다. 리팩토링은 화려하지만 과도한 설계가 아니라 투박하지만 적합한 설계에 이를 수 있는 방법을 알려준다. TDD는 언제라도 리팩토링할 수 있는 안전망을 제공한다. 이 두 가지 지침을 활용하면 과도한 설계와 분석 마비 상태에서 허비하는 시간을 줄이고 다양한 기능을 포함하는 품질 높은 코드를 빠른 시간 안에 작성할 수 있게 될 것이다.

TDD와 리팩토링이 개인 차원에서 진화적인 설계를 가능하게 하는 실천방법이라면 '지속적인 통합(Continuous Integration)'은 팀 차원에서 이를 가능하게 하는 지원 도구다. 협업하는 사람들간의 주기적인 동기화를 통해 피드백을 얻을 수 있다면 불안과 근심 없이 설계를 발전시킬 수 있다.

[진화적 설계를] 가능하게 하는 많은 실천 방법들이 존재한다. 핵심적인 실천 방법은 테스팅과 지속적인 통합이다. 테스트를 통해 얻어지는 안정성 없이는 XP의 나머지 부분들이 불가능해질 것이다. 지속적인 통합은 팀을 동기화 시키기 위해 필요하며, 이를 통해 다른 사람들의 코드와 통합하는 것에 대해 걱정할 필요 없이 자신의 코드를 수정할 수 있다. 이 두 실천 방법은 변경 곡선에 큰 영향을 준다. … 리팩토링 역시 유사한 효과가 있다. XP가 제안하는 방식대로 코드를 리팩토링하는 사람이라면 느슨하고, 임의적인 구조 변경과는 확연히 다른 차이를 느끼게 될 것이다

- Martin Fowler, Is Design Dead?

Martin Fowler는 그의 저서 “리팩토링”에서 유연성에 대한 관점이 변했음을 언급하고 있다.

리팩토링에서는, 변경의 위험에 대한 접근을 다르게 한다. 여전히 잠재적 변경이나 융통성 있는 솔루션에 대해서는 생각한다. 그러나 이 융통성 있는 솔루션을 구현하는 대신 자신에게 “간단한 솔루션을 융통성 있는 솔루션으로 리팩토링하는 것이 얼마나 어려울까?”하고 묻는다. 만약 대답이 “아주 쉽다(거의 대부분의 경우겠지만)”라면 그냥 간단한 솔루션으로 구현한다.

- Martin Fowler, Refactoring

그러나 TDD와 리팩토링과 같은 프랙티스를 따른다고 해서 진화적 설계의 길에 들어서는 것은 아니다. 설계를 진화시키기 위해서는 훌륭한 설계 원칙과 원리, 다양한 이론에 관해 학습하고 이를 지속적으로 적용시킬 수 있는 기술이 필요하다. 이론적인 토대 위에 서지 못한 프랙티스는 엉성하고 겉만 화려한 단순한 구호에 지나지 않는다. 연재를 진행하는 동안 설계를 진화시키기 위해 알아두면 유용한 몇 가지 설계 원칙이나 원리에 관해 간단히 언급할 것이다.

진화적 설계를 지탱하기 위해 사용할 수 있는 여러 가지 프랙티스와 원리들이 있지만 본 연재에서는 ‘패턴(Pattern)’의 활용에 초점을 맞추고자 한다.


덧글

  • mangofever 2011/04/14 13:53 # 삭제

    이런 깔끔한 정리ㅠㅠ
    복잡하게 단어만 맴돌던 제 머릿속을 시원하게 뻥 뚫어주는 느낌었습니다~

    앞으로도 좋은 글 많이 부탁드립니다!
  • 이터너티 2011/04/22 01:02 #

    방문 감사드립니다. 글을 쓸 수 있는 시간이 점점 부족해져서 걱정이네요.
    제 글이 조금이나마 도움이 되었으면 좋겠습니다. ^^
  • 우울한 인상 2011/04/16 15:16 # 삭제

    정말 잘 읽었습니다.
    내용도 내용이지만 문장들도 너무 좋네요!
  • 이터너티 2011/04/22 01:01 #

    감사합니다. 요즘에 글이 잘 안써져서 걱정인데 우울한 인상님 덧글에서 용기를 얻었네요. ^^
  • 민달 2011/04/18 10:17 # 삭제

    최근 파견 나온 팀에서 스티븐 맥코넬의 소프트웨어 생존전략이라는 책을 스터디하고 있어요. 책에는 프로젝트 후반기로 갈수록 변경(=변화)을 잘 통제해야 프로젝트가 성공한다는 얘기가 나오더라고요. 이 생각을 완전히 부정하는 것은 아니지만 XP에서 얘기하는 변화를 포용하라라는 것과는 입장의 차이가 있음이 느껴졌고, 프로젝트의 성공이 과연 누구(고객 혹은 프로젝트개발팀)의 성공이냐라는 데 상당히 의문이 생기더라고요.

    경험적으로 굉장히 공감합니다. 오늘도 글 잘 읽고 가요 ㅎㅎ
  • 이터너티 2011/04/22 00:59 #

    저도 전적으로 공감하지는 못 하지만 그렇다고 요구사항 변경을 무조건적으로 수용하는 것은 문제가 있을 것 같아요. 가치가 있는 변경을 수용하기 위해서는 가치 없는 변경에 대한 효과적인 통제 장치는 필요할 것 같네요. XP나 스크럼에서 이터레이션 중간에 들어오는 추가적인 요구사항이나 변경은 허용하지 않는 것도 비슷한 맥락이 아닐까요?

    고객에게 일정이 가장 큰 가치가 있다면 프로젝트 후반부에 일정을 뒤흔드는 요구사항 변경을 수용하는 것은 무리가 있을 것 같습니다. 정답은 없을 것 같네요. ^^

    민창 과장님 이쪽에 오시면 연락 주세요. 식사 한번 하게요. ㅎㅎ
  • 백명석 2011/04/26 20:14 # 삭제

    항상 조영호님의 글을 잘 받아보고 있습니다. 그런데 윗글에서
    "진화적 설계를 위한 프랙티스와 원칙" 바로 윗 문장에서 리팩토링을 설명하실 때 "구현 전에 설계하는 것"이 아니라 "구현 후에 설계하는 것"이 맞지 않나요 ?
  • 이터너티 2011/04/27 01:13 #

    백명석님 안녕하세요.
    말씀하신 것처럼 "구현 후에 설계하는 것"이 맞습니다.
    본문 내용 수정했습니다.
    잘못된 내용을 알려 주셔서 감사합니다. ^^
  • 김종욱 2011/06/10 20:55 # 삭제

    우연히 찾아왔습니다.
    이런 진리의 결정체 같은 찬란한 글에 눈부셔서 댓글을 달지 않을수가 없네요.
    ㅠㅠ
  • 김종욱 2011/06/10 21:12 # 삭제

    분석마비와 과도한 설계에 대항할 명쾌한 통찰을 얻었습니다.
  • expsample 2016/10/05 14:23 # 삭제

    뒤늦게 발견했습니다. 좋은 정리 감사합니다
※ 로그인 사용자만 덧글을 남길 수 있습니다.