336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
A팀은 여러 가지 리팩토링을 수행하고 있습니다. 리팩토링 하기 전 단위 테스트를 철저하게 작성하고 인수 테스트 또한 철저하게 작성했습니다. 품질에 자신감이 생긴 A팀은 QA를 요청했습니다. 며칠 후 QA가 이야기 합니다. “이렇게 버그가 없는 프로젝트가 없었어요!” 바로 배포를 해도 좋다는 QA의 승인이 떨어집니다. 개발팀은 높은 자긍심을 느끼며 배포를 합니다. 그런데 잠시 후 고객문의가 폭주합니다. 서비스에 접속이 안 된다고 합니다. 당황하며 문제를 찾아봅니다. 아뿔싸! 배포 스크립트가 정상적으로 동작하지 않은 것입니다. 배포 스크립트는 소스상의 특정 파일을 읽어 배포를 진행하는데 이번 리팩토링에서 해당 파일을 사용되는 곳을 찾지 못해 지운 것이 이유였습니다.

위 예는 End-To-End 테스트의 필요성을 얘기하기 위해 지어낸 가상 사례입니다. 하지만 현실에서도 저런 일이 종종 발생합니다. 이 글을 보면 단위 테스트와 인수 테스트를 철저하게 작성한 것으로 인해 버그가 없었고 QA도 칭찬을 할 정도였습니다. 그러나 결국 배포를 할 때 문제가 발생하였습니다. 단기적인 관점에서 보면 잘만든 단위 테스트와 인수 테스트는 아무 소용이 없었고 고객은 리팩토링으로 인해 피해만 입은 것입니다. 이러한 일이 발생한 원인은 단위 테스트와 인수 테스트 그리고 QA조차도 검증할 수 없었던 영역이 있었다는 점입니다.

End-To-End 테스트는 서비스가 고객에게 서비스 되기 위해 거치는 모든 경로를 테스트하자는 취지를 가집니다. 즉 소스 코드의 품질 뿐 아니라 빌드, 배포, 웹서비스의 경우에는 웹서버까지 테스트하자는 것입니다. 
 

그림#1. 웹서비스에 적용 된 End-To-End 테스트의 예시

위 그림은 End-To-End 테스트의 예시를 보여줍니다. 하루마다 CI서버에서 End-To-End 테스트가 시작됩니다. 우선 빌드를 해서 컴파일 오류 등이 없는지 검사하고 단위 테스트를 수행하여 결함이 없는지 검사합니다. 만약 결함이 없다면 빌드 된 결과물을 배포 시스템을 이용하여 실제 웹서버와 동일하거나 유사한 환경을 가진 웹서버로 배포합니다. 마지막으로 CI 서버는 핵심 기능에 대한 검증을 하는 인수 테스트나 UI 테스트를 실행하여 방금 빌드해서 배포 한 결과물이 문제가 없는지 확인합니다.

만약 위와 같은 End-To-End 테스트가 미리 적용되어 있었다면 위 가상 사례의 문제는 어떻게 됐을까요? 문제의 파일을 지운 그날 일일빌드 시 배포가 정상적으로 되지 않았을 것입니다. 그리고 곧 이어 실행 된 UI 테스트는 배포가 되지 않아 정상적으로 실행되지 않고 실패 메일을 발송했을 것입니다. 개발자는 다음 날 결함이 있음을 발견했을 테고 문제를 조기에 발견할 수 있었을 것입니다.

End-To-End 테스트 역시 사람이 수동으로 수행할 수 있지만 다른 테스트와 마찬가지로 자동화하는 것이 좋습니다. 또한 단위 테스트와 마찬가지로 CI 서버와 통합하면 빠른 피드백을 받아 검증시점을 앞당길 수 있습니다. 다만 End-To-End 테스트는 보통 많은 시간이 걸리기 때문에 특별한 경우가 아니라면 커밋빌드 보다는 일일빌드에서 실행하는 것이 좋다고 생각합니다. 방금 위에서 소개했던 그림이 좋은 모델이 될 수 있으리라 봅니다.

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
단위 테스트의 정의는 비교적 명확한데 비해 인수 테스트는 용어의 혼란이 있어 보입니다. 그래서 저는 위키피디아를 참고하여 인수테스트의 정의를 살펴보았습니다. 위키피디아에도 문맥에 따른 여러 가지 정의가 있지만 XP(eXtream Programming)에서 사용하는 정의가 제가 이 글에서 사용하려는 의미에 가장 근접 하다고 생각합니다.

“Acceptance testing is a term used in agile software development methodologies, particularly Extreme Programming, referring to the functional testing of a user story by the software development team during the implementation phase.”

“인수 테스트는 애자일 개발 방법론, 그러니깐 특별히 XP에서 구현단계에서 개발팀이 사용자 스토리 기반의 기능 테스트를 하는 것을 나타내기 위한 용어이다.”

위 설명에 예를 덧붙히자면 "사용자가 로그인하면 환영 메시지가 출력되어야 한다.","목록 버튼을 누르면 목록 페이지로 이동해야 한다."와 같이 특정 기능에 대해 테스트하는 것을 인수 테스트라 볼 수 있습니다. 아래는 인수 테스트의 작성예입니다. 제 블로그의 초기화면에 접속해서 "리팩토링"이라는 문자열이 있는지 검사합니다.


그림#1. Selenium 2.0을 이용한 인수 테스트

앞서 얘기했듯이 단위 테스트는 정말 탁월한 검증방법 중 하나이지만 단위라는 문맥을 벗어나지 못하는 것이 단점입니다. 다시 말해 테스트의 초점이 각 단위에 맞춰져 있기 때문에 시스템의 여러 요소들이 상호작용하는 것을 테스트하는 데 있어서는 적합하지 않습니다. 리팩토링 프로젝트를 할 때 저는 단위 테스트에 무척 매료되었던 적이 있습니다. 단위 테스트를 잘 작성하면 어떠한 리팩토링이든 잘 검증할 수 있을 것 같았습니다. 저는 특정 부분을 리팩토링을 하며 지나칠 정도로 꼼꼼하게 단위 테스트를 작성했습니다. 하지만 결국 QA 단계에서 오류가 발견되었습니다. UI 영역에서 발생한 오류였습니다. 자세히 살펴보니 제가 작성한 단위 테스트가 UI 영역 까지 검증하지 못했기 때문에 발생한 일이었습니다. 사실 이런 부분은 인수 테스트로 검증하는 것이 좋습니다. 인수 테스트는 보통 기능 단위로 테스트하고 하나의 기능은 굉장히 많은 단위가 결합되어 있습니다. 그래서 단위 테스트만으로는 찾아내기 힘든 부분인 여러 요소들이 상호작용하며 생길 수 있는 문제점을 발견할 수 있게 도와줍니다.

인수 테스트는 사람이 수행할 수도 있고 실제로 QA인력이 수행하기도 합니다. 하지만 검증비용을 생각한다면 Selenium, Fitness와 같은 도구들을 이용하여 자동화하는 것이 좋습니다. 제가 일하는 환경에서는 인수 테스트를 QA인력이 수행했었으나 최근 위 도구를 이용하여 자동화를 시도하고 있습니다. 

단위 테스트와 마찬가지로 인수 테스트를 CI 서버합하면 빠른 피드백을 받아 검증시점을 앞당길 수 있습니다. 하지만 단위 테스트와 달리 인수 테스트는 보통 실행하는 데 시간이 오래 걸리기 때문에 커밋빌드에 포함시키기 보다는 일일빌드에 포함시키는 경우가 많습니다. 하지만 이 경우 피드백 주기가 느려지는 단점이 생깁니다. 이에 대한 보완책으로써 매우 핵심적인 인수 테스트만 구분하여 커밋빌드에서 실행하는 방법이 있습니다. 




WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
앞선 글에서 영향력 검토, 검증방법, 검증시점이 리팩토링 후 검증에 있어 필수적으로 고려해야 하는 부분이며 검증비용에도 영향을 미친다고 얘기했습니다. 이에 따라 단위 테스트를 분류 해보자면 검증방법으로써 분류될 수 있습니다. 하지만 단위 테스트의 탁월한 점은 단위 테스트가 검증방법으로써 가장 탁월한 선택 중 하나가 되면서 동시에 영향력 검토와 검증시점에도 긍정적 영향을 미친다는 점입니다. 위 세 가지 관점에서 단위 테스트를 평가해보았습니다.

첫째 단위 테스트는 영향력을 줄이는 데 도움이 됩니다. 단위 테스트를 하게 되면 자연스레 의존성에 대해 생각하게 됩니다. 어떤 대상에 대해 테스트를 하려면 해당 대상을 테스트 가능한 상태로 준비시켜야 하는데 이때 의존성을 없애고 단위 테스트로서의 초점을 좀 더 명확하게 하기 위해 의존성 중지(Dependency Breaking)를 하게 됩니다. 즉, 데이터베이스에 질의하는 부분을 담당하는 DAO(Data Access Object)를 사용하고 있다면 테스트 시 관심초점이 아닌 데이터베이스에 질의하는 부분을 실제로 수행하기보다는 해당 DAO를 데이터베이스에 성공적으로 질의한 것처럼 행동하는 가짜(Stub, Fake Object, Mock 등)로 바꾸는 것입니다. 이런 과정을 통해 개발자는 여러 의존성을 느끼게 됩니다. 너무 많은 의존성이 있다고 느끼는 것은 그만큼 연결된 곳이 많다는 증거이고, 이는 영향 범위를 다시 생각해보라는 신호가 될 수 있습니다. 만약 이 신호에 귀를 기울인다면 영향범위를 좀더 줄여볼 수 있습니다. 이것은 OOP(Object Oriented Programming)에서 강조하는 의존성 최소화(Loosed Coupling)와 같은 맥락입니다.

둘째 단위 테스트는 검증비용을 줄여줍니다. 검증비용을 줄이는 데에는 크게 두 가지 부분이 작용합니다. 단위 테스트는 매우 빠르게 수행된다는 점과 한번 작성해놓은 단위 테스트는 큰 노력 없이 다시 실행할 수 있다는 점입니다. 이 부분은 사람이 테스트를 수행하는 것과 비교할 때 좀 더 명확해집니다. 사람이 어떤 기능 10개를 테스트 할 때 하나에 1분씩 본다고 해도 10분이 걸립니다. 또한 나중에 같은 테스트를 해보려면 또 10분을 투자해야 합니다. 하지만 단위 테스트를 그렇지 않습니다.

마지막으로 단위 테스트는 검증시점을 앞당겨 줍니다. 예전에 여러 코드에 존재하는 잘못된 코드 관례(Code Convention)를 수정해야 했습니다. 이 작업을 수동으로 하기엔 작업범위가 넓었기 때문에 정규표현식을 이용하여 한꺼번에 수천 개의 파일을 수정했습니다. 수정 후 특별히 컴파일 오류가 발생하지 않았고 저는 잘 되었다고 생각하고 커밋(Commit)을 했습니다. 그런데 몇 분 후 CI서버에서 메일이 한 통 왔습니다. 내용을 보니 특정 단위 테스트가 실패했다는 메일이었습니다. 자세히 살펴보니 방금 수정하면서 오류가 새롭게 생겨 발생한 문제였습니다. 저는 바로 코드를 롤백(Rollback)했고, 정규표현식을 고쳐 다시 파일을 수정한 후 일을 마무리 지을 수 있었습니다. 만약 단위 테스트가 없었더라면 어떤 결과가 일어났을까요? 코드 관례에 관계 되어 다소 부담이 적은 수정이었고 컴파일 오류 또한 발생하지 않았기 때문에 십중팔구 그냥 모르고 지나갔을 것입니다. 게다가 너무 광범위한 수정이라 QA를 받기 어려워 QA를 따로 받지 않았을 것입니다. 결국 서비스 중에 문제가 발생했을 테고 고객문의가 들어왔을 것입니다. 하지만 단위 테스트가 있었고 CI가 커밋이 되는 순간 테스트를 수행하고 실패한 경우 메일을 보내주었기 때문에 문제를 조기에 인식할 수 있었습니다.

이렇게 단위 테스트는 모든 방면에 있어 탁월함을 자랑하며 이로 인해 리팩토링에 대한 투자비용을 상당부분 줄여줍다. 다만 단위 테스트를 작성하는 데는 다소의 초기비용이 필요합니다. 하지만 리팩토링 뿐 아니라 어떤 형태로든 코드는 계속 변한다는 점을 생각해볼 때 단위 테스트를 작성해놓는 것은 계속적으로 도움을 줄 것이고 시간이 가면 갈수록 현명한 투자라고 느끼게 될 것입니다.

이어 단위 테스트는 인터페이스 변경 없이 내부코드만 변경이 있을 경우 그 영향력이 아무리 넓다 하더라도 영향력을 깨끗이 제거해줄 수 있습니다. 변경범위의 측면에서 내부코드의 변경은 인터페이스 변경과는 확연하게 구분되는 중요한 특징을 가집니다. 그것은 인터페이스 변경은 영향을 받는 곳 즉 해당 인터페이스를 사용하는 곳의 변경도 필요한 반면, 내부코드 변경의 경우는 영향 받는 곳의 코드변경은 없다는 점입니다. 이 점을 통해 알 수 있는 사실은 만약 우리가 변경되는 내부코드의 변경 전/후의 동일성을 확실히 보장한다면 내부코드 수정으로 말미암아 영향 받는 부분에 대해서는 더는 신경 쓰지 않아도 된다는 것입니다. 왜냐하면 변경이 발생한 곳은 내부코드뿐이고 내부코드의 동작은 전과 동일하기 때문입니다. 그리고 이것을 가능하게 해주는 것이 단위 테스트입니다. 그렇다면 단위 테스트를 이용하여 변경 전/후의 동일성을 확실히 보장하기 위해서는 어떻게 해야 할까요? 이 부분은 무척 어려운 주제라 생각하지만 본인의 개인적인 경험을 이야기하면 약간의 참고가 될 수 있으리라 봅니다.

저는 지난 리팩토링 프로젝트에서 리팩토링을 시작하기 전, 즉 변경 전 기준으로 단위 테스트를 신중하게 작성했습니다. 가장 먼저 주요하게 자주 실행되는 부분(주 흐름, Main Flow)에 대해서 테스트를 작성했습니다. 만약 테스트가 통과하면 예외적 흐름에 대해서도 테스트를 작성했습니다. 제가 인지하고 있는 모든 흐름을 테스트 한 후에는 커버리지(Coverage) 측정 도구를 수행해보았습니다. 커버리지 측정 도구는 아래 그림처럼 실행 된 코드 경로는 녹색으로 보여주었고 단위 테스트가 실행하지 않은 부분은 빨간색으로 보여주었습니다. 그리고 만약 빨간색 부분이 보이면 그 부분이 실행되도록 단위 테스트를 더 보강하였습니다.


그림#1. 실행 된 부분과 실행되지 않는 부분을 구분할 수 있다. 위 화면은 Undercover를 이용하여 생성했다.

