때는 2007년 봄. 나는 대학교 외에서의 첫 실무 프로젝트를 시작하게 되었다. 20명 남짓 규모의 작은 SI 회사에 취직하게 되었고, 나에게 주어진 임무는 이 회사에서 기존에 만들었던 것과 비슷한, 하지만 다른 고객 회사를 위한 어플리케이션을 작성하는 것이었다.

작은 회사가 원래 다 그런건지 아니면 SI 회사가 다 그런건지는 모르겠지만, 목표만 던져줬을 뿐, 코딩 스타일 가이드나 기본적인 설계와 철학, 이 회사에서 사용하는 라이브러리나 기본적인 프로젝트 시작 방법에 관하여 별 다른 업무 지도를 받지 못하였다. 뭘 어떻게 만들라는건지 갈피를 못 잡다가 마감일이 점점 다가오기 시작하자 나는 조급해졌다. 아무래도 이대로는 힘들다 싶어 선배님의 코드를 열어 살피기 시작했다.

참고로 당시 비주얼 C++ 6.0 을 사용하고 있었다. 비주얼 스튜디오 프로젝트 파일을 더블클릭하는 순간, 나는 어디선가에서 나는 강한 악취를 느꼈다. 그 악취는 바로 눈앞의 소스 코드에서 풍겨져 나오고 있었던 것이다.

당시 내가 보았던 코드는 내 기억에 따르면 아래와 비슷했다:

---------------
char g_szMstNum[3];
...
memset(g_szMstNum, 0, 3);
memcpy(g_szMstNum, "Bar", 3);
---------------

위 코드를 보고 혹시 문제점을 몇 가지 찾아낸 사람 있는가? 아니면 이해가 잘 되고 어디가 문제라는건지 모르겠는가?

내가 생각하는 이 코드의 문제점은 다음과 같다.

1. sz라고 써있지만 null로 끝나는 문자열이 아니다.

일반적으로 C/C++ 에서는 스트링의 마지막을 표시하기 위해 0 또는 null 을 사용한다. 그런데 여기서는 3바이트 캐릭터 배열에 3글자를 집어넣었기 때문에 sprintf(file, "%s", g_szMstNum); 따위의 코드를 넣었다가는 수백 수천 바이트가 파일에 기록될 수도 있고 잘못된 메모리 주소에 접근하여 크래시가 날 수도 있다. 그러나 변수 이름에서 이 사실을 알아낼 수 없으며 오직 정의 부분과 "Bar"를 대입하는 코드를 둘 다 찾아보고 메모리 크기와 스트링 크기가 정확히 일치한다는 것을 눈치채야만 알 수 있다. 요약하자면 완전히 잘못된 정보를 주고 있다.


2. 변수 이름만 봐서는 이 변수가 무엇을 의미하는지, 어디서 사용되는지 쉽게 알 수 없다.

g_szMstNum 변수에서 헝가리안 표기법에 해당하는 g_sz 를 떼어내고 나면 "MstNum"이 남는다. 이게 무슨 의미인지 알 수가 없다. Master 인지 Most 인지 Minimum Spanning Tree 인지 보는 사람마다 다르게 해석할 수 있을 것이다.


3. 변수 이름과 변수 내용에 접점이 없다.

Num으로 미루어 보아 숫자일 것이지만 sz 접두어를 보면 문자열이다.


위는 약간 각색하긴 했지만 실제로 있었던 일이다. 특히 sz 접두어를 붙였지만 문자열이 null 로 끝나지 않도록 만들어 둔 바람에 여기다가 스트링 처리를 했던 모든 코드들에서 문제가 생겼었다. 몇 시간 동안 디버거를 통해 변수가 어떻게 초기화되는지 찾아보고 나서 위와 같은 코드를 찾은 나는 헝가리안 표기법에 대한 깊은 의구심이 들기 시작했고 이에 대한 외국 포럼의 토론 글을 찾아보기 시작했다. 그리고 내린 결론이다:




나는 헝가리안 표기법(Hungrian notation)은 이제 사용하지 않아야 한다고 강력히 주장하는 바이다.

첫 번째 이유는 사람들이 헝가리안 표기법을 잘 못 이해하고 사용할 가능성이 높으며, 이 경우 큰 혼란을 초래할 수 있다는 것이다. 가령 sz가 String terminated by Zero 의 약자인데도 사람들이 잘 모르고 스트링을 대표하는 접두어로 그냥 쓴다는 것이다. 만약 해당 변수가 null로 끝나지 않는다면 나와 같은 상황이 생길 수도 있다. 이 외에도 int 형 변수의 접두어를 누구는 n을 쓰고 누구는 i를 쓰고, bool 변수는 b를 쓰기도 하고 is를 쓰기도 하며 float 변수는 f를 쓰기도 하고 fl을 쓰기도 하며 static 변수 앞에 s를 붙이기도 하지만 스트링 앞에 s를 붙이기도 하는 등 일관성이 없는 문제가 있다. 일관성이 없어지기 쉽다는 것이야말로 이 방법이 잘못되기 쉽다는 반증이기도 하다.

두 번째 이유는 변수의 '타입'을 변수 이름에 기재해야 하는 이유가 없다는 것이다. 요즘은 IDE가 발달해서 변수의 타입 정도는 아주 빨리 알아낼 수 있다. 그리고 C와 달리 C++은 클래스가 도입되어 사용자가 원하는 타입을 거의 무한대로 만들 수 있는데 접두어를 일일히 고안해낼 수도 없는 일이다. PlayerState 클래스 변수의 접두어는 무엇을 할 것인가? ps? iAge를 생각해보자. 나이는 당연히 숫자인데 int 형이라는 정보를 굳이 줄 필요가 있는가?

