메뉴 건너뛰기


Projects > LIBNICK
NICKLIB Developer's C/C++ Coding Style Guide Ver1.0
 
 
여러 코딩 스타일 가이드를 참조하여 Nicklib의 코딩 스타일을 규정하도록 하겠습니다.
필요에 따라 계속해서 수정될 수 있겠고 이전에 이미 작성되었던 코드와 앞으로 작성될 코드 더라도 여러가지 이유에서 따르지 못하는 경우도 있겠지만 기본 틀을 잡는다는 기분으로 스타일을 다음과 같이 규정하도록 합니다.
 
 
 
1. (, ) 괄호의 사용
 
 1) 함수의 이름과 인자의 전에 오는 괄호는 붙여쓴다.
  예)   옮은예: PutString("ABC");
         잘못된 예: PutString ("호호호");
 
 2) 함수의 선언시에도 붙여쓴다.
  예)  void PutString(const char *);
 
 3) for, if, while, switch 같은 명령어 후에 오는 괄호도 한칸 떼어쓴다.
  (단, 괄호내부는 붙여서 쓴다)
  예)  옳은예: if (a==1)
        잘못된 예: if ( a==1 )    if(a==1)       if( a==1 )   if( a ==1)
 
2. {, } 괄호의 사용
 
 1) 함수를 정의나 class를 정의 할 경우에는 { 를 한줄 아래에 쓴다.
  예)  
    void PutString(const char *)
    {
         ....
    }
 
    class Cobject
    {
        ....
    }
 
 2) 그 외의 경우는 K&R형식을 따른다. 즉, 명령문과 같은 행에 하나의 공백을 사용하여 { 를 적는다.
  예)  

    if (a == 1) {
        ....
    }
  
    for (int i = 0; i < 10; i++) {
        ....
    }
 
    do { 
        .... 
    } while (condition);
 
    if (x == y) { 
        .... 
    } else if (x > y) { 
        .... 
    } else { 
        .... 
    }
 
 3) if, for, while 등에서 한줄의 코드가 있더라도 추후 확장성을 고려하여 { }를 사용한다. 
 
3. 띄어쓰기
 
 1) 함수의 인자를 구분할 때에는 컴마나 세미콜론의 왼쪽글자는 붙이고 오른쪽에는 뗀다.
    예)  PutString("ABC", abc, 1);
          for (A; B; C)
 
 2) 이항 연산자와 피연산자 사이에도 공백을 넣는다.
    예)  a = b + c * d + 3 + 8;
 
 3) .연산자와 -> 그리고 [ ] 연산자는 좌우변이 굉장히 긴밀한 관계이므로 붙여 쓴다.
    예) obj.mem, obj->mem
 
 4) 또한 *pi, a++ 등의 단항 연산자는 하나의 연산 단위이므로 공백을 두지 않는다.
    예) *pi, a++
 
4. 인덴테이션 (들여쓰기)
 
 1) 인덴테이션은 4자 Space로 한다.
    주의할 것은 Tab이 아니라 Space다.
    논쟁의 여지가 있다는 것을 안다.
    인덴체이션으로 Tab을 사용하는 이유는 파일의 크기가 필요없이 커지는 것을 방지하기 위함과 커서의 이동시 빠른 이동을 위해서 이지만 현재의 컴퓨팅 환경에서는 파일의 크기를 절약하기 위한 노력과 이를 통한 불편을 감소할 당위성은 없어 졌다고 생각한다.
    오히려 4자의 Space로 만듦으로서 얻어지는 장점은 가로로 볼 수 있는 코드의 양이 많아진다. 소스의 인쇄시에도 마찬가지다.
    멀티 플랫폼 및 멀티 IDE환경에서의 개발자인 경우 IDE의 설정에 따라 소스 자체의 정렬이 깨지는 것을 방지 할 수 있다. (즉, 항상 동일한 화면을 볼 수 있다.)
    4자 정도면 8자 보다는 못하겠지만 어느 정도 인덴테이션의 효과를 얻을 수 있다.
 
    참고로 VC++ 6.0의 경우 메뉴 중 Tool > Options > Tabs 탭을 선택하여 인덴테이션에 대해 설정할 수 있다.
 