이렇게 했던 이유는 단위 테스트에 의해 실행되지 않는 부분이 테스트 되지 않아 동일성 보장에 허점이 생기는 것을 방지하기 위함이었습니다. 이렇게 변경 전 기준으로 테스트 작성을 완료하고 나서야 리팩토링을 시작했습니다. 조금 고치고 테스트 수행을 해서 통과하면 다시 조금 고치는 것을 반복했습니다. 그리고 모든 리팩토링이 완료 되면 QA 부서에 리팩토링 완료를 알렸습니다. 하지만 때때로 영향력 범위 등의 문제로 QA가 검증하는 것이 합리적이지 않다고 판단하면 QA부서를 통하지 않고 리팩토링을 바로 완료시키기도 했습니다. 이것은 제가 무모해서가 아니라 단위 테스트에 의해 동일성이 보장되었다는 자신감이 있었기 때문에 가능했던 일입니다.

동일성을 보장했음에도 QA단계를 거쳤던 것은 계속적으로 강조했던 것처럼 이중검증을 해서 단위 테스트에서 동일성 보장을 하려 했음에도 실수 등으로 인해 생길 수 있는 문제를 발견하기 위해서였습니다. 이 판단은 틀리지 않았는데 아무리 신경 써서 단위 테스트를 만들어도 이후에 때로 잘못된 부분이 발생되곤 했기 때문입니다. 이로 인해 단위 테스트만으로는 부족함이 있다는 것을 깨달았으며 인수(Acceptance Testing) 테스트의 필요성을 느꼈습니다.

추가로 이렇게 많은 도움을 주는 단위 테스트를 만드는 것도 중요하지만 제가 코드 관례를 수정할 때 경험했던 것처럼 단위 테스트를 실제로 실행하여 잘못된 부분에 대한 피드백(Feedback)을 빠르게 받는 것도 매우 중요합니다. 하지만 큰 규모의 환경에서 순수한 단위 테스트와 데이터베이스 혹은 외부 네트워크와 통신하는 통합 테스트가 섞여 있을 때 로컬 개발 환경에서 무엇인가를 수정 할 때마다 모든 테스트를 수행해보는 것은 실행속도 때문에 개발자에게 부담스러운 일이 됩니다. 따라서 빠른 개발을 위해 본인이 현재 고치고 있는 부분에 대해서만 테스트를 수행하게 되는 경향이 있습니다. 하지만 내가 부분적으로 고치고 있다고 생각하는 부분이 다른 곳에도 영향을 주는 경우는 많습니다. 이런 경우 CI 서버의 커밋빌드를 활용하면 좋습니다.

유명한 CI 서버 대다수가 커밋빌드 기능을 제공합니다. 커밋빌드란 소스가 저장소에 커밋 되는 순간 CI 서버가 알아차리고 빌드와 테스트를 수행하는 기능입니다. 만약 컴파일이나 테스트가 실패하면 CI 서버는 커밋한 개발자에게 메일이나 SMS를 발송하여 실패를 알립니다. 개발자는 이런 알림을 이용하여 빠르게 문제발생을 인지할 수 있고 결국 검증시점이 앞당겨지는 것과 동일한 효과를 취하게 됩니다.

수정 이력
2010/05/07 : 이 주제가 여러 편에 걸쳐 게시되었기 때문에 내용이 중복되지 않고 읽는이가 부드러운 흐름을 타게하기 위해 내용을 다소 수정합니다.


WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
인터페이스를 수정하는 경우 제게 있어 가장 인상 깊었던 부분은 컴파일러가 영향력 검토에 있어 매우 중요한 역할을 한다는 것이었습니다. 예를 들어 특정 메서드의 인자를 변경해야 한다고 가정해봅시다. 해당 메서드의 인자를 변경하고 컴파일을 수행합니다. 이때 만약 컴파일 오류가 발생한다면 해당 메서드를 사용하고 있는 곳이 있다는 뜻이며 변경에 따라 영향을 받는 곳이 있다는 증거입니다. 실제로 컴파일은 이클립스와 같은 IDE를 통해 실시간으로 이뤄지기 때문에 개발자는 매우 빠르고 정확하게 영향력 검토를 할 수 있습니다.

하지만 이 같은 컴파일러의 지원에도 영향력 검토가 쉽지 않은 경우가 있습니다. 대표적인 경우 중 하나는 클래스 경로 정보를 갖고 있는 설정 파일입니다. 최근 많이 사용되는 여러 프레임웍에서는 설정 파일에 클래스 경로를 적게 하고 실행 시에 그 정보를 이용하여 클래스를 동적으로 생성합니다. 이런 경우 해당 클래스의 이름이나 위치가 변경되었을 때 컴파일러를 통해서는 영향력을 확인할 수 없게 됩니다. 또 한 가지 대표적인 경우는 JSP의 표현식(Expression Language)에서 특정 클래스의 메서드를 참조하는 경우입니다. 이 경우에도 역시 컴파일러를 통해서는 영향력을 확인할 수 없습니다. 따라서 이런 부분에 대해 자동으로 영향력 검토를 해주는 도구가 없는 경우, 설정 파일이나 JSP의 표현식과 같은 영역에 대해서는 개발자가 직접 검색을 하는 등의 방법을 통해 영향력 검토를 수행해야 합니다. 이런 경우 컴파일러만을 이용하여 영향력 검토를 할 경우보다 훨씬 더 큰 비용이 필요해지며 사람이 개입함으로 인해 실수가 생길 확률도 높아지게 됩니다. 따라서 컴파일러를 이용한 영향력 검토는 한계가 있다고 볼 수 있습니다.

한편 컴파일러를 검증방법으로써도 활용할 수 있습니다. 마틴 파울러의 리팩토링을 보면 메서드의 이름을 이해하기 좋게 바꾸거나 세부적인 코드의 변경 없이 특정 메서드를 다른 클래스로 옮기거나 하는 것도 리팩토링으로 분류하고 있습니다. 저 역시 지난 프로젝트에서 이러한 유형의 리팩토링을 많이 했습니다. 특히 첫 번째 리팩토링 프로젝트에서 코드를 수정하기 보다는 클래스나 메서드를 옮기고 이름을 바꾸는 등의 일을 많이 했습니다. 당시 저는 리팩토링 후 코드가 전과 같이 문제 없는 상태로 있다는 것을 어떻게 확신 할 수 있는지에 대해 부단히 고민했습니다. 그리고 위와 같이 클래스를 옮기는 등에 리팩토링을 가만히 살펴보았더니 이들에 대한 검증은 컴파일러가 맡고 있었습니다. 예전에는 컴파일러는 자바코드를 JVM이 실행 가능한 바이트 코드(Byte Code)로 변경해주는 도구라고만 생각했었습니다. 그러나 위 경험을 통해 컴파일러의 검증기능에 주목하기 시작했고 이어 컴파일러의 검증범위에 대해 생각해보게 되었습니다. 당시 작업을 하며 가장 어려울 때는 전에 얘기한 설정파일이나 JSP 표현식이였습니다. 이 부분은 컴파일러에서 검증하지 못했습니다. 결국 컴파일러의 검증범위 밖에 있는 것들이 문제였고 이런 부분들도 컴파일러에서 검증할 수 있으면 얼마나 좋을까라는 생각을 했습니다. 즉 컴파일러의 검증범위가 좀더 넓어졌으면 좋겠다고 생각한 것입니다.

