저자: 한동훈
[지난기사보기]
프로그래밍 스타일(3)
프로그래밍 스타일(2)
프로그래밍 스타일(1)
2. 코딩 스타일
코딩 스타일에 있어서는 가장 많은 논란이 있을 것이다. 그러나 코딩 스타일에는 이거다라고 할만한 것은 없다. 언어에는 각각의 언어마다의 표기 방법이 있고, 그들 세계의 규칙이 있다. 그리고 대부분의 경우에 그러한 규칙을 따르는 것이 좋다.
필자는 처음에 C 언어를 익혔고, 그 다음에 HTML, Perl, JavaScript, C++, VBScript, LPI-COBOL, SQL, Informix/4GL… 등의 순서대로 언어를 익혔다. 그리고 혼자서 언어를 익히는 기간동안은 모든 언어에서 사용할 수 있는 코딩 스타일을 만들어나가고자 했다. 즉, C 언어에서도 만족할 수 있고, C++에서도 만족할 수 있고, Perl에서도 만족할 수 있기를 바란 것이다. 그리고 COBOL에 와서 그 꿈은 깨져버렸고, 프로젝트를 시작하면서는 자신의 코딩 스타일에 대해서 염려하기는커녕 팀 내에서의 코딩 스타일을 따라가기에 바빴다. 이미 작성된 수십만줄이 넘는 코드속에 자신이 부속품처럼 끼어들어가 필요한 모듈을 작성해나가야했다. 역시나 많은 시행착오를 겪었지만, 이미 마련되어 있는 지침들에 따라서 코드를 작성해 나갔다. 그리고 그러한 지침들에 익숙해졌을 때는 다른 사람이 작성한 코드도 알아보기 쉬웠다. 물론, 처음 팀별 작업을 하지 않았을 때에는 자신의 개성을 죽이는 것이며, 지금까지 자신이 사용한 스타일을 버리는 것이 되지만, 그렇게 함으로써 팀내에서 의사소통을 원활하게 할 수 있으며, 프로젝트를 빠르게 진행시킬 수 있다는 것은 당연한 것이다.
때문에 여러분이 사용해왔던 프로그래밍 언어가 몇 개가 되든지 간에 관계없다. 여러분이 사용하는 언어에서 사용하는 관습적인 프로그래밍 방법을 따르는 것이 대부분의 경우에 여러분의 삶을 편한하게 만들어줄 것이다. 그리고 여기에 자신만의 개성을 덧붙여 나가라. 만약, 팀원간에 작업을 해야하고, 해당 언어의 관습적인 방법에는 없는 방법을 사용한다면 팀원간에 논의하고 지침을 만들어 모두가 그 지침을 지키도록 해야한다.
리눅스 커널을 비롯한 주요 오픈 소스 프로젝트에는 포함된 문서에는 코딩 스타일과 관련된 문서가 반드시 포함되어 있다. 때문에, 오픈 소스에 공헌하고 싶다면 이런 코딩 스타일을 숙지하고 이러한 스타일로 작성된 코드를 제출해야만 오픈 소스 커뮤니티에 받아들여질 것이다.
2.1 지침을 만들어라
앞에서 시작한 이야기지만, 자신의 프로그래밍 스타일에 대한 지침을 만들어야한다. 그러나 겨우 자신이 자신을 위해서 이러한 것을 만든다는 것은 대부분의 경우에 하지 않는 일이다. 물론, 하지마라. ^^;
그러나 팀원간에 작업을 해야하거나 회사내에서 작업을 하는 경우라면 반드시 코딩 스타일에 대한 지침을 만들어야한다. 그렇지 않으면 옆사무실에 있는 팀이 만든 코드조차 우리 사무실에서 만드는 코드와 스타일이 다르기 때문에 혼란스럽게 된다.
문제는 이것만으로 끝나지 않는다. 우리 팀이 만든 것과 다른 팀이 만든 컴포넌트들을 모두 이용하는 다른 회사에서 이렇게 투덜댈 수도 있다. 같은 회사에서 만든 제품들이 스타일이 달라서 사용하는 데 있어서 두 번의 교육이 필요하다라라고…
그러나 일관된 지침을 사용한다면, 미국에 있는 팀원들이 만든 코드와 한국에 있는 팀원들이 만든 코드 모두 읽기 쉬울 것이고, 코드가 정확히 무엇을 하는지 추측하기가 쉬워진다.
2.1.1 코딩 스타일을 지키기 위한 조언
상황에 따르지만 전산기획팀은 시스템 전체를 기획하고 그에 대한 산출물을 UML로 작성한다. - RUP나 CBD와 같이 따르는 방법론에 따라 산출물의 종류나 이름은 다르지만 표기법은 UML이며, 설령 UML을 사용하지 않는 회사들도 UML을 모방한 표기법을 사용하고 있다 - UML 산출물에 따라 개발자가 개발을 한다. UML 산출물에는 클래스의 설계를 나타내는 클래스 다이어그램, 업무 흐름을 나타내는 시퀀스 다이어그램등이 있다. 이 산출물대로 코드들을 작성해 나간다. 코드 작성이 끝나면 QA팀에 넘긴다. QA를 담당하는 부서나 사람은 해당 코드를 테스트하고, 코드가 회사의 코딩 스타일을 제대로 따르고 있는지 검토한다. UML 산출물과 다른 코드를 갖고 있거나 회사의 코딩 스타일을 따르지 않으면 코드를 해당 개발자에게 다시 반환한다. 또한, 코드가 이해되지 않는 부분에 대해 개발자의 설명을 직접 들어도 QA팀이 이해하지 못할 경우 코드를 쉽게 작성할 것을 요구하며 코드를 반환한다.
QA팀을 통과하지 못하면 그 작업 단위는 완료되지 못한 것이 되기 때문에 모든 개발자는 이를 준수할 수 밖에 없다. QA팀을 두거나 QA를 위해 한 사람을 고용하는 것은 낭비라고 생각할 수 있다. 개발팀이 작성했으나 마치 한사람이 작성한 것과 같은 코드를 산출해 내게 된다면, 문제가 발생했을 때 누구나 쉽게 코드를 읽고 버그를 추적할 수 있게 해준다.
최소한 그 부분의 로직은 너무 복잡해서 "직접 개발한 A씨가 아니면 그 부분은 아무도 손댈 수 없어요"라는 이야기는 나오지 않는다.
반드시 이런 수작업에 의존할 필요는 없다. indent와 같이 코딩 스타일을 조절해 주는 전통적인 프로그램부터 거의 22가지 언어에 대한 작업을 대신해주는
PolyStyle 까지 있다.
"ctags -x rulefile"와 같은 명령어는 C/C++ 코드에 대해서 해당 코딩 스타일을 지키고 있는지 검사까지 대행해준다.
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.2.1 반환값에 대한 이야기
프로그램의 로직에서는 0이 거짓이나 실패를 1이 참이나 성공을 의미한다. 그런데, 함수의 반환값에서는 0이 성공을 나타낸다. 왜 이와 같이 작성했을까?
만약, 성공일 때 1을 반환하는 함수를 작성했다면 2나 3과 같은 숫자도 성공이라고 가정할 수 있게 된다. 성공일 때 0을 반환하는 함수를 작성했다면 1을 비롯한 나머지 숫자들은 모두 에러 상황에 대한 설명으로 대체할 수 있으며 그 의미가 혼동되지도 않는다.
0이라는 단 하나의 상황만을 성공으로 두고, 나머지 값을 실패로 두는 것이다.
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/default가 들어가야한다.
C 언어에서 가장 많은 경우지만 다른 언어를 작성하는 프로그래머들 사이에서도 많이 볼 수 있는 유형은 else 부분에서와 같이 절대로 처리되지 않아야하는 부분이 처리되는 경우에 많은 프로그래머들은 0이나 -1등을 반환하도록 프로그래밍을 하고 있다. 그러나 0과 1 같은 반환값은 대부분의 경우에 프로그램의 동작을 멈추게 하지 않으며, 버그를 내부에 숨기게 되는 오류를 범하게 된다. 그렇다면 사용하는 함수의 변수가 사용하는 값 중에 가장 큰 값을 반환하도록 한다. 예를 들면, -31000과 같은 값을 반환하도록 한다. 그러면 여러분은 코드에서 말도 안되는 값을 반환하는 부분을 쉽게 찾아낼 수 있을 것이고, 무엇인가 잘못되었다는 것을 알 수 있을 것이다.
2.3.1 비어있는 Else 문의 위력
2.3의 예제 코드를 다시 생각해보자. 이제는 수개월이 지나도 Else 부분이 레코드가 없는 경우라는 것을 알 수 있다. 레코드는 DB의 검색결과 일 수도 있고, 메일함의 수신메일 개수일수도 있다. 이전에는 수신메일이 없으면 아무것도 보여주지 않았는데, UI팀이 지적한바에 따르면 "빈 목록화면이 시스템 오류인지 수신된 메일이 없는지 구분되지 않기"때문에 "수신된 메일이 없습니다"와 같은 메시지를 출력해주는 것이 좋다고 한다. 이럴 때, 코드를 작성할 때 Else 부분에 대해 작성한 부분 때문에 코드에서 필요한 부분을 보다 쉽게 찾을 수 있게 해주고 지적사항을 남들보다 빠르게 반영할 수 있을 것이다.
나중에는 수신된 메일이 없을 때 사용자를 다른 곳으로 클릭을 유도하기 위해 "수신된 메일이 없습니다. 새로운 소식을 얻는 보다 쉬운 방법으로 XX뉴스가 있습니다"를 출력하게 변경사항 요청이 있을 때도 보다 쉽게 처리할 수 있을 것이다. 자신이 아닌 다른 개발자도 쉽게 이 문제를 해결할 수 있을 것이다.
필자의 또 다른 경험은 절대 예외가 발생하지 않을 것이라 생각한 부분에도 Else문을 두고, "unhandled exception"이라는 문구를 사용자 몰래 기록하게 해놓은 부분을 작성해 놓는다. 정말 아무 문제가 없었는데, 3년후에 특정 조건에서만 문제가 발생한다는 이야기를 들었고, unhandled exception이 숨어있는 것을 발견했다. 물론, 3년전의 코드는 ASP였고 ASP를 사용하지 않은지가 3년이나 되었지만 쉽게 문제가 발생한 곳을 찾아서 문제를 해결해 줄 수 있었다. Else 문은 마치 값싸게 이용할 수 있는 try … catch 문이라 생각할 수 있다.
2.3.2 복합 if문의 단순화 방법
다음과 같은 코드를 생각해보자.
if(A) B
else if(C) D
else if(E) F
else G
이와 같이 여러 개의 if문으로 되어 있는 경우에는 코드를 이해하는 것이 어렵다. 이런 구조는 A를 비교해서 없으면 C를 비교해보고, E를 비교해보는 식으로 순차적으로 진행한다.
매우 길고 복잡한 형태의 if문이 된다면 switch/select … case 문을 사용하는 것을 고려해보기 바란다.
2.3.3 다중 if문의 단순화 방법
if(A) B
if(A") B"
if(A"") B""
K
else if(C) D
이와 같은 코드를 작성했다고 하자. 이 코드의 문제점은 A 조건을 만족하지 않는 경우, A는 만족시키되 A"을 만족하지 않는 경우, A와 A"은 만족하되 A""은 만족하지 않은 경우에 실행되는 코드는 모두 K 지점이 된다는 것이다. 이러한 동작이 의도된 것이라면 다행이지만 대부분의 경우 복잡하게 중첩된 다중 if문은 조건이 복잡하며 문제를 해결하기도 어렵다.
때문에 각 if에 Else를 넣어두는 것도 하나의 방법이 될 수 있지만 여기서는 좋은 방법이 아니다.
이런 경우에는 각 조건 A, A", A"", C에 대해 TRUE/FALSE에 대한 매트릭스를 만들어본다. 매트릭스를 작성하면 보다 간단한 if 문의 형태로 코드를 압축할 수 있으며, 예기치 않은 경우가 발생할 수 있는지를 테스트할 수 있다.