∇ 일반 강좌

 ▼ 코드 내의 주석에 대한 단상

N/A

N/A

프로그램의 주석에 대한 나의 생각은 이렇다.

나는 원래 주석이라는 것은 필요없다고 주장해왔다. 이 주장은 항상 일반적인 상식과는 상반되기 때문에 반대 의견을 많이 듣는다. 회사에서는 회사의 일반적인 룰을 따라 주는 것이 맞다고 생각하기에 회사에서 생산한 코드에는 기본적인 주석을 붙이고는 있지만 개인적인 프로젝트에는 주석을 따로 붙이지 않는다.

// 0에서 100까지의 합을 구한다.
 
// 합계를 저장할 변수
int total = 0;
 
for (int i = 1; i <= 100; i++)
    total += i; 
 
// 계산한 합계를 출력한다.
printf("total = %d\n", total);

위의 코드에서 주석은 하나도 필요 없다고 생각한다. 소스는 그 언어의 매뉴얼도 아니고 알고리즘 서적도 아니다. 위의 코드와 변수명을 보고도 바로 내용을 파악하지 못하면 도리어 그게 더 문제가 있는 것이 아닐까? 주석을 넣지 않음으로서 얻는 이득은 한 페이지에서 볼 수 있는  소스 코드의 양이 많이 때문에 도리어 소스를 읽고 해석하는데 눈이 편하고 분석 시간이 단축된다는 것이다.

프로그래밍을 함에 있어서 상식에서 벗어나지 않는 코드로 구현을 한다면 굳이 주석을 달 필요가 없다고 생각한다. 아래의 코드를 보자. (글 쓰면서 에디터 상에서 코딩한 것이므로 사소한 syntax error나 logic의 문제가 있을 수 있다... -_-;;;)

bool CGameMain::SendItem(int actor, int actee, int itemId)
{
    CPlayer* pActorInst = CPlayer::GetPlayerInstance(actor);
    CHECK_FAILED(pActorInst)
 
    CInventory* pInventory = pActorInst->GetInventory();
    ASSERT(pInventory)
 
    int itemIndex = pInventory->SearchFor(itemId);
    if (itemIndex > 0)
    {
        CPlayer* pActeeInst = CPlayer::GetPlayerInstance(actee);
        CHECK_FAILED(pActeeInst)
 
        TItem item = pInventory->PickOut(itemIndex);
        pActeeInst->GetInventory()->Add(item);
    }
 
    return true;
}

코드를 읽으면서 쭉 내려간다면 코드에서 다음과 같은 보이지 않는 주석을 읽을 수 있다.

1. 이 함수는 CgameMain이란 class의 public member function이며 item을 send하는 기능을 하여 그 성공 여부를 bool형으로 돌려준다. 각 파라미터는 행동 주체자, 피동자, send할 item의 id이다.
(주. public, protected, private의 함수 명명법은 서로 구분되게 한다)

2. CPlayer라는 class의 static function인 GetPlayerInstance()는 player id를 받아서 player instance를 CPlayer* 또는 그 후손 객체 형으로 넘겨준다.
(주. 일반적으로 앞이 대문자 'C'로 시작하는 것은 namespace가 아닌 class이므로 static class에 대한 것임을 안다.)

3. CHECK_FAILED()는 macro이며 pActorInst가 비정상적인 값일 때 함수를 중단하는 기능을 한다. 아마 이것이 컴파일 에러가 없으니 return false;로 중단하는 것으로 예상되며 비정상적이라는 판단을 하기 위한 방법은 구체적으로 알 필요는 없지만, debug 모드에서는 NULL 검사 후 player list를 직접 travers한다든 해서 검사할 수도 있고, release 모드라면 NULL 검사 정도만 할 것 같다.
(주. 일반적으로 대문자로 쓰며 ubder-score로 단어를 구분하는 것은 macro를 표현하는 방식이다.)

4. GetInventory()라는 Inventory를 Get하는 것인데 포인터 형을 리턴하고 있으므로 Inventory의 내용을 복사하지는 않고 단지 주소만 돌려준다. 그리고 그 아래에는 assertion을 통해 값의 유효성을 판단하고 있으므로 이 함수는 항상 NULL이 아닌 값만 돌려주게 되어 있다. 아마도 CPlayer는 항상 유효한 inventory를 보유하고 있다는 의미가 된다.

5. inventory에서 주어진 item id에 대한 실제 item index 값을 찾는다. if문의 쓰임새를 봐서는 이 인덱스가 0 이상일때만 찾은 것으로 생각된다.