이 때문인지 몰라도 Effective Java 2판의 저자인 Joshua Bloch는 그의 책을 통해 무엇인가 잘못 되었을 때 컴파일러가 알아차릴 수 있게 코드를 작성하는 것을 강조합니다. 저는 컴파일러가 검증방법으로써 이점이 있지만 한계 또한 있다는 점을 이미 언급했습니다. 하지만 컴파일러 검증을 이용하면 테스트 보다 더 이른 시점에 검증이 가능하다는 점과 단위 테스트와 검증부분이 중복 된다 하더라도 이중검증을 할 수 있다라는 점 때문에 Effective Java 2판의 내용과 같이 컴파일러의 검증범위를 넓히려는 노력은 매우 합리적이라 봅니다.

아래는 Effective Java 2판에서 저자가 강조했던 내용 중 실무에서 자주 발생하는 사례를 정리한 것으로써 컴파일러의 검증범위를 넓히기 위해 활용될 수 있습니다.

1. Generic 활용

Generic을 사용하지 않는 코드
List myIntList = new LinkedList();
myIntList.add(new Integer(0));
String x = (String) myIntList.iterator().next();

위 예제는 Generic을 사용하지 않고 List를 사용하는 예제입니다. 위 코드는 정상적으로 컴파일이 되지만 실행시점에 ClassCastingException이 발생합니다. 왜냐하면 두 번째 줄에서 Integer객체를 생성해 넣었는데, 세 번째 줄에서는 해당 객체를 String 사용하려고 했기 때문입니다. 여기서 주목해야 할 점은 오류를 컴파일 시에는 발견할 수 없었다는 점입니다. 하지만 자바 1.5에서 도입 된 Generic로 말미암아 컴파일 시점에 위 오류를 발견할 수 있게 되었습니다.

Generic을 활용한 코드
List<Integer> myIntList = new LinkedList()<Integer>;
myIntList.add(new Integer(0));
String x = myIntList.iterator().next();

위 예제는 Generic을 사용하여 바뀐 코드입니다. 위 예제는 첫 번째 예제와는 달리 세 번째 줄에서 컴파일 오류가 발생합니다. 왜냐하면 해당 List는 Integer로 사용하기로 결정되었기 때문에 String으로 사용할 수 없기 때문입니다. 위와 같이 Generic을 이용하면 컴파일러에서 더 많은 검증을 할 수 있습니다.

2. Enum 활용

Enum을 사용하지 않는 코드
public boolean isModifable(String osType) {
... // 유효한 osType은 Linux, Window이다.
}

위 코드에서 볼 수 있듯이 예전에는 누군가가 실수하여 type에 "Computer"라는 문자열이 잘못 들어와도 컴파일러가 검증할 방법이 없었습니다. 예를 들어 허용되지 않는 문자열인 Computer가 데이터베이스에 저장되어도 그것을 특별히 검사하는 코드가 없다면 문제를 발견하기 어려웠습니다. 하지만 Enum에 도입으로 컴파일 시점에 오류를 발견할 수 있게 되었습니다.

Enum을 활용한 코드
public boolean isModifable(OSType osType) {
...
}

public enum OSType {
LINUX, WINDOW
}
        
위와 같이 Enum에 사용으로 OSType에 미리 정의되지 않은 값은 넘길 수 없게 되었습니다. 만약 LINUX나 WINDOW가 아닌 다른 타입을 넘기려고 하면 컴파일 오류가 발생할 것입니다.  

3. Switch 문을 Constant-specific methods 로 변경

Constant-specific methods를 사용하지 않는 코드
public enum Operation {
  PLUS, MINUS, TIMES, DIVIDE, POWER;

  double eval(double x, double y) {
    switch(this) {
      case PLUS:   return x + y;
      case MINUS:  return x - y;
      case TIMES:  return x * y;
      case DIVIDE: return x / y;
    }
    throw new AssertionError("Unknown op: " + this);
  }
}

위 예는 SUN의 공식 문서에서 가져온 예입니다. 위 예는 컴파일도 잘 되고 동작도 잘 됩니다. 하지만 자세히 보면 POWER 타입이 추가 되었는데 실수로 switch 문에 새로운 타입에 대한 코드를 넣지 않았음을 볼 수 있습니다. 이 때 컴파일은 잘 되지만 실행 중 오류가 발생합니다.

Constant-specific methods를 사용하지 않는 코드에 Type 추가 시
public enum Operation {
  PLUS   { double eval(double x, double y) { return x + y; } },
  MINUS  { double eval(double x, double y) { return x - y; } },
  TIMES  { double eval(double x, double y) { return x * y; } },
  DIVIDE { double eval(double x, double y) { return x / y; } };

  abstract double eval(double x, double y);
}

그래서 코드를 위와 같이 작성하면 eval 메서드를 사용하는 측에서는 코드 변경이 전혀 없으면서도, 타입이 추가 되었을 때 eval 메서드를 구현하지 않으면 컴파일 오류가 발생하게 됩니다.

저는 컴파일러를 이용한 영향력 검토와 검증방법으로써 사용이 가능하지만 한계가 있다는 점을 설명했습니다. 또한 Effective 2판의 조언에 따라 컴파일러 검증범위의 확장이 가능한 점 또한 설명했습니다. 하지만 여전히 컴파일러의 검증범위는 미비하며 컴파일러를 아무리 잘 활용해도 검증하지 못하는 범위가 많음을 알 수 있습니다. 따라서 컴파일러를 적극적으로 활용해야 할 필요는 있지만 한편으로는 컴파일러가 하지 못하는 부분에 대한 검증을 시도해야 할 필요가 있습니다. 이 점이 바로 단위 테스트, 인수 테스트, End-To-End 테스트와 같은 테스트가 필요한 이유입니다.

수정 이력
2010/05/07 : 이 주제가 여러 편에 걸쳐 게시되었기 때문에 내용이 중복되지 않고 읽는이가 부드러운 흐름을 타게하기 위해 내용을 다소 수정합니다.

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
마틴 파울러는 그의 저서인 ‘리팩토링’에서 리팩토링을 다음과 같이 정의했습니다.

“리팩토링은 외부의 동작을 바꾸지 않고 내부의 구조를 개선하는 것”

검증의 시각에서 이 정의를 볼 때 우리가 리팩토링을 하며 개선 외에 추구해야 할 또 다른 목표를 알 수 있습니다. 바로 리팩토링을 하더라도 외부의 동작을 바꾸지는 않는 것입니다. 다시 말해 리팩토링을 어떻게 하든 예전의 동작은 그대로 유지되어야 합니다. 지난 글에서 이 부분을 ‘검증’이라 불렀습니다. 검증을 하려 하다 보면 항상 반복적으로 고려하는 요소가 있음을 알게 됩니다. 바로 영향력 검토, 검증방법 결정, 검증시점입니다.

영향력 검토는 내가 수행하는 리팩토링이 어디에 영향을 미치는지 확인하는 단계입니다. 예를 들어 사용자가 남자이면 True를 반환하는 메서드가 있고 이 메서드는 프로젝트 전반에 걸쳐 광범위하게 사용되고 있다고 가정해봅시다. 이때 리팩토링 중 실수로 남자임에도 False가 반환되게 되었습니다. 이때 프로젝트에는 어떤 영향이 미칠까요? 아마 이 메서드를 사용하는 모든 곳의 기능이 정상적으로 동작하지 않을 것입니다. 이것이 바로 해당 메서드가 가지고 있는 영향력이며, 우리는 리팩토링 전에 이 영향력을 반드시 알아내야 합니다. 때로 리팩토링 시 내가 변경하는 곳으로 인해 생기는 영향력에 대해 정확히 파악하기가 어려운 경우가 있습니다. 이런 경우는 정확한 검증을 할 수 없다는 뜻과 동일하기 때문에 영향력을 파악하기 전에는 리팩토링 진행을 멈추는 것이 좋습니다,