세 번째 이유는 변수의 가독성이 떨어지고 변수명이 쓸데없이 길어진다는 것이다. 우리는 대부분의 시간에 코드를 쓰기보다는 "읽는다". 변수의 이름은 변수가 어떤 역할을 하는지에 충실해야지 타입을 나타내는데 낭비되어서는 안된다고 생각한다. m_iAge 와 Age 중 어떤게 보기 편할까? 별 차이가 없다고 생각할 지 모르겠지만, 사람은 문자를 읽을 때 마음속으로 모든 글자를 소리내어 발음하려는 경향이 있다. m_iAge 는 "엠, 아이 에이지"로 읽지만 Age 는 그냥 "에이지"로 읽게 된다. 불필요한 문자들의 추가는 가독성을 현저히 떨어뜨리며 코드를 이해하는데 방해가 된다.


네 번째 이유는 변수의 타입이 변경될 경우 변수명을 모두 교체해야 한다는 것이다. 간혹 겪는 일인데, 정수로 계산하던 값이 부동소수점이 되어야 할 일이 생겨서 타입을 바꾸는 경우가 있다. 예를 들어, 캐릭터의 체력은 화면에 정수로 표시되므로 int 타입을 줘서 iHealth 로 만들었다가 나중에 초당 0.5씩의 피해량, 또는 50% 감쇠된 피해량 등을 계산해야되서 float 로 바꾸는 경우가 그것이다.

다섯번째 이유는 타입을 알아야 하는 것 자체가 문제라는 것이다. 예를 들어, 총알을 쐈을 때 적에게 주는 피해량을 50% 증가시키는 버프를 만든다고 생각해보자. 아마 여기서 enemyHealth -= damage * damageBuffRate; 정도의 코드면 이해하는 데 문제가 없다. 그러나 이게 m_iEnemyHealth -= iDamage * fDamageBuffRate; 이면 눈이 피로해지고 잘 읽어지지 않는다. 물론 타입을 알아야 할 순간이 오기는 한다. 그런데 왜 그걸 선언부가 아니고 변수명에 주렁주렁 달고 다니는가?


여섯번째 이유는 기본 타입(Primitive type) 강박증이 생기기 딱 좋은 습관이라는 것이다. 위 g_szMstNum 을 보면 알겠지만 C++ 에 기본적으로 딸려오는 STL 클래스인 std::string 으로 대체해도 충분하며 오히려 char 배열 변수를 사용하는 이유가 무엇인지 물어보고 싶어질 정도이다. 헝가리안 표기법에서는 항상 변수를 만들 때 타입 이름을 무엇을 넣을까 생각하는 과정에서 이미 잘 알려진 타입(i, b, sz, f 등)을 쓰려는 경향이 있다. 따라서 적절한 클래스를 만들어 넣어야겠다는 생각을 하기 어렵고 C native 타입들을 사용하게 되기 마련이다. 기본 타입 강박증은 코드 라인 수가 상당히 길어지고 헤더가 비대해지며 응집도가 낮은 코드를 만드는 경향이 있다. 또 제네릭한 코드와 객체 지향 코드와는 상당히 맞지 않는다.


일곱번째 이유는 변수 이름을 대충 짓는 습관이 생기기 좋다는 것이다. 단지 타입을 변수 이름에 집어넣는 것으로 iPlayer, szPlayer 같은 변수 이름이 얼마나 많이 보이는지 생각해보자. playerIndex, playerName 으로 바꾸면 얼마나 보고 이해하기 좋은가?


여덟전째 이유는 헝가리안 표기법을 썼던 MS 조차도 이제는 쓰지 않는다는 것이다. 헝가리안 표기법을 쓰는 사람 중 일부는 예전 Windows API 스타일을 보고 이 똑똑한 사람들이 만든 법칙에는 분명 무언가 중요한 이유가 있을 것이라는 것을 느끼고 따라한다. 그러나 MS가 만든 현대적인 언어인 C#을 다뤄본 사람은 알겠지만, 헝가리안 표기법은 MS 내에서 더 이상 사용되지 않으며 실제로는 사용이 금지되었다. 

https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/general-naming-conventions

위 링크는 MS의 프레임워크 디자인 가이드 중 명명법(Naming) 항목이다. 여기에 가면 다음과 같은 문장이 있다:

DO NOT use Hungarian notation. (헝가리안 표기법을 사용하지 말 것)

이 정도면 말 다 했다고 생각한다.




정리하면,

  • 좋은 코드의 핵심은 코드를 알아보기 쉽게 하고 가독성을 높이는 것에 있다. 그러나, 헝가리안 표기법은 가독성에 큰 도움을 주지 못하며 오히려 해친다. 
  • 읽는 사람에게 잘못된 정보를 전달하여 버그를 만들 수 있다. 
  • 코드를 작성하는 사람에게는 비 객체 지향적인 코드를 작성하게 만든다. 또 변수 이름에 타입을 넣음으로써 어느 정도 정보를 전달했다고 믿게 만들고 변수명을 대충 짓게 만든다.
  • 타입 검사는 컴파일러에게 맡길 것. Visual Studio 2010부터는 컴파일 없이도 타입 체크가 되어 빨간 색으로 밑줄을 그어주니까 훨씬 편하다.
  • 만약 타입을 반드시 알아야 한다면 클래스 등으로 캡슐화한다거나 해서 추상화 수준을 올려보는 것이 좋다고 생각한다. 


---

2010년에 원고를 썼는데 7년이 지난 이제서야 퇴고하고 올린다.

반응형

+ Recent posts