6. 2.에서 한 것과 같은 방법으로 피동자에 대한 instance를 얻어 온다.

7. Inventory에서 지정된 그 item를 빼낸다. 빼내는(pick out) 행동이므로 행동 주체자의 inventory에서는 그 item이 하나 빠지게 된다.

8. 빼낸 item을 피동자의 inventory에 추가한다.

9. 전체적인 동작을 정리하면 actor에게 itemId의 item이 있으면 그것을 actee에거 넘겨 주는 일련의 과정이다.

그리고 좀 더 숙련되면 아래와 같은 코드도 위와 똑같은 속도로 읽을 수 있을 것이다.

bool CGameMain::SendItem(int actor, int actee, int itemId)
{
    CInventory* pInventory =
                CPlayer::GetPlayerInstance(actor)->GetInventory();

    int itemIndex;
    if ((itemIndex = pInventory->SearchFor(itemId)) > 0)
    {
        TItem item = pInventory->PickOut(itemIndex);
        CPlayer::GetPlayerInstance(actee)->GetInventory()->Add(item);
    }
 
    return true;
}

여기에서는 CPlayer의 instance를 CPlayer::GetPlayerInstance()를 통해 받으면서 NULL 검사를 하지 않는다는 것을 알 수 있다. NULL 검사를 하지 않아서 프로그램에 이상이 생기는 경우는 CPlayer::GetInventory()를 찾다가 문제가 생기거나 그 함수 안에서 this의 무엇인가를 참조할 때이기 때문에 그 두 문제가 아예 발생하지 않게 해두면 위와 같이 사용할 수 있다. 그런 안전 장치를 했을 경우에는 첫 줄의 pInventory도 NULL이 들어 올 수 있다. (CPlayer가 instance가 NULL일 경우 CPlayer::GetInventory()도 NULL이 돌아오게 해야 하므로) 따라서 CInventory도 NULL 객체에 대한 호출에 대비하게 만들어져 있을 것이라 예상할 수 있다.

그리고 코딩 스타일에 대한 차이가 있는데

    int itemIndex = pInventory->SearchFor(itemId);
    if (itemIndex > 0)

이렇게 되었던 부분이 아래처럼 되어 있다.

    int itemIndex;
    if ((itemIndex = pInventory->SearchFor(itemId)) > 0)

위쪽의 코드를 해석하면 이렇게 된다.

1. Inventory에서 itemId에 해당하는 item index를 얻는다.
2. 그 item index가 0보다 크다면 if 문이 true이다.

아래쪽 코드를 해석해보면 이렇게 된다.

1. item index가 0보다 크다면 if 문이 true이다.
2. 그런데 item index는 inventory에서 itemId에 해당하는 index이다.

위 쪽은 영어로 보면 I look at a book. it is thick. 이고 아래 쪽은 I look at a book which is thick. 으로 생각할 수 있다. 위 쪽은 항상 2-pass를 거쳐야 전체를 이해할 수 있지만 아래 쪽은 첫번 째 pass에서 최종 결과가 나오고 더 자세히 알고 싶다면 두번 째 pass의 내용을 봐도 된다. 코드를 읽는 사람은 'item index가 0보다 크면 조건문을 들어가는구나'라고만 알고 싶을 때도 있고 그 과정을 더 자세히 알고 싶은 사람도 있다. 특히 if 문이 true일 때 내부 알고리즘만 읽고자 하는 사람은 자기가 덜 관심있는 부분은 간략하게만 이해해도 되는 것이다.

하지만 주석을 꼭 넣어야 하는 부분도 있다. 주로 한 함수가 2-4개의 파트로 나누어져 있을 때 '이 블록의 코드는 이런 일을 하기 위한 것이다'라는 것을 간단하게 기입해 두면 자신이 필요한 부분과 필요 없는 부분을 가려서 빨리 소스를 이해할 수 있을 것이다. 또한 지나치게 알고리즘 적인 부분이나 이미 최적화를 해서 코드의 readability가 떨어져 있을 경우에는 꼭 주석을 써 두는 것이 좋다. 나의 경우에는 최적화 하기 전의 코드 자체를 주석으로 둔다.

나의 주장은, 주석을 많이 넣는데 드는 노력과 시간을 줄이고, 코딩 스타일에 대한 자체적인 guide-line을 두어서 소스가 스스로 주석이 되도록 하는 것이다.