0145.png
 
  만약, 이맥스(Emacs) 사용자라면 리눅스 커널 들여쓰기 스타일을 다음과 같이 ~/.emacs 파일에 저장하면 편하게 사용할 수 있다.(add-hook 'c-mode-common-hook
         (lambda ()
           (c-set-style "k&r")
           (setq c-basic-offset 4)))
  21.x 버전 이후의 ''이맥스''에서는 다음과 같은 설정으로 충분하다.(add-hook 'c-mode-common-hook
         (lambda ()
           (c-set-style "linux")))
  Vi(m) 을 사용한다면 다음과 같은 설정을 ~/.vimrc 파일에 넣는 것 만으로 충분하다.
 
    set ts=4
    if !exists("autocommands_loaded")
        let autocommands_loaded = 1
        augroup C
            autocmd BufRead *.c set cindent
        augroup END
    endif
 
 
 2) 새로운 블럭이 시작되는 다음 줄 부터 인덴테이션을 한다.
 
 3) 닫는 괄호는 인덴테이션을 빼고 쓴다.
    예)  
      for (int a=1; a<10; a++) 
      {
            b += a;
      }
 
 4) 인덴테이션이 4중, 5중 이상으로 넘어서 한화면 안에 열고 닫는 괄호가 들어오지 않을때에는 닫는 부분에 주석을 달아준다.
  예)  
       for (int a = 1; a < 10; a++)
       {
             for (int b = 1; b < 10; b++)
            {
                  for (int c = 1; c < 10; c++)
                 {
                       for (int d = 1; d < 10; d++)
                      {
                             for (int e = 1; e < 10; e++)
                            {
                                   ... many lines of code
                            } // end of for .. e
                      } // end of for .. d
                 } // end of for .. c
            } // end of for .. b
       } // end of for .. a
 
 5) switch ~ case 구문은 case 내의 코드만 인덴테이션한다.
   예)  
       switch(ch){
       case 'A':
           printf("A\n");
           break;
       case 'B':
           printf("B\n");
           break;
       default:
           printf("Z\n");
           break;
       } 
 6) 함수 호출시의 인수의 정렬
   함수 호출시 한라인에 모두 작성하는 것을 원칙으로 하되 인수가 너무 많다면 다음과 같이 괄호 다음 첫번째 인수에 맞춰 정렬한다.
    function(first_param, second_param,
                third_parameter, fourth_param);
   함수 이름이 너무 길어 인수를 적을 공간이 적을 경우 다음과 같은 형태도 가능하다.
   long_long_long_long_name_function(
        first_parameter,             
        second_parameter,       
        third_parameter,           
        fourth_parameter);
 
5. Scope 지시자
 
 1) 멤버 함수 선언시에는 :: 의 앞과 뒤를 붙인다.
  예)  void CMap::Init(int a, int b)
 
 2) 명시적으로 부모 클래스의 메소드를 호출하는 경우에도 붙여쓴다.
  예)  
    void CArcurusActor::SendMsg(int msgld)
    {
         C2dActor::SendMsg(msgld);
    }
 
6. pointer(*) 지시자
 
 1) * 앞에는 한칸 떼고 뒤는 붙인다.
  예)  int *pi;
 
7. 작명방식(네이밍)
 
  1) 함수명과 변수명
 
      모두 소문자를 사용하며 다음의 형식으로 정의한다.
 
{프로젝트_}모듈{_하위모듈}_동사/형용사{_목적어}
     ▲ 함수/변수명 정의 형식
     
     여기서 중괄호({})로 둘러 쌓인 부분, 즉 프로젝트 이름과 하위모듈은 경우에 따라 생략될 수 있다는 것을 의미한다.
 
    프로젝트 이름은 약자를 사용할 수도 있고, 프로젝트 이름을 그대로 사용할 수도 있다.
