본문 바로가기

SW 이야기

헝가리안 표기법 (틀린 코드를 틀리게 보이도록 만들기)

틀린 코드를 틀리게 보이도록 만들기

2005년 5월 11일 목요일



...(중략)

다음 C코드는 문법도 정확하고, 코딩 표준도 지켰습니다. 일부러 이렇게 짠 것 같아 보이기 까지 합니다.

char* dest, src;


하지만 C로 코딩을 많이 해본 사람이라면, dest는 char형 포인터지만, src는 단순한 char형이란 사실을 금세 알아차립니다.

... 코드에서 더러운 냄새가 납니다.


...

조금 더 미묘한 문제도 있습니다.

if (i != 0)
    bar(i);
    foo(i);


중괄호 팔아먹기 신공으로 여러분 뒤통수를 때립니다. i값이 뭐든 foo(i)를 호출해버리는군요.

이렇게 중괄호가 없는 코드 블록이 나타나면, 뭔가 찝찝하고 불편한 느낌이 들어야 합니다.


프로그래머가 격는 세가지 단계를 알려드리겠습니다.

1. 뭐가 깨끗하고 뭐가 더러운지 구분하지 못합니다.

2. 눈에 보이는 깨끗함을 이해합니다. 코딩 표준(coding convention)을 준수하는 단계입니다.

3. 얇은 껍질 밑에 감춰진 더러운 냄새를 맡습니다. 이 냄새를 실마리로 더러운 코드를 찾아 바로잡는 단계입니다.


하지만 좀 더 높은 단계도 있습니다. 정말로 여러분이 달성해야 할 단계입니다.

4. 더러운 냄새를 풍기는 코드가 나 좀 고쳐 달라고 저절로 모습을 드러내는 코딩 표준을 만드는 경지에 이른 단계입니다.

(즉, 틀린 코드가 저절로 틀리게 보이게 만드는 코드 입니다.)


(이 진정한 예술을 실현하는 기술이 바로 헝가리안 표기법입니다.)


>> 예제

웹 어플리케이션을 만드는데 아래와 같은 슈도 코드가 있습니다.

s = Request("name")


HTML 폼에서 입력한 값을 읽습니다.

Write "Hello, " & Request("name")


여러분 사이트는 바로 XSS 공격에 노출됩니다. 한줄이면 끝납니다. 공격 받는 건 시간 문제죠.

사용자가 입력한 name 을 HTML로 내보내기 전에 인코딩해야 합니다.

여기서인코딩이란 "를 &quot;로, >를 &gt;로, <를 &lt;로 바꾼다는 뜻입니다.

Write "Hello, " & Encode(Request("name"))

이제 안전합니다.


지금부터 코드가 스스로 잘못을 드러내는 코딩 표준을 만들어 보겠습니다.

적어도 틀린 코드가 틀리게 보이기만 한다면 코드 검토자나 작업자가 쉽게 잘못을 잡아낼 것입니다.


가능한 해법 #1

첫 번째 해법은 사용자로부터 받은 문자열을 즉시 변환해 버리는 방법입니다.

s = Encode(Request("name"))


그렇다면, 코딩 표준에 이 규칙을 써 넣어야겠군요. "Encode하지 않고 Request를 호출한 코드는 분명히 잘못된 코드다"

사용자가 입력한 문자열을 DB에 저장할 때 변환해서 저장하다니 말도 안됩니다.

그래서 아래와 같이 코딩 규칙을 변경합니다.

"모든 문자열은 애플리케이션 내부에서 변환하지 않은 형태로 쓰이다가 HTML로 내보내기 바로 전에 변환한다"

따라서, 아주 잠시지만 문자열을 위험한 형태로 유지할 필요가 있습니다.


가능한 해법 #2

모든 문자열을 외부로 내보내기 직전에 변환하도록 코딩 표준을 확장하면 되겠군요.

s = Request("name")

// 한참 뒤에:
Write Encode(s)


이제 Encode 없이 Write가 등장하는 코드를 만나면 뭔가 이상하다는 느낌이 딱 들어야 합니다.


그런데, 가끔은 변환하지 말아야 하는 HTMl 문자열도 필요합니다.

If mode = "linebreak" Then prefix = "<br>"

// much later:
Write prefix


Write로 문자열을 외부에 출력하기 전에 반드시 Encode로 변환한다는 코딩 표준을 어기네요.

다음과 같이 써야 코딩 표준에 맞습니다.

Write Encode(prefix)

그랬더니 <br> 이 아니라 lt;br&gt;로 변환돼 버려서 화면에 빈줄이 아니라 그냥 <br>이란 글자가 보일 뿐이네요. 문제 있습니다.

상황에 따라 코딩 표준을 다르게 할 수 없으니 위의 해법은 문제가 있습니다.

