1. 개요
프로그래밍 언어에서 변수 및 함수의 인자 이름 앞에 데이터 타입을 명시하는 코딩 규칙이다.찰스 시모니(Charles Simonyi) 가 마이크로소프트의 개발 책임자로 있을 때 제안했으며, 80년대 당시에는 IDE라는게 다들 부실했기 때문에 이 규칙이 엄청난 센세이션을 불러 일으켰다. 하지만 지금은 MS도 공식 가이드라인에서 사용하지 말 것을 권고[1]하고 있다.
헝가리안 표기법이라는 명칭은 제안자인 찰스 시모니가 헝가리인이라서 붙은 것이다.
2. 적용하기
아래 표만 잘 기억하면 된다.물론 이렇게 정해야 한다는 명확한 룰 자체가 없으므로, 사용자나 이를 설명한 사이트에 따라 조금씩 다를 수는 있다.
2.1. Systems Notation
데이터의 타입을 명시하는 방식으로, 널리 알려진 방식이다. 이런 방식 자체는 6,70년대 C언어 이전 타입시스템이 없는 언어에서 변수명에 변수의 타입을 명시해서 사용한 것이 시초이다.2.1.1. 공통
접두어 | 데이터 타입 |
b | byte , boolean |
n | int , short |
i | int , short (주로 인덱스로 사용) |
c | int , short (주로 크기로 사용) |
l | long |
f | float |
d , db | double |
ld | long double |
w | word |
dw | double word |
qw | quad word |
ch | char |
sz | NULL 로 끝나는 문자열 |
str | C++ 문자열 |
arr | 배열 (문자열 제외): 다른 접두어와 조합 가능 |
p | 포인터 (16비트, 32비트): 다른 접두어와 조합 가능 |
lp | 포인터 (32비트, 64비트): 다른 접두어와 조합 가능 |
psz | NULL 로 끝나는 문자열을 가리키는 포인터 (16비트, 32비트) |
lpsz | NULL 로 끝나는 문자열을 가리키는 포인터 (32비트[2], 64비트) |
fn | 함수 타입 |
pfn | 함수 포인터 (16비트, 32비트) |
lpfn | 함수 포인터 (64비트) |
2.1.2. OOP
접두어 | 데이터 타입 |
g_ | 네임스페이스의 글로벌 변수 |
m_ | 클래스의 멤버 변수 |
s_ | 클래스의 static 변수 |
c_ | 함수의 static 변수 |
m_lpszName
- 클래스 멤버 변수인 문자열 포인터)이 접두어들은 당연히 private 멤버에 사용하는 것이다. 절대 public으로 오픈하지 말 것.
2.1.3. Windows API
접두어 | 데이터 타입 |
h | 리소스 핸들 (HWND 를 제외한 모든 HANDLE 타입) |
Windows API 문서를 보면
wParam
과 lParam
이 지겹게 등장하는데, 접두어대로 wParam
은 word나 dword 기반, lParam
은 int나 long 기반이다.2.2. Apps Notation
데이터 타입이 아닌, 데이터의 논리적 상태를 명시하는 방식이다. 변수 뿐 아니라 함수에도 사용가능하다.사실상 이쪽이 시모니가 의도한 진짜 사용법이라고 볼 수 있다. <조엘 온 소프트웨어>로 유명한 조엘 스폴스키도 블로그에서 이러한 점을 지적하며 잘 작성된 헝가리안 표기법을 사용하면 코드에서 "버그가 스스로 드러나는" 코딩을 할 수 있다고 주장했다.#영어 의미에 따라 접두어가 붙기 때문에 의도치 않은 대입 등을 바로 알 수 있기 때문이다.
시모니가 MS에서 작성한 헝가리안 표기법 제안을 봐도 단순히 기계적으로 타입명을 붙이라고는 하지 않았다. 접두사
ch
에 대한 설명을 보면 "char형. 일반적으로 아스키 문자"라고 되어 있다. 즉, "ch가 붙은 변수는 문자라는 '의미'이므로 문자 말고 다른 값이 들어가지 않도록[3] 주의하며 코딩해라"라는 의미.2.2.1. 예시
타입이 아니라 의미에 따라 이름을 붙이는 방법이기 때문에 정해진 규칙이 없다. 이런점이 Apps Notation을 배우기 어렵게 하고 Systems Notation이 대세가 된 이유이기도 하다.접두어 | 의미 |
n | Count 변수 |
d | 두 값의 차이를 담는 변수 |
us | 안전이 확인되지 않은 변수(UnSafe).[4] |
s | 안전이 확인된 변수(Safe) |
Locked | 함수의 접미사. 스레드 안전하지 않은 함수기 때문에 사용하기 전에 Lock을 잡아야 한다 안드로이드에서 사용된다. |
간단히 예를들면
#!syntax java
int sValue = usValue; // Unsafe 값을 Safe 변수에 바로 넣었다. 양변의 의미가 다르므로 문제가 있는 코드
sValue = sCheck(usValue); // us값을 체크해서 s로 바꿔주는 함수 sCheck를 거쳤다. 양변이 모두 s이므로 문제없는 코드
sValue = sCheckLocked(usValue); // 의미는 맞지만 Locked 함수를 쓰기 전에 락을 걸지 않았다. 멀티스레드에서 동작시 문제가 생길 수 있다.
3. 장점
- 데이터 타입을 변수명에서 바로 추정할 수 있다.
- IDE가 없을 때 작업하는 경우 (특히 vi나 emacs로 터미널에서 작업할 때) 여러모로 유리해진다.
- 같은 의미를 가지는 서로 다른 타입의 변수가 있을 때 이름 충돌을 방지할 수 있다.
4. 단점
- 변수나 함수 인자의 이름을 기억하기가 힘들다.
- 데이터 타입이 바뀌면 변수 또는 함수 인자의 이름을 바꿔야 한다. 리팩토링을 지원하는 IDE가 없으면 지옥도가 펼쳐질 것이다.
- 코드의 의미론을 해친다. 나이를 굳이 iAge로 표기해야 할까? 나이는 따로 표기하지 않아도 정수형인 게 자연스럽다.
- 코드의 가독성을 해친다. 사람은 문자를 읽을 때 마음속으로 모든 글자를 소리내어 발음하게 된다. m_iAge 는 "엠, 아이 에이지"로 읽지만 Age 는 그냥 "에이지"로 읽게 된다. 불필요한 문자들의 추가는 가독성을 현저히 떨어뜨리며 코드를 이해하는 데 방해가 된다.
- 시스템 아키텍처에 따라 데이터 타입의 명세가 암묵적으로 바뀌는 경우 혼란을 빚게 된다.[5] 예를 들어
w
는 16비트일까? 32비트일까? 64비트일까? - primitive type 강박증이 생긴다. 객체지향적이고 제네릭한 코드를 짜기 힘들어진다. 헌데 이건 헝가리안이 OOP가 없던 시절에 나온 표기법이라 당연한 것이다. 표기법 자체가 아니라, 환경이 달라졌는데도 계속 쓰는 사람이 문제다.
5. 몰락과 유산
IDE의 눈부신 발전 덕분에 헝가리안 표기법은 단숨에 구식으로 변하고 말았다. 최신 IDE에서는 마우스 커서만 올리면 심볼의 데이터 타입은 물론 선언 및 참조 위치까지도 한 눈에 확인할 수 있다. IDE가 제공하는 타입 분석 기능이 너무나 강력한 바람에, 심지어는 후대에 나온 동적 타입 언어들까지도 정적 타입 비슷한 기능을 붙여서 IDE의 기능을 최대한 뽑아내려고 할 정도다.
상술한 이유로 대부분의 최신 개발 환경에서는 헝가리안 표기법이 나쁜 습관으로 간주된다. 특히 map/set/array(list) 등 자료형의 이름을 심볼 이름으로 쓰는 습관은 반드시 고치는 게 좋다. cardList보다는 cards가 더 좋고, 여기에 논리적인 응집이 더 필요하면 Deck이라는 클래스를 만드는 식으로 생각해야 한다.
다만, 데이터의 논리적인 상태를 나타내는 Apps Notation과 유사한 관습은 지금도 간간히 남아있다.
- 클래스 멤버의 접근 수준을 명시하기 위해 심볼 앞에 밑줄(
_
) 붙이기.
개발 언어가 접근 제한자를 지원하지 않을 때 자주 쓰인다. Python은 아예_
(밑줄 1개)를protected
로,__
(밑줄 2개)를private
으로 간주하는 것이 표준 명세다. - 내부 명세와 외부 명세를 가려야 할 때 심볼 앞에
_
또는m
붙이기.
컬렉션 등 필드 내에서 다른 참조를 가리켜mutable
(읽고 쓰기가 가능)와immutable
(읽기만 가능)을 구분지어야 하는 경우나, 내부 자료형과 외부 자료형이 일치하지 않는 등으로 외부 접근 시 전처리를 거쳐야 하는 경우 많이 쓰인다. 가령 같은 참조를mList
와list
로 구분하여, 내부에서만 사용하는mList
는mutable
로 사용하고, 외부에 노출되는list
는immutable
로 사용하는 식. Android의 내부 코드가 이렇게 구현되어 있다. Kotlin, C# 등 속성 기능을 지원하는 프로그래밍 언어에서 이런 특징이 더 두드러지는데, 내부 사용과 외부 노출을 확실히 구분지어야 하는 일이 자주 일어나기 때문이다.[6] - 쓰지 않는 변수의 심볼을 다른 문자 없이
_
만 여러 개 이어붙여서 만들기.
아예_
를 예약어로 정의해, 굳이 구분짓지 않고 통일시켜도 문제가 되지 않는 경우도 있다. Kotlin이나 JavaScript 등이 이런 예. Java의 경우 1.7까지는 식별자 이름으로 쓸 수 있었으나, 1.8에서 사용 자제가 권고되더니, 9에서는 아예 예약어로 변경돼 식별자 이름으로 사용할 수 없게 되었다. - 참거짓 자료형 심볼 앞에
is
붙이기.
정 붙일 이름이 없을 때 궁여지책으로 쓰게 된다. 다른 자료형과 달리 참거짓형은 변수 이름을 붙이기 애매할 때가 많기 때문이다. 그래도 가능한 한 의미론적인 명명 규칙을 적용하는 게 좋다. (예: isCompleted 대신 completed) - 심볼 뒤에
enum
,type
등 붙이기.
이런 의미론적인 표기법은 IDE가 직접 지원하기도 한다. 최신 IDE에서 특정한 정규표현식(예: 맨 앞에 '_')을 만족하는 심볼에 대해 특정 코드 검사(예: 안 쓰는 변수)를 비활성화하는 옵션 등을 비교적 쉽게 찾아볼 수 있다.
6. 관련 문서
- https://en.wikipedia.org/wiki/Hungarian_notation
- https://msdn.microsoft.com/en-us/library/aa260976(VS.60).aspx - 찰스 시모니가 MS에서 제안한 표기법 문서 (MSDN)
- https://msdn.microsoft.com/en-us/library/aa378932(VS.85).aspx - MS 코딩 규칙
[1] https://msdn.microsoft.com/en-us/library/ms229045(v=vs.100).aspx 내용 자체는 .NET Framework에 적용되는 문서이긴 하지만 새로 만드는 SDK의 API들은 헝가리안 표기법에서 탈피하고 있다.[2] 윈도우즈 API에서 아주 많이 쓰인다. 함수의 파라미터 중에 문자열은 LPCTSTR lpName 식의 표기가 대부분이다.[3] C언어에서는 문자도 그냥 1바이트 정수로 취급하므로 숫자를 대입해도 아무 이상이 없다.[4] 사용자 입력값을 그대로 받아서 무결성 검증이 되지 않았고(나이에 음수를 넣었다거나), SQL injection처럼 해킹의 의도를 가진 문장일수도 있다.[5] 과거 C언어의 16비트에서 32비트로의 전환기에 헬게이트가 열렸었다.[6] Java 코드를 Kotlin에서 갖다 쓸 때에는 내부 식별자와 외부 식별자가 일치(내부 필드는
data
이고, 외부 접근 메서드는 getData()
, setData()
)해도 문제가 되지 않으나, Kotlin 네이티브인 경우는 식별자 이름 충돌 등 심각한 문제가 된다.