2011년 10월 18일 화요일

유지보수가 어렵게 코딩하는 방법(1)


via -  http://www.hanb.co.kr/network/view.html?bi_id=1753

서문

자신의 무능함을 남의 탓으로 돌리지 말라.
- 나폴레옹(Napoleon)
자바 프로그래밍 분야에 종사자가 많아지기를 바라는 마음에서 아래 팁을 전수하려 한다. 이 팁은 유지보수가 어려운 코드를 작성하기로 유명한 스승으로부터 전수받은 것이다. 사람들이 스승이 남겨놓으신 코드에 간단한 수정을 추가하는데도 몇 년 이상이 걸리곤 했다. 진심을 다해 아래 규칙을 지켜 코딩한다면 본인 외에는 누구도 그 코드를 유지보수할 수 없게 된다. 즉, 평생 직장을 보장받게 될 것이다. 혹은 모든 규칙을 진심으로 따른다면 본인조차도 자신이 만든 코드를 유지보수 할 수 없는 날이 올 것이다!
상황이 이 정도까지 극단적이 되길 원하는 사람은 없을 것이다. 우리가 만든 코드가 겉보기에도 유지보수 가망이 전혀 없는 코드처럼 보이는 상황은 피해야 한다. 그렇지 않으면 우리가 만든 코드를 리팩터리하거나 최악의 경우 다시 작성해야 하는 위험에 처할 수 있다.

일반 규칙

Quidquid latine dictum sit, altum sonatur.
- 라틴어에는 뭔가 특별한 것이 있다.
유지보수를 담당하는 프로그래머를 좌절시키려면 그가 생각하는 방식을 이해해야 한다. 유지보수 담당 프로그래머는 우리가 만든 거대한 프로그램을 넘겨받았다. 그가 모든 코드를 읽기는 힘들기 때문에 해당 프로그램에 대한 이해도가 우리보다는 낮은 편이다. 아마도 그는 가능한 한 빨리 수정할 곳을 찾아내어 코드를 수정한 다음, 해당 수정으로 발생하는 부작용이 없는지 확인하고 작업을 마무리하려 할 것이다.
유지보수 프로그래머는 화장실에 걸린 두루마리 휴지 관을 통해 우리 코드를 살펴보는 것과 마찬가지 상황에 처해있다. 그가 휴지 관을 통해 우리 코드를 보면서 전체적인 그림을 그려낼 수 없게 하는 것이 핵심이다. 그가 찾고 있는 코드를 가능한 한 찾기 어렵게 만들어야 한다. 더욱 중요한 사항은 그가 안심하고 코드를 무시할 수 있도록 가능한 한 서툴게 코드를 작성해야 한다는 점이다.
프로그래머는 규약(convention)을 통해 안심하는 습성이 있다. 간혹 조금이라도 규약 위반을 발견하면 그는 돋보기를 들고 코드 전체 라인을 샅샅이 조사할 가능성이 크다.
언어의 모든 기능이 코드 유지보수를 어렵게 만든다고 생각할지 모르지만, 이는 사실이 아니다. 오직 언어의 기능을 적절하게 오용해야 유지보수가 어려운 코드를 만들 수 있다.

이름 짓기