그 다음으로는 검증방법을 결정해야 합니다. 특정 부분을 리팩토링 한 후에 이 부분의 동작이 예전과 정확히 동일한지를 실제로 검사하는 단계입니다. 예를 들어봅시다. F를 리팩토링 할 예정입니다. 이 부분은 기능 A와 B에 영향을 미칩니다. 따라서 리팩토링 후 A와 B에 대한 기능을 테스트 인력이 검토하기로 했습니다. 이 예는 영향력 검토를 하고 검증방법을 결정하는 예를 보여줍니다. “A와 B에 대한 기능을 테스트 인력이 검토하기로 했다” 부분이 검증방법을 결정한 것이라 볼 수 있습니다. 예에서처럼 테스트 인력이 검증할 수도 있지만 자동화 된 단위 테스트 등을 이용할 수 있습니다.

마지막으로 검증시점에 대한 고려가 필요합니다. 검증시점은 말 그대로 검증을 수행하는 시점을 뜻합니다. 앞선 내용에서 이미 언급했지만 보다 빠르게 검증하는 것은 비용 면에서 많은 유익을 줍니다. 지난 글에서는 표를 이용하여 개발 중, QA 중, 서비스 중으로 나눠서 설명을 했었는데 이 것은 서비스 흐름 관점의 구분이라 볼 수 있습니다. 좀 더 기술적으로는 컴파일 시점과 실행 시점으로 분류해볼 수 있습니다. 이 경우 컴파일 시점(Compile Time)이 실행 시점 보다 이르며 따라서 문제는 컴파일 시점(Runtime)에 발견되는 것이 좋습니다. 이 점은 컴파일러 검증의 필요성을 나타냅니다. 또한 실행 시점 중에서도 고객에게 서비스를 하고 있는 실행 시점보다는 테스트를 수행하고 있는 실행 시점에 문제가 발견되는 것이 더 좋습니다. 이 점은 단위 테스트와 CI서버 도입의 필요성을 나타냅니다.

위 세 가지 점에 대해 고려한 후에는 리팩토링을 하기 위해 필요한 비용과 리팩토링 후 검증을 하기 위해 필요한 비용, 즉 리팩토링에 대한 투자비용을 생각해볼 수 있습니다. 투자비용을 생각해보는 이유는 리팩토링 또한 일종의 투자이기 때문입니다. 리팩토링은 현재 어떤 노력을 기울여 이후의 유지보수 등의 노력을 적게 들이려는 노력의 일환입니다. 그렇기 때문에 리팩토링에 대한 투자비용을 따져보고 우리가 투자를 할 때 의례 하듯이 이 투자로 인해 얻어질 거라 생각하는 이익보다 투자비용이 크다고 판단한다면 리팩토링을 진행할지에 대해 다시 한번 생각해보는 것이 좋습니다. 또한 투자의 관점에서 리팩토링을 보면 적게 투자해서 많이 얻는 것도 중요합니다. 주식투자에서 이것을 가능하게 가능 것이 저 평가 된 우량주를 발견하는 것이라면, 리팩토링에서는 아래 그림과 같이 영향력과 검증방법에 따른 비용을 줄이고 검증시점을 앞당기는 것이 적게 투자해서 많이 얻는 열쇠라 할 수 있습니다.


그림#1. 영향력, 검증비용을 감소시키고 검증시점을 앞당기는 것

수정 이력
2010/05/07 : 이 주제가 여러 편에 걸쳐 게시되었기 때문에 내용이 중복되지 않고 읽는이가 부드러운 흐름을 타게하기 위해 내용을 다소 수정합니다.

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
최근 진행했던 두 번의 프로젝트는 내게 무척 새로운 프로젝트였습니다. 왜냐하면 프로젝트의 목적이 특정 기능의 개발이 아닌 기존 코드의 리팩토링이었기 때문입니다. 첫 프로젝트를 진행할 때 저의 최초 관심은 '개선'이었습니다. 어떻게 하면 소스를 좀 더 유지보수하기 좋게 만들 수 있을까? 하지만 실제로 프로젝트를 시작하고 전반적인 작업의 진행을 시작하자 저의 관심은 개선에서 점차 '검증'쪽으로 향하게 되었습니다. 왜냐하면 개선을 하며 자연스레 발생하는 영향력이 어마어마하다는 것을 깨달았기 때문입니다. 좋은 예로써 핵심 클래스의 인터페이스를 수정해야 하는 경우가 있었습니다. 그런데 그 수정으로 인해 영향 받는 부분을 살펴보자 영향 받는 하위 프로젝트만도 10개[1]가 넘었습니다. 따라서 개선도 개선이지만 검증을 성공적으로 하는 것이 매우 중요하게 느껴졌습니다.

어떻게 하면 검증을 성공적으로 할 수 있을까? 가장 먼저 생각난 것은 QA(Quality Assurance)였습니다. 제가 일하는 환경에는 능력이 매우 뛰어난 QA부서가 있었고, 사내 표준 프로세스상 배포해야 하는 모든 제품은 반드시 QA를 거쳐야 했습니다. 따라서 열심히 리팩토링을 한 후 QA에게 검증을 요구하는 것은 매우 자연스러운 일이었습니다. 하지만 곰곰이 생각해볼 때 몇 가지 면에서 문제를 느꼈습니다.

첫째는 위에서 얘기했던 것처럼 영향 받는 범위가 매우 크다는 점이었습니다. 제가 일하는 환경의 QA는 보통 블랙박스 관점으로 기능 중심의 검증을 수행했습니다. 따라서 보통의 경우 개발자가 특정 부분을 수정하면 수정한 부분으로 인해 영향 받는 기능을 QA에게 이야기 했습니다. 이 경우 QA는 매우 기본적이며 핵심적인 주요 기능에 대한 검증을 진행하는 것과 더불어 수정으로 인해 영향 받은 기능을 추가적으로 검증했습니다. 하지만 제가 진행했던 리팩토링의 경우 위와 같은 방법으로 수정범위를 얘기하려고 하면 거의 모든 기능을 검증 해달라고 말해야 했습니다. 왜냐하면 위에서 얘기했듯이 영향력이 어마어마했기 때문입니다. 더욱이 당시 QA 자원이 많이 부족한 상태였는데 거의 모든 기능에 대해 검증해달라고 말하는 것은 바람직하지 못해 보였습니다.

둘째는 문제 발견 시점에 따른 비용증가 때문이었습니다. FEDEX에는 1-1-100원칙이 있습니다. 이 원칙은 제품을 출고하기 전에 문제를 발견하면 비용이 1만 필요하지만 출고 후에는 10의 비용이 필요하며 고객에게 항의가 들어오면 100의 비용이 필요하다는 원칙입니다. 이 원칙은 소프트웨어에도 그대로 적용된다고 봅니다. 아래 표는 실제 내가 일하는 환경에서 발생하는 현상을 바탕으로 작성했습니다.

 문제 발견 시점  관련 자원(문제 처리 비용)
 개발자가 개발 중  담당 개발자
 QA 중  BTS(Bug Tracking System)
 외부와 의사소통 담당하는 개발자
 담당 개발자
 배포 후 서비스 중
 고객
 문제/장애 알림 시스템
 서비스 기획자
 서비스 운영 책임 개발자
 BTS(Bug Tracking System)
 담당 개발자
 QA
 배포 담당자(재배포가 필요하기 때문에)

