2012년 3월 16일 금요일

컴파일 경고하나당 3 대씩 맞습니다

컴파일 경고(warning) 얼마나 신경쓰세요? 현재 작업중인 코드베이스를 컴파일하면 경고가 몇개나 나오나요? 하나라도 있다면 반드시 다 고치세요. 구차한 변명따윈 필요없습니다. 하나도 안나오게 다 고치세요. 왜인지는 얼마전에 있던 제 경험담을 통해 아주 짧게 주저리주저리 설명드립니다.

며칠 전에 발견한 컴파일 경고 하나
현재 마무리를 도와주고 있는 콘솔게임을 PS3용으로 컴파일하다(즉 g++을 사용하면) 운좋게(왜 운좋은건지는 나중에 설명) 다음과 같은 컴파일 경고를 봤습니다.

1>..\..\src\monster.cpp(2814): warning : unused variable 'temp'

보통 때 같으면 무시하겠는데(왜 무시하는지도 나중에 설명.. 원래는 절대 무시하면 안됨) 그날 따라 무슨 신내림을 받았던지...

'응? 이놈이 뭐지? 궁금한걸?'

이란 생각이 더블클릭을 해서 소스코드를 봤지요.


bool swapped = false;
if( dist1 > dist2 )
{
  swapped = true;
  float temp = dist1;
  dist1 = dist2;
  dist2 = dist1;
}

어라... 버그가 보이는군요. 무슨 버그일까요.... 잠시 여백을 두어 생각하실 기회를 드리겠습니다... 한 눈 팔지 말고 코드 분석하세요...

