표준 코딩 규칙의 필요성

gxLib와 관련하여 최근 글인 "PNG 이미지 출력을 위한 분석 1"을 올렸습니다만, 첨부된 gxLib 0.5.6 버전을 보시고 황당해 하실지 모르겠습니다. 왜냐하면, 함수 이름과 광역 변수 이름이 대소문자에서 소문자와 밑줄(언더 바)로 바로 바뀌었기 때문이죠. 저도 처음 C 언어를 사용했을 때에는 소문자만 사용했습니다. 그리고 두개 이상의 단어가 조합된다면 밑줄로 구분했죠.

그러다가 Microsoft Windows 프로그래밍으로 넘어 가면서 헝가리언 명명법에 따라 대소문자를 사용하게 되었는데, 처음에는 이상했지만 몇 년 동안 사용하다 보니 익숙해져서, 이제는 소문자로만 작성하는 것이 이상합니다. 그래서 눈 딱 감고, gxLib를 대소문자로 변경했습니다만, 예전처럼 소문자로 바꾸어 달라는 요청이 많아서(실은 몇 분 안 되지만, 그나마 이렇게 말씀을 주시는 경우가 없었던 경험으로 봐서 많은 편입니다. ^^;) 다시 소문자로 변경하게 되었습니다.

앞으로 gxLib가 점점 커져갈 것이고, gxLib뿐만 아니라 다른 라이브러리도 작성할 것이며, 작은 샘플 프로그램이라도 미리 대비하여 표준 코딩 방법을 정해야 하지 않을까 해서, 이렇게 글을 올리게 되었습니다.

C 언어의 어려움

C 언어의 가장 큰 특징이라면 역시 자유로움이라고 생각합니다. 또 많은 분이 그렇게 생각하시리라 생각합니다. C 언어를 사용하다가 다른 언어를 사용하면, 왜 이렇게 타이프할 것이 많은지요. 그러나 자유로운 만큼 문제도 많아서 프로그래머별로 코딩하는 개성이 매우 뚜렸해서, 때로는 소스를 분석하는데 어려움을 느낄 때가 많습니다.

그래서 팀을 이루게 되면, 제일 먼저 표준 코딩 방법부터 정하고 시작하는데, 그마만큼 들여쓰기부터 눈에 거슬리는 것이 C 코딩입니다. 물론 다른 언어도 그렇겠습니다만, 유독 C 코딩은 유별난데가 있습니다. 오죽하면 남들이 이해하지 못하게, 아주 어렵게 짜는 대회가 있었겠습니까. 오래 전의 기억입니다만, 1등했던 코드를 봤더니 무슨 암호문 같더라는 거죠.

다른 분의 소스를 분석할 때에는 먼저 들여 쓰기나 블록의 시작과 끝인 { 와 } 문자의 위치, 등등을 먼저 맞추어 놓고 시작하시는 분도 많이 보았습니다.

포럼 표준 코딩 ver 0.0.1 작성에 대한 이야기

버전 번호를 붙여 놓은 이유는, 저도 최근에 C 언어를 다시 시작했기 때문에 가장 좋은 방법을 알지 못합니다. 그래서 코딩 중에 방법을 자꾸 바꾸게 됩니다. 또한 제 생각뿐만 아니라 가끔 건의도 받는 형편이라 앞으로 코딩 방법을 버전으로 관리하고 싶어서 사용하게 되었습니다.

함수, 변수 명명법

일단 간단한 것부터 차례로 정리하죠. 첫 번째 상수입니다. C 언어에서는 상수를 #define 문을 이용하게 되는데, 상수는 대문자와 밑줄로 작성합니다. 예로 아래와 같이 말이죠.

#define BUFFER_MAX_SIZE    1024

두 번째로 함수와 변수의 명명법을 말씀드려야 하는데, C 언어에서는 대부분이 소문자와 밑줄을 애용하시더군요. Microsoft C/C++이나 파스칼을 사용하시는 분은 대소문자를 섞어 사용하시는데, 대소문자를 섞어 사용하는 방법도 다양합니다.

헝가리안식을 애용하시는 분은 변수의 유형을 접두사로 사용하여 아래와 같이 사용합니다.

int   nWidth, nHeight;

함수 같은 경우는 반환하는 값이 있더라도 변수 유형까지 넣지는 않고 그냥 대소문자를 사용하시는, 단어가 두개 이상이라면 역시 프로그래머에 따라 개성이 나옵니다.

int   showMessage()
{
   첫번째 단어는 소문자로 시작
}
int   HideWindow()
{
   단어는 모두 대문자로 시작
}