각 환경에 따라 다소 차이는 있겠지만 큰 맥락에서 위 표와 크게 다르지는 않을 것이라 생각합니다. 위 표를 보면 FEDEX 원칙과 유사하게 문제가 늦게 발견되면 될수록 처리하는 데 필요한 관련 자원이 증가함을 볼 수 있습니다. 따라서 특수한 환경이 아니라면 QA 단계에서 문제를 발견하는 것 보다는 개발자가 문제를 발견하는 것이 조직 전체 비용 관점에서는 유리합니다.

마지막으로 QA가 탐지하기 어려운 영향력이 있다는 점 때문입니다. 위에서 잠시 애기했듯이 QA는 블랙박스 관점으로 기능중심의 검증[2]을 수행합니다. 이 경우 화면으로 즉시 보여지는 부분에 대해서는 매우 높은 수준의 검증이 가능합니다. 예를 들어 화면상의 특정 부분이 일그러져 보이거나, 확연히 구분할 수 있게 문자 등으로 오류가 보이는 등의 문제점입니다. 하지만 월말이 되어 통계 화면에서 보여지는 데이터의 입력이 누락되거나 하는 문제는 발견하기 어렵습니다. 이러한 문제는 외부로 즉시 드러나지 않기 때문입니다.

따라서 저는 QA에게 모든 검증을 의존하는 것은 좋지 못한 선택이라고 생각합니다. 그것보다는 개발자가 최전방에서 적극적으로 보다 많은 검증을 수행해야 한다고 봅니다. 물론 QA를 배제할 생각은 없습니다. 다만 일차적으로 개발자가 나름의 방법으로 검증을 먼저 진행한 후 이차로 QA가 검증하는 이중검증이 더 좋은 결과를 만들 것이라 생각합니다. 이중검증은 동일한 문제를 전혀 다른 방법으로 검증하여 검증의 정확도를 더욱 높이는 방법입니다. 일반적으로 개발자와 QA의 시각은 많이 다른 편이기 때문에 각자가 생각하는 최선의 방법으로 이중검증[3]을 하면 분명히 긍정적 효과가 있으리라 봅니다.

참고 설명

[1] 제가 리팩토링을 진행했던 프로젝트는 공통 라이브러리 성격의 프로젝트였습니다.
[2] 제가 일하는 환경에는 QA가 블랙박스 테스트만 하지만, 개발자 출신의 QA가 코드검토와 같은 화이트박스 테스트를 진행하는 곳도 있다고 합니다.
[3] 켄트벡의 익스트림 프로그래밍이라는 책에 보면 재확인(Double Checking) 원칙이 소개됩니다. 이 원칙은 문제를 해결할 때 전혀 다른 방법 두 가지로 각각 문제를 해결한 후에 결과를 비교해 보아 결과의 정확성을 보장하는 방법입니다. 마찬가지로 개발자는 개발자의 관점에서 QA는 QA의 관점에서 각기 다르게 검증을 수행한다면 좀더 정확한 검증을 할 수 있을 것이라 생각합니다.

수정 이력
2010/05/07 : 이 주제가 여러 편에 걸쳐 게시되었기 때문에 내용이 중복되지 않고 읽는이가 부드러운 흐름을 타게하기 위해 내용을 다소 수정합니다.

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
저는 오늘 오랫동안 가지고 있던 글 하나를 발행했습니다. 그런데 글을 발행하고 나니 문득 드는 느낌과 생각이 있어 간단히 정리해보고자 합니다.

오늘 발행을 하고 나니 현재 거의 다 완성했음에도 불구하고 발행하고 있지 못한 많은 글들이 생각났습니다. 이 글들은 검토해볼 때 결론 부분이 약한 것 같아서, 주제를 명확하게 드러내지 못하는 것 같아서 혹은 지나치게 감정적이거나 주관적인 글 같아서 발행을 못하고 있었습니다. 하지만 좀더 솔직히 내 마음을 살펴보면 결국은 자신감의 부족이였던 것 같습니다. 글에 대한 자신감이 부족한 것 입니다.

글을 발행, 아니 어떤 하나의 일을 마친다는 것은 그것이 작은 일이라도 성취감과 더불어 자신감을 주는 것 같습니다. 이런 관점에서 때로 모든 것이 어렵게 느껴지고 그로 인해 자신감이 결여될 때는 작은 것 하나라도 완료하고 성취감을 느껴보는 것도 좋은 해결책이 될 수 있을 것 같다는 생각이 듭니다. 그런데 이 글을 다 쓰고 나니 어디선가 읽었던 내용을 되풀이 한 것 같네요. :)

2009/07/03 ~ 2010/02/18

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
최근 리팩토링 경험담이라는 시리즈 글을 쓰고 있습니다. 해당 시리즈 글의 흐름상 이 글의 내용이 부분적으로 들어가게 되어 아래 글을 해당 문백에 맞도록 약간의 수정을 하고 이동시키게 되었습니다. 원래 글의 내용은 취소선을 그어 남겨놓았으며, 내용은 거의 유사하오니 가능하면 아래 링크를 통해 해당 글과 관련 글을 읽어보시기를 추천드립니다. 불편을 드려 죄송합니다.

이동한 주소 리팩토링 경험담 #8 - 검증시점을 앞당기기 위한 빠른 실패

올해 초 저는 서버에서 발생하는 NullPointerException를 수정하는 업무를 하게 되었습니다. 로그를 보니 다양한 상황에서 다양한 형태로 NullPointerException이 발생하고 있었습니다. 그런데 업무을 진행하며 문제를 분석하다보니 그동안 어렴풋이 생각하던 부분이 좀더 명확해졌고 저는 이번 글에서 당시 했던 생각을 나눠보고자 합니다.

아래는 오류의 원인을 찾고있는 한 개발자의 가상사례입니다.
어느날 한 개발자가 웹 애플리케이션의 오류로그를 살펴봅니다. 그런데 데이터베이스에 나이를 저장하는 쿼리 부분에서 제약조건위배 오류가 발생하고 있습니다. 오류에 대해 자세히 살펴보니 나이 필드에 NOT NULL 제약이 걸려있음에도 Null 값이 입력되고 있다는 내용입니다. 곧바로 쿼리 부분에 문제가 있나라는 생각이 머리에 스칩니다. 소스를 재빨리 찾아 쿼리에 값을 넣어주는(Binding) 부분을 자세히 살펴봅니다. 그런데 큰 문제가 없어보입니다. 자연스럽게 비즈니스 레이어(Business Layer)를 살펴보게 됩니다. 혹시 나이 부분에 대해 뭔가 다루는 부분이 있나 자세히 살펴봅니다. 하지만 특별한 것을 발견하지 못합니다. 마지막으로 프리젠테이션 레이어(Presentation Layer)를 살펴봅니다. 그리고 프리젠테이션 레이어에서 나이 값이 잘 들어왔는지에 대한 검사를 하고 있지 않다는 것을 발견합니다. 거의 이 부분이 원인일 것이라 생각하지만 확신할 수는 없습니다. 왜냐하면 프로그래밍에는 너무 다양한 상황이 있다는 것을 알고 있기 때문입니다. 

2010/02/18 추가 
위의 경우는 오류로그가 Stacktrace를 포함하지 않는 경우에만 유효한 이야기입니다. 왜냐하면 Stacktrace를 볼 수 있다면 원인을 금방 알아낼 수 있기 때문입니다. 따라서 좀 억지스럽지만 Stacktrace를 찍지 않았다고 가정한 경우로 생각하시고 본래 전달하고자 하는 의미에 집중해주시면 좋을 것 같습니다.