짧은 약어 같은 이름을 사용하면 안된다. 전역변수나 구조체, 클래스에 종속된 변수는 반드시 길고 서술적인 이름을 사용해야 한다.
    지역 변수(local variables) 는 "i"나 "tmp"처럼 짧은 이름을 사용할 수 있다. (이것은 전역변수와 구별해주는 방법이 된다.)
 
  2) 매크로(macro)명과 열거형(enum)명
 
     모두 대문자를 사용하며 밑줄로 단어를 구분하며 "함수/변수명 정의 형식"을 사용한다.
 
  3) 클래스(class)나 타입(type), 구조체(struct) 명
 
    "함수/변수명 정의 형식"을 사용하되 _를 사용하는 대신 단어의 첫자를 대문자로 사용한다.
    구조체의 경우 typedef 문으로 정의하여 사용하는 경우 이름 앞에 '_' 문자를 이름 앞에 붙여 타입(type)과 구조체(struct)를 구분한다.
    객체 안에 포함되는 함수(member functions)도 위의 "함수/변수명 정의 형식"을 따른다.
    예)   typedef struct _Abc{
                  .......
           } Abc;
 
  4) 파일명
    
     모두 소문자를 사용하고 밑줄로 단어를 구분한다.
     파일 이름은 동일한 이름의 모듈, 구조체, 클래스 이름을 따른다.
     파일이 라이브러리의 일부일 경우 라이브러리 이름을 접두어로 항상 붙인다. (예: libpantilt 라이브러리의 파일은 pantilt_ 로 시작)
     라이브러리를 만들 경우 외부에 공개되지 않는(라이브러리 생성시에만 사용되는) 헤더의 경우는 밑줄로 시작하게 파일명을 정한다.
 
  5) 네이밍 단어 리스트
 
     이름에 대한 일관성을 유지 하기 위해 네이밍 단어 리스트를 별도의 파일에 만들어 놓는다.
 
* 목적어(명사) 권고안 width: 넓이 height: 높이 size: 메모리의 크기(byte) len: 문자열의 크기(글자수) widget: 위젯 * 주요동사/형용사 권고안 create: 새로운 객체의 생성, 내부적인 데이타구조의 세팅 destroy: 개체를 외부에서 명시적으로 파괴 init: 개체의 초기화 reset:개체의 재 초기화 exit: 개체의 종료처리 process: 일반적 자신의 상태 갱신 load: 외부 파일로부터 내용 읽기 save: 외부 파일로 내용 저장 get: 속성 엑세스 set: 속성 넣기 draw: 2 차원적으로 그림 그리기 render: 3 차원적으로 그림 그리기 insert: 중간에 추가 append: 뒤에 추가 remove: 삭제 lock: 독점적 사용권 얻기 unlock: 독점적 사용권 풀기 calc: 어떤 값을 수치적으로 계산 is: 상태확인 * 약자 권고안 tmp: temporary cnt: count num: number obj: object err: error * 동의어 권고안 new: create free: destroy
     ▲ 네이밍 단어 리스트의 예
 
   필요시에 네이밍 단어 리스트에 계속 추가되어야 하며 전문용어를 사용하는 것은 될 수 있으면 피한다.
 
8. 소스코드의 구성
 
 1) 함수 작성법 및 함수의 분리
 
    함수는 짧고 예뻐야 하며 단지 한가지 일만 해야 한다.
    switch ~ case문이 아닌 if ~ else if 가 길게 반복되는 코드에서 각 블럭이 길어질 경우 하나의 함수로 분리하는 것을 고려한다.
    성능을 고려해야 된다면 static inline 을 함수 앞에 붙이는 것과 같은 컴파일러의 인라인(in-line) 기능을 이용한다.
    지역변수는 5~10개를 넘지 말아야 한다. 만일 그렇다면 뭔가 잘못하고 있는 것이다. 다시 생각해보고, 더 작은 단위로 나눈다.
    모든 함수와 변수는 항상 static 으로 선언한다. 그리고 현재 모듈 바깥에서 사용하는 것만 진짜 전역(global) 함수로 선언한다.
    임의로 변경되면 안되는 데이터에 const 키워드를 남용해야 한다.
      예: const char *man_get_name_by_id(int id);
 
 2) 매크로 및 enum
    매크로 및 enum규칙이 없는 마법수(magic values) 는 코드에 숫자 그대로 적지 말고 항상 매크로를 사용해 정의한다.
 
        
        #define GNOME_PAD       8
        #define GNOME_PAD_SMALL 4
        #define GNOME_PAD_BIG   12
 
    한 변수에 가능한 값이 여러개일 경우 그 값을 매크로가 아닌 enum으로 정의해 사용하라.(디버깅 시에도 숫자 대신 enum심볼을 참조할 수 있게 된다.)
 
        
        typedef enum {
            GTK_SHADOW_NONE,
            GTK_SHADOW_IN,
            GTK_SHADOW_OUT,
            GTK_SHADOW_ETCHED_IN,
            GTK_SHADOW_ETCHED_OUT
        } GtkShadowType;
 
        void gtk_frame_set_shadow_type (GtkFrame *frame, GtkShadowType type);
 