이렇게 두개 단어 이상일 경우에는 첫 번째 단어를 소문자로 시작하는 경우가 있는데, 대표적으로 QT 함수가 이렇게 되어 있습니다. 그러나 대부분 모두 대문자로 시작하죠. 그러나 단어가 하나일 경우에는 소문자로 하시는 분도 있습니다.

int   ShowMessage()
{
   단어는 모두 대문자로 시작
}
int   show()
{
   단어가 하나라 구분할 필요 없을 때에는 소문자로 사용
}

그러나 포럼에서는 C 언어에서는 모두 소문자와 밑줄을 사용하기로 하겠습니다. 이유는 정말 많은 C 프로그래머가 소문자와 밑줄을 사용하시기 때문입니다. 저 혼자 모든 일을 처리하면 좋겠지만, 인터넷 세상에서 다른 사람의 코드를 많이 봐야 하는 일이 많기 때문에, 많이 애용하는 방법을 따르는 것이 아무래도 좋겠다는 생각에서 입니다.

물론 개성을 중시할 수 있겠습니다만, 그만큼 다른 분과 같이 일할 때에는 나 뿐만 아니라 다른 분도 고통을 감수해야 하기 때문에 가급적이면 개성 보다는 다수가 사용하는 방법이 좋을 것이라 생각됩니다.

블록 구조

이 또한 매우 말이 많은 부분이라 때론 말싸움이 나올 정도입니다. 프로그래머가 아닌 사람이 보면, 뭘 저런 것 가지고 싸우나 하겠지만 블록의 시작 키워드인 '{' 나 '}' 문자의 위치는 아주 오래전부터 논란 거리였습니다. 공백 몇 개 차이인데, 정말 싸움까지 난다니까요. C 언어를 사용하는 프로그래머의 개성이 가장 두드러지게 나오는 부분이 바로 이부분으로 몇 가지 유형을 보겠습니다.

// 유형 1
if ( )
{
   처리 내용
}
else
{
   처리 내용
}

// 유형 2
if ( ){
   처리 내용
}
else{
   처리 내용
}

// 유형 3
if ( ){
   처리 내용
} else{
   처리 내용
}   

// 유형 4
if ( ){
   처리 내용
   }
else{
   처리 내용
   }

이 외에도 더 많은 유형이 있습니다만, 과연 여러 분은 어떤 유형을 사용하시나요? 제 경험 상으로 가장 많이 사용하는 유형은 1번 이라고 생각되어 포럼 표준 코딩에서는 유형 1번을 따르겠습니다..

사실 저는 유형 4번을 사용하고 있었습니다. 블로 구분이 뚜렿하고, 한 화면에 많은 행을 볼 수 있어서, 처음 C를 할 때부터 유형 4번을 사용했습니다. 그래서 유형 4번으로 고집을 부리고 싶었지만, 포럼인 만큼 학습 사이트이고, 앞으로 온라인에서 다른 사람의 소스를 많이 봐야 하는 관계로, 역시 많이 사용하는 유형을 따르는 것이 좋겠다고 생각되서 입니다.

탭 문자 보다는 공백문자

의외로 이부분은 말씀드리기 쉽습니다. 탭 문자를 사용하시던 분도 몇번 말씀 드리면 두말 없이 바꾸는 것이 공백문자 사용입니다. 일단 탭 문자는 사용하는 사람마다 지정한 공백 문자 개수가 틀립니다. 기본이 8자이지만 이렇게 사용하시는 분은 드물고, 3개 또는 4개 공백문자를 지정해서 사용하니다.

문제는 탭 문자가 삽입된 소스코드 그대로 다른 사람에게 전달하면 아래와 같은 문제가 발생하게 됩니다.

int count_ethercard( void)
{
  FILE   *fp;
  char    buff[BUF_SIZE];
         char   *cmd_ifconfig   = "ifconfig -a ¦ grep eth";
  int    cnt_ether;

  fp = popen( cmd_ifconfig, "r");
         if ( NULL == fp)
    return 1;

  // 랜카드의 개수는 출력되는 행의 개수로 확인한다.
         cnt_ether = 0;
  while( NULL != fgets( buff, BUF_SIZE, fp))
    cnt_ether++;
  pclose(fp);

  return cnt_ether;
}

짦은 루틴도 이렇게 엉망으로 보이는데, 하물며 긴 소스일 경우에는 황당하죠. 그러나 탭 문자를 사용하지 않고 공백 문자만 사용한다면 이런 경우가 실수가 아니면 발생하지 않습니다.

탭 크기

소스 파일 내에서는 탭문자를 사용하지 않기로 했지만, 탭 키는 사용해야 하므로 탭에 대해 공백 문자 몇 개를 사용해야 할지를 정해야 합니다. 탭에 대한 공백 문자는 얼마만큼 들여 쓰기를 하겠냐는 내용이라, 이 부분도 말씀이 많습니다. 심한 경우는 공백 문자 하나를 사용하시는 분을 본 적이 있습니다. 탭 문자까지 손이 가는 것이 귀찮다나요? 이그~ 뭐, 개성이면 개성이니 뭐라 못하겠습니다. 포럼 표준에서는 3 개의 공백 문자를 사용하겠습니다.