위의 예를 통해 저는 두 가지 문제점을 봅니다. 첫 번째는 개발자가 원인을 조사하기 위해 여러 파일들을 살펴보아야 했다는 점입니다. 두 번째는 시간을 들여 여러 파일을 살펴보았음에도 불구하고 명확한 결론이 아닌 거의 확실한 추정 정도의 수준에서 조사를 끝낼 수 밖에 없다는 것입니다.  

여기서 위 사례에 한 가지 가정을 더해보겠습니다. 마지막에 개발자가 추정한 것이 실제원인이였다는 가정입니다. 이 가정 하에서 보면 위와 같이 일을 어렵게 만든 원인을 알 수 있습니다. 바로 '오류 희석'입니다.

널리 사용되거나 공식적인 용어는 아니지만 오류 희석은 시간 혹은 공간을 지남에 따라 오류가 본래의 모습을 점점 잃어버리는 것을 뜻합니다. 위 사례의 경우 근본 원인은 사용자가 나이를 입력하지 않았다는 것입니다. 하지만 실제적으로 문제는 프리젠테이션 레이어와 비즈니스 레이어를 지나 데이터베이스에 질의하는 부분에서 전혀 다른 모습으로 발생하였습니다. 그 결과로 개발자는 문제를 찾기위해 문제가 발생한 지점 까지 도달할 수 있는 모든 경로를 역추적해야 했습니다. 

그렇다면 해결책은 무엇인가요? 해결책은 무척 간단합니다. 바로 프리젠테이션 레이어에서 나이가 정확히 들어오는지를 검사하고 만약 나이가 정상적으로 들어오지 않는다면 엄격하게 예외처리를 해주면 됩니다. 사용자에게 '나이를 입력해주세요.'라는 문구를 보여줄 수도 있고, 바로 에러를 발생시켜 처리를 중지할 수도 있습니다. 이렇게 처리한다면 문제가 발생했을 때 사용자든 개발자든 문제를 정확히 알 수 있습니다. 특히 개발자가 에러로그를 보았을 경우 개발자는 소스 파일을 탐색하거나 추정을 할 필요가 없습니다. 왜냐하면 에러로그 그 자체만으로도 무슨 문제인지 너무나도 명확하게 알 수 있기 때문입니다.

Fedex에는 1-10-100 원칙이라는 것이 있다고 합니다. 간단히 설명하자면 문제를 조기에 처리하지 않으면 처리비용은 점점 증가한다는 것입니다. 저는 소프트웨어 개발의 많은 부분에서도 동일한 원칙이 적용된다고 봅니다. 문제가 발생했을 때 그 문제가 시간 혹은 공간을 지나기 전에 가능한 빨리 인지하고 처리하면 보다 낮은 비용으로 처리가 가능하지만, 그렇지 못했을 경우에는 위 가상사례와 같이 더 많은 비용이 필요하게 되는 것입니다. 특히 S/W분야에서는 추구하는 방향 관점에서 보았을 때 빠른 실패(Fail-Fast)가 Fedex의 1-10-100 원칙과 유사점을 가집니다. 좌측 링크가 걸린 위키피디아 내용에서 직접적으로 언급되지 않았습니다만, 필요한 곳에서 적절히 사용된 빠른 실패는 많은 비용을 줄일 수 있을 것입니다.

하지만 주의해야 할 점은 모든 곳에서 빠른 실패가 필요하지는 않다는 점입니다. 때로는 문제 혹은 결함을 허용하는 것이 매우 중요한 일인 경우가 있습니다. 예를 들어 포털의 메인화면을 생각해볼 수 있습니다. 포털의 메인화면은 매우 다양한 구성요소로 이루어져 있습니다. 그런데 그 중 우측 하단 쇼핑 정보를 가져오는 부분에 문제가 생겼다고 가정을 해봅시다. 이 경우 화면을 보여주기 위한 모든 처리를 중지한 후 화면을 표시하지 않는 것 보다는 그 부분을 제외하고 다른 부분은 정상적으로 보여주는 것이 현명한 판단일 것입니다. 

2009/05/06 ~ 2009/12/02




'

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
많은 프로젝트에서 문서화를 합니다. 문서화는 프로젝트 중간에 진행되기도 하지만 보통 프로젝트 종료 후 진행 됩니다. 제가 보기에 문서화의 필요성에 대해서는 많은 사람이 공감하고 있는 듯 합니다. 그렇지만 그 사람들에게 문서화를 통해서 만들어진 문서가 실제로 도움이 되었느냐 묻는다면 얼마나 고개를 끄덕일지 모르겠습니다. 우리는 경험적으로 그동안 보아왔던 문서들은 큰 도움이 되지 않는다는 것을 알고 있습니다. 도대체 왜 이러한 일이 발생하나요? 

제가 생각했던 가장 큰 문제는 문서에 너무 많은 내용을 담으려고 한다는 것입니다. 보통 문서화에 할당되는 시간은 한정되어 있습니다. 그런데 보통 문서화를 요구하는 사람들은 개발 프로세스로부터 정의 된 몇 가지 고정된 필수항목들을 요구합니다. 이런 경우 한정된 시간에 문서화를 완료하려하다보면 자연스레 구색만 맞춘 문서가 만들어집니다. 전달하고 싶은 내용이 있어도 적절한 항목이 없거나 시간이 없어서 못 만드는 경우도 있습니다. 이렇게 만들어진 문서를 읽다보면 어려움을 느낄 때가 많습니다. 조금 읽어보면 내가 필요로 하는 정보는 찾을 수 없고 다른 정보만 잔뜩 들어있기 때문입니다.

그 다음은 변화하지 않는 문서입니다. 즉, 소프트웨어는 변했는데 문서는 변화하지 않는 경우입니다. 이런 문서에 근거하여 어떤 일을 진행하다 많은 시간을 허비하거나 문제를 일으키는 경우가 있습니다. 왜냐하면 문서의 내용이 잘못 되어있기 때문입니다. 사실 이런 문서라면 아예 없는 것이 나을 수도 있다고 생각합니다. 왜냐하면 문서가 없다면 해당 일을 진행할 때 자체적으로 조사를 해서 더 원활히 일을 진행할 수도 있기 때문입니다.

사실 위에서 얘기했던 왠만한 개발자라면 누구나 알만한 매우 기초적인 문제점들에 대한 지적입니다. 오히려 중요하지만 제가 미처 언급하지 않은 문제점이 있을 수도 있겠지요. 아무튼 위와 같은 문제점을 지적했느니 저는 제가 생각하는 효율적인 문서화에 대해서 얘기해보고자 합니다.

제가 생각하는 효율적 문서화의 구조는 크게 두 가지 부분으로 나눌 수 있습니다. 첫 번째 부분은 잘 변하지 않는 즉 가장 추상화 된 부분을 다루는 소프트웨어 구조에 대한 문서입니다.

예)
설계 배경
설계에 대한 근거
주요 구성요소의 역활

위와 같은 내용은 거의 변하지 않습니다. 왜냐하면 소프트웨어를 만들 때의 배경과 설계 근거는 이미 과거에 일어난 사실이기 때문입니다. 또한 소프트웨어의 주요 구성요소도 정말 큰 변화가 없는한 그대로 유지됩니다. 이것은 이 문서가 한번 만들어지면 문서의 유지보수에는 더 이상 크게 신경쓰지 않아도 된다는 것을 뜻합니다.

위 문서의 목표는 유지보수 하는 개발자가 문서를 보았을 소프트웨어에 대한 전반적 이해를 가질 수 있게 하는 것입니다. 만약 해당 개발자가 이 문서를 통해 제대로 된 이해를 갖게 할 수 있다면 나머지 세부적으로 파악해야 할만한 내용은 해당 개발자가 코드를 보며 빠르게 파악할 수 있을 것입니다. 그렇지만 누군가는 이런 의문을 제기할 수도 있습니다.

