진정 버그없는 프로그래밍을 원하신다면...
일독하시길 권합니다.
내용이 조금 길다보니 편안히 앉아서 읽어보세요!
---------------------------------------------------------------------------------------
Feed: 소프트웨어 세상
Title: Programming Style for Bugless Programming 복원
- serene, Programming Style for Bugless Programming이라는 주제로 썼던 2001년 경의 글입니다.
이후, 이 글의 2배 정도 길이의 개정판을 썼지만 공개한적이 없은 채로 원고가 소실되었습니다.
인터넷에 아직도 제 글이 남아 있어 어떻게든 복원시켜둡니다.
마음에 드신다면 얼마든지 퍼가세요.
글쓴이: 한동훈
글쓴시기: 2001년도...
- serene.
--------------------------------------------------------------------------------
프로그래밍 스타일 - Bugless Programming프로그래머가 되려는 사람, 프로그래밍을 하고 있는 사람, 모두에게 있어서 긴 논쟁이 되어 왔던 많은 주제들이 있다. 어떠한 언어가 더 우수하다는 것에서부터 프로그래머가 되려면 어떤 것들을 먼저 학습해야 한다는 것에 이르기까지 정말로 많은 주제들이 있다. 그리고 그 중에 하나가 아마도 프로그래밍 스타일에 대한 것일 것이다.
프로그래밍 스타일은 다르게 말하면 함수의 명명 방법이라던가, 들여쓰기 간격, 괄호의 위치와 같이 쓸데 없는 것일 수도 있다. 조금 더 심하게 말하면 독재자가 '너희는 이것을 따르라'라고 말하는 것일 수도 있다. 하지만 프로그래머마다 프로그래밍하는 스타일이 다르고, 프로그래머 각각의 개성이라 받아들여질 수 있는 것이기도 하다. 하지만 혼자서 프로그래밍하는 것이 흔한 일은 아니기 때문에 분명히 어떠한 규칙이 있어야 한다. 그렇지 않으면 저마다 만들어내는 코드들의 모양새가 다르기 때문에 분명히 혼란스러운 모습을 보일지도 모른다. 흔히들 '코드는 읽기 쉬워야 하고, 알기 쉽게 주석은 많이 들어가야 한다'라고 얘기하지만 프로그래밍 스타일에 대해서 다루어야 하는 더 큰 이유는 어떻게 하면 조금이라도 더 버그없는 프로그램을 만들어 낼 것인가라는 것이다. 필자는 간혹가다가 다른 프로그래머들이 이렇게 말하는 것을 들을 때가 있다.
"전에 근무하던 회사에서 내가 작성한 코드를 수정해야 하는데 현재 담당자가 이해하지 못하고 있다지 모야. 그래서 코드를 작성한 나한테 직접 물어보기 위해 연락을 했더군"
글쎄, 이것이 자신에게는 자랑이라고 생각한 모양이지만 실제로 그가 작성한 코드에 버그가 있어서인지, 이해하기 어려운 알고리즘이었는지, 프로그래밍적 기교를 많이 사용했는지, 그 이유는 모르겠지만, 다른 사람이 알아볼 수 없는 코드를 작성한 것에 있어서 그는 실패한 코드를 작성한 것이 된다. 필자는 이러한 프로그래머와 함께 일하고 싶지 않으며, 인사 결정권이 있다면 아무리 능력이 뛰어난 프로그래머라 해도 당장에 해고시키고 말 것이다. (아마도 이 사람이 작성한 코드는 다른 프로그래머에 의해 보다 읽기 쉬운 코드로 재작성된 뒤에 폐기처분 될 것이다. 그리고 이에 따른 피해는 모두 회사에 돌아간다.)
여기서 프로그래밍 스타일에 대해서 이야기 하고자 하는 것은 어떻게 하면 버그가 없는 프로그램을 만들어 낼 것인가, 다른 사람이 이해하기 쉬운 코드를 만들려면 어떻게 해야 하는가라는 주제에 대한 것이다. 그리고 프로그래밍 스타일이라는 것은 어떻게 보면 독재자처럼 보일지도 모른다. 그러나 여기에 있는 모든 이야기들은 그저 이렇게 하는 것이 좋다라는 하나의 방법론일 뿐, 반드시 그렇게 해야한다라는 것은 아니다. 그리고 여러분의 프로그래밍 스타일은 여러분이 작성한 코드의 길이가 5천줄, 1만줄, 10만줄, 100만줄 씩 변해감에 따라 계속해서 변해가게 될 것이다. 지금까지 1만줄 미만의 코드에서 문제없던 프로그래밍 스타일은 10만줄에서는 다시 문제를 일으킬 수도 있다.
끝으로 이 글에 대해서 어떤 것이 옳다, 이것은 옳지않다라는 소모적인 논쟁을 바라지 않는다. 이것을 참고로 조금이라도 버그 없는 코드를 작성하는 데 도움이 되었으면 할 뿐이다.(GNU 코딩 스타일에 대한 리처드 스톨만과 리눅스 커널을 작성한 리누스와의 코딩 스타일에 대한 논쟁처럼 소모적인 것을 바라지 않는다. 리누스는 리처드 스톨만의 GNU 코딩 스타일을 찾아서 불 속에 던져버려라 라고 하였다.)
1. 작업 환경에 대하여
프로그래밍을 하는 데 있어서 작업 환경은 아주 중요하다고 생각한다. 예를 들면, 이런 것이다. 필자는 프로그래밍을 하는 동안에는 절대로 전화를 받지 않고, 다른 사람에게 전화를 받도록 지시하곤 한다(원래, 독재자 스타일이다. -_-). 필자에게 용건이 있거나 프로그램에 대해서 문제가 있는 경우에도 메모 하도록 한 다음에 나중에 일괄적으로 전달받는 형태를 취했다.
프로그래밍은 단순 노동이 아니라 정신을 집중해서 해야 하는 일이다. 단순히 1층 창고에 있는 노트북 200대를 2층 창고에 옮겨라 라는 것처럼, 하루 30분씩 4번에 나누어서 노트북을 옮기면 되는 작업이 아니다. 아주 잠깐 1분 동안 다른 곳에 정신을 팔아도, 내가 지금 어디까지 했는지, 이 부분에 무슨 코드를 넣어야 할지 다시 고민해야 하고, 신경을 써야 한다.
프로그래머들에게 올빼미족이 많은 것은 결코 우연이 아니다. 그들은 아무에게도 간섭받지 않고 모든 것을 잊고, 오직 코드 하나에 집중해서 작업할 수 있기를 바라는 것이다.
그러나, 당신이 단순히 말단 프로그래머라면 회사에서 이렇게 멋대로(?) 행동하는 것이 어려울 수도 있다. 그렇다면 당연히 윗사람을 설득시킬 수 있어야 한다.
또 다른 얘기, 당신이 팀장이라면 팀 구성원들이 작업 보고서나 다른 업무 때문에 시간을 낭비하는 일이 없도록 해야 한다. 마찬가지로 팀장이 팀 구성원들과 필요한 얘기를 할 때도 시시때때로 불러댄다면, 그것은 팀 내의 생산성을 헤치는 일이 될 것이다. 필요한 것이 있다면 업무 시작 전에 오늘 해야 할 일에 대해서 얘기하고, 퇴근 전에 시간을 정해서 오늘 한 일과 미처 끝내지 못한 일들에 대해서 얘기하는 것이 더 나을 것이다.
과연 이것만으로 모든 것이 끝나는 것일까? 미안하게도, 회사내에 있게 되면 다른 부서 사람들의 요청에 대해서 도움을 줘야하는 경우도 있고, 외부 사람들의 요청에 대해서 도움을 줘야하는 경우가 있다. 그리고 마침내는 외부 요청으로 인해 작업 시간을 모두 빼앗기는 경우도 생기게 된다. 결국, 외부 요청에 대해서는 오후 5시 이후에 처리한다와 같은 계획을 세워야 한다.
필자는 정말로 해야 할 일이 많은 경우에는 회의실에 전화기를 뽑아버리고, 문에 '절대 들어오지 마시오'라고 써 붙이고, 문을 잠궈버렸다. 그리고 그 5시간 동안 작업한 일들이 내가 일주일동안 한 일 보다 더 많았을 때도 있다.
1.1 자기 자신을 관리하라
프로그래머에게 자기 관리는 아주 중요한 문제다. 프로그래머가 자기 관리에 실패하는 경우에 생산성이 급격히 떨어질 수 밖에 없다. 따라서, 숨막히는 갑갑한 분위기는 싫지만, 어느 정도 자기 통제를 할 수 있어야 한다.
우리가 흔히 하는 실수 중에 하나는 프로젝트 초기의 여유로움과 프로젝트 마감 때의 급박함이라는 것이다. 그러나 프로젝트 초기에 할 일이 없다고 빈둥대지 않았으면 한다. 실제로 이렇게 얘기하면 초기에는 내가 해야 할 일이란 아무것도 없다라고 항변할 수도 있다.
그러나 프로젝트 초기에 요구 분석을 진행함과 동시에 자신만의 설계를 생각해보는 것이 좋다. 실제로 팀장과 클라이언트와의 회의 이후에 팀장은 각각의 팀원들에게 회의의 내용과 해야 할 것들에 대해서 알려줄 것이고, 자신이 해야 할 것이 무엇인지에 대해서 알 수 있을 것이다. 결국, 그 이외의 남는 시간에 세밀한 설계까지 생각해 볼 필요가 있다. 그것이 실제로 쓰일지, 그렇지 않든지 간에 말이다.
프로젝트 완료 시기가 다가오게 되면, 아직도 해야 할 일들이 이렇게 많은데 라거나 마감 시간을 맞추고 코드를 빨리 완성해야 한다는 압박감 때문에 자기 통제를 쉽게 잃게 된다. 그리고 설계보다 코드를 작성하는 것에 더 우선 순위를 두게 된다. 그러나 실제로는 마감시간보다 설계에 더 높은 우선 순위를 두어야 한다. 또한 점검에 우선 순위를 두어야 한다. 성급하게 코드를 작성함으로써 생기는 조잡한 실수를 막도록 주의해야 한다. 실제로 전체 코드에서 성급하게 완성된 코드에서 더 많은 버그가 발생할 것이다.
1.2 버그를 관리하라
코드 작업을 하는데 있어서 버그를 관리하는 것은 중요하다. 아마, 대부분의 프로그래머들은 버그가 발생하면 버그가 발생한 부분을 찾고, 버그를 없애는 것으로 그만일 것이다.
그러나 괜찮다면, 코드내에 이전에 쓰였던 코드를 주석으로 남겨두어야 하며, 어떤 버그였는지 기록해야 한다. 그것이 나중에 시간이 흘러서 버그가 있다라고 생각한 로직이 올바른 로직인 경우도 있고, 무엇 때문에 버그가 발생했는지 알 수 있기 때문에 다음 프로젝트에서 여러분의 코드에서 버그를 예방할 수 있게 해준다.
또한, 이러한 기록은 어떤 로직에서 버그가 가장 많이 발생했는가를 알려준다. 이것으로 여러분은 전체 코드에서 어떤 부분이 가장 중요하고, 복잡한 처리를 하는 부분인가를 어렵지 않게 알 수 있다. 또한, 나중에 코드에 버그가 발생했을 때, 어느 부분에서 버그가 발생했을 확률이 높은지도 쉽게 알아낼 수 있다.
또한, 팀장의 입장에서 여러분은 그 사람이 만들어 내는 코드를 보고, 그 사람의 코드 작성하는 스타일과 어떠한 버그를 자주 만들어내는지 알아낼 수 있을 것이다. 프로젝트가 완료된 후에는 각각의 팀원에게 취약한 부분을 지적해 줄 수도 있으며, 잠시 교육기관에 보내서 재교육을 시킬 수도 있다. 하하, 조금 무서운가? 하지만 이것이 팀장 뿐만 아니라 팀원에게도 약이 된다는 것을 납득시킬 수 있어야 하고, 회사에도 납득시킬 수 있어야 한다. 이런 면에서 필자는 독재자 기질기 충만하다. 회사내의 작은 정치(?)에 대해서는 굳이 쓰지 않겠지만, 여러분은 팀원이라면 스스로 이러한 스타일을 지키고, 프로젝트가 끝난 다음에 주석 처리된 코드들을 다시 한 번 살펴보는 것으로 점검해보기 바라고, 팀장이라면 팀원들에게 이러한 것을 요구하고 납득시켜라.
1.3 최적화를 하지마라
조금 이상하게 들릴 것이다. 최적화는 뒤로 미루는 것이 좋다. 하지 말라는 것은 아니다. 아마도 신입이나 초급 프로그래머가 작성한 코드를 보면 자신이 직접 코드를 만지고 싶다는 충동을 느끼게 될 것이다. "이것도 코드라고 작성한거냐?"라는 생각으로 머리 속이 꽉 차고, 당장에 고쳐버리고 싶다고 느끼게 된다. 하지만 일단 잘 작동하는 코드라면 그대로 두도록 한다. 프로젝트중에 잘 동작하는 코드를 건드려서 동작하지 않도록 하는 것보다 낫다.
최적화는 크게 프로그램이 수행되는 속도와 메모리 사용량등과 같은 것들을 말한다. 그러나 이러한 최적화들은 대개의 경우에 읽기 어려운 코드를 만들어낸다. 또한, 실제로 프로그램이 수행되는데 있어서 중요하지 않거나 효과도 없는 부분에 자신의 역량을 집중하게 되는 문제를 낳게 된다. 알아보기 어려운 코드는 대개의 경우에 더 큰 버그를 만들어낼 소지를 갖고 있으며, 자신만이 사용하기 쉬운 코드, 이해할 수 있는 코드를 만드는 지름길이 되며, 다른 프로그래머가 이해하는 데 더 많은 시간이 걸리는 코드를 만들어 내게 될 것이다.
만약 A와 B의 값을 교환하는 함수 swap를 만든다고 하자. 대개의 프로그래머는 다음과 같이 코드를 작성할 것이다.
A = 1
B = 2
tmp = A
A = B
B = tmp
이렇게 사용하면 int형 변수 3개를 사용한 것이라면 2 * 3 = 6, 6 바이트를 사용하는 함수가 될 것이다. 그런가 하면 다음과 같이 작성할 수도 있다.
A = 1
B = 2
A = A Xor B
B = A Xor B
A = A Xor B
분명히 이러한 기교를 사용할 수도 있고, C 언어라면 포인터를 사용해서 두 값을 바꿔낼 수도 있다. 그러나 이것은 단지 2바이트를 아끼기 위해서는 너무 큰 희생이다. 이전 코드보다 더 모호해졌다. 대개의 경우에 이런 문제는 더 이상 의미를 가지지 않는다. 512kb, 640kb와 같이 한정된 메모리만을 갖고 있던 XT 시절에나 각광 받을(?) 기술이다.
즉, 자신이 갖고 있는 프로그래밍 능력이 어느 정도나 되는지는 관심이 없다. 팀원 전체가 쉽게 읽을 수 있는 코드를 만들 수 있으면 된다. 당신이 작성한 코드를 나중에 다른 프로그래머가 볼 수 있으면 그것으로 되는 것이다.
최적화는 코드를 완성한 다음에 해도 늦지 않다. 전체 실행시간의 95%는 5%의 코드가 사용한다. 최적화는 이 5%의 코드에 집중해서 하는 것이 전체의 수행 성능 향상을 위해서 좋다.
1.4 코드에 집중하지 마라
프로그래머는 대개의 경우에 코드에 집중한다. 그러나 실제로 프로그래머가 보다 많은 시간을 투자해야 하고 집중할 것은 설계(Design)다. 며칠 전 술자리에서 얘기한 것처럼, 필자는 하루종일 조용히 모니터를 쳐다보고 종이에 끄적이다가 퇴근하기 1-2시간 전에 사무실을 날려버릴 것과 같은 키보드 소음공해를 일으키고 온다고 했다. 심지어는 머리가 맑을 때 코드를 작성하는 것이 아니라, 가장 졸리고 신경 안 쓰는 일을 할 때 코드를 작성한다. 아니, 코드를 작성하는 것은 지극히 단순 노동이라고 얘기하곤 한다. - 물론, 실제로 100% 맞는 말은 아닐 것이다. - 실제로 여러분이 해야 할 일은 설계다. 코드를 작성하기 전에, 함수 하나 작성하기 전에 여러분은 설계에 집중해야 한다.
직원들의 급여를 계산하는 함수를 작성하는 경우를 예로 들어보자. 처음에는 함수의 이름을 먼저 생각한다. 그리고 함수가 무엇을 할지를 쓰게 된다. 그리고 그 순서는 다음과 같을 지도 모른다.
1차
CalculatePay()
사원 ID를 받는다.
사원 ID에 따른 직급을 구한다.
직급에 따른 월급을 구한다.
월급을 반환한다.
2차.
급여를 사원 ID에 따라 계산한다
CalculatePay(사원ID)
필요한 변수를 선언한다
사원 ID에 따른 직급을 구한다.
직급에 따른 월급을 분류한다.
사장
과장
대리
사원
구한 월급을 반환한다.
3차.
급여를 사원 ID에 따라 계산한다
CalculatePay(사원ID)
필요한 변수를 선언한다
사원 ID가 맞는지 확인한다
ID가 맞으면 사원 ID에 따른 직급을 구한다.
틀리면 에러로 넘긴다.
직급에 따른 월급을 분류한다.
사장
과장
대리
사원
전체에서 예외가 발생했으면 예외처리로 넘긴다.
전체에서 예외가 발생하지 않았으면 구한 월급을 반환한다.
보통은 이와 같이 종이에 작성하게 될 것이다. 자, 이게 다 되었으면 이것을 도서 받침대 같은 데 올려놓고, 그대로 타이핑하도록 하자, 그리고 자신이 사용하는 언어에 따라 적절한 문법에 따라 코드로 옮겨 놓기만 하면 될 것이다.
이것으로 무엇을 할 수 있었는가? 전체의 설계를 할 수 있었다. 또한, 코드를 작성하기 전에 한 번 점검함으로써 예상치 못한 부분들에 대해서도 생각해 볼 수 있었고, 그에 대한 문제도 적절히 처리할 수 있었다. 위의 의사 코드를 보면 들여쓰기가 되어있을 것이다.
그렇다, 위 의사 코드를 에디터에 그대로 옮겨놓고, 모두 주석 표시를 해주고, 문장 중간 중간에 코드를 써 넣어라. 그러면 된다. 이것으로 여러분이 작성한 코드는 조금 더 버그가 적어질 수 있고, 주석이 풍부한 코드가 될 수 있다.
1.4.1 설계와 프로젝트
여러분이 선호하는 언어가 있을 것이고, 기술이 있을 것이다. 닷넷과 관련된 언어를 한다면 웹 서비스를 작성하는 방법, 사용자 정의 웹 폼을 만드는 방법, 사용자 정의 ASP.NET 웹 컨트롤을 만드는 방법과 같은 것들을 아는 것이 중요하다고 생각할지도 모른다. 물론, 이러한 것들도 중요하지만, 엔터프라이즈 개발, 대규모 응용 프로그램, 큰 팀 단위의 개발에서는 일반적으로 도구의 선택은 낮은 우선 순위를 갖는다.
진실을 말하자면 소프트웨어 개발 프로젝트를 성공적으로 만드는 딱 한 가지가 있는데, 그것은 설계다. CORBA, COM+, .NET과 같은 기술들도 확장성 있는 설계와 아키텍처가 아니라면 프로젝트는 결코 성공할 수 없다.
좋은 설계와 성공적인 프로젝트를 위해서 소프트웨어 공학, 개발 방법론, UML, AOP, SOFA, 디자인 패턴과 같은 분야에 대해서 학습하기 바라며, MS 솔루션에 관심 있는 개발자라면 MSF(Microsoft Solution Framework)를, IBM 솔루션에 관심 있는 개발자라면 DNA Architecture와 같은 문서들을 검토하기 바란다.
1.5 꾸준히 하라
프로그래머가 짓고 있는 죄 중에 가장 큰 죄가 무엇인가? 그것은 꾸준히 하지 못한다는 것이다. 대부분의 프로젝트는 원대한 꿈(?)을 갖고 시작하지만, 결국에는 마감시간 안에 끝내기에 급급하다는 것이다. 그래서 처음에 세웠던 계획대로 코딩을 하지 못하고, 설계에 들이는 시간보다 곧바로 코딩을 하기에 바쁘고, 수정한 코드들에 대해서 주석을 남기지 않는 실수를 하게 된다. 그래서 나중에는 코드와 주석에 설명된 내용이 전혀 엉뚱하게 되어버리는 경우도 생기게 된다.
그리고 무엇보다 가장 큰 문제는 시간이 흘러서 예기치 못했던 곳에서 에러가 생긴다고 하는 것이다. 그리고 그 버그를 잡는데 들이는 시간이 여러분이 코드를 작성하는 데 들인 시간보다 더 많다는 것을 알게 될 것이다. 그것은 프로젝트의 끝이 아니다.
아무리 급해도 '빠르고 성급하게' 가 아니라 '꾸준히 완전하게' 하는 것을 목표로 삼아라.
2. 코딩 스타일
코딩 스타일에 있어서는 가장 많은 논란이 있을 것이다. 그러나 코딩 스타일에는 이거다라고 할만한 것은 없다. 언어에는 각각의 언어마다의 표기 방법이 있고, 그들 세계의 규칙이 있다. 그리고 대부분의 경우에 그러한 규칙을 따르는 것이 좋다.
필자는 처음에 C 언어를 익혔고, 그 다음에 HTML, Perl, ?JavaScript, C++, VBScript, LPI-COBOL, SQL, Informix/4GL… 등의 순서대로 언어를 익혔다. 그리고 혼자서 언어를 익히는 기간동안은 모든 언어에서 사용할 수 있는 코딩 스타일을 만들어나가고자 했다. 즉, C 언어에서도 만족할 수 있고, C++에서도 만족할 수 있고, Perl에서도 만족할 수 있기를 바란 것이다. 그러나 COBOL에 와서 그 꿈은 깨져버렸고, 프로젝트를 시작하면서는 자신의 코딩 스타일에 대해서 염려하기는커녕 팀 내에서의 코딩 스타일을 따라가기에 바빴다. 이미 작성된 수십만줄이 넘는 코드속에 자신이 부속품처럼 끼어들어가 필요한 모듈을 작성해나가야 했다. 역시나 많은 시행착오를 겪었지만, 이미 마련되어 있는 지침들에 따라서 코드를 작성해 나갔다. 그리고 그러한 지침들에 익숙해졌을 때는 다른 사람이 작성한 코드도 알아보기 쉬웠다. 물론, 처음 팀별 작업을 하지 않았을 때에는 자신의 개성을 죽이는 것이며, 지금까지 자신이 사용한 스타일을 버리는 것이 되지만, 그렇게 함으로써 팀 내에서 의사소통을 원활하게 할 수 있으며, 프로젝트를 빠르게 진행시킬 수 있다는 것은 당연한 것이다.
때문에 여러분이 사용해왔던 프로그래밍 언어가 몇 개가 되든지 간에 관계없다. 여러분이 사용하는 언어에서 사용하는 관습적인 프로그래밍 방법을 따르는 것이 대부분의 경우에 여러분의 삶을 편안하게 만들어줄 것이다. 그리고 여기에 자신만의 개성을 덧붙여 나가라. 만약, 팀원간에 작업을 해야 하고, 해당 언어의 관습적인 방법에는 없는 방법을 사용한다면 팀원간에 논의하고 지침을 만들어 모두가 그 지침을 지키도록 해야 한다.
2.1 지침을 만들어라
앞에서 시작한 이야기지만, 자신의 프로그래밍 스타일에 대한 지침을 만들어야 한다. 그러나 겨우 자신이 자신을 위해서 이러한 것을 만든다는 것은 대부분의 경우에 하지 않는 일이다. 물론, 하지마라. ;
그러나 팀원간에 작업을 해야 하거나 회사 내에서 작업을 하는 경우라면 반드시 코딩 스타일에 대한 지침을 만들어야 한다. 그렇지 않으면 옆 사무실에 있는 팀이 만든 코드조차 우리 사무실에서 만드는 코드와 스타일이 다르기 때문에 혼란스럽게 된다.
문제는 이것만으로 끝나지 않는다. 우리 팀이 만든 것과 다른 팀이 만든 컴포넌트들을 모두 이용하는 다른 회사에서 이렇게 투덜댈 수도 있다. 같은 회사에서 만든 제품들이 스타일이 달라서 사용하는 데 있어서 두 번의 교육이 필요하다라고…
그러나 일관된 지침을 사용한다면, 미국에 있는 팀원들이 만든 코드와 한국에 있는 팀원들이 만든 코드 모두 읽기 쉬울 것이고, 코드가 정확히 무엇을 하는지 추측하기가 쉬워진다.
2.2 공격적인 코드를 작성하라
이 부분에 대해서는 많은 의견들이 있다. 필자는 여전히 방어적인 코드를 작성하는 것을 좋아하지만, 필자의 방법역시 굳이 방어적이라고 할만한 것은 아니다. 다음과 같이 팩토리얼을 구하는 함수가 있다고 하자.
function factorial(numeric as integer)
int retVal
if numeric = 0 then
retVal = 1
return retVal
else
retVal = numeric * factorial(numeric - 1)
return retVal
아마도 이 함수는 입력 값으로 0이나 양수일 때는 잘 동작하겠지만, 음수일 때는 동작하지 않을 것이다. 이러한 경우에는 함수의 첫 문장에 debug.assert numeric >= 0과 같은 문장을 넣어서 원하는 값이 들어가지 않는 경우에는 중지시킬 수 있도록 해야 한다. 어쨌거나 이러한 코드는 정확히 말해서 명료한 코드도 아니고 잘된 코드도 아니다(return을 두 곳에 사용한 것도 잘못된 코드 양식이다).
에러가 발생하는 경우에 보통의 프로그래머들은 0이나 ?1 등을 반환하도록 배웠을 것이다. 그러나 이것은 대부분의 경우에 프로그램에서 적절한 값을 반환했다고 생각하며 문제없이 수행될 소지가 아주 크다. 때문에, 이러한 경우에는 반환할 수 있는 데이터타입에서 가장 큰 값을 반환하도록 해야 한다. -32768과 같은 값을 반환하면 여러분은 쉽게 무엇인가가 잘못되었다는 것을 알 수 있을 것이다.
2.3 if
대부분의 경우에 If문에 대해서 올바르지 않은 방법을 사용하고 있다고 생각한다. 예를 들어서 다음과 같은 경우다.
If rs.RecordCount > 0 Then
'처리 코드
End If
이러한 코드가 반드시 올바르다고 할 수 없다. 그래서 방어적인 코딩을 하라고 조언하곤 한다. 위 코드는 다음과 같이 다시 작성할 수 있을 것이다.
If rs.RecordCount > 0 Then
'처리 코드
Else
'처리 코드
End If
Else 부분에서 특별히 처리할 것이 없다면 주석에 do nothing과 같은 주석을 달아도 좋다. 그러나 대부분의 경우에 do nothing이라고 주석을 다는 것은 적당하지 않은 방법이다. 따라서 Else 부분에서 아무것도 하지 않더라도, 어떤 경우에 예외가 발생할 수 있는지에 대해서 주석으로 설명해야 한다. 필자는 절대로 일어나지 않을 오류인 경우라 해도 반드시 if만을 쓰는 경우는 없다. 모든 판별문에는 else를 두고 있다.
오랜시간이 지나서 같은 코드를 보는 경우에 대부분의 복잡한 코드에서 Else 부분이 어떤 경우에 발생할 수 있는지 잘 기억나지 않는다. 코드를 작성하고 있는 시점에서만 Else가 어떤 경우에 발생할 수 있는지 가장 명확하게 이해하고 있으므로, 코드를 작성할 때 Else 부분이 발생하는 경우에 대해서 적어두어야 한다.
if 뿐만 아니라 select/switch case와 같은 조건문에도 else가 들어가야 한다.
C 언어에서 가장 많은 경우지만 다른 언어를 작성하는 프로그래머들 사이에서도 많이 볼 수 있는 유형은 else 부분에서와 같이 절대로 처리되지 않아야 하는 부분이 처리되는 경우에 많은 프로그래머들은 0이나 -1등을 반환하도록 프로그래밍을 하고 있다. 그러나 0과 1 같은 반환 값은 대부분의 경우에 프로그램의 동작을 멈추게 하지 않으며, 버그를 내부에 숨기게 되는 오류를 범하게 된다. 그렇다면 사용하는 함수의 변수가 사용하는 값 중에 가장 큰 값을 반환하도록 한다. 예를 들면, -31000과 같은 값을 반환하도록 한다. 그러면 여러분은 코드에서 말도 안 되는 값을 반환하는 부분을 쉽게 찾아낼 수 있을 것이고, 무엇인가 잘못되었다는 것을 알 수 있을 것이다.
2.4 문서화하라.
프로젝트를 진행하는 동안에 여러분이 작성한 의미 없는 문서, 회의시간동안에 남았던 문서들, 프로그램의 버그 등을 문서화한 것 등의 수 많은 문서가 남아있게 될 것이다. 뿐만 아니라 팀원들의 작업 결과나 작업 진행, 보고서 등을 받아보게 될 것이다.
이러한 것들을 팀원들에게
history@xyz.com과 같이 하나의 계정으로 모두 보내도록 하고, 프로젝트가 완료하는 시점에 이것들을 모두 일괄출력해서 보관하도록 한다.
이 문서들은 모두 프로젝트 진행기간 동안 다시 쳐다보는 일도 없을 것이고, 많아야 1-2번 정도 쳐다보게 될 것이다. 그러나 프로젝트가 끝나고 버그가 발생했을 때, 무엇이 문제였는지, 어디를 제일 먼저 확인해야 하고 수정해야 하는지를 쉽게 찾는 데 도움이 된다.
이러한 것들을 잘 모아두었다면, 프로젝트가 완료되는 시점에서 문서화하는 것도 그리 어려운 것은 아닐 것이다(적어도 문서화작업이라는 미명하에 처음부터 모든 것을 다시 살펴보는 일은 막을 수 있다).
2.5 버그를 기록하라
버그를 기록하는 것이 필요하다. 이러한 버그들을 기록하는 가장 일반적인 형식을 만들어서 팀원들이 모두 같은 양식으로 작성하여 리포트 할 수 있도록 해야 한다.
대개는 다음과 비슷한 형식이 될 것이다.
버그 발견일 : 팀 내부나 테스트 과정에서 버그가 발견되고 보고될 것이다.
원인 : 버그의 원인.
기능부호가 H인 경우에 대해서 잘못 처리하는 경우와 같은 로직상의 버그에서부터 참조할 수 없는 메모리를 참조하는 포인트 사용의 오류등과 같이 다양할 것이다.
증상 : 버그로 인해 생기는 증상을 자세히 기록한다.
기능부호 H를 소비하는 경우에 신품재고는 그대로 유지되고 재생품재고가 감소해야 하는데 신품과 재생품 재고가 모두 감소하는 경우와 같을 것이다.
해결책 : 버그를 어떻게 없앴는가.
예방법 : 이와 같은 종류의 버그를 예방할 수 있는 방법
관련 파일 : 버그가 있는 파일과 버그와 관련되어 같이 변경되어야 하는 종속 파일들을 모두 기록한다.
최종 수정일 : 누가, 언제 최종적으로 수정했는가
이와 같은 버그 리포트는 모두
bug@xyz.com과 같이 하나의 메일함으로 보내도록 한다. 이렇게 하면 팀원 모두가 어떤 버그가 있었는지 확인할 수 있으며, 프로젝트가 완료 후 이러한 기록들을 모두 모아서 버그를 분석할 수 있다.
버그 분석을 통해서 프로젝트에서 사용했던 방법보다 더 빠르고 정확하게 대처할 수 있는 방법을 찾을 수도 있고, 프로젝트 진행 과정에서 어떤 부분에서 버그가 가장 많이 발생했는지 알아낼 수 있고, 진행 과정에 문제가 있는지도 알아낼 수 있다.
버그 리포트는 버그를 발견하는 즉시 작성되어야 하며, 수정하지 않거나 미루게 되더라도
bug@xyz.com으로 보내도록 해야 한다. 그러나 될 수 있으면 버그는 발견되는 순간에 제거하도록 해야 하며, // fix me와 같이 주석을 남겨서 일을 미뤄서는 안 된다.
나중에 코드가 완성된 다음에 버그를 수정하는 경우 나머지 코드와 예기치 않은 오류를 만들어낼 수 있으며, 더욱 더 문제를 어렵게 만들 수도 있다.
2.6 변수
변수의 사용에 있어서 만큼 프로그래머들에게 많이 이야기 된 것은 없다. 그리고 필자의 몇 가지 이야기는 분명히 논쟁거리가 된다는 것을 알고 있다. 아마도 여러분이 생각하는 상식적인 이야기와 여러분이 생각하기에 비상식적인 이야기로 꽉 찰 것이라는 것이다. 처음에 얘기했듯이 반드시 따라야 하는 규칙은 아니다. 마음에 들면 따르고, 마음에 들지 않으면 안 따르면 그만이다.
2.6.1 모호한 변수는 사용하지 마라
모호한 변수는 사용하지 마라. 언어에 따라서 variant 형 변수를 지원하거나 지원하지 않을 것이다. 필요하다면 variant를 쓸 수 있지만, 대부분의 경우에는 정확한 데이터 형식을 지정하라. Variant는 사용하지 말아야 할 악과 같은 데이터 타입이라고 생각하자.
물론, Perl과 같은 언어는 변수의 데이터 타입이 없으며 variant 타입이 기본 타입이며, 변수에 저장되는 값에 따라서 각각의 서브 타입을 갖고 있다.
그러나 VB와 같은 언어의 경우에 variant 타입과 자동형 변환과 같은 암시적 타입 변환은 프로그래머에게 가장 해독한 독과 같다. 암시적 타입 변환의 경우에 언어마다 작동하는 방식이 조금씩 다르기 때문에, 기존 프로그래밍 언어의 지식을 자신이 현재 작업하고 있는 새로운 언어에 작용하는 경우에 여러분이 원하는 대로 작동하지 않게 된다.
A = 123
B = 456
Print A & B
Print A + B
Print "Pay" + A & B
이러한 경우에 여러분은 이것이 어떻게 처리될 지 알 수 없다. A가 정수인지, 문자열인지 알 수 없게 되어 버린다. 따라서 반드시 변수의 타입을 지정해서 사용해야 한다.
위에서와 같이 숫자가 문자열로 암시적인 형변환이 일어나는 경우에 해당 언어에 익숙하고, 세세한 것까지 기억하는 프로그래머가 아닌 한 틀리기 쉽다. 이러한 경우에는 CStr, CInt, CLng와 같은 함수를 사용해서 변환할 형을 미리 지정해주도록 해야 한다(언어에 따라 함수는 다르다).
Variant는 어떤 경우에 사용하는가? 대부분의 경우에 의문을 갖겠지만, VB와 같은 언어에서는 컨트롤과 같은 객체를 넘겨받는데 Variant 타입을 사용한다. 그 외에 4 * 4와 같이 정사각형의 배열을 만드는 것에 익숙하지만, 경우에 따라 1 행은 7열, 2행은 3열, 3행은 4열과 같은 불규칙 배열이 필요한 경우에 Variant 타입을 쓸 수 있다(1차원 배열 3개에 대한 컨테이너로 Variant 타입을 이용하는 경우다).
최근의 프로그래밍 언어들은 대부분 변수의 초기값을 설정한다. 숫자형에 대해서는 0, 문자열에 대해서는 문자열의 길이가 0인 문자열, 객체에 대해서는 null로 설정한다. 그러나 이러한 것들이 다른 언어에서 반드시 적용되는 것은 아니다. 변수는 반드시 초기값을 선언하도록 한다.
2.6.2 변수는 분명하게 선언한다
int I, j, k;
Dim I, j, k as Integer
첫번째는 C 언어이고 두 번째는 VB이다. 이 경우에 첫번째는 3개의 변수 유형이 모두 int 형이지만, 두 번째 줄에서는 I, j는 Variant 형이고, k만 Integer 형이 된다. 이와 같이 하나의 언어에서 사용하는 문법이 다른 언어에서는 전혀 다르게 사용될 수 있다. 따라서 위와 같은 코드들은 모두 다음과 같이 고치는 것이 좋다.
Int I;
Int j;
Int k;
Dim I as integer
Dim j as integer
Dim k as integer
이렇게 고침으로써 다른 사람이 코드를 읽고 이해하기 쉬워진다.
위 코드는 또 잘못되어있다. 이 변수들은 모두 초기값을 갖지 않는다. 초기값을 갖도록 선언해야 한다. 또한, 옆에는 각각의 변수가 무엇 때문에 선언되는지 적어야 한다. 만약 코드를 작성하는 도중에 필요 없게 된 변수가 있어서 코드내에서 해당 변수를 지웠다면 변수의 선언을 지우는 것도 잊지 않도록 한다.
C 언어에서는 변수의 선언 여부를 엄격하게 검사하지만, VB와 같은 언어들은 변수의 선언을 엄격하게 검사하지 않는다. 이러한 경우에 VB에서는 Option Explicit등을 사용해서 변수의 선언을 엄격하게 검사할 수 있다. 가장 흔한 실수는 변수명을 ?PrintPage와 ?PirntPage와 같이 적는 것이다. 변수 선언 여부를 검사하지 않으면 VB와 같은 언어는 새로운 변수로 생각하여 ?PirntPage라는 새로운 변수를 생성하게 되고 프로그램이 문제없이 실행되더라도 알지 못할 버그를 내포하고 있는 것이다.
2.6.2.1 Option Explicit에 얽힌 이야기
2.6.2에서와 같이 Option Explicit을 사용해서 반드시 사용하는 변수를 선언해서 사용해야 한다라는 것은 사람들에 따라 의견이 분분하다. VBScript, VBA를 사용하다가 VB를 사용하는 프로그래머나 Perl 프로그래머들은 그러한 것들을 사용하는 것은 오히려 거추장스러운 것이라고 얘기한다. 반면에 처음부터 VB를 사용한 프로그래머나 C/C++과 같이 변수 선언을 엄격하게 검사하는 언어를 사용하다가 VB를 사용하는 프로그래머들은 반드시 Option Explicit을 써야 한다고 말한다.
아주 능숙한 프로그래머 중에도 Option Explicit을 사용하지 않아도 되며 타이핑 오류를 잡는 데에 아주 능숙한 프로그래머들도 많다.
결국, 어떠한 방식을 선택하느냐는 여러분의 몫이다. 필자는 대부분의 언어에서 변수 선언을 검사하기를 좋아한다.
2.7 명명 규칙
명명규칙에는 많은 것들이 있다. 지역 변수, 모듈 변수, 전역 변수와 같이 변수의 범위를 지정하는 헝가리안 표기법도 있고, 파스칼식의 변수 명명법도 있고, 낙타혹 표기법도 있고, _을 이용한 표기법, Perl의 세계에서만 쓰이는 표기법등 그 표기법도 셀 수 없이 많다. 어떤 표기법이 가장 좋다는 생각은 옮지않다. 어떤 프로그래머들은 헝가리안 표기법이야말로 써서는 안 되는 그야말로 쓰레기 표기법이라고 혹평하고 있으며, 다른 프로그래머들-그들 대부분은 윈도우 프로그래머인-은 헝가리안 표기법이야말로 단순하면서 사용하기 쉬운 표기법이라고 한다.
그만큼 명명규칙에는 많은 논쟁이 있다. 필자는 어떤 것이 옳다고 얘기할 수 없다. 다만, 자신의 마음에 들면 쓰고, 마음에 들지 않으면 쓰지 마라. 그리고 이 부분은 자신의 프로그래밍 경험이 쌓여감에 따라서 다시 한 번 펼쳐보고 다시 한 번 생각해보는 것으로 자신의 방법을 세워나가길 바란다(이점에 있어서는 필자도 마찬가지다).
2.7.1 변수의 범위를 정하는 표기법을 사용하라
대부분의 언어에서 헝가리안 표기법을 사용하는 것은 바람직하다. 전역변수를 뜻하는 g, 멤버 변수, 즉 클래스 전체에서 쓰이는 변수를 뜻하는 m과 이러한 접두어가 붙지 않는 지역변수와 같이 변수의 범위에 따라 나누는 것도 적절하다.
헝가리안 표기법이 싫다면, 변수명 전체를 대문자로 쓰거나 변수의 첫번째 글자만을 대문자로 쓰는 것과 같은 표기법으로 전역 변수라는 것을 나타낼 수 있다. 코드로 표현하면 다음과 같을 것이다.
PAY_METHOD
Pay_Method
PAYMETHOD
PayMethod
이러한 변수 표기법을 써도 좋다. 전역 변수를 ?PayMethod와 같이 첫 글자를 대문자로 쓰기로 했다면 지역 변수들은 payMethod와 같이 첫 글자를 무조건 소문자로 시작하는 것도 좋은 방법이다.
헝가리안 표기법은 변수의 범위 외에 변수의 타입을 접두어로 표기한다. 문자열에 대해서는 str을 쓰거나 객체에 대해서는 obj, long 타입에 대해서는 lng 등을 사용하고, 컨트롤에 대해서는 cmd, lst 등의 여러 가지 접두어를 사용한다.
헝가리안 표기법을 따르기로 했다면 이러한 표기를 따르도록 하고, 만약 다른 접두어를 사용하려 한다면 팀원간에 협의하에 다른 접두어를 사용하도록 한다.
예를 들어서 필자는 예전에는 레코드셋 객체에 대해서 obj?RsEmployee와 같이 사용했지만, 현재는 rsEmployee와 같은 접두어를 사용한다. rs와 같은 접두어를 사용한다면 이러한 표기법에 대해서 팀원간에 의견을 나누고 문서로 만들어서 팀원 모두가 공유하여야 한다.
헝가리안 표기법을 따르는 것과 따르지 않는 것은 여러분의 자유다. 그러나 어떤 프로젝트에서 사용할 표기법을 만들고, 팀원간에 그 표기법을 공유해야 한다. 또한, 프로젝트 진행 중에 그 표기법에서 문제점이 나타나더라도 표기법을 변경해서는 안 된다. 이미 진행된 프로젝트는 정한 표기법에 따라서 진행하고, 나타난 문제점은 프로젝트가 끝난 다음에 분석하여 다음 프로젝트를 위해서 보다 다듬어진 표기법을 만들도록 한다.
2.7.2 변수의 이름은 최대한 알기 쉽게 해라
어떤 변수 이름이 알기 쉬운 것인가? 라는 것에도 의견이 다르다.
종업원 수에 대해서 변수명을 지어보라고 하면 사람들마다 다른 변수명을 내놓을 것이다. iEmployee, employee, ?NumEmployee, ?NumberEmployee, ?NumberOfEmployee, ?EmployeeNumber, ?EmployeeNo와 같이 다양하게 될 것이다.
숫자라는 것을 뜻하는데 num, no, number, numberof가 쓰일 수도 있고, 여기에 헝가리안 표기법과 같은 접두어가 더해지면 문제는 더욱 더 다양해진다.
대부분의 경우에 num, no와 같은 축약형은 사람들마다 선호하는 축약형이 다르기 때문에 문제를 복잡하게 만들기 때문에 적절하지 않다. ?NumberOf와 같은 것들도 임의적인 것들이기에 적합한 것은 아니다. 대부분의 경우에 ?NumberEmployee나 ?EmployeeNumber와 같은 것들을 쓰는 것이 적당하다.
2.7.3 성격에 따라 명명하라
성격에 따른 명명법은 변수보다는 주로 클래스 파일이나 모듈 파일의 명명에 많이 사용하게 되지만, 변수 또한 다르지 않다.
다음과 같은 두 가지 코드에 대해서 한 번 생각해 보자.
ProvinceCodeAdd
UserJobCodeAdd
UserResidenceCodeAdd
UserAdd
AddProvinceCode
AddUserJobCode
AddUserResidenceCode
AddUser
첫번째와 두 번째는 모두 다르다. 첫번째는 어떤 대상이 중심이 되는 명명법이고, 두 번째는 어떤 행위가 중심이 되는 명명법이다. 필자는 첫번째와 같은 명명법은 주로 변수명에 사용한다. 물론, 변수명이니 뒤에 붙은 Add는 뺀다. 이러한 명명법을 사용하는 경우는 ?UserName, ?UserAge, ?UserPhoneNumber와 같이 어떤 대상을 위주로 명명하는 경우다. 객체에서 클래스를 명명하는 경우에는 User.Name, User.Age, User.?PhoneNumber와 같이 명명하게 되고, 이러한 속성들의 값은 ?UserName, ?UserAge, ?UserPhoneNumber 등에 저장하는 습관을 같고 있다.
두 번째와 같이 주로 어떤 행위에 대해서 명명하는 경우에는 대부분 모듈이나 함수명이 된다. 어떤 행위에 해당하는 동사를 앞으로 두는 것으로 코드를 찾는 것이 쉬워지고, 코드를 유추하는 것도 쉽다. ?PrintPage, ?PrintScreen과 같이 Print + 'Print할 디바이스 명'의 형식으로 사용한다.
2.7.4 클래스의 표기법을 통일하라
조금 이상하게 들릴지 모르지만, 프로젝트가 보다 큰 프로젝트라면 하나의 팀에서 모든 작업이 이루어지지 않고, 팀별 간에 작업이 나누어지게 된다. 객체지향 언어를 사용하기로 결정했다면, 클래스의 속성, 메소드의 표기법에 대해서 일관성을 갖도록 한다.
여기에는 약간의 문제점이 있다. 팀 전체가 다른 팀의 소스까지 모두 볼 수 있도록 전체에서 일관된 코딩 스타일을 사용할 것인가, 아니면 팀 내에서는 개개인 각자의 코딩 스타일을 그대로 사용하지만 클래스에서 노출되는 메소드 명명법에 대해서 일관된 스타일을 사용할 것인가라는 것이다.
많은 사람들은 객체 지향 프로그래밍이고, 클래스의 내부에 대해서 신경쓰지 않아도 되고, 각자의 코딩 스타일을 지켜(?)나갈 수 있으므로 두 번째를 좋아할지도 모른다. 물론, 두 번째를 써도 된다. 그러나 개인적으로는 팀간에 소스 코드를 공유하고, 수정하는 일이 없더라도 소스 코드에 대해서도 일관된 스타일을 사용하는 것이 좋다.
필자는 전체가 일관된 스타일을 쓰는 곳에서 일을 할 때, 그 스타일을 익히는 것은 다소 짜증나고 귀찮았지만, 익숙해지고 몇 개의 프로젝트를 끝낸 다음에, 다른 팀의 완전히 다른 소스를 넘겨받아서 프로젝트를 진행하게 되었을 때도 편하게 코드를 읽을 수 있었고, 코드에서 사용된 흐름도, 코드 체계, 코드 설명에 대한 문서들을 찾는 방법을 알고 있었고, 코드에서 이 부분은 어떤 것을 처리하는 부분인지 쉽게 알 수 있었다.
예를 들면, 이런 거다. 1000번대 이름을 갖는 코드들은 현금 계정을 처리하는 코드들이고, 2000번대 이름을 갖는 코드들은 물자 계정을 처리하는 코드들이라는 것이며, S로 끝나는 코드들은 어떤 데이터들의 요약을 행하는 코드들이라는 것과 같은 것이다. 물론, 코드 안에 사용된 변수들도 이러한 통일된 규칙을 갖고 있었기 때문에 전혀 다른 업무를 처리하는 프로그램이지만 무리 없이 코드를 분석하고 일을 진행할 수 있었다.
클래스의 표기법에 대한 약간의 힌트를 제공하자면, 언어에 따라 다음과 같이 사용한다.
VB와 같은 언어들은 메소드가 대문자로 시작한다.
MyObject.CalcuateLoan()
Java, C#, Delphi 같은 언어들은 메소드가 소문자로 시작한다.
MyObject.calculateLoan()
VB에서는 속성의 이름도 대문자로 시작한다.
MyObject.BackgroundColor = MyColor.Red
C#과 같은 언어에서 속성은 소문자로 시작한다.
MyObject.backgroundColor = MyColor.red;
여러분이 VB에서 프로그래밍을 했다고 해서 C#과 같은 언어를 사용하면서 메소드와 속성에 대해서 모두 대문자로 시작하도록 한다면, 다른 C# 프로그래머들은 어떤 것이 메소드이고 속성인지 이해하기 어려울 것이다. 이와 같이, 특정 언어에서 따르는 고유의 명명법을 따르는 것과 팀 내에서 이러한 명명법을 정하고 문서화한다.
2.8 if의 스타일
if, select와 같은 분기문을 사용할 때는 항상 중첩된 분기문을 쓰지 않도록 해야 한다. 분기문 내의 코드가 길어지면 길어질수록 코드 중간에 나오는 else와 같은 문장이 어떤 if에 속한 것인지 이해하기가 어렵게 된다.
If 조건문
아주 긴 코드
…
…
…
else
코드
end if
이와 같은 구조를 갖는 코드가 있다면 else 부분에 있는 조건문을 if에 있는 조건문과 바꾸는 것이 좋다.
If 조건문
코드
else
아주 긴 코드
…
…
…
end if
이와 같이 하는 것이 더 코드를 이해하기 쉽도록 해준다. 또한 if 문과 else 문에는 반드시 어떠한 조건이 일어나는 경우에 분기가 일어나는지 주석을 달아야 한다. 종종, 바쁘다는 이유만으로 If나 else에 주석을 생략하거나 간략하게 달아놓은 경우 한참 시간이 흘러서 다시 보게되면 무엇을 판단하는 것인지 이해하기 어려운 경우가 많다. 그래서 어떤 경우에 분기가 일어나는지 이해하는 데 주석을 다는 것보다 더 많은 시간이 걸린다. 대개의 경우에 충분한 주석을 다는 것으로 디버깅시에 여러분의 노력은 충분한 보상을 받을 것이다.
if에 주석을 다는 경우에는 개인적으로 다음과 같은 주석 스타일은 바람직하지 않다.
If 조건문 // 이것은 주석입니다.
대개의 경우에 이것은 간단한 주석인지 모르나, 조건문이 길어지는 경우에 80 컬럼을 넘어가게 된다. 모든 프로그래머가 여러분과 같이 80 컬럼 이상을 표시하는 디스플레이와 에디터에서 작업하는 것은 아니다. 또한, 필자처럼 1280*1024나 1600*1200과 같은 높은 해상도에서 작업하는 것은 아닐 것이다. 여러분의 코드를 보게 될 다른 사람을 배려하도록 한다. 개인적으로 만든 것이라 하더라도, 누군가는 언젠가 여러분의 코드를 보게 된다. 분기문에는 다음과 같은 주석을 쓰는 것이 좋다.
// trax가 술에 취한 경우에 실행되는 부분입니다.
If 조건문
개인적으로 코드의 주석을 소설처럼 읽듯이 코드를 읽는 경우가 아니라, 코드에 쓰인 조건문이 너무 많아서 코드 전체가 분기문으로 뒤덮인 경우에는 코드에 보다 집중하기 위해 다음과 같은 주석 스타일을 쓰기도 한다.
If 조건문
// trax가 술에 취한 경우에 실행되는 부분입니다.
두 가지의 차이는 첫번째는 주석을 보고 코드를 이해하기 좋다는 것이고, 두 번째는 코드에 대해서 이미 잘 알고 있고, 주석보다는 코드의 로직을 판단하는데 더 용이하다는 것이다. 시선이 가장 먼저 가는 것에 따른 차이다. 그러나 대개의 경우에는 첫번째와 같은 주석을 쓰는 것이 좋다.
2.9 함수의 길이
컴퓨터 세계에서 가장 길이길이 남겨진 이야기 중에 하나는 아주 크고, 어려운 문제가 있다면 그것을 잘게 쪼개고, 하나씩 해결해 나가다보면 전체를 해결할 수 있다는 것이다. 영어로 쓰면 Divide and Conquer! 라던가?
보통 클래스 전체를 작성하는 경우가 아니라 특정 함수만을 작성하거나 함수를 수정하는 경우에 새로운 주석을 작성한다. 함수의 기능에 대한 주석을 작성할 때, 한 두줄 이내로 함수의 기능이 무엇인지 설명하지 못한다면 함수에 지나치게 많은 기능이 있는 것이다. 이러한 경우에는 좋지 못한 함수를 작성한 것이다. 함수는 작고 간결해야 한다. 모든 기능을 갖고 있는 만능 함수는 복잡하고 이해하기 어렵기 때문에 버그를 갖기 쉬우며, 유지보수하기 어렵다. 따라서, 함수의 기능을 한 두줄 이내로 설명하기 어렵다면, 설계자에게 설계가 잘못된 것인지 문의해서 확인할 필요가 있고, 설계를 수정해야 한다.
대부분의 모듈들이 1,000에서 2,000 라인 정도의 길이를 갖고 있었는데, 그 중에 한가지 모듈만이 가장 많은 처리를 하고 있었고, 18,000 라인 정도의 길이로, 다른 코드들에 비해서 10배 이상이나 긴 코드를 갖고 있었다. 결국, 다른 모듈들에 발생한 버그들은 쉽게 처리할 수 있었지만, 모든 기능을 갖고 있는 18,000 라인을 갖고 있는 모듈에서 버그가 발생하면 몇 시간이 아니라 며칠에 걸쳐서 디버깅을 해야 했던 경험이 있다.
단 몇 줄의 코드를 작성하더라도 여러분이 생각지 못한 버그는 반드시 있다. 그렇다면 여러분은 단 몇 시간동안 디버깅하겠는가, 머리를 싸매며 며칠동안 디버깅하겠는가?(야근 필수다~ ;)
2.10 변수는 나누어 선언하라
다음과 같은 두 개의 코드를 비교해보자. 하나는 VB이고, 다른 하나는 C 언어다.
Dim Age, Size, Length As Integer
int age, size, length;
이 경우에 C 언어에서는 age, size, length가 모두 int형이 되지만, VB에서는 Age, Size는 Variant형이 되고, Length만이 Integer형이 된다. 때문에 다른 언어를 사용하는 프로그래머들간에 혼란을 일으킬 수 있다. 따라서 번거롭더라도 다음과 같이 선언하도록 한다.
Dim Age As Integer
Dim Size As Integer
Dim Length As Integer
int age;
int size;
int length;
2.11 들여쓰기 형식
들여쓰기에 대해서는 많은 사람들이 다르게 생각할 것이다. 아마 아주 구시대 프로그래머들에게는 들여쓰기는 탭의 기본 간격인 8칸일 것이고, COBOL 프로그래머들은 7번째 칸부터 시작하면 주석, 8번째 칸부터 시작하면 데이터 타입 선언과 같은 규칙을 따라야 할 것이다(따르지 않으면 코드가 컴파일이 안 된다. -_-).
결국 들여쓰기는 언어에 따라서, 여러분의 취향에 따라서 선택사항이다. 8칸 들여쓰기를 고수하는 사람도 있고, 4칸 들여쓰기, 2칸 들여쓰기를 좋아하는 사람도 있다. 필자는 대부분의 언어에서 2칸 들여쓰기를 좋아한다. 그러나 어떤 사람은 3칸 들여쓰기를 좋아하기도 한다. 어떤 들여쓰기를 하건간에 해당 프로젝트에서는 정해진 들여쓰기 간격을 지켜야 한다.
탭 간격을 조정하거나 여백 문자로 바꿀 수 있는 적절한 에디터를 사용하도록 한다. 그렇게 하지 않으면 이러한 탭 여백을 변경할 수 없는 에디터에서는 코드의 들여쓰기가 엉망이 된 채 표시되고, 아무리 간단한 코드라도 이해할 수 없게 된다.
연구 결과에 의하면 4칸 들여쓰기를 사용할 때 코드를 가장 읽기 쉽다고 한다.
2.12 괄호 형식
괄호형식은 크게 다음과 같은 형태가 가장 많이 쓰인다.
for(int I = 0; I < 100; i++) {
// some code
}
for(int I = 0; I < 100; i++)
{
// some code
}
필자는 대부분의 언어에서 두 번째와 같은 형식을 사용한다. 첫번째와 같은 형식은 구조가 복잡할수록 괄호의 위치를 찾기가 어려워지고, 로직보다는 괄호의 위치에 더 신경을 쓰게 한다. 그러나 아직도 많은 C, Java 프로그래머들은 이러한 위치를 선호한다. 물론 첫번째와 같은 방법을 쓰면 코드의 라인수는 줄일 수 있지만, 수행 성능에는 영향을 미치지 않는다. 원하는 형식을 사용할 수 있으며, 개인적으로는 두 번째와 같은 형식을 권한다.
VB, Pascal과 같은 언어에서는 위와 같은 중괄호를 사용하여 블록의 시작과 끝을 표현하지 않는다.
2.13 코드를 절과 단락으로 분리하기
대부분의 사람들이 작성한 코드를 살펴보면 코드들이 라인을 분리하지 않고 다닥다닥 붙여서 코딩하는 것을 쉽게 볼 수 있다.
' We have another branch. Solve the branch recursively
If Not rsSubtopics.EOF then
Response.Write("<TD><A onClick=""Toggle(this)""><IMG SRC=""minus.gif""> " +
topic + "</A><DIV>")
Do While Not rsSubtopics.eof
subtopicID = rsSubtopics("topicID")
subtopic = rsSubtopics("name")
call SubTopicsInTree(dc, subtopicID, subtopic, depth + 1)
rsSubtopics.movenext
Loop
' We have a leaf. Simply print the leaf
Else
Response.Write("<TD><IMG SRC=""leaf.gif""> " + topic + "<DIV>")
End If
직관적인 코드라고 볼 수도 없으며, 단락으로 구분해야 할 것을 다닥다닥 붙여놓아서 알아보기가 힘들다. 보다 읽기 쉬운 코드는 다음과 같을 것이다.
' We have another branch. Solve the branch recursively
If Not rsSubtopics.EOF Then
Response.Write("<TD><A onClick=""Toggle(this)""><IMG SRC=""minus.gif""> " +
topic + "</A><DIV>")
Do While Not rsSubtopics.eof
subtopicID = rsSubtopics("topicID")
subtopic = rsSubtopics("name")
Call SubTopicsInTree(dc, subtopicID, subtopic, depth + 1)
rsSubtopics.MoveNext
Loop
' We have a leaf. Simply print the leaf
Else
Response.Write("<TD><IMG SRC=""leaf.gif""> " + topic + "<DIV>")
End If
분명히 이와 같은 코드는 라인수는 더 길게 나온다. 하지만 각 코드가 단락에 따라서 구분되어 있기 때문에 코드를 보다 쉽게 읽을 수 있다. 코드는 결코 딱딱하게 머리 싸매면서 봐야 하는 것이 아니다. 코드는 소설처럼 쉽게 읽을 수 있도록 절과 단락으로 적절히 구분되어 있어야 한다.
2.14 주석은 코드의 내용을 기술하는 것이 아니다
먼저 2.13에서 살펴본 코드에서 if 문을 다시 한 번 보자.
If Not rsSubtopics.EOF then
이 주석에 대해서 여러분이 주석을 달게 된다면 어떻게 할까? 아마도 다음과 같이 주석에 쓰기 쉬울 것이다.
'Subtopics 레코드셋에 레코드가 있다면
또 다른 코드를 살펴보자.
Employee = Employee + 1;
이 코드에 대한 주석은 보통 다음과 같이 할 것이다.
// Employee에 1을 더한다.
그러나 올바른 주석은 코드를 해석하는 것이 아니라, 코드가 행하는 내용이 무엇인지를 써야 한다. 먼저, if 문에 대한 주석은 다음과 같을 것이다.
' 트리구조에서 노드를 갖고 있는 경우. 노드는 재귀적으로 해결한다.
두 번째 Employee에 대한 주석은 다음과 같이 내용을 알 수 있게 바꿔야 한다.
// 다음 종업원을 처리한다.
어떤 변수에 1을 더하거나 레코드셋에서 루프를 도는 것과 같은 것들은 굳이 주석으로 쓰지 않아도 알 수 있다. 그러나 그것이 어떤 의미인지 코드를 하나하나 이해하기는 어렵다. 주석에는 이 코드가 어떤 의미를 갖는지 그 내용을 쓰도록 해야 한다. 심지어 필자는 단 30줄도 안 되는 함수에 대한 주석을 3 페이지씩 썼던 적도 있다.
2.15 매크로 사용
예를 들어서, 엔진을 제어하는 코드를 작성한다고 할 때, 엔진의 최대속도가 80km/h이고, 엔진의 속도가 80km/h를 넘으면 엔진이 고장 났다는 메시지를 출력한다고 하자.
$current_speed = 60;
if ( $current_speed < 80 )
{
print "engine is workingn";
}
else
{
print "engine is out of order!n";
}
그런데 엔진을 제조하는 기술이 발전해서 엔진의 최대속도가 120km/h가 되었다면 80이라는 숫자를 120으로 변경해야 할 것이다. 만약, 이렇게 직접 숫자를 사용한 부분이 코드에서 여러 곳이라면 모든 부분을 일일이 찾아서 수정해야 한다. 이렇게 수정하다보면, 깜빡하고 수정하는 것을 잊어버린 코드도 생기게 되고, 버그의 원인이 된다. 이러한 경우에는 80과 같이 숫자를 직접 사용하는 대신에 전역 상수를 선언하도록 해야 한다.
$SPEED_LIMIT = 100;
$current_speed = 60;
if ( $current_speed < $SPEED_LIMIT )
{
print "engine is workingn";
}
else
{
print "engine is out of order!n";
}
이와 같이 작성하면 나중에 엔진의 최고 속도를 수정할 필요가 생기더라도 한 곳만을 수정하면 된다.
이러한 방법은 대부분의 언어에서 모두 사용할 수 있다. (위 코드는 Perl로 작성했다.)
자, 이제 다른 것을 논의해보자. 만약에 여러분의 응용 프로그램이 20명의 종업원을 다루도록 했다고 하자. 방금 배운 지식을 활용해서 다음과 같이 작성했다고 하자.
Private Const MAXEMPLOYEE = 20
Public Sub PrintAllEmployeeName()
Dim LoopCtr As Integer
Dim Employee(MAXEMPLOYEE) As New Employee
For LoopCtr = 1 To UBound(Indx)
Debug.Print Employee(LoopCtr).Name
Next LoopCtr
End Sub
VB와 같이 대부분의 언어는 배열을 0에서 시작하기 때문에 이 경우에는 21개의 Employee 정보가 들어갈 수 있게 된다. 이와 같은 것이 혼란스럽다면 다음과 같이 조금 더 고쳐 쓸 수 있다.
Private Const EMPLOYEENUMBER = 20
Private Const MAXEMPLOYEE = EMPLOYEENUMBER - 1
Public Sub PrintAllEmployeeName()
Dim LoopCtr As Integer
Dim Employee(MAXEMPLOYEE) As New Employee
For LoopCtr = 1 To UBound(Indx)
Debug.Print Employee(LoopCtr).Name
Next LoopCtr
End Sub
위와 같이 간단한 팁을 사용하여 프로그래머가 이해하기 쉽도록 할 수 있다.
위 코드에서 한 가지 더 보아야 할 것은 For 부분이다. 대부분의 프로그래머들은 이 부분을 다음과 같이 사용할 것이다.
For LoopCtr = 1 To MAXEMPLOYEE
와 같이 매크로 상수를 사용하거나 직접 숫자를 넣어서 사용할 것이다. 그러나 배열에 항상 정해진 수만큼 값이 들어있는 것은 아니다. 배열에 최대한 넣을 수 있는 값은 20개지만, 실제로는 14개만 들어가 있는 경우가 더 많을 것이다. 이러한 경우에 배열에서 가장 큰 범위 값을 가져와서 이용하는 것이 보다 융통성있는 코드를 작성할 수 있도록 해준다.
2.16 round-off 에러
최근에 VB는 부정확한 언어라고 하여 산수 계산도 제대로 못한다라는 기사를 본적이 있다. 그러한 기사가 인터넷을 통해서 전세계에 널리 퍼졌던 것을 보고 배꼽잡고 웃지 않을 수 없었던 일이 있다. 다음과 같은 코드를 실행시켜 보도록 하자.
Dim Result As Single
Dim LoopCtr As Integer
Result = 0
For LoopCtr = 1 To 10000
Result = Result + 0.00001
Next LoopCtr
Label1.Caption = Result
VB에서 레이블을 하나두고 다음과 같은 코드를 실행시켜보도록 한다. 0.00001을 1만 번 반복해서 더하는 것이니 결과는 0.1이 나와야 할 것이다. 불행히도 결과는 0.0999915와 같이 된다. 이러한 오차가 생기는 것은 숫자를 이진수로 표현하는 컴퓨터의 표현 방식에 따른 것이다. 따라서 컴퓨터로는 0.1과 같은 간단한 수 조차도 제대로 표현할 수 없다. 우리가 0.1이라고 믿는 숫자는 컴퓨터에서는 실제로 0.100000000000000035와 같이 매우 작은 오차를 갖고 있을 것이다. 위와 같이 각각의 머신이 나타낼 수 최소한의 값을 머신 입실론(Machine Epsilon) 값이라 한다. 따라서 이러한 수치 오차를 줄이기 위해 Single 대신에 Double이나 Float와 같이 보다 정확한 수치 데이터 형을 사용해야 하고, 위와 같은 코드의 오차를 줄이기 위한 방법을 써야 한다.
C 언어에서 보자면 다음과 비슷한 코드를 사용할 수 있을 것이다. C 언어 프로그래머들에게는 꽤 익숙한 기교적인 방법일 것이다.
#include <stdio.h>
int main(int argc, char* argv)
{
int loopctr;
loopctr = 0;
while ( loopctr < 4 )
{
loopctr = loopctr + 4/3;
printf("Loop : %fn", loopctr);
}
}
이것은 C 언어 코드이다. 4/3을 통해서 루프를 4번만 돌도록 한 것이다. 이 코드에서 loopctr은 int 형으로 되어 있기 때문에 4/3의 결과에서 소수점 부분은 없어지고, 정수 부분만 남게 된다. 따라서 결과는 1, 2, 3, 4가 출력된다. 그러나 실제로 C 언어에서는 4/3의 결과는 무조건 1이 된다. 4/3의 결과인 1.3333…이 계산되도록 하려면 명시적인 타입 캐스팅을 사용해야 한다.
다음은 고의적으로 round-off 문제가 일어나도록 재 작성한 코드다.
#include <stdio.h>
int main(int argc, char* argv)
{
float loopctr;
loopctr = 0.0;
while ( loopctr < 4 )
{
loopctr = loopctr + (float)4/3;
printf("Loop : %fn", loopctr);
}
}
이 코드를 실행시키면 결과는 다음과 같다.
[traxacun@ns works]$ cc roundoff.c
[traxacun@ns works]$ ./a.out
Loop : 1.333333
Loop : 2.666667
Loop : 4.000000
결과에서처럼 프로그래머가 의도한 대로 4번의 루프를 돌게되는 것이 아니라 루프는 3번만에 끝나게 된다. 또한 두 번째 루프에서부터 round-off 에러에 의해서 2.666667로 되어 있는 것을 알 수 있다(보다 자세히 확인하고 싶다면 %f대신에 %.24f를 사용하도록 한다. 32비트 데이터형은 24 비트의 mantissa 데이터를, 8 비트의 exponent 데이터를 저장한다).
이와 같이 여러분이 어떤 프로그래밍 언어를 사용하고 있더라도 round-off 문제를 일으킬 수 있다. 따라서 실수형의 데이터를 다루게 된다면 매우 조심해서 다뤄야 한다. 또한, 이러한 round-off는 많은 방법으로 처리할 수 있으며, 이러한 에러 자체를 피해갈 수 있는 방법도 존재한다는 사실을 기억한다(Machine Epsilon에 대해 더 자세히 알고 싶다면 C, C#, VC++에서의 Machine Epsilon에 대한 필자의 글을 참고하기 바란다.)
재정 관련 데이터를 처리하는 프로그램을 작성하는 경우에 이와 같은 머신 입실론 값에 따른 오차 한계가 매우 중요한 문제가 된다(적어도 돈 계산에는 오차가 용납되지 않는다. 공학에서처럼 10의 -5승과 같은 오차한계가 용납되지 않는다. ;).
따라서 여러분이 사용하는 시스템과 언어에 따라서 오차 한계를 정확히하고, 이러한 문제를 피하도록 한다. 이에 대한 논의는 이 글의 범위를 벗어나므로 생략하며, 관심 있는 분들은 수치해석(Numerical Method) 관련서를 찾아보기 바란다.
2.17 대충 설계하지 마라
필자는 Y2K를 수정하는 작업을 담당한 적이 있으며, 하루 18시간을 UNIX 머신 앞에 앉아서 작업한 경험이 있다. 살도 빠지고, 몸무게도 줄고 있는데 배만 나오는 그 상황을 생각해 보라.
대충 설계하지 마라는 것은 대충 만들어 놓지 말라는 것과 같은 말이다. 어떤 것을 작성하는 데 있어서 나중에 해야지라는 생각은 곤란하다. 분명히 Y2K로 인해서 수정해야 하는 소프트웨어들도 누군가 나중에 수정해야지라고 했을 것이다. 그리고 그것은 소프트웨어에 대재앙을 불렀을 것이다.
누군가는 종종 그런 얘기를 한다. 아무리 잘 만든 소프트웨어라 할지라도 곧 수정사항이 생기고 변경사항이 생기기 마련이므로 3년 이상 쓰는 소프트웨어란 없다고 말이다. 그러나 필자는 초기에 작성되어 계속해서 유지보수 되어 20년 이상 사용되고 있는 소프트웨어들을 수도 없이 봐왔다. 그 당시 최신 기술들이 사용되고, 이 언어, 저 언어, 이 기술, 저 기술 등이 사용되어 점점 눈덩이처럼 불어나서 아무도 시스템 전체를 이해하지 못하고, 무엇이 시스템에서 돌아가고 있는 것이며, 무엇이 돌아가는 않는 것인지 알 수 없는 상황에 직면한 적이 있다. 불행히도 그것을 정리해내고 바로 잡아서 시스템을 분석해내는 것은 아주 힘든 작업이며 시스템을 새로 개발하는 것에 버금가는 비용이 든다.
소프트웨어를 제대로 설계하고, 제대로 문서화를 해오면서 일관성을 유지한다면 유지보수에 그렇게 큰 어려움이 있지는 않을 것이다.
여러분이 어떤 소프트웨어를 작성하게 되더라도, 누군가 처음에 단지 임시로만 쓸 것이라고 얘기하더라도 여러분은 30년 동안 쓸 것을 만든다고 생각하고 만들어야 한다.
2.18 대충 만들지 마라
여러분이 어떤 문제를 해결하는 데 있어서 쉽고 빠른 방법과 일반적이고 융통성있는 방법 중에 한 가지를 선택하라고 한다면 일반적이고 융통성있는 방법을 선택한다.
모든 소프트웨어는 반드시 변경사항이 생겨나고 요구사항이 생긴다. 그리고 이러한 유지보수가 발생했을 때 쉽고 빠른 방법을 사용한 경우에 대개는 융통성이 없기 때문에 유지보수하기가 어려워진다. 따라서 조금 오래 걸리더라도 보다 일반적이고 융통성있는 방법을 선택하도록 해야 한다.
2.19 UNIX Time과 datetime 포맷
UNIX 세상에서는 epoch time이라는 것이 있다. 이 시간은 1970년 1월 1일을 뜻하며, UNIX에서 시간을 세기 시작한 시간을 뜻한다. Epoch time은 대개의 경우에 UNIX Time으로 더 잘 알려져 있다(MySQL의 from_unixtime()과 같은 함수에서 알 수 있는 것처럼).
이 UNIX Time을 저장하는 데이터타입은 long형이며, 초단위로 시간을 기록하고 있다. 이 숫자는 2038년이 되면 데이터가 꽉 차게 된다. 그리고 마지막 32비트가 1로 바뀌게 되면서 부호가 ?로 바뀌게 된다. 하지만 시스템 시간은 unsigned long형이므로 계속해서 시간을 세게 될 것이다. 2038년 문제는 제 2의 Y2K라 할만하다.
때문에 UNIX에서는 이러한 시간을 대체하기 위해서 time_t라는 데이터 타입을 만들어냈다(엄밀히는 C 언어지만). 여러분의 UNIX 시스템에서 time.h 헤더파일에서 time_t가 선언된 typedef 부분을 찾아보라. 또한 gnu/types.h 등을 확인해보면 GNU 소프트웨어들이 아직 2038년 문제를 해결하기 위한 time_t의 변경을 하지 않았다는 것을 확인할 수 있을 것이다.
몇 가지 조언을 하자면 시간은 long형에 저장해서는 안되며, time_t에 저장해야 한다. 또한, time_t가 현재는 부호가 있는 값이지만, 부호가 없는 값으로 바뀔지도 모른다고 생각해야 한다. 또한, time_t가 반드시 32 비트 데이터 형이라고 생각해서도 안 된다.
몇 가지 논의는 2038년 문제를 해결하기 위해 unsigned long을 사용하자는 것과, 64 비트 데이터 형인 long long을 사용하자는 것으로 나뉘어져 있다. MySQL의 datetime 타입 역시 UNIX Time을 사용하고 있기 때문에 2038년 문제를 갖고 있다.
그외 Y2K와 관련한 문제로 C 언어의 tm 구조체, Perl의 시간 함수들은 모두 2000년을 100으로 반환하고 있다. 따라서 이러한 코드들은 1900과 같은 값을 임의로 더하는 방법을 사용하도록 한다.
2.20 서드 파티 컴포넌트를 사용하지 않는다
서드 파티 컴포넌트를 사용함으로써 보다 나은 기능을 응용 프로그램에 구현할 수 있고, 그러한 기능을 자체적으로 개발하는 데 드는 비용을 절감할 수 있다.
그러나 응용 프로그램에 버그가 발생하여 수정하게 되었을 때, 서드 파티 컴포넌트에서 버그가 발생한 경우에 버그의 수정을 서드 파티 컴포넌트 개발사에 얘기해야 하고, 최악의 경우에는 버전업된 컴포넌트를 구입해야 하고, 이전 버전에 대해서는 지원을 중단하는 경우다. 이러한 경우에 여러분은 버그를 없애기 위해 같은 기능을 하는 컴포넌트를 개발해야 하거나 새로운 컴포넌트를 구입해야 할 것이다.
소스가 공개되어 있지 않은 상용 소프트웨어를 이용하는 경우에 문제가 있어도 여러분이 직접 수정할 수 없다. 따라서 개발하는 응용 프로그램에서 꼭 필요한 경우가 아니라면 서드 파티 컴포넌트의 사용을 자제하도록 한다.
필자의 경험에 따르면 같은 회사에서 제공한 컴포넌트의 버전에 따라서 작동하는 절차 같은 것들이 너무나 다르게 변경된 적이 있어서 고생한 경험이 있다(믿거나 말거나 아직도 이 응용 프로그램을 유지보수하기 위해선 오래된 소프트웨어만을 사용하고 있다. 전국에 있는 각 지사 사용자에게 무엇을 받아서 설치하고 하는 등의 지시를 일일이 할 수도 없고, 또 이것만으로 다양한 환경에서 발생하는 문제를 해결하지 못한다는 것을 이미 경험을 통해서 너무나 잘 알고 있기 때문이다).
코드의 규모가 작고, 사소한 변화라면 여러분이 직접 코드를 찾아다니며 변경하고 테스트할 수 있을 것이다. 그러나 코드가 이미 한 개인이 수정하기에는 너무 커졌다면 이와 같은 변화 자체가 여러분의 프로젝트에 엄청난 재앙이 될 것이다. 따라서 서드 파티 제품의 사용은 최대한 자제하는 것이 좋다. 설령, 서드 파티 제품을 전혀 사용하지 않는다고 해도 기본 라이브러리 자체의 버그만으로도 여러분을 충분히 괴롭히고도 남을 것이다.
2.21 makefile을 사용한다
makefile을 사용하여 다른 프로그래머가 여러분의 소스 코드를 컴파일하는 방법을 알 수 있도록 해야 한다. 이와 같이 하지 않으면, 아무도 여러분의 코드를 컴파일하지 못하게 될 수도 있다.
2.22 주석을 철저히한다
프로젝트에서 필자는 변수는 한 줄에 하나씩 선언하고 주석을 달도록 했다. 뿐만 아니라 코드 전체에 있어서 모든 주석을 달도록 한 적이 있다. 그리고 프로젝트가 끝나고, 그 회사에 코드를 넘겨줬을 때, 그들은 일반적인 코드에 사용된 주석을 불필요한 것이라하여 모두 정리하고, 꼭 필요하다고 판단한 주석만을 남겨두었다.
결국에는 시간이 흘러 코드를 이해할 수 없게 되어서, 모든 코드를 다시 작성해야만 했다. 이 예에서 알 수 있는 것처럼 주석은 지나쳐서 부족한 것이 아니다. 많으면 많을수록 좋다.
프로그래밍 스타일 관련 도서
프로그래밍 스타일 관련 도서
프로그래밍 스타일에 대해 참고할 수 있는 책과 관련 자료는 다음과 같다.
코딩 스타일, 이만용
C 프로그래밍의 이해, 개정 3판
Steve Oualline, 1997, O'Reilly, ISBN 1-56592-306-5
이만용씨를 비롯한 이미 많은 개발자들에게서 극찬을 받고 있는 책이며, 코딩 스타일에 대한 조언 또한 구할 수 있을 것이다.
Practical C++ Programming
Steve Oualline, 1995, O'Reilly, ISBN 1-56592-139-9
좋은 프로그래밍 스타일에 대해서 설명할 뿐만 아니라 C++ 클래스 설계, 디버깅, 최적화, 일반적인 프로그래밍 실수와 같은 전반적인 내용을 설명하고 있다.
C 프로그래머를 위한 C++
그레고리 새터, 더그 브라운, 1999, 한빛미디어, ISBN 89-7914-082-7
이 책은 객체 지향 개념에 대한 좋은 설명으로 가득하다. 절차지향 프로그래밍을 하던 사람뿐만 아니라 객체 지향 언어를 학습하는 사람에게도 자신이 어느 정도의 수준에 이르렀는지, 얼마나 객체 지향 개념을 소화하고 있는지 알 수 있도록 해주며, 객체 지향 기반 설계에 대해서 알 수 있다.
The Practice of Programming
Kernighan, Brian W. & Pike. Rob, 1999, Addison-Wesley, ISBN 0-201-61586-X.
높은 품질의 프로그램을 작성하는 것에 관한 최고의 논문 현장에서도 꼭 필요한 책이 될 것이다.
Programming Perl, 3rd Edition
Larry Wall; 2000; O'Reilly; ISBN 0-596-00027-8
다양한 주제에 대해서 설명하고 있으며, 프로그램을 보다 향상시킬 수 있는 방법과 좋은 관리에 대해서 설명한다.
Writing Efficient Programs.
Bentley, Jon Louis; 1982; Prentice-Hall, Inc.; ISBN 0-13-970251-2 or 0-13-970244-X.
이 책은 Bentley의 방법론과 능률적으로 프로그램을 향상시키기 위한 규칙들을 제공하며 많은 예제를 포함한다.
후기
드디어 다가오는 2002년으로 PC를 접한지 16년째가 되어간다. 이미 내 주변에 10여년 이상 쓴 사람들도 흔하고, 8 비트 PC에서 하던 게임들, 베이직 언어들을 얘기하곤 한다. 어느덧 사용해온 언어가 21개나 되었다. 그 중에 지금 매일 사용하고 있는 2가지 언어를 제외하면 나머지 언어들은 항상 잊어버린다. HTML, XHTML의 모든 태그와 속성을 외워서 썼던게 엊그제 같은데 안 쓴다고 1년도 채 안 되서 기억이 잘 안 나는 걸 보면 대단한 돌머리다. -_-
(사실 요즘 필자가 하는 일이라곤 기획과 관련된 일이고, 아주 가끔 코드를 만지고, 취미로 프로그래밍 언어 몇 개를 더듬어 보곤 한다. ? 사용된 언어보다는 관심 있는 분야의 책을 취미로 떠들어보는 중이다. 최근의 관심사는 신경망과 퍼지, 유전자 알고리즘과 데이터 마이닝, STL이다.)
프로젝트를 진행하다보면 업무 규칙과 프로그래밍 스타일에 대한 지침을 만들고, 모두가 그것을 숙지하고 작업할 때와 그렇지 않을 때의 차이가 너무나 크다. 여러 회사의 프로젝트에서 팀원으로, 팀장으로 일하면서 그러한 차이를 너무나 많이 느낀다. 프로그래밍 스타일은 여러분의 코드를 돌로 만드느냐, 보석으로 만드느냐의 차이를 가져다준다.
그나저나 아직 필자는 이런 글을 쓰기에는 20년은 이른 것 같다.
"2038년? 아… 그건 그때 사람들에게 맡기자고… 그때 우리는 손자나 보면서 놀아야지~ 이 사람이 지금 그런 거 생각하고 있나?" -_-;
뭐… 이러면서 무책임하게 얘기하고 있지만, 지금까지 프로그래머들이 저질러온 죄악들 때문에 많은 사람들이 고통스러워하고 있다. 필자가 저지른 범죄가 얼마나 많던가. 아직도 필자가 만들어낸 버그로 인해서 고통스러워 하는 사용자들의 비명소리가 귓가에 들리는 듯하다.
하지만, 이런저런 이유에도 불구하고 이런 글을 써 내려갈 생각을 한 것은 이 책, 저 책에서 짜깁기 해낸듯한 몇 페이지 분량의 프로그래밍 스타일이라는 미명하에, 괴상하고 어려운 것을 써야만 실력 있는 프로그래머라는 식의 발상에 현혹되는 초보자들에게 조금은 도움이 되었으면 해서다.
그리고 프로그래머에게 있어 사용하는 언어라든가, 기술보다 중요한 것이라고 생각한 것이 이것이다. 프로그래밍 스타일에 대해서 논의된 것은 그리 오래되지 않았다. 필자가 처음 C 언어를 공부했을 때는 조금 더 어렵고, 한 번 봐서 이해하기 어려운 코드를 작성하는 것이, 10줄에 작성한 것을 3줄로 줄이는 것이 가장 좋은 프로그래밍이라 여겼던 시절인 탓도 있다.
하지만, 지금과 같은 환경에서는 전혀 맞지 않는 말이다. 미국의 정보통신 잡지 데이터메이션의 조사에 의하면 1980년에서 1990년 사이에 작성된 코드의 평균 길이는 23,000줄에서 1,200,000줄 정도이며, 시스템 평균 사용기간은 평균 4.75년에서 9.4년이라고 한다. 2000년대를 살아가는 지금은 보다 긴 코드를 작성하고 있을 것이다. 그에 비해서 우리가 진행하고 있는 프로젝트의 기간은 별반 달라질게 없다. 오히려 치열해진 경쟁상황에서 프로젝트 기간은 점점 짧아져 가고 있는 것이다. 즉, 개인이 제어할 수 있는 범위를 이미 넘어가 버린 것이다.
프로그래밍 스타일이라는 것은 단순히 코드를 작성하는 방법만을 뜻하는 것은 아니다. 팀을 이루어서 작업하는 데 필요한 프로그래밍 지침, 방법 등을 정하는 것이다. 때문에 여기서는 보다 넓은 범위를 다루었다.
끝으로 하고 싶은 얘기가 많았는데, 이것밖에 풀어내지 못해서 아쉬움이 남는다. (헉헉… 너무 힘들다. -_-)
끝으로 이 글을 작성하는 데 도움을 주신분들이 있다. TRSong님, Perl에서의 자신의 프로그래밍 스타일을 얘기해주신 골빈해커님, PL/SQL에서의 프로그래밍 스타일에 대해서 얘기해주신 포카리님에게 감사의 뜻을 전한다. 그리고 긴 글을 교열하고, 편집하시는 한빛미디어 최선임씨, 송관대리님에게도 감사를…