이유를 설명 드리면 if ()문 때문입니다. 공백 문자 3개이면 아래와 같이 들여 쓰기가 됩니다.

if ( )
{  ¦
   처리 내용
}  ¦
else
{  ¦
   처리 내용
}

함수 별 주석 달기

주석 달기도 참 말이 많지요. 사용하시는 분 마다 천차 만별한 것이 주석 달기 입니다. 우선 모듈 단위의 주석을 보겠습니다. 이 방법은 다른 분의 방법을 모으고 모아서 정리한 것인데, 너무 길어도 좋지 않더군요. ^^

/********************************************************************************
 모  듈 : tcp/udp 소켓 사용
 작성자 : 장길석
 버  전 : 0.0.2
 설  명 : netrx 를 위한 tcp/udp 소켓 사용함수 정의 
 참  고 :
 
          1. tcp 는 서버 모드로 설정한다.
          2. udp 는 상대 PC 로 전송하기 위한 IP 를 처음에
             가질 수 없기 때문에 따로 상대 PC IP 를 지정하여
             전송에 사용한다.
             
             ip_sock_t 에서 to_ip, to_addr

 버  전:
         0.0.1  tcp/udp 처리를 하나로 묶음
         0.0.2  udp 소켓을 위해 ip_sock_t 에 to_addr 과 to_ip 추가
                to_ip   : string    목적지 ip
                to_addr : in_addr_t 목적지 ip 에 대한 inet_addr() 값
********************************************************************************/

이와 같이 /* */ 문자를 사용했으며, 내용에는 모듈에 대한 제목과 작성자의 이름, 버전 번호, 설명 및 참고, 버전별 수정 내용을 입력합니다.

함수에 대한 주석

함수별로 주석을 다는 것도 매우 중요합니다. 문제는 너무 화려하게 하면 소스 코드 길이가 늘어나서 어지러워 보이더군요. 예를 들어 아래와 같은 경우입니다.