당장 1시간 안에 접근 허용 IP 목록을 추가해야 할 때는 어떻게 합니까? 구조를 아직 파악하지 못했다면요? 혹 구조를 파악했다 하더라도 신속히 수행하기 힘들지 않나요?

예, 이런 문제를 보완하기 위해 두 번째 문서가 존재해야 합니다. 윈도우 운영체제 이론에 정통한 교수님이라 하더라도 윈도우의 창의 폰트를 바꾸는 법을 찾기 위해 1시간 이상 헤맬수도 있는 것이죠.

위와 같은 부분은 운영 문서에서 다루어야 합니다. 운영 문서는 철저하게 실용적이고 가장 구체적으로 구성되어 있어야 합니다. 또한 운영하는 입장에서 빈번히 필요할 만한 상황을 고려하고 있어야 합니다. 예를 들자면 아래와 같은 것들이 될 수 있습니다.

서버 시작하고 정지하기
접근 IP 추가하기
컴포넌트 추가하는 절차 및 방법

이렇게 두 개의 문서가 구성된 후 유지보수 하는 개발자는 선택을 할 수 있습니다. 해당 소프트웨어의 이슈가 많지 않아 최소한의 운영만 해주면 된다면 굳이 시간을 들여 구조에 대한 문서를 보지 않고도 운영에 대한 문서를 필요 시마다 참고할 수 있습니다. 만약 운영 문서에서 다루지 않는 문제가 발생한다면 스스로 해결 후 문서를 수정해주면 됩니다. 하지만 만약 해당 소프트웨어에 이슈가 많고 장기적으로 많은 수정과 개선이 필요하다면 구조 문서를 숙지하면 됩니다. 구조에 대한 확실한 이해를 바탕으로 소프트웨어를 견고하게 유지보수 하고 개선해 나갈 수 있을 것입니다. 

이러한 방식으로 문서화를 했을 때 서두에서 제기했던 문제들도 어느정도 해결 될 수 있습니다. 첫째로 얘기했던, 문서에 너무 많은 내용을 담으려 할 때 생기는 문제는 위에서 제가 얘기한 구조를 채택함으로써 자연스레 해결될 수 있습니다. 두 개의 문서만 만드는 접근 방식은 그동안 보아왔던 보편적 문서화와 비교할 때 문서량이 매우 적습니다. 또한 특별한 필수 형식을 요구하지 않기 때문에 문서화를 하는 사람은 자신의 판단에 근거하여 전달하고 싶은 부분을 중점적으로 문서를 만들 수 있게 됩니다.

두 번째 변화하지 않는 문서의 문제 역시 많이 경감될 수 있습니다. 왜냐하면 위 구조의 경우 운영문서만이 변화에 대한 영향을 받기 때문입니다.


#그림1. 소프트웨어 문서화 대상

결론적으로 제가 주장하고 싶은 부분은 위 그림과 같이 소프트웨어의 양극단만을 문서화 하자는 것입니다. 소프트웨어의 개발 흐름을 보면 아이디어로 부터 시작되어 소프트웨어 구조 작업이 시작 됩니다. 그 후 구현 코드가 만들어지고, 마지막으로 구현 코드를 쉽게 작동시킬 수 있는 운영 도구가 나옵니다. 이 때 우리는 구현 코드와 같이 자주 변하면서도 문서화 했을 때 큰 가치를 주지 못하는 중간 영역을 문서화 하려고 노력하기 보다는 잘 변하지 않는 소프트웨어 구조와 구체적인 운영 도구에 대한 설명에 집중함으로써 최소한의 노력으로 최대의 효과를 거둘 수 있을 것입니다.

2009/03/16 ~ 2009/10/19

수정 이력
2009/10/19 22:30 : 전반적으로 문장을 부드럽게 고침.

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
저는 현재 운영하고 있는 제 블로그에 대한 애정이 무척 많습니다. 왜냐하면 이 블로그는 제가 경험하고 느끼고 생각했던 것들을 불특정 다수의 사람들과 나눌 수 있도록 도와주기 때문입니다. 그래서 저는 살아가면서 또는 일을 하면서 무엇인가를 깨달았을 때 그것을 블로그에 글로 정리하려고 노력해왔습니다.

하지만 저는 최근 글을 써놓기만 하고 발행을 못했습니다. 왜냐하면 제가 쓴 주제와 비슷하면서도 훨씬 탁월한 글들이 주위에 너무 많았기 때문입니다. 실제로 어떤 주제로 글을 쓰면서 관련 자료를 찾아보면 책이든 인터넷이든 입이 딱 벌어질정도의 탁월한 글들이 널려있었습니다. 이런 상황이다보니 제가 쓴 글을 발행하는 것은 점점 힘든 일이 되어가고, 예전에 겁없이 글을 발행했던 아련한 추억만을 가지고 살아가고 있었습니다. 그러던 중 기독교 신학자이자 나니아 연대기의 저자로 유명한 C.S.루이스의 저서인 시편사색앞 부분에서 저자의 비슷한 고민을 발견할 수 있었습니다. 

이 책은 다만 비전문가가 비전문가들을 대상으로 쓴 것입니다 이러한 책이 필요할 것이라고 생각한 이유가 있습니다. 공부를 하다가 의문점이 생겼을 경우, 선생님께 그 문제를 여쭈어 보기보다는 옆에 있는 친구에게 물어볼 때 더 잘 해결되는 경우가 자주 있습니다. 저마다 겪어 본 일이겠지만, 선생님께 여쭈어 보면 우리가 이미 알고 있는 이야기만 해 주시거나 다른 이야기를 잔뜩 늘어놓으실 뿐 정작 이해하고 싶은 부분을 정확히 짚어내 해결해 주시지 못할 때가 참 많습니다. 저는 이런 상황을 양쪽 모두의 입장에서 경험해 보았습니다. 대학 강단에서 학생들을 가르칠 때 학생들이 갖고 온 문제에 답을 해 주었는데, 그들의 얼굴 표정에서 과거 제가 선생님들에게서 겪었던 절망감을 읽게 되는 경우가 종종 있었습니다. 함께 배우는 학생이 선생님보다 더 도움이 될 수 있는 이유는 그 친구가 선생님보다 아는 게 적기 때문입니다. 우리가 친구에게 설명을 부탁하는 문제는 그도 최근에 고민해 본 문제입니다. 반면 전문가인 선생님은 그 문제를 너무 오래 전에 겪어서 이미 그 일에 대해 잊어버렸습니다. 게다가 지금은 그 주제 전체를 전혀 다른 시각으로 바라보기 때문에, 학생이 대체 뭘 어려워하는지 잘 이해하지 못합니다. 대신 선생님의 눈에는 그 학생이 마땅히 질문해야 함에도 불구하고 묻지 않는 다른 문제들이 보일 뿐입니다.


C.S.루이스의 주장대로라면 아는 것이 적을 때에만 만들어낼 수 있는 가치가 분명히 있는 것 같습니다. 따라서 이러한 C.S.루이스의 경험에 공감하고 그의 주장에 찬성한다면 현재 본인의 경험이나 지식 그리고 깨달음 등에 대해 본인만의 표현으로 공유하는 것은 사회 공동체를 위해 충분히 의미있는 일이 될 수 있을 것이라 생각합니다

WRITTEN BY
차민창
르세상스 엔지니어가 사회를 이끌어나가는 상상을 하며!

,