한 눈 팔지 말랬잖아요.. 이 이미지를 보시면 이미 한눈 파신 겁니다. (그래도 출처는 http://curiousanimals.net)

자, 문제점을 찾으셨나요? 찾으셨을거라 믿습니다... 아니시라면.... 당신은 프로그래머가 아니야~ 버럭!

그래도 또 설명하는 친절한 포프씨 -_-
위 코드가 하려는 일은 두 거리(dist1, dist2) 중에 dist2가 언제나 dist1보다 크도록 변수값을 교환해주는 거군요. 매우 간단하죠. 근데... 뭔가 이상하죠? temp 변수가 안쓰였다니... 프로그래머가 아니신 분들을 위해 친절하게 예를 들어 한 단계씩 보여드리죠..


1. 일단 대충 변수값을 제맘대로 대입
다음과 같이 변수값을 정해서 이게 왜 버그인제 예를 보여드리겠습니다.

  • dist1 = 20
  • dist2 = 10


2. float temp = dist1;이 라인을 실행하면 변수값이 다음과 같이 됩니다. 별문제는 없죠 아직.

  • temp = 20
  • dist1 = 20
  • dist2 = 10

3. dist1 = dist2; 
이 놈 이후엔 변수값이 이리 됩니다. 역시 괜찮습니다.

  • temp = 20
  • dist1 = 10
  • dist2 = 10

4. dist2 = dist1;  

이게 마지막 라인이군요. 드디어 버그가 보이는군요. 이걸 실행하면 최종 변수값이 이따위가(?) 됩니다.

  • temp = 20
  • dist1 = 10
  • dist2 = 10

temp에 저장해눴던 값을 dist2에 대입하는 대신 dist1을 대입함으로 해서... 버그가 생긴거지요...  그럼, 이 버그로 인해서 생기는 문제는 뭐였을까요?

전.모.릅.니.다.
정말 몰라요....(출처: http://knowyourmeme.com)


대충 코드를 훑어봤는데... 파일 이름은 monster.cpp이고 함수이름은 updateTail() 이었습니다. 사실 정확히 어떤 버그가 생기는지는 제대로 살펴보지 않았습니다. 그냥 대충 생각해보니 몬스터들이 팔을 휘두르거나 그럴 때 자취를 보여주기 위해 띄를 하나 그려 주거든요? 가끔, 그 띄가 뒤로 역류해서 겹쳐보이는 경우가 있었습니다. 그게 바로 이 버그 때문일지도 모릅니다. 하지만... 귀찮아서 더 자세히 보진 않았습니다. 그냥

'이 버그 발견했으니 고쳐주세요.'

라고 이메일만 보냈습니다.

버그는 어느 프로그래머나 만든다.
뭐 이정도 버그야 어느 프로그래머도 만들 수 있는 버그입니다. 그 부분에 대해선 프로그래머를 탓하지 않겠습니다. 저같은 꽃미남 프로그래머도 만드는 버그입니다.

뭐, 위의 예는 약간의 비주얼적인 하자.... 별 큰 문제는 아니었죠... 근데... 이게 뭔가 더 중요한 코드였다면 엄청난 하자가 될수도 있죠. 특히나 이처럼 특정조건에서만 생기는 문제였다면 디버깅도 쉽지 않습니다.

하지만 버그를 안만들기 위해 노력은 해야할거 아닌가?
따라서 제가 탓하고 싶은 건 컴파일 경고가 떡하니

"너 뭔가 잘못하고 있을지도 몰라. temp변수가 안쓰였잖아"

라고 말해주는데 그걸 무시한 만행(?)입니다.

컴파일러 경고를 고치는 것만으로도 상당히 많은 수의 버그를 미리 잡아낼 수 있습니다. 이 코드가 들어가있는 게임... 수백만장 팔립니다... 개발비도 수백만불 됩니다. 이런 프로젝트에서 가장 쉽게 코드품질을 보장할 수 있는 법을 무시하는 프로그래머의 자세 좋지 못하죠. -_-

보통 이따위로 깔끔하지 못하게 프로그래머하시는 분들... 전 타박해 드립니다... 하지만, 이번에는 그러지 못하는 이유가 있었습니다. 그것은....

너도 범인이다.
사실 문제는 이 코드를 작성한 프로그래머에게만 있는 건 아니었습니다. 이 회사의 모든 프로그래머 또는 제대로 된 정책을 만들지 않은 리드급 프로그래머에게도 문제가 있었습니다.

왜냐면..... 이 코드베이스를 컴파일하면 컴파일 경고가 1019개 나오기 때문입니다. -_- 1019개나 되는 경고속에 파묻힌 경고 하나를 찾으라고 하는게 어불성설이지요.....

자네...나보고 어쩌라는 겐가....?

보통 때 같으면 전 이런 경고 다 고쳐버리고,

"앞으로 경고 나오면 경고 하나당 저에게 3대씩 맞습니다."

라고 통보를 해드립니다. 하지만 그럴 수 없었던 이유가 이미 너무 늦었다는거죠.  -_-.... 게임 마무리 단계에서는 아무리 사소한 거라도 안건드립니다. (이건 전에 '게임 출시전 개발자가 갖춰야할 마음가짐'이란 글에서 말씀 드렸었습니다.) 딱히 발견된 버그가 있지 않는한 절대 안고칩니다. 코드를 바꿀 때마다 새로운 버그가 나올 위험이 있거든요. 그래서.... 안고칩니다.... 무조건 안고칩니다.

게임 출시하고 나서 고치죠. 근데 이 프로젝트 이후에 제가 이 팀을 도와줄 일이 없을거 같으니....

아마 평생 안고칠지도.... -_-

컴파일 경고를 방지하는 무대뽀 실용적인 방법
자, 그럼 저희처럼 너무 늦기전에 이런 일이 발생하는 것을 방지합시다. 당장 오늘부터라도 시작합시다.

컴파일러 경고를 오류로 처리한다.
가장 실용적인 방법은 컴파일러 경고가 나는 경우 이걸 아에 오류(error)로 처리해서 실행파일 조차 안만들어지게 하는겁니다. 아주 무대뽀 실용적이지요. 간단히 Visual Studio에서 컴파일 옵션만 켜주면 됩니다.

아주 실용적(?)인 방법

이러면 경고가 날 때마다 아예 실행파일이 안만들어지니 테스트를 못하지요! 그래서 프로그래머는 반드시 이 경고를 고쳐야만 합니다. 그래야 제대로 도는지 테스트라도 하니.... 근데 문제는.... 임시적으로 뭐 테스트하고 지우기 위해 대충 코드짜놨는데 경고가 나면...... 짜증나지요... 프로그래머들의 반란(?)을 부추기는 계기가 됩니다.... -_-

그래서 이 옵션은 release 빌드용으로만 켜주는게 좋습니다. debug 빌드에서는 프로그래머가 뭔짓을 해도 냅두는게 장수의 비결입니다. 문제는 프로그래머가 debug 빌드에서만 테스트하고 경고있는 코드를 그대로 check-in 했을 때인데요.

뭐, 이럴 때는 자동빌드머쉰(automatic build machine)이 release 빌드에 실패한뒤 그 프로그래머에

"당장 경고를 고치지 않으면 구워먹겠다!"

라는 협박 이메일을 보낼 거라고 믿습니다. 자동빌드머쉰이 없다면 만드세요. 이것도 없이 어떻게 게임 개발하지...? -_-;;;

정말 말도 안되는 경고는 꺼준다
가끔 정말 말도 안되는 경고도 있습니다. 별 의미가 없는 경고도 있고요. 이런 건 꺼주시면 됩니다. 단, 자기 맘대로 끄지 마시고 다른 프로그래머들과 상의한 뒤 동의를 얻어 꺼주세요. 자기 맘대로 끄는 것을 허용하면 경고 고치기 싫어서 그냥 꺼버리는 몰상식한 프로그래머들 꼭 나옵니다. 이런 분들 발견하시면 퇴사처리 해버리셔도 됩니다. -_-

끄는 법에는 2가지가 있습니다. 프로젝트 전체로 꺼주는 법도 있고, 각 파일별로 (또는 그보다 작은 단위로) 꺼주는 법도 있습니다. 원하는 대로 골라서 쓰시면 됩니다.

프로젝트 전체에서 경고 꺼주기
아래처럼 프로젝트 속성에서 꺼주면 됩니다. 아래 예는 프로젝트 전체에서 경고 #4507을 다 꺼주는 겁니다.




파일별로 꺼주기
그냥 아래처럼 #pragma warning()을 써주시면 그 뒤부터는 경고가 안나옵니다.

#pragma warning( disable : 4507 )

이걸 나중에 다시 켜줄려면

#pragma warning( default: 4507 )

을 해주면 됩니다.


대충 정리
뭐... 결국 할 말은.... '경고를 반드시 고쳐주세요.'였으나 역시나 너무나 주저리주저리 써서 대충 정리.

  • 컴파일 경고만 살펴봐도 정말 명백한 버그들을 고칠 수 있다.
  • 릴리즈 빌드에서는 컴파일 경고를 컴파일 오류로 처리해서 프로그래머들이 언제나 경고없는 소스코드를 유지할 수 있게 돕는다.
  • 필요없는 경고는 프로젝트 속성에서 전역적으로 꺼주거나 파일별로 #pragma warning()을 이용해서 꺼준다.


p.s. 앞으로도 대충 어이없는 경험을 바탕으로 '제발 이러지 맙시다.'라는 글을 종종 올리겠습니다. 은근 좋아하는 분들이 많으신듯..... -_-


댓글 4개:

  1. 저는 계속 워닝을 줄이는데 다른 사람들이 워닝을 늘리고 있을 때는 어떻게 해야하나 싶습니다 (...)

    답글삭제
    답글
    1. 말씀드린대로 release빌드에서 워닝을 에러로 처리해서.. 빌드머쉰에서 무조건 실패하게 만드는 법이 있습니다.... 이왕이면 리드 프로그래머 아찌가 나서서 해주시면 좋죠...

      삭제
  2. 진짜 결벽증 있는사람은 좋아할듯 ㅋㅋㅋ 단 하나의 워닝도 못참아!!

    답글삭제
    답글
    1. ㅎㅎ 결벽증의 문제가 아니라.. 저렇게 하나둘씩 경고를 두게 되면 정말 중요한 경고를 놓치거든요.. 제글보도 경고 제거하다가 "헉 이런 걸 방치하다니 큰일날뻔 했다!"라고 하신 분도 봤어요..

      코딩 스탠다드 글을 봐서 아시겠지만 전 쓸데없이 까탈스런 규칙 딱 싫어하는데.. 이것하나만은 까탈스럽게 고수합니다 ㅎㅎ

      삭제