이런 방법은 비트 필드(bit field)방식의 집합에도 적용할 수 있다.
 
       
       typedef enum {
           GNOME_CANVAS_UPDATE_REQUESTED  = 1 << 0,
           GNOME_CANVAS_UPDATE_AFFINE          = 1 << 1,
           GNOME_CANVAS_UPDATE_CLIP              = 1 << 2,
           GNOME_CANVAS_UPDATE_VISIBILITY      = 1 << 3,
           GNOME_CANVAS_UPDATE_IS_VISIBLE     = 1 << 4
       } GnomeCanvasUpdateType;
 
 
 3) 빌드를 할때 가장 재 컴파일 시간이 적게 드는 방향으로 소스와 헤더파일을 분리한다.
     불필요한 include 가 있는지 자주 확인한다.
     소스를 계속 고치다보면 안쓰는 include 가 생기기 마련이다.
 
 4) 헤더파일 중에서도 자주 변하는 부분 (열거형 정의부분등) 은 별도의 파일로 다시 분리한다.
     이런 파일의 뒷부분에는 알기 쉽게 표시를 한다.
     (열거형 정의 파일은 ~enum.h 로 끝나는 식)
 
 5) 가급적 전역변수나 외부변수 참조, include 부분은 파일의 최선두로 몰아놓는다.
 
 6) 클래스 하나에 헤더, 소스 파일 각 한개씩을 넣는것을 원칙한다.
 
 7) 작업을 하다가 중간에 그만두는 경우에는 대문자로 // TODO 주석을 달아 놓는다.
    Global Find 명령으로 'TODO' 를 넣으면 모든 미완성 부분이  나타나도록 한다.
 
9. 주석 규칙

 1) 주석은 JavaDoc의 주석 규칙을 사용함을 원칙으로 한다.
     이는 JavaDoc 주석 처리 방법을 통해 Doxygen이나 javadoc을 이용해 자동 문서화를 이뤄내기 위한것이다.
     문서화에는 필요없는 주석은 JavaDoc 스타일을 꼭 따라야 하는 것은 아니지만 JavaDoc스타일로 반드시 주석을 작성해 주어야 하는 부분은 다음과 같다.
     - 라이브러리 : 외부로 공개되는 (C의 경우 static으로 선언되지 않은) API만 기술 
     - 그외 모듈 : 외부 공개 API + 내부 구조를 이해하는데 반드시 필요한 API
 
     JavaDoc을 사용하더라도 더욱 세부적인 문법은 UsingDoxygen 을 참고하기 바란다.
 
 2) 소스 파일 선두에 소스 파일 정보를 주석으로 추가한다.
   
 
 3) 함수 앞에 사용하는 주석은 다음과 같은 형식을 사용한다.
    예)
        

 4) 일반 주석달때 용어의 통일
 
   // TODO: 당장은 없어도 되지만 앞으로 버전업시 추가되어야 할 내용
 
   // TEMP: 임시로 추가해놓은 내용일때 써놓음
 
   // NOTE: 복잡한 내용일때, 혹은 본 소스에 쓰인 방식 이외에 다른 더 좋은 대안이 존재한다던가 할때 참고하라고 쓰는 내용
 
   위와 같이 통일된 방식으로 정리를 해 놓아야 나중에 search 로 'TODO:' 같은 것을 뒤져본다던가 하는것이 가능하다.
 
 5) 주석의 위치 및 내용
 
    - 주석은 해당 코드가 작성되기 전에 라인을 할당하여 작성하는 것을 원칙으로 한다.
    - 너무 단순한 주석은 코드 뒤에 적을 수도 있다.
    - 설명할 내용이 많다면 (알고리즘 설명과 같은) 별도의 문서를 작성하고 이의 위치 정보를 표시한다.
    - 소스를 설명하려는 주석을 다는 것 보다 소스 코드 자체를 설명적으로 작성한다.
 
 