// gx_get_buffer_dc -------------------------------------------------------------
// 설명: 화면 출력을 빠르게 하기 위한 버퍼 DC를 구함.
// 참고: BitBlt를 빠르게 처리하기 위해 DC Type를 DCTYPE_SCREEN으로 지정한다.
// 인수: width       버퍼의 폭
//       height      버퍼의 높이
// 반환: 버퍼 Device Context 핸들
//-------------------------------------------------------------------------------
dc_t *gx_get_buffer_dc( int width, int height)
{
   int   szStruct;
   dc_t  *dc;

   szStruct = sizeof( dc_t);

함수 이름이 코드에 있음에도 주석에 또 달아 놓았습니다. 그리고 함수 코드 부분에 있는 인수 설명이 위에 있다보니 연결이 되지 않고, 주석이 함수 코드 위에 있어서 같은 몸뚱이로 보이지 않고 다른 부분으로 보이고, 프로그램 중에도 위에 있는 주석을 확인하지 않는 문제가 있습니다.

그래서 아래와 같이 주석을 함수 이름 밑에 두며, 설명, 참고, 인수 및 반환값에 대한 정보를 입력합니다.

dc_t *gx_get_buffer_dc( int width, int height)
// 설명: 화면 출력을 빠르게 하기 위한 버퍼 DC를 구함.
// 참고: BitBlt를 빠르게 처리하기 위해 DC Type를 DCTYPE_SCREEN으로 지정한다.
// 인수: width       버퍼의 폭         --> 변수와 설명은 공백 문자 여러 개로 분리
//       height      버퍼의 높이
// 반환: 버퍼 Device Context 핸들
{
   int   szStruct;
   dc_t  *dc;

   szStruct = sizeof( dc_t);

함수 내부의 주석

함수 내부의 주석은 아래의 두 가지 모두 사용하겠습니다.

                    :
dc->release_dc       = release_buffer_dc;
dc->bits_per_pixel= gx_fb.bits_per_pixel;   

// 칼라 깊이에 따라 라인 별 소요되는 바이트를 계산하며,
// 깊이에 따른 가상 함수를 지정한다.

switch( gx_fb.colors)
{
case 1   :  dc->bytes_per_line  = width / 8;
                    :

또는

                    :
dc->release_dc       = release_buffer_dc;
dc->bits_per_pixel= gx_fb.bits_per_pixel;   
/*
 * 칼라 깊이에 따라 라인 별 소요되는 바이트를 계산하며,
 * 깊이에 따른 가상 함수를 지정한다.
 */
switch( gx_fb.colors)
{
case 1   :  dc->bytes_per_line  = width / 8;
                    :

행 별 주석 달기

행 별 주석은 // 를 사용하며, 시작은 81컬럼부터 합니다. 81 컬럼인 이유는 탭 크기가 공백 3 문자이기 때문입니다. 예전에는 81컬럼 보다 작은 72  컬럼을 사용했지만, 요즘 모니터가 와이드형이 많아서 72컬럼은 너무 작더군요.

결언

아직 더 정리할 내용이 많습니다만, 앞으로 계속 추가해 가도록 하겠습니다. 이제 위의 내용을 정리해 보겠습니다. 표준 고딩에 대해서는 당연히 여러 주장이 있겠습니다. 좋은 생각과 많은 의견을 올려 주시면, 수렴하여 표준을 계속 정립해 나가겠습니다.

포럼 표준 코딩 0.0.1

  • 규칙 1) 상수는 모두 대문자와 밑줄(언더 바)을 이용한다.
    BUFFER_SIZE, WIDTH_MAX, USER_CAPTON

  • 규칙 2) 함수와 변수는 모두 소문자와 밑줄을 이용한다.
    show_message(), bmp_filename, hide()

  • 규칙 3) 블로 들여 쓰기는 아래와 같이 블록의 시작과 종료 문자를 블록의 시작에 위치한다.
    if ( )
    {
    }
    else
    {
    }
    for ()
    {
    }
    while ()
    {
    }
    


  • 규칙 4) 탭 문자 크기를 공백 문자 3개로 한다.

  • 규칙 5) 탭 문자를 사용하지 않는다. 들여쓰기는 모두 공백 문자를 이용한다.

  • 규칙 6) 모듈 별 주석 달기(소스 상단의 전체 주석)
    /* */를 사용하며, /*****.....**** 의 마지막 별은 81 컬럼까지 배열한다.
    /********************************************************************************
     모  듈 : tcp/udp 소켓 사용
     작성자 : 장길석
     버  전 : 0.0.2
     설  명 : netrx 를 위한 tcp/udp 소켓 사용함수 정의 
     참  고 :
     
              1. tcp 는 서버 모드로 설정한다.
              2. udp 는 상대 PC 로 전송하기 위한 IP 를 처음에
                 가질 수 없기 때문에 따로 상대 PC IP 를 지정하여
                 전송에 사용한다.
                 
                 ip_sock_t 에서 to_ip, to_addr
    
     버  전:
             0.0.1  tcp/udp 처리를 하나로 묶음
             0.0.2  udp 소켓을 위해 ip_sock_t 에 to_addr 과 to_ip 추가
                    to_ip   : string    목적지 ip
                    to_addr : in_addr_t 목적지 ip 에 대한 inet_addr() 값
    ********************************************************************************/
        

  • 규칙 7) 함수 별 주석 달기
    함수 이름 밑에 '//' 주석 문을 사용하며, 설명, 참고, 인수, 반환을 입력한다. 작성할 내용이 없으면 생략
    dc_t *gx_get_buffer_dc( int width, int height)
    // 설명: 화면 출력을 빠르게 하기 위한 버퍼 DC를 구함.
    // 참고: BitBlt를 빠르게 처리하기 위해 DC Type를 DCTYPE_SCREEN으로 지정한다.
    // 인수: width       버퍼의 폭
    //       height      버퍼의 높이
    // 반환: 버퍼 Device Context 핸들
    {
       int   szStruct;
       dc_t  *dc;
    
       szStruct = sizeof( dc_t);
    

  • 규칙 8) 함수 내부 주석 달기
    // 와 /* */ 두가지 모두 사용한다.
                        :
    dc->release_dc       = release_buffer_dc;
    dc->bits_per_pixel= gx_fb.bits_per_pixel;   
    
    // 칼라 깊이에 따라 라인 별 소요되는 바이트를 계산하며,
    // 깊이에 따른 가상 함수를 지정한다.
    
    switch( gx_fb.colors)
    {
    case 1   :  dc->bytes_per_line  = width / 8;
                        :
    

    또는

                        :
    dc->release_dc       = release_buffer_dc;
    dc->bits_per_pixel= gx_fb.bits_per_pixel;   
    /*
     * 칼라 깊이에 따라 라인 별 소요되는 바이트를 계산하며,
     * 깊이에 따른 가상 함수를 지정한다.
     */
    switch( gx_fb.colors)
    {
    case 1   :  dc->bytes_per_line  = width / 8;
                        :
    

  • 규칙 9) 행별 주석 달기
    81 컬럼에 // 코드로 주석을 삽입한다. 81컬럼인 이유는 탭 문자에 대해 공백문자 3개를 이용하기 때문입니다.