험프티 덤프티(Humpty Dumpty)는 다소 경멸적인 어조로 말했다. "내가 단어를 사용할 때에는 더도 덜도 아닌 딱 그 의미를 전달하려는 것이다."
- Through the Looking Glass, 6 장 중에서(루이스 캐롤(Lewis Carroll) 지음)
변수와 메소드의 이름을 짓는 방법은 유지보수 할 수 없는 코드를 작성하는데 있어 상당히 중요한 기술이다. 이름은 컴파일러에 영향을 주지 않는다. 이름 짓기 기술로 유지보수 프로그래머의 정신을 혼미하게 만들 수 있다.
태아 작명법의 새로운 용도
태아 작명법 서적을 구입하자. 그러면 변수명을 뭐로 지어야 할지에 대한 고민을 덜 수 있을 것이다. Fred는 멋진 이름이며 입력하기도 쉽다. 입력이 쉬운 변수명을 원한다면 asdf를 사용해 보기 바란다.
단일 문자 변수명
변수명을 a, b, c 등으로 정한다면 간단한 텍스트 편집기로 해당 인스턴스를 검색하는데 애를 먹게 된다. 뿐만 아니라 그 변수가 무엇에 쓰이는 것인지 추측할 수 없게 방지하는 역할도 한다. 포트란(FØRTRAN)에서는 오랫동안 I, j, k를 인덱스 변수로 사용해왔다. 혹시라도 이러한 훌륭한 전통을 조금이라도 깨뜨리려는(예를 들어, ii, jj, kk 등으로 이름을 변경하려는) 사람이 있는가? 스페인 종교재판에서 이교도에게 어떠한 형벌을 가했는지를 그에게 경고하자.
창의적 오타
어쩔 수 없이 뭔가를 설명하는 변수명이나 함수명을 사용해야 하는 상황이라면 오타라는 무기를 선택하자. 몇몇 함수명과 변수명에 오타를 내고 다른 곳에서는 오타를 사용하지 않는다면(예를 들어, SetPintleOpening과 SetPintalClosing처럼) grep이나 IDE 검색 기술을 효과적으로 무력화할 수 있다. 이 방법은 생각보다 놀라운 효과를 발휘한다. 각기 다른 theatres/theaters(둘 다 극장을 의미)에 tory나 tori같이 국제적인 취향도 추가해본다.
추상화하라
가능한 한 it, everything, data, handle, stuff, do, routine, perform, 숫자 등과 같이 추상적인 단어를 변수명이나 함수명에 많이 사용하자(좋은 예, routineX48, PerformDataFunction, DoIt, HandleStuff, do_args_method).
머.리.글.자.
머리글자로 코드를 간결하게 만든다. 진짜 사나이는 머리글자를 풀어 설명하지 않고 있는 그대로를 선천적(유전적)으로 이해한다.
진화한 유의어 사전
유의어 사전에서 되도록이면 많은 단어가 같은 동작(예를 들어, 뭔가를 보여준다는 의미를 가진 display, show, present 등과 같은)을 가리키도록 하는 것도 지루함을 달랠 좋은 방법 중 하나다. 실제 의미상 차이가 없는 단어라 할지라도 알 듯 말듯하게 모호한 힌트를 제공하는 것도 잊지 말자. 때로는 비슷한 두 함수가 완전 다른 동작을 하는 경우가 있다. 이러한 경우에는 두 함수를 같은 단어로 설명한다(예를 들면, "파일 기록", "종이에 잉크 칠하기", "화면에 보여주기"를 print라는 한 단어로 설명할 수 있다). 어떤 경우에도 명확한 어휘를 사용해 특수 프로젝트에 사용할 용어집을 만들어 달라는 요구에 굴복하지 말아야 한다. 이러한 요구에 굴복하는 행위는 정보 은닉(information hiding)이라는 구조화된 디자인 원칙을 위반하는 프로답지 못한 행동이다.
다른 언어의 복수형을 사용하라
VMS 스크립트는 "Vaxen(Vax 컴퓨터의 복수형)"에서 반환하는 다양한 "statii(status의 복수형)"를 추적한다. 에스페란토(현재는 거의 쓰이지 않는 인공어), Klington(스타 트렉에 등장하는 전사 종족의 언어), Hobbitese(소설에 등장하는 가상 종족 호빗의 언어) 등을 사용하면 보다 효율적이다. Pluraloj와 같이 단어 뒤에 oj를 추가해서 에스페란토 어를 모방할 수 있다. 이러한 에스페란토를 사랑하는 작은 노력이 세계 평화에 기여하는 것임을 기억하자.
새로운 개념의 낙타표기법(CapiTaliSaTion)
무작위로 단어의 중간 음절 첫 글자를 대문자로 표기하자. 예를 들면 ComputeRasterHistoGram()처럼 메소드 명을 정할 수 있다.
이름을 재사용하라
언어의 규칙이 허용하는 범위 내에서 클래스, 생성자, 메소드, 멤버 변수, 파라미터, 지역 변수에 같은 이름을 사용하자. 이에 안주하지 말고 더 나아가서 {} 블록 내에서 이미 사용되고 있는 지역 변수명을 재사용할 수 있는지 고민하자. 이렇게 함으로써 유지보수를 담당하는 프로그래머가 모든 인스턴스의 범위를 유심히 살펴보도록 만들 수 있다. 특히 자바 언어에서는 일반 메소드를 생성자처럼 보이게 할 수 있다.
강세가 있는 단어
변수명에 강세가 표시된 단어를 사용하자. 아래 코드를 살펴보면,
typedef struct { int i; } int; 
두 번째 int에서 i 에 강세가 있다. 간단한 텍스트 편집기로는 미묘한 차이를 구별하는 것이 거의 불가능하다.
컴파일러의 이름 길이 제한을 악용하라
컴파일러가 인식할 수 있는 변수명의 길이는 정해져 있다. 만약 변수명의 8글자만을 인식할 수 있는 컴파일러가 있다면, var_unit_update()과 var_unit_setup()처럼 마지막 부분만 살짝 바꿔보자. 그럼 컴파일러는 두 이름을 동일한 var_unit으로 인식할 것이다.
밑줄(underscore)은 진정한 친구다
_와 __를 식별자로 사용하자.
언어를 혼용하라
두 언어(사람의 언어나 컴퓨터의 언어)를 무작위로 배치하자. 만약 상사가 자신의 언어를 사용할 것을 강요한다면 어떻게 할 것인가? 상사에게 나만의 언어를 사용해야 생각을 더 잘 정리할 수 있다고 설명하자. 신사적인 설명으로 해결되지 않는다면? 언어 차별 행위에 대해 이의를 제기하고, 당장 고용주를 고소를 할 수도 있으며 거액의 배상금을 내야 하는 상황에 처할 수 있다고 협박하자.
확장 아스키(Extended ASCII)
ß, Ð, n 등과 같은 확장 아스키 문자도 변수명에 사용할 수 있다는 사실을 잊지말자. 간단한 편집기에서는 복사/붙여넣기 말고는 확장 아스키 문자를 입력할 수 있는 방법이 없다.
다른 언어의 이름을 활용하자
외국어 사전은 다양한 변수명을 제공하는 마르지 않는 샘과 같다. 예를 들어, point 대신 독일어 punkt를 사용할 수 있다. 비록 우리가 독일어를 잘 알진 못하지만, 유지보수 코더로 하여금 의미를 해독하면서 다양한 문화를 경험할 수 있게 해줄 수 있다.
수학에서도 이름을 구할 수 있다
아래와 같이 수학적 기호를 나타내는 단어를 변수명으로 사용해보자.
openParen = (slash + asterix) / equals; 
정말 멋진 이름
의미상으로 전혀 관계없는 이름을 변수명으로 사용해보라.
marypoppins = (superman + starship) / god; 
이 글을 읽는 사람은 자신도 모르게 단어의 뜻에 더 집중하게 되고, 실제 로직은 이해하기가 어려워진다.
이름을 변경하고 재사용하라
이름을 변경하고 재사용하는 기법은 Ada에서 특히 잘 먹힌다. Ada는 많은 표준 역컴파일 방지(obfuscation) 기법을 무력화시키는 언어다. 현재 사용하고 있는 모든 오브젝트와 패키지 이름을 처음 붙인 사람 대부분이 멍청이다. 언제까지 그들이 변하기를 기다릴 수 없으므로, 우리가 바뀌고 또 바꿔야 한다. Ada의 subtype과 renames를 이용해 우리만의 이름으로 개정하자. 방심하는 자들을 위한 함정으로, 예전 이름에 대한 래퍼런스 몇 개는 남겨두는 것이 효과적이다.
i가 필요할 때
다른 변수는 몰라도 절대로 i 를 가장 안쪽의 루프 변수로 사용하지 말라. 이외의 용도로는 i 를 자유롭게 사용할 수 있다. 특히, 정수가 아닌 변수에 사용할 때 더욱 효과적이다. 비슷한 방법으로 루프 인덱스로 n 을 사용할 수 있다.
규칙에 얽매이지 말지어다
썬 마이크로 시스템즈 스스로도 지키지 않는 선 자바 코딩 규칙(Sun Java Coding Conventions)은 가볍게 무시하자. 특수한 상황에서만 뜻이 미묘하게 달라지도록 이름을 정해보자. 어쩔 수 없이 낙타 표기 규칙을 따라야만 한다면 모호한 상황을 적극 활용해야 한다. 예를 들어, inputFilename와inputfileName을 혼용하는 것도 좋은 방법이다. 창의력을 발휘해서 이름을 복잡하게 지을 수 있는 자신만의 비법을 개발하자. 좋은 비법을 개발했는데도 따르지 않는 자가 있다면 바로 질책하자.
소문자 l과 숫자 1은 닮았다
Long 상수를 표현할 때 소문자 l을 사용해 보라. 예를 들어, 10l로 표기하면 10L이 아닌101로 착각하기 쉽다. uvw wW gq9 2z 5s il17|!j oO08 `'" ;,. m nn rn {[()]} 등의 문자를 명확하게 구분해주는 폰트를 멀리하자. 창의력을 발휘해보자.
전역으로 사용한 이름을 지역에 재사용하라
모듈 A에 전역 배열을 선언하고 같은 이름의 배열을 모듈 B의 헤더파일에 쥐도 새도 모르게 선언하자. 그러면 십중팔구 B에 선언한 배열을 전역 배열로 착각할 것이다. 물론 주석에 이와 같은 중복 선언이 있다는 사실을 알리는 행동은 삼가야 한다.
함수의 선언과 구현의 재활용
때로는 변수명을 정반대로 재활용해서 혼란을 야기하는 방법도 있다. 지역 변수 A, B와 지역 함수 foo, bar를 선언한다고 가정하자. 일반적으로 A는 foo의 매개변수로, B는 bar의 매개변수로 사용한다. 그러나 함수 선언을 실제 사용과 반대로 foo(B)와 bar(A)로 정의한다. 이렇게 해서 같은 변수명을 다른 용도로 사용하는 것처럼 만들 수 있다. 유지보수 프로그래머는 우리가 쳐놓은 혼란의 거미줄에서 빠져나가기 힘들어진다.
변수를 재사용하라
변수 존재 범위 규칙이 허용한다면 아무 관련 없는 기존의 변수명을 재사용해보라. 하나의 임시 변수를 전혀 관련이 없는 다양한 상황에 사용할 수 있다(변수를 재사용함으로써 스택 슬롯을 절약하는 것처럼 위장할 수 있다). 조금 더 사악한 방법을 원한다면 변수 자체의 의미를 변형하는 기법을 이용하라. 코드의 길이가 매우 긴 메소드의 가장 윗 부분에서 변수에 값을 할당한 다음 중간 어딘가에서 슬그머니 변수의 의미를 바꿀 수 있다(예를 들어, 0 기반 좌표를 1 기반 좌표로 바꾸는 등). 물론 이와 같은 변경을 문서화 해놓는 실수를 범하지 않아야 한다.
Cd wrttn wtht vwls s mch trsr
변수명이나 메소드명에 약어를 사용할 때에는 비록 이름이 길어질 수 있겠지만, 같은 단어에 다양한 변형을 더해 지루함을 없앨 수 있다. 이 기법은 문자열을 검색해서 우리 프로그램의 일부 기능을 이해해 보려는 게으름뱅이를 효과적으로 골탕먹일 수 있다. 철자를 어떻게 변형할 수 있는지 생각해 보라. 예를 들어, 국제적으로 사용하는 colour에 미국식 color 그리고 격식 없이 사용하는 kulerz 등 색을 가리킬 수 있는 단어를 다양하게 혼용할 수 있다. 이름을 생략하지 않으면 다양성을 상실한다. 결국 유지보수 프로그래머가 기억하기 쉬운 이름이 될 수 있다. 단어를 축약하는 방법은 다양하므로 한 단어를 같인 목적으로 사용하는 경우라도 다양하게 축약할 수 있다. 의도한 것은 아니지만 다양한 축약 표현을 사용하다보면 유지보수 프로그래머는 각각의 축약 단어가 서로 다른 변수라는 사실조차 눈치채지 못할 수 있다.
삼천포로 인도하는 이름
메소드의 이름이 의미하는 것보다 더 많은(혹은 더 적은) 동작을 수행하도록 프로그래밍하자. 간단한 예로 isValid(x)라는 메소드에 기능을 추가해 x값을 이진수로 변환하고 결과를 데이터베이스에 저장하도록 구현한다면 모두를 깜짝 놀랄 것이다.
m_
C++의 세계에서는 멤버 이름 앞에 "m_"을 붙이는 규약이 있다. 이는 메소드와 멤버를 구별하려고 만든 규약인데, 이 규약을 만든이는 메소드(method) 역시 "m"으로 시작한다는 사실을 잊은 듯 하다.
o_apple obj_apple
클래스의 인스턴스명을 "o"나 "obj"로 시작함으로써 우리가 크고 다형성을 갖춘 그림을 염두에 두고 있다는 사실을 보여주자.
헝가리 표기법
헝가리안 표기법은 소스 코드 판독을 어렵게 하는 핵폭탄급 기법 중 하나다. 헝가리안 표기법을 사용해보자! 소스코드는 방대하므로 헝가리안 표기법을 활용해 적절하게 코드를 오염시킨다면, 그 어떤 방법보다도 효율적으로 유지보수 엔지니어를 쓰러뜨릴 수 있다. 아래는 헝가리안 표기법의 본래 의도를 무력화하는 팁이다.
C++에서 "c"를 const에 사용하라(C++). "c"는 C++ 이외의 언어에서는 보통 변수가 상수임을 가리킨다.
다른 언어에서는 다른 의미로 해석되는 헝가리안 물혹(wart, 덧붙이는 음절이나 단어)을 찾고 사용하라. 예를 들어, C++ 코딩에서 모든 제어 형식에 "l_"과 "a_"와 같은 범위를 가리키는 접두어(파워빌더에 l은 지역을 a는 매개변수를 가리킨다)와 VB 스타일의 헝가리안 물혹을 사용하라. MFC 소스 코드에서 제어 형식에 헝가리안 물혹 표기법을 사용하지 않는 다는 사실을 마치 모르는 것처럼 행동하라.
공통적으로 자주 사용하는 변수는 되도록이면 추가 정보를 포함하지 말아야 한다는 헝가리안 원칙을 항상 위반하자. 위에서 설명한 기법들을 총 동원하고 각 클래스 형식은 커스텀 접두 물혹을 가지고 있다고 주장하므로 이를 달성할 수 있다. 물혹이 없는 것은 클래스임을 의미한다는 사실을 간파할 수 없게 해야 한다. 이는 정말 중요한 원칙이다. 이 원칙을 지키지 못하면 소스코드에 모음/자음 비율이 높아지면서 짧은 변수명이 범람하게 된다. 최악의 경우 소스코드 판독 방해 작전이 실패할 수 있고 자신도 모르는 새에 영어 표기법이 코드에 나타날 수 있다!
함수 파라미터와 여타 심볼은 이름을 통해 의미를 나타내야 한다는 헝가리안 개념을 노골적으로 위반하자. 그러나 헝가리안 형식 물혹 자체의 사용이 변수를 임시 변수로 보이게 할 수 있다.
의미상 전혀 연관이 없는 헝가리안 물혹 여러 개를 덧붙여 사용해보자. 실생활에서 활용된 예를 들면 "a_crszkvc30LastNameCol"같은 변수를 만들 수 있다. 유지보수 엔지니어 팀 전체가 이 변수명을 "이 변수는 const이고 레퍼런스 형식으로 함수 매개변수로 사용되는데 테이블의 기본 키 가운데 하나인 ‘LastName’이라는 이름의 Varchar[30] 형식의 데이터베이스 열에서 가져온 데이터를 담고 있다."라고 해독하는데 3일이 걸렸다. "모든 변수는 public이어야 한다"라는 규칙을 이 기법에 접목하면 수천 라인의 코드를 대체할 수 있는 막강한 파워를 발휘할 수 있다!
사람의 뇌는 동시에 오직 7 개의 정보를 유지할 수 있다는 원칙을 마음껏 활용하자. 위 규칙을 따른다는 것은 다음과 같은 코드를 작성한다는 것을 의미한다.
  • 하나의 할당문에 14개의 형식과 이름 정보를 사용한다.
  • 하나의 함수가 3개의 매개변수를 전달하고 29 개의 형식과 이름 정보를 가진 결과값을 할당한다.
  • 적당히 복잡한 중첩 구조를 이용하면 단기 기억의 한계를 가볍게 초과할 수 있다. 특히 유지보수 프로그래머가 블록의 시작과 끝을 한눈에 확인할 수 없는 경우에 효과가 커진다.
  • 유지보수 프로그래머가 각 블록을 한 화면에 확인하기 어렵게 만들 수 있다면 중첩 구조체로도 단기 기억 메모리 한계를 간단히 초과할 수 있다.
헝가리안 표기법의 변형
헝가리안 표기법을 활용한 또 다른 술책으로 "변수명은 그대로 사용하되 변수 형식을 바꾸는" 방법이 있다. 이 방법은 윈도 응용 프로그램이 Win16 WndProc(HWND hW, WORD wMsg, WORD wParam, LONG lParam)에서 to Win32 WndProc(HWND hW, UINT wMsg, WPARAM wParam, LPARAM lParam)로 변경되는 경우와 같은 상황에서 어김없이 등장한다. 여기서 w는 words임을 가리키는 듯 하지만 실제로는 long을 가리킨다. Win64로 응용 프로그램을 변경하는 경우 이 사실이 더욱 명확해진다. Win64에서는 파라미터가 64비트이지만 기존의 "w"와 "I" 접두어는 변하지 않는다.
줄이고, 재사용하고, 재활용하라
콜백(callback)에 사용할 데이터를 저장할 구조체를 정의해야 한다면, 그 구조체를 PRIVDATA라고 부르자. 모든 모듈은 자신만의 PRIVDATA를 정의할 수 있다. 이 구조체로 VC++의 디버거를 교란시킬 수 있다. 변수 watch 윈도우에 PRIVDATA 변수가 있는 상태에서 해당 변수를 펼치려고 하면 디버거는 어느 PRIVDATA를 의미하는 것인지 결정할 수 없어 아무것이나 선택한다.
쉽게 찾지 못하게 숨겨라
16진수 값 $0204FB를 할당할 상수 변수명으로 blue 대신 LancelotsFavouriteColour와 같은 이름을 사용하라. 화면에는 완전한 파랑색이 나타나겠지만, 유지보수 프로그래머는 0204FB값을 판독(아마 그래픽 도구를 이용해서)해야 의미를 파악할 수 있을 것이다. 몬티 파이썬의 성배(Monty Python and the Holy Grail)라는 1975년 영국 영화를 좋아하는 광팬이라면 랜슬롯(Lancelot)이 좋아하는 색이 파랑색이라는 사실쯤은 금방 알아차릴 수도 있을 것이다. 몬티 파이썬의 성배 영화 전체 내용을 기억하지 못하는 유지보수 프로그래머가 있다면 프로그래머로써 자질이 없는 분이라고 생각할 수 밖에 없다.
다음회 계속...

[협업 노하우 ⑤] CVS/SVN을 이용한 버전 관리

via - http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039163229

대다수의 개발자들은 버전 관리 도구를 사용해서 처음 개발에 착수한 시점에서 고객에게 제품을 인도할 시점까지의 모든 변화를 기록하고 관리하고 있다. 버전 관리는 팀 단위로 개발을 진행해 나가는데 있어서 필수적인 형상 관리 요소의 하나이기 때문이다. 특집 5부에서는 아직도 버전 관리를 실천하지 않는 개발자들을 위해 그 필요성과 대표적인 도구들의 사용 방법에 대해 살펴볼 것이다.

소프트웨어 형상 관리(SCM: Software Configuration Management)는 크게 1)소프트웨어 변경 요구가 발생한 것부터 구현까지의 전 과정을 제어하고 기록하고 보고하는 변경 관리와 2)그러한 개발 주기 동안의 변화하는 코드와 라이브러리, 관련 문서 등을 저장하고 관리하는 버전 관리, 3)제품의 릴리즈나 빌드에 반영된 변경을 감사하고 관리하는 릴리즈 관리 등으로 구성된다. 

이러한 형상 관리 절차 중에서 이 장에서 소개할 버전 관리를 하지 않을 경우 어떤 문제들이 일어나는지 살펴보자. 

  버전 관리의 필요성

여러 사람이 함께 작업할 경우 작업 결과를 모아두는 곳이 필요하다. 이때 쉽게 떠올릴 수 있는 것이 FTP 서버나 공유 서버에 작업 결과물을 모아둘 저장소를 만들어 두는 것이다. 그 저장소에는 프로그램의 소스 코드도 있을 것이고, 개발 과정을 정리한 문서, 프로그램에서 사용하는 라이브러리 등도 있을 것이다. 

각각의 개발자는 작업을 하기 위해서 그 저장소에 저장된 파일을 가져와서 자신의 로컬 PC에 복사본을 만들어서 작업을 수행한다. 그런 다음 작업이 끝나면 다시 그 결과물을 저장소에 넣어둘 것이다. 이런 상황에서는 몇 가지 문제점이 발견된다. 

우선 서로 다른 두 명의 개발자가 동시에 같은 파일을 복사해서 작업한 다음 저장소에 올리는 경우를 생각해보자. 열심히 작업을 해서 한 명이 먼저 작업을 끝내서 저장소에 올렸다. 잠시 후에 그 사실을 모르고 있는 또 다른 개발자가 저장소에 자신의 작업 결과물을 저장소에 보내는 순간 덮어쓰기가 일어나며 앞선 개발자의 작업은 덧없이 날아가 버린다. 

물론 저장소에 올리기 전에 내가 내려 받은 파일의 최종 수정일을 기록해뒀다가, 올리기 전에 서버의 최종수정일이 변하지 않았는지를 확인하면 되지만 너무나 귀찮은 일이다. 

혹 그런 귀찮음을 무릅쓰고 철저하게 확인을 한다 하더라도, 날짜가 변경되어 있을 때 도대체 누가 어떤 목적으로 그 파일의 어떤 부분을 수정했는지 알아내려면 모든 개발팀에게 “누가 언제 왜 어떤 목적으로 어떤 부분을 수정했나요?”하고 일일이 물어봐야 한다. 

물론 이러한 확인이 이뤄진 후에도 앞서 작업한 개발자와 상의해서 서로 다른 두 개의 결과물을 조정해서 합의된 최종 결과물을 만든 후에 서버에 올리는 것도 여러분의 몫이다.

또 다른 경우는 며칠 전의 상태로 작업 결과를 되돌려야 하는 상황이 발생할 때이다. 1차 개발을 완료한 시점에서 추가적인 요구 사항을 처리하기 시작했다고 가정해보자. 이 때 잘못된 판단으로 도저히 회복하기 힘든 큰 실수를 저질러서 차라리 1차 개발이 완료된 시점에서 다시 개발하는 것이 좋을 때도 있을 것이다. 

단순한 공유 서버를 사용했을 때 이런 상황을 해결하기 위한 방법은 전체 작업 결과물을 특정 날짜 별로 복사해서 따로 관리하는 것이다. 

이 방법은 물론 특정 날짜의 작업으로 되돌릴 수 있지만, 작업 결과물의 덩치가 크고 개발기간이 길수록 불필요한 디스크의 낭비가 심해진다. 만일 2시간 전의 상태로 저장소를 되돌리고 싶다면 어떻게 할까? 

매 시간마다 또 특정 태그를 붙인 복사본을 만들면서 작업을 할 것인가? 또 어떤 내용은 그대로 두고 특정 파일만을 이틀 전의 상태로 되돌리고 싶을 때는 어떻게 할 것인가? 

소프트웨어의 큰 특징 중 하나는 프로젝트가 진행되는 기간 동안 그 소프트웨어는 계속해서 변경된다는 것이다. 이 때 변경되는 것은 여러분이 작성한 코드 뿐 아니라, 관련된 문서나 적용하는 라이브러리를 포함한 프로젝트의 모든 산출물이다. 

여러 명이 동시에 작업하는 프로젝트라면 그러한 변경으로 인해 모든 개발자의 작업이 영향을 받게 된다. 바로 이런 문제들을 해결하기 위해 등장한 것이 바로 버전관리 시스템이다.

  버전 관리시스템 유형

버전 관리 시스템은 수십 개의 제품이 알려져 있는데 크게 세 가지 기준에 의해 구분할 수 있다. 첫 번째 기준은 저장소 구조의 차이다. 개발자 개개인이 공유된 저장소를 가지는 형태의 분산 모델 유형과, 서버에 저장소를 두고 각자 복사본을 가지고 작업하는 형태의 클라이언트-서버 모델 유형이 있다. 

두 번째는 소스 공개 유형에 따라 오픈소스 제품과 상용 제품으로 구분된다. 마지막 세 번째 기준은 서로의 작업이 충돌하는 것에 대한 인식의 차이다. 

작업 충돌이 잘 발생하지 않기 때문에 자유롭게 사용하고 충돌이 나면 해결하자는 낙관적 잠금 방식을 채택한 제품과, 충돌이 발생하면 처리 과정이 복잡하기 때문에 한 사람이 작업을 할 때는 다른 사람이 작업을 할 수 없도록 막아버리는 비관적 잠금 방식을 채택한 제품으로 구별된다. 

많이 알려진 제품들 중에서 Gnu Arch는 분산 모델을 채택한 오픈소스 제품이고, IBM의 ClearCase는 클라이언트-서버 모델을 채택한 상용 제품이다. 

분산 모델보다는 클라이언트-서버 모델이 일반적으로 사용된다. 클라이언트-서버 모델 중에서도 마이크로소프트의 Visual SourceSafe는 비관적 잠금 방식을 채택한 상용 제품이고, 이 글에서 소개할 CVS와 SVN은 낙관적 잠금 방식을 채택한 오픈소스 제품이다. 그럼 이제부터 CVS와 SVN에 대해 살펴보자.

  CVS를 극복한 SVN

CVS(Concurrent Version System)는 1986년 Dick Grune에 의해서 개발된 가장 대중적인 버전관리 시스템이다. 오랜 시간 개발자들에게 버전관리 시스템의 대명사로 불리다 보니, 버전관리를 위한 다양한 기법들이 연구되었다. 그 결과로 윈도우 버전과 웹 버전, 편리한 GUI 클라이언트 등도 개발되었다. 현재까지도 많은 프로젝트나 기업에서 CVS를 버전관리 시스템으로 사용하고 있다. 

그러나 CVS 프로토콜은 한 번 등록된 디렉토리나 파일의 이동이나 이름 변경을 허용하지 않았으며, 역사적으로 RCS를 따라 나왔기 때문에 개별 파일 단위로만 버전관리가 되었다. 

그 뿐 아니라 아스키코드로 된 파일명만 지원할 뿐 유니코드에 대한 지원이 제한적이어서 우리나라처럼 유니코드로 지원되는 언어를 쓰는 국가에서는 파일 이름을 모두 영어로 변경해야 했다. 

2004년부터 CVS의 핵심 개발자들이 보다 나은 CVS를 표방하며 개발하기 시작한 SVN(SubVersion : 서브버전)이 등장한 이후에는 버전관리 시스템의 대명사라는 자리는 빠르게 SVN으로 옮겨가고 있다. CVS와 비교한 SVN의 장점은 다음과 같다.

• 소스코드 뿐 아니라 바이너리(문서/라이브러리 등)도 지원한다.
• 커밋의 단위가 개별 파일이 아닌 변경된 작업 단위이다.
• 디렉토리나 파일 별로 세밀한 접근제어가 가능하다.
• CVS에 비해 빠르다.
• CVS와 개념 및 사용법이 거의 같아서, CVS 사용자가 쉽게 옮겨올 수 있다.

이미 CVS로 오랜 기간 버전관리를 수행해 왔으며, CVS의 상대적인 약점을 전혀 불편해 하지 않는 사용자를 제외한다면 버전관리 시스템으로 SVN을 선택하는 것은 당연한 것처럼 느껴진다.

  SVN 서버 설치

SVN을 사용하려면 우선 서버에 SVN을 설치해야 한다. SVN의 설치와 사용에 관한 내용은 Subversion HOW-TO(한글) 문서에 잘 정리되어 있다. SVN을 통한 버전관리를 주제로 책 한 권이 나올 정도로 깊이 소개하자면 끝이 없기 때문에 여기서는 핵심적인 몇 가지 내용만을 짚고 넘어가고자 한다.

SVN은 클라이언트-서버 모델을 채택하고 있기 때문에 클라이언트와 서버가 통신할 표준 프로토콜을 결정해야 한다. 사용하는 프로토콜은 svn이다. 

따라서 접속할 저장소의 URL은 ‘svn://서버명:[포트]/repos/저장소명’의 구조를 가진다. 설정에 따라 웹을 통한 접근을 허용한 경우 http로 시작하는 URL을 통해서도 내용을 볼 수 있으며, 중요한 데이터라면 SSH를 통해 보안성을 높일 수도 있다.

SVN의 저장소(Repository)는 버전 관리되는 대상들이 저장되는 곳이다. 저장소 안에는 어떤 파일이 어떤 이유에서 누가 언제 어떻게 변경했는지와 같은 메타정보도 함께 저장된다. 

SVN은 내부적으로 Berkeley DB를 사용해서 파일을 관리한다. SVN의 저장소는 관례적으로 trunk, branches, tags라는 세 개의 디렉토리를 가지고 시작하는 것이 좋다. Trunk는 나무의 몸통을 뜻하는데 프로젝트의 원본이 관리되는 곳이다.

Branches는 몸통에서 뻗어 나온 나뭇가지를 뜻하는데, 개발을 하다 보면 고객시연용 개발 등과 같이 원본과는 다른 목적으로 프로젝트가 분기되는 경우가 있다. 이런 경우, 프로젝트의 원본을 분기시킨 버전을 Branches에 만들어 관리하게 된다. Tags는 꼬리표를 뜻하며, 정기적으로 1.0 버전, 2.0 버전과 같은 식으로 특정 시점의 릴리즈를 따로 관리하기 위한 곳이다.

SVN 관련 용어 

● 체크아웃(CheckOut) : SVN에 의해 관리되는 프로젝트에 참여하기 위해 맨 처음 저장소에서 작업할 대상 파일을 받아오는 것을 뜻한다. 체크아웃을 하기 위해서는 익명 권한을 허용하도록 설정되어 있지 않은 이상 해당 시스템의 계정과 비밀번호를 필요로 한다. 성공적으로 체크아웃이 이뤄지면 SVN 저장소의 복사본이 로컬 PC에 생성되어 독자적으로 개발을 진행할 수 있다.

● 커밋(Commit) : 체크아웃한 로컬 PC에서 작업을 수행(파일 추가, 내용 수정, 파일 삭제, 이름 변경)하면 저장소에 있는 파일과 다른 형태가 된다. 커밋은 로컬 PC에 있는 복사본을 기준으로 저장소와 동기화하는 기능이며, 작업 내용이 서버의 저장소에 반영된다. 

● 업데이트(Update) : 일정 시간이 지나면 다른 사람이 작업한 결과를 커밋하기 때문에 로컬 PC의 복사본이 최신의 것이 아닐 수 있다. 이럴 경우 업데이트를 수행해서 저장소를 기준으로 로컬 PC의 복사본을 동기화하는 것이 업데이트다. 체크아웃과 달리 업데이트는 프로젝트 진행기간 동안 빈번하게 발생하며, 적절한 주기로 자주 업데이트를 받는 것이 좋다. 특히 작업 내용을 커밋하기 전에 한번 업데이트를 진행해서 충돌 여부를 확인한 다음, 충돌을 해결하고 커밋 하는 것이 효과적이다.

● 리비전(Revision) : SVN에서는 커밋을 단위로 저장소에 있는 리비전 정보가 증가한다. 체크아웃을 받을 때 리비전 번호가 6500이라면, 팀 동료들에 의해 6,500회의 커밋이 이뤄진 프로젝트란 걸 알 수 있다.

● 임포트(Import) : 처음 저장소를 만든 시점에서 저장소에 맨 처음 파일을 넣는 작업을 뜻한다.

● 익스포트(Export) : 체크아웃과 같이 저장소에서 로컬 PC로 작업 결과물을 가져온다. 차이점은 버전 관리를 위한 메타 정보를 제외한 순수한 작업 결과물만을 가져온다는 점이다. 

SVN을 설치 후에 해야 할 필수적인 일은 저장소를 만들고 그 저장소의 권한을 설정하고 사용자를 추가하는 일이다. 다음의 명령은 SVN 관리 툴인 svnadmin을 이용해서 sample이라는 이름의 저장소를 만드는 과정을 보여준다.

[root@devcais11 repos]# pwd
/home/svn/repos
[root@devcais11 repos]# svnadmin create --fs-type fsfs sample
[root@devcais11 repos]# ls -l
drwxrwxrwx 7 root root 4096 11월 17 14:23 sample
[root@devcais11 repos]# chmod 777 -R sample

이렇게 생성한 sample 저장소를 누구에게 어떤 권한으로 사용을 허락할 것인지를 지정해야 하는데, 이 정보는 svn-authz-file에 담겨있다. 해당 파일을 에디터로 열어서 저장소에 필요한 권한 설정을 추가한다. 다음 예제는 접근한 모든 사용자에게 읽기/쓰기 권한을 부여하도록 sample 저장소를 구성한 것이다.

[root@devcais11 etc]# vi svn-authz-file
[sample:/]
* = rw

이제 저장소를 이용한 사용자를 추가할 차례다. /etc에 svn-auth-file에는 사용자 정보가 담겨 있다(앞에서 본 파일명에 z가 빠져있음). 사용자는 htpasswd 명령으로 쉽게 추가할 수 있다. 사용자 추가 시 입력한 비밀번호는 해시 값으로 해당 파일에 덧붙여진다. 

[root@devcais11 etc]# htpasswd –m svn-auth-file seal
[root@devcais11 etc]# cat svn-auth-file
seal:$apr1$AaEkN...$cL/nC1OGQ/k8L5WVWoL/a/

기타 자세한 설정은 참고 자료에 명시된 자료들을 참고하길 바란다.

  SVN 클라이언트

팀에 소속된 개발자가 각자의 개발 환경에서 SVN을 사용하려면 별도의 클라이언트 프로그램이 필요하다. 자바 개발자의 경우를 예로 들면, 일반적으로 개발을 위해 이클립스와 같은 IDE를 사용하고 있을 것이다. 

CVS는 버전관리 시스템의 대명사로 통하기 때문에 별도의 설치 없이 사용할 수 있지만, SVN을 사용하려면 추가적인 플러그인 형태로 클라이언트를 설치해야 한다. 

이클립스 플러그인 Subversive
예전에는 SVN을 개발한 tigris에서 배포하는 Subclipse라는 플러그인을 주로 사용했지만, 지나치게 불안정해서 많은 자바개발자로 하여금 다시 CVS로 되돌아가게 만드는 원인이 되곤 했다. 

최근에는 Subversive라는 플러그인이 그것을 대체하여 사랑받고 있다(Subclipse는 이클립스 프로젝트에 공식적으로 채택되어서 이클립스 3.2 버전부터는 Callisto내에 포함된다). Subversive의 장점은 trunk/branches/tags와 같은 관용적 표현을 툴 자체적으로 지원하며, 저장소를 보여주는 기능이 개선되었다는 것이다. 

또, Subclipse와 같은 불안정한 오동작도 거의 없다. Subversive는 이클립스 [Help]-[Software Updates] 메뉴를 이용해서 http://www.polarion.org/projects/subversive/download/1.1/update-site/ URL을 입력하면 손쉽게 설치 가능하다.

설치한 후에는 우선 SVN Repository Perspective로 이동해서 저장소를 등록해야 한다. 저장소 등록이 이뤄지면, 서버에서 버전 관리를 하고자 하는 프로젝트를 선택하고 [team]-[share project] 기능을 통해 저장소에 해당 프로젝트를 등록한다. 


<화면 1> 이클립스에서 Subversive 동작 샘플?Java 관점

그 저장소에 등록한 프로젝트에 속한 개발자는 같은 저장소를 등록하고, 등록된 내용과 똑같은 프로젝트를 갖기 위해 최초에 한번 체크아웃을 수행해야 한다. 체크아웃이 이뤄지고 나면 Subclipse의 다양한 기능을 통해 팀 차원의 버전 관리를 수행할 수 있다.

<화면 2> 이클립스에서 Subversive 동작 샘플 ? SVN 저장소 관점

Subversive에서 실행하는 기능들 중에서 몇 가지 추천할 만한 기능과 주의할 사항에 대해 간단히 알아보자. SVN 서버는 대소문자를 구별하는 유닉스 계열을 사용하고, 이클립스는 대소문자를 구별하지 않는 윈도우 환경을 사용한다면 파일의 대소문자 관리에 주의를 기울여야 한다. 

윈도우에서는 AA.java와 Aa.java가 같은 이름이기 때문에 이름을 변경해도 상관이 없지만, 유닉스에서는 AA.java와 Aa.java를 다른 이름으로 받아들이기 때문에 커밋을 해버리면 AA.java는 그대로 있고 Aa.java가 추가된 형태로 저장소는 인식한다. 이때 누군가 업데이트를 요청하면 AA.java가 있는 상태에서 새로운 Aa.java를 업데이트 받는데 이미 파일이 존재하기 때문에 기대했던 업데이트는 이뤄지지 않는다. 

Subversive에서 작업 결과를 커밋 할 때는 귀찮더라도 Comment에 의도가 드러나도록 작업에 대한 요약을 적어주는 것이 좋다. 이런 히스토리는 SVN Resource History 탭에서 확인할 수 있는데, 특정 버전으로 돌아가기 위해 변경 시점을 찾을 때 도움이 된다. 

특히 시스템이 오동작 할 때 그런 일이 일어나지 않도록 정한 약속을 어긴 범인을 찾을 때에도 History 정보는 도움이 된다. 

또 한 가지의 유용한 기능은 Revert이다. Revert는 가장 최근에 저장소에서 업데이트 받은 상태로 돌려준다. 업데이트를 받고 나서 1시간 정도 작업을 했는데, 뭔가 꼬인 것 같아 작업하기 이전 상태로 되돌아가고 싶을 때 Revert를 실행하면 고민이 해결된다. 

마지막으로 소개할 팁은 Synchronized 기능이다. 복잡한 변경을 수행하고 나서 변경 결과를 커밋 하는데 한참을 진행하다가 그제서 충돌을 발견하고 전체 결과가 롤백 되어 버리면 굉장히 허탈할 것이다. 

그래서 커밋하기 전에 먼저 Synchronized 기능으로 서버와 동기화 시켜 추가된 부분이 있다면 업데이트를 받고 충돌이 있다면 diff 기능으로 충돌을 해결하고, 그런 다음에 커밋을 수행하는 것이 좋다. 

충돌이 발생했을 때 양쪽을 비교해보면서 서버 쪽 작업이 옳아서 내 작업을 취소해야 한다면 ‘Override and Update’ 기능을 실행하고, 충돌을 해결한 자신의 코드를 서버에 등록하고 싶다면 ‘Override and Commint’ 기능을 실행한다.

<화면 3> Subversive의 Synchronized View 기능

익스플로러에 녹아드는 TortoieSVN
Subversive외에 추천하고 싶은 SVN 클라이언트는 일명 거북이라고 불리는 TortoiseSVN이다. GPL 라이선스이기 때문에 누구나 설치해서 사용할 수 있다. TortoiseSVN은 IDE와 연동되는 형태가 아닌 다른 형태의 산출물을 버전관리 하는 목적으로 사용될 때 아주 탁월한 SVN 클라이언트이다. 

예를 들어 공동으로 특정 기술을 연구하는 과정에서 나오는 각종 오피스 문서들을 버전관리 한다거나, 웹 에이전시에서 나오는 홈페이지 제작에 필요한 산출물을 버전관리 할 때 유용하다. 

<화면 4> 윈도우 탐색기에서 TortoiseSVN 동작 샘플

TortoiseSVN은 플러그인 될 별도의 IDE가 없기 때문에 윈도우 탐색기와 결합된 형태로 동작한다. 특정 폴더에서 마우스 오른쪽 버튼을 누르면 나타나는 메뉴에 SVN의 클라이언트 기능이 추가되는 식이다. TortoiseSVN을 설치하면 버전 관리되는 파일은 특수한 형태의 아이콘으로 변경되어 버전관리 대상임이 표시된다.

한 가지 특이한 점은 TortoiseSVN은 ‘Create Repository hear’ 메뉴를 통해 서버 없이 그 자체만으로도 저장소를 만들 수 있다는 것이다. 이 경우 로컬에서 저장소를 운영하기 때문에 svn 프로토콜이 아닌 file 프로토콜을 이용해서 ‘file:///C:\repos\maso’와 같은 식으로 저장소 URL이 결정된다. 

필자의 경우는 이 기능을 통해 TortoiseSVN을 개인 문서 작업용 버전관리 도구로 사용하고 있다.

지금까지 버전관리의 필요성을 언급하고, 버전관리 시스템의 유형과 대표적인 버전관리 시스템인 SVN에 대해 살펴보았다. 상세한 설명은 참고자료에 소개된 웹페이지나 책을 통해 얻을 수 있다. 버전관리 시스템을 이해하는 것은 협업을 기본으로 하는 개발자에게 있어서 기본적인 예의이다. 

현재까지도 버전관리 시스템에 익숙하지 않은 개발자가 있다면, 이번 기회에 도전해 보길 바란다.


참고자료
(1) 버전관리시스템의 종류
http://en.wikipedia.org/wiki/List_of_revision_control_software 
(2) CVS - http://www.nongnu.org/cvs/ 
(3) Subversion HOW-TO(한글) - www.pyrasis.com/main/Subversion-HOWTO
(4) Subclipse - http://subclipse.tigris.org/ 
(5) TortoiseSVN - http://tortoisesvn.tigris.org/
(6) Subversive 
http://www.polarion.org/index.php?page=overview&project=subversive
(7) 서브버전을 이용한 실용적인 버전관리, Mike Mason, 정보문화사
(8) 서브버전 : 실무자가 꼭 알아두어야 할 차세대 버전관리 시스템, 에이콘