10. 소스 파일의 전체 구성
 
 1) 적절한 줄바꿈
 
     일반적인 터미널의 문자해상도(80x25)를 기준으로 한라인의 길이가 80자를 넘지 않도록 하는 것을 원칙으로 한다. 이는 소스코드의 인쇄를 위해서도 필요하다.
    어쩔 수 없이 80자가 넘어가는 경우는 일반적으로 코드가 들여쓰기에 의해 깊이가 너무 깊어진 경우일 것이다.이는 리눅스코딩스타일 에서도 지적한 바와 같이 들여쓰기 깊이가 3단계를 넘어갈 경우 함수 설계가 잘못 되었다는 걸 의미한다. 즉, 그런 경우 다른 함수로 모듈화하거나 다른 방법을 검토해 보아야 한다.소스코드가 씌어지는 텍스트의 너비는 80칸 을 기준으로 한다. 넘어가게 될 경우 다음 줄에 한 단계 더 들여쓰기를 해 이어 작성한다. 하지만 만일 이어지는 코드가 의미적으로 반드시 같은 라인에 있는게 코드의 가독성을 높일 경우 한 라인에 작성해도 된다. 80칸 기준에 너무 얽매이기 보다는 적당한 여유를 두되 전체적으로 일관성을 유지하도록 하는게 좋다.
    하지만 한가지 작업을 위해 작성된 코드가 여러줄로 작성된 경우는 버전관리 프로그램(CVS, Subversion 등)에서 코드 비교시 여러줄이 비교대상이 되어 더 복잡할 수 있다는 것도 감안하라.
 
 2) 헤더파일
 
    한 모듈의 헤더 파일은 그 모듈의 인터페이스 역할을 한다.
    즉, 외부에서 모듈에 접근하기 위한 API와 그 API를 사용하는데 필요한 자료구조나 정의만 포함해야 한다. 모듈 안에서 헤더 파일을 포함(include)할때는 다음과 같은 순서로 정렬한다.
     - 시스템 라이브러리 헤더 파일
     - 다른 모듈 헤더 파일
     - 자신의 인터페이스 헤더 파일
 
 3) 좋은 코딩 습관
 
    위에서 적은 코딩 스타일 외에 다음 내용은 머리와 손이 습관처럼 따라야할 프로그래밍 습관이다.
특별한 예외가 아닌 이상 파일 이름도 위의 이름 규칙을 따른다.

    - 파일 이름과 모듈 이름은 가능하면 일치시켜라. 예를 들어 e_video_* 형태의 API는 e_video.c 파일에 위치하는 것이 좋다.
    - 헤더파일에는 그 모듈의 인터페이스만 두어라. 반드시 extern 을 앞에 붙이고, 다른 모듈에서 사용할 수 있는 그 모듈의 API만 두어라.
    - 선언이나 형도 API를 사용하는데 필요한 것만 헤더파일에 둔다. 절대로 애매한 코드는 작성하지 말고, 항상 엄격하게 작성하라.
    - 수식을 명확하게 표현하는 것 이상의 괄호를 사용하지 말라.
    - 모든 키워드의 앞뒤, 콤마(,) 다음, 이진연산자(+, -, ...) 앞뒤에 공백을 넣어라.
    - 어설프게 코드를 수정하려 하지 말고, 명료하고 확장가능하며 유지보수가 쉬운 코드를 다시 만들어라.
    - 컴파일시 단 하나의 경고(warnings) 메시지도 무시하지 말고, 먼저 처리해서 없애라. 이는 헤더파일에 있는 함수 프로토타입 일치성 등과 같은 흘리기 쉬운 버그들을 잡도록 도와 준다.
    - 모듈 외부로 전역 변수를 공개해야만 한다면 전역변수에 접근하기 위한 인터페이스 함수를 제공하라.
    - 주석을 달아라. 각 함수 앞부분에 무엇을 하는 녀석인지 설명해 두는 것을 잊지 말라. 절대로 필요한 경우가 아니면, 어떻게(HOW) 동작하는지는 적지 말고 무엇을(WHAT) 하는지 기술하라. 그런 건 코드를 읽어보면 안다. 그렇지 않다면, 코드를 이해하기 쉽도록 다시 작성하라. 하지만, 라이브러리나 운영체제, 환경 상의 이유로 일부러 그렇게 작성한 코드는 반드시 주석을 달아야 한다.
    - 정수나 실수에 대한 비교 조건문을 작성할 경우 상수를 먼저 사용한다. 즉, (num==1) 이 아니라 (1==num)과 같이 사용한다. 이는 (num=1)과 같이 대입문을 만드는 실수를 없앨 수 있다.
 
 
참조:
 
* Linux 커널 코딩 스타일: http://blog.empas.com/vfdpsj/18266428
 
 
Creative Commons License
Creative Commons License이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://bbs.nicklib.com