코딩 표준을 없앤다고요? 이런 위험한 코드들이 바로 활개를 칠겁니다.


s = Request("name")

...pages later...
name = s

...pages later...
recordset("name") = name // store name in db in a column "name"

...days later...
theName = recordset("name")

...pages or even months later...
Write theName

Write를 해야 하는데 문자열 변환을 했던가?

이런 식으로 코드를 짠다면 문자열을 외부로 출력할 때마다 위험은 없는지 확인하려고 소스를 몽땅 뒤지는 짜증나는 작업을 해야 합니다.



진짜 해법

진짜 해법은 다음과 같은 한가지 규칙만 정의하면 됩니다.

규칙 : 사용자가 보낸 모든 문자열은 'us(Unsafe String의 약자)'로 시작하는 변수나 데이터베이스 칼럼에 저장한다.

변환한 문자열이나 안전하다고 검증된 문자열은 's(Safe String의 약자)'로 시작하는 변수나 데이터베이스 칼럼에 저장한다.

새로윤 코딩 규칙에 맞춰 변수 이름만 바꿔봅니다.


us = Request("name")

...pages later...
usName = us

...pages later...
recordset("usName") = usName

...days later...
sName = Encode(recordset("usName"))

...pages or even months later...
Write sName


아래와 같은 코드는 자기가 문제 있다고 스스로를 드러낸다는 점에 주목하세요

s = Request("name")


이렇게 작성해야죠

us = Request("name")


이 코딩 표준을 따르면 눈이 저절로 Write usXXX 와 같은 코드를 찾아냅니다.


사실, 이 코딩 코준을 약간 확장해서 Request와 Encode 함수 이름을 변경하여 좀더 눈에 띄게 합니다.

us = UsRequest("name")
usName = us
recordset("usName") = usName 
sName = SEncode(recordset("usName"))
Write sName


어떻습니까? 대입 연산자를 중심으로 양쪽 모두 같은 접두사로 시작해야 하기 때문에 오류가 좀 더 쉽게 눈에 띄지 않나요?

us = UsRequest("name") // ok, both sides start with US
s = UsRequest("name") // bug
usName = us // ok
sName = us // certainly wrong.
sName = SEncode(us) // certainly correct


한 걸음 더 나아가 봅시다. Write를 WriteS로, SEncode를 SFromUS로 바꿔봅니다.

us = UsRequest("name")
usName = us
recordset("usName") = usName 
sName = SFromUs(recordset("usName"))
WriteS sName


오, 오류가 훨씬 잘 보이는군요


헝가리안

헝가리안 표기법은 마이크로소프트 프로그래머 찰스 시모니가 만들었습니다.

이 표기법에 맞춰 작성한 코드는 마치 헝가리어처럼 보이기도 하고, 시모니가 헝가리 사람이기도 해서 헝가리언 표기법이란 이름이 붙었습니다.

시모니가 만든 헝가리언 표기법에는 다음과 같은 문구가 있습니다.

"모든 변수는 변수가 담고 있는 내용에 따라 종류(kind)를 구분할 수 있도록 소문자로 된 식별자를 접두어로 붙인다"


처음에 시모니가 실수로 종류가 아닌 타입(type)이라고 잘못 쓰는 바람에 수많은 후세 프로그래머들이 시모니가 주창한 참뜻을 곡해했고,

이 상태로 몇 세대가 흘러버렸습니다.


시모니가 작성한 좌표계를 보면 rw와 col이라는 접두어를 굉장히 많이 볼 겁니다.

척 보면 알겠지만, 행가 열을 나타냅니다.

둘 다 정수형이지만, 서로 대입할 일은 하늘이 두 쪽 나도 없습니다.

만약 rw1 = col1 와 같은 코드를 본다면, 머리 속에 바로 '나쁜 코드 사이렌'이 울려야 합니다. 틀린 코드가 분명하죠?


(너무 길어서 줄이자면...)

배열 첨자를 뜻하는 'ix', 개수를 뜻하는 'c', 두 숫자의 차이를 뜻하는 'd' 같이 의미가 풍부한 접두사가 진짜 헝가리안 표기법.


dwFoo, nCount와 같은 type을 나타내는게 곡해된 헝가리안 표기법.


결국 마이크로 소프트가 .NET을 발표하면서 "헝가리안 표기법을 권하지 않습니다"라고 발표했습니다.



................................................

글이 너무 길어졌지만 ㅠ

헝가리안 표기법에 대해 새롭게 생각해 보았다는 결론.

진자 헝가리안 표기법을 이제야 알았다는 뒤늦은 깨달음.




<출처>

1. 조엘온소프트웨어를 넘어서, 228p

2. http://joelonsoftware.com/articles/Wrong.html