메뉴 건너뛰기


Developer > Application

C,C++ 엠스톤 프로그래밍 가이드라인

2013.11.27 02:39

푸우 조회 수:24146

출처 : http://emstonebebop.wordpress.com/projects/programming_rules/emstone_guidelines/intro


머릿말

이 문서는 EMSTONE 개발자가 프로그래밍을 할때 반드시 지켜야 할 원칙이나 지키면 좋은 몇몇 제안을 서술한다. 모든 엠스톤의 개발 프로젝트는 팀웍을 필요로 하는 공동 작업이기 때문에 이 문서에서 기술하는 내용은 모든 개발 과정에서 유익할 것이다.
잘 작성된 라이브러리나 프로젝트는 깔끔하며 일관성이 있고, 유지보수성이 뛰어나다. 이처럼 이 문서는 엠스톤 프로젝트 결과물의 질적 향상 뿐 아니라 가능하면 좋은 프로그래밍 습관을 유도하여 잠재되어 있는 버그를 줄이고, 구성원 상호간의 코드 이해 및 수정이 용이하도록 하는데 있다.
물론 좋은 코딩 습관이 프로그래밍의 전부는 아니며, 디자인패턴 등과 같은 좋은 리퍼런스에 기반한 훌륭한 설계 방법과 효율적인 테스트, 디버깅 방법 등도 매우 중요하다. 또한 Doxygen(UsingDoxygen)이나 DocBook과 같은 도구를 이용한 오버헤드가 적으면서 효율적인 문서화 방안도 필요하다.
이 글은 '그놈 프로그래밍 가이드 라인' 과 '리눅스코딩스타일(LinuxCodingStyle)', 애플 의 커널 프로그래밍 스타일 을 기초로 하여 작성되었다. 따라서 대부분의 내용은 이미 익숙하거나 또는 이미 사용하고 있는 것일 수도 있다. 더불어 이 글은 실제 엠스톤 프로젝트에 따라 수정이 필요한 부분이나 추가 내용을 담고 있으며, 앞으로의 경험에 따라 계속해서 갱신해 나갈 계획이다.
 

좋은 코드 작성의 중요성

대부분의 소프트웨어 개발과 마찬가지로 한 회사의 프로젝트로 개발되는 소프트웨어는 많은 모듈과 라이브러리로 구성되며, 프로그래머 역시 한 사람이 아닌 여러 명이 되는 경우가 대부분이다. 또한 프로젝트를 구성하는 인원 역시 항상 같은 인원이 유지되는 것도 아니고 한 모듈을 한 사람이 처음부터 끝까지 담당해서 코딩하는 경우는 드물다고 볼 수 있다.
이러한 소프트웨어 개발에서 프로그래머 개인의 잘못된 코딩 습관은 인원이 교체되거나 다른 사람이 작성한 모듈을 디버깅시 많은 비용을 요구하게 되어 결과적으로 소프트웨어의 연속적인 개발 및 효율을 저하시키는 요인이 된다.
혼란스러운 코드는 읽기도 힘들고, 코드 이해를 위한 필요한 흥미나 자신감도 잃어버리게 한다. 또한 프로그래머가 코드에 대한 이해가 느리면 버그를 수정하거나 기능을 확장하는데 더 많은 시간을 허비해야 한다. 소스 코드는 일종의 통신 형식(a for of communication)이기 때문에 프로그래머는 항상 자신 이외의 다른 사람을 고려하면서 좋은 코드를 작성하기 위해 노력해야 한다. 이 점은 몇 개월 후에 다시 지금의 소스 코드를 들여다 보게 될 지도 모르는 자신을 위해서도 좋은 일이다.
몇 가지 좋은 코드의 질을 판단하는데 중요한 항목들이 있다.

명료성(Cleanliness)

깨끗한 코드는 읽기 쉽다. 이는, 사람들이 최소한의 노력으로 쉽게 읽고 이해할 수 있도록 해준다.

일관성(Consistency)

일관적인 코드는 프로그램이 어떤 식으로 동작하는지 이해하는 걸 쉽게 한다. 일관적인 코드를 읽으면, 무의식적으로 코드가 어떻게 작동하는지에 대한 많은 가정과 기대를 구상하기 때문에, 수정하는 일이 더 쉽고 안전해진다.

확장성(Extensibility)

일반적인 목적으로 작성된 코드는 재사용과 수정이 쉽다. 반면에, 코드에 박혀있는 많은 전제를 가지는 특정 코드는 고치거나 다시 사용하기 어렵다. 코드가 시작할때부터 확장성있게 누군가 프로그램에 새로운 기능을 추가하고자 할때 설계되었다면 일은 분명히 더 쉬워질 것이다. 이런 식으로 작성되지 않은 코드는 어떤 기능을 추가하기 위해서 어쩔 수 없이 다른 사람들이 질이 안 좋은 코드 수정(ugly hack)을 하도록 유도할 것이다.

정확성(Correctness)

마지막으로, 정확하게 설계된 코드는 사람들이 버그에 대해 고민하는 시간을 줄여주고, 프로그램의 기능을 확장하는데 더 많은 시간을 벌어준다. 누구도 죽어버리는(crash) 소프트웨어는 안좋아하기 때문에, 사용자도 옳바른 코드에 감사할 것이다.
요약하자면, 이러한 항목들이 지켜지면서 작성된 코드는 소프트웨어의 품질을 더욱 향상시킬 것이다.
 

튼튼한 코딩하기 (Robust Coding)

Assertion 및 인수검사

ASSERT() 류의 함수는 일반적인 프로그래밍에서 많이 사용한다. 이는 특정한 조건이 반드시 어떠한 순간에 만족해야 하는 점을 확인하기 위해 개발시와 릴리즈시에 다른 컴파일 옵션을 주어 없앨 수 있다. 하지만, 이런 방법론은 일반적으로는 맞지만, 대부분의 경우 숨겨진 버그는 존재하기 때문에 개발과 출시마다 다르게 적용하는 것은 어떤 경우에는 부적절할 수 있다. 또한 어떠한 상황에서도 프로그램이 종료되면 안되는 경우 프로그래머가 미처 발견하지 못한 코드 때문에 전체 코드가 멈춰버리거나 오동작을 하는 경우가 발생할 수 있다.
GLib에서 제공하는 g_return_if_fail() 류의 함수는 이러한 방법론을 보완해 준다. 이를 일반적으로 사용하기 위해 엠스톤 라이브러리에서 제공하는 RETURN_IF_FAIL() , RETURN_VAL_IF_FAIL() 은 GLib뿐 아니라 다른 환경에서도 사용할 수 있는 일반적인 매크로를 제공한다.
모든 함수의 진입 부분에서는 모든 인수가 적절한지 검사해야 한다. 특히 외부로 공개되는 API는 반드시 이 원칙을 지켜야 한다. 하나의 인수마다 하나의 매크로를 이용해야 더욱 정확한 보고를 얻을 수 있다. 다음은 그 예이다.
char *func1(char *dst, char *src, int len)
{
       RETURN_VAL_IF_FAIL(dst != NULL, NULL);
       RETURN_VAL_IF_FAIL(src != NULL, NULL);
       RETURN_VAL_IF_FAIL(len > 0, NULL);

       ...

       return dst;
}
이러한 코딩 습관은 튼튼한 코드를 작성하는 기본이 되어 준다.
문자열이 포인터를 검사할때 쉽게 다음과 같은 방식 사용하지 말고,
int func(const char *str)
{
       RETURN_VAL_IF_FAIL(str, -1);

       ...
}
다음처럼 비교식을 정확하게 넣어주는게 좋다. 왜냐하면 경고 메시지는 프로그래머가 적어준 식을 그대로 출력하기 때문에 자세할 수록 더 많은 정보를 준다.
int func(const char *str)
{
       RETURN_VAL_IF_FAIL(str != NULL, -1);

       ...
}

들여쓰기를 줄여 튼튼한 코드 만들기

간단한 코드 리팩토링을 통해서 코드 흐름의 명료성을 유지하고 많은 들여쓰기를 피할 수 있는 경우도 있다. 대부분 실제 프로그래밍에서 매우 많이 접하게 되는 경우이다.
첫번째 예는 조건에 의해 분기될 경우 주흐름이 아닌 코드를 앞에 배치해버리는 방법이다.
int check_psycho(const char *name, int age)
{
       RETURN_VAL_IF_FAIL(name != NULL, -1);

       if (!strcmp(name, "lethean)) {
               printf("hey~ are you crazy?n");
               age -=10;

               //...1000 lines more...

       } else {
               printf("you are psycho.n");
               return 1;
       }

       if (age > 10) {
               printf("you are a real psycho!n");
               return 0;
       }

       return 1;
}
위 코드는 다음과 같이 정리할 수 있다.
int check_psycho(const char *name, int age)
{
       RETURN_VAL_IF_FAIL(name != NULL, -1);

       if (strcmp(name, "lethean)) {
               printf("you are psycho.n");
               return 1;
       }

       printf("hey~ are you crazy?n");
       age -=10;

       //...1000 lines more...

       if (age <= 10)
               return 1;

       printf("you are a real psycho!n");
       return 0;
}
물론 위 코드는 많은 논쟁을 불러 일으킬 수 있는 소지가 있다. 그런 논쟁을 뒤로 하고 두번째 코드가 첫번째 코드보다 나은 이유는 우선 1000 라인이나 되는 코드를 두번 들여쓰기할 필요가 없다는 점이다. 그리고 무엇보다도 위와 같은 방식의 흐름은 전통적인 유닉스 코드의 전통으로, 함수의 주요 역할에 해당하는 메인 흐름에 필요하지 않거나 적합하지 않은 조건일 경우 그 단계에서 바로 정리를 해주고 빠져나감으로써, 전체적으로 중요한 메인 흐름에 집중할 수 있다는 점이다.
일반적으로 개발자는 사람이기 때문에 주요 흐름을 따라 프로그래밍을 하고, 조건 분기가 발생하면 조건이 참일 경우를 먼저 생각하고 구현하게 된다. 하지만 반대로 생각하면, 바로 여기, 조건 분기 부분에서 참이 아닌 경우를 먼저 깔끔하게 처리해 버리면 다음부터는 메인 흐름만 생각하면 되기 때문에 코드의 가독성을 크게 향상시킬 수 있다. 또한 이러한 프로그래밍 습관은 인수로 넘겨진 데이터 또는 작업을 위해 필요한 정보에 대한 예외 처리 과정을 싫더라도 고려하게 만들기 때문에 자연스럽게 좀 더 튼튼한 코드를 만들 수 있도록 돕는다.
다음은 극단적인 경우의 다른 예다.
void func(...)
{
       if (cond1) {
               //...blah...
               if (cond2) {
                       //...blah...
                       if (cond3) {
                               //...blah...
                       }
               }
       }
}
설마 자신이 이렇게 코드를 작성하고 있다고 여기지 않을 사람들이 많을 테지만, 위에서 조건식과 코드를 실제 복잡한 일을 하는 코드로 대체하면 상황은 달라진다. 정말로 많은 사람들이 이런 방식으로 코드를 만들고 있다. 다음은 위의 룰에 의해 다시 작성한 코드이다.
void func(...)
{
       if (!cond1)
               return;

       //...blah...

       if (!cond2)
               return;

       //...blah...

       if (!cond3)
               return;

       //...blah...

}
들여쓰기가 깊어진다고 여겨질때는 흐름을 제어하는 사고를 한 번 뒤집어보기를 권한다.

재진입 가능한 코드

일반적인 쓰레드 기반의 코드는 반드시 재진입(reentrant)이 가능해야 한다. 하지만, 반드시 쓰레드 방식으로 동작하는 코드가 아니더라도 이는 프로그램의 안정성 뿐 아니라, 전역 변수의 사용을 자제할 수 있기 때문에, 모듈간 결합도(coupling)를 낮추고 응집력을 높일 수 있는 좋은 프로그래밍 습관이다.
코드를 재진입할 수 있도록 작성하기 위해서는 다음과 같은 사항을 검사해야 한다.
  • 함수가 전역 변수(global variables)를 사용하는지 여부 (읽기만 할 수 있는 상수 제외)
  • 함수가 고정 변수(static variables)를 사용하는지 여부 (읽기만 할 수 있는 상수 제외)
  • 함수에 오직 지역 변수만 사용하는지 여부
  • 스택에 지역적으로 할당된 데이터를 함수가 리턴하는지 여부

형(Types)

int32_t 나 guint32 , WORD 등 크기와 형태가 미리 정의된 많은 타입이 있다. 이러한 함수는 API가 요구하는 형식이거나 반드시 필요한 경우에만 사용한다. 특히 루프를 돌리기 위한 변수 같은 경우 특별한 크기가 요구되지 않는한 플랫폼에서 가장 자연스러운 타입인 int 를 사용해야 한다. 이는 컴파일러의 코드 최적화를 돕는다. 물론, int 가 항상 32비트 정수형일리는 없다는 사실도 잊지 말자.

단위 테스트 (Unit Test)

모듈 제작시 가능한 TDD(TestDrivenDevelopment) 방법을 이용하여 단위 테스트 코드를 만들면서 개발하는 것이 좋다. 이는 개발 도중 각 모듈 단위별로 기능의 완전성을 테스트할 수 있도록 한다. 이는 또한 코드 통합 이후 다른 모듈과 연동하여 동작할때 디버깅을 매우 수월하게 해준다. 또한 이렇게 작성한 단위 테스트 기능은 추후 안정성 테스트(Regression Test)에도 그대로 이용할 수 있다.

커널 수준 프로그래밍시 유의사항

이는 디바이스 드라이버 개발 뿐 아니라 일반 어플리케이션이나 시스템 프로그래밍에도 적용되는 내용들이다.

  • 리소스를 잡은 채 sleep하지 말라. 완전히 그럴 수는 없겠지만, 데드락을 피하는 가장 좋은 방법임은 분명하다.
  • 메모리 할당과 해제하는 함수를 일치하는데 유의하라. 예를 들어 A라는 모듈에서 할당한 메모리는 반드시 A라는 모듈에서 해제하도록 해야 한다.
  • 참조회수(reference counts)를 이용해 사용중인 메모리를 해제하는 걸 방지하라. 참조회수가 0에 이를때에만 메모리를 해제하는 것을 확실하게 하라.
  • 객체에 대한 적업 전에 락을 걸어라. 심지어 참조회수만 건드리더라도 말이다.
  • 절대로 NULL 인지 검사하지 않은채 포인터를 역참조(dereference)하지 말라. 특히, 다음과 같은 코드는 작성하지 말라.
int foo = *argptr;
argptr 이 NULL 이 아님을 검사하지 않았다면, 이 코드는 상당히 위험하다.
  • 반드시 한 엔디언(endian), 예를 들면 리틀 엔디언(littiel endian)에서만 작동한다는 가정하에 코드를 작성하지 말라.
  • 한 타입의 인스턴스 크기가 절대 변하지 않으리라는 가정하에 코드를 작성하지 말라. 이 정보가 필요하다면 항상 인스턴스(변수)에 대한 sizeof 등을 이용하라.
  • 포인터 크기가 항상 int 나 long 과 같은 크기라고 가정하지 말라.

코드 지역화 (Code Localization)

코드 지역화(Localization)는 프로젝트에서 모듈(module) 혹은 클래스(class) 단위로 코드를 분리하는 것을 의미한다.

전통적인 소프트웨어 개발 시나리오에서 소프트웨어 시스템은 여러 모듈(혹은 서브시스템)으로 나누어지고, 이 각각은 개인 혹은 팀에게 할당된다. 이러한 나눠서 정복하기(divide-and-conquer) 방법 은 실제로 잘 이루어지만, 고립(insulation) 이라는 본질적인 결함을 가지고 있다. 즉, 다른 사람의 담당한 코드에 대한 책임은 물론 관심까지 사라져 전체 시스템의 불안정성이 발생했을때 디버깅을 어렵게 하고, 관리자로 하여금 담당자의 부재를 두렵게 한다.
XP(eXtreme Pgoramming) 에서는 공동 코드 소유권(collective code ownership) 을 통해 이를 해결한다. 이는 프로젝트에 참여하는 모든 개발자는 프로젝트의 모든 코드에 대해 책임을 가지도록 한다. 이로 인해 누구나 어떤 코드 어떤 설계에 대해 제안할 수 있고 변경할 수 있다. 분산 개발의 고참 개발자(doyens), 버전 컨트롤의 석학(savants), 패치의 달인(gurus)들은 모두 이러한 방식으로 이미 옛날부터 해오고 있다.

모든 모듈(클래스)는 코드와 데이터(변수)가 함께 따라다녀야 한다. 다른 모듈에게는 최소한의 필요한 부분만 인터페이스(매크로, 선언, 함수)로 공개한다. 모듈화의 목적 중 하나는 특정 기능에 대한 코드 및 데이터를 하나의 모듈에서만 접근하므로 코드 수정으로 인한 부작용(side effect) 를 크게 줄일 수 있다는 점이다. 이는 문제 발생시 디버깅 범위를 줄이는데 매우 효율적이다.

모듈(클래스)의 크기가 1000라인 혹은 몇백 라인을 넘는가? 아니면 모듈의 어떤 기능이 부가적인 기능인가? 그럼 그 모듈은 다시 서브모듈(서브클래스)로 나누는 것이 좋다. 모듈화 과정 중 이 모듈이 특정 모듈에서만 사용되지 않을 것 같다면 일반화해서 모듈화하는 것이 좋다.
예를 들어 display 모듈에서 YUV 이미지에 마킹하는 작업을 display_marker 라는 모듈로 분리했으나, 분리 작업 도중 이 모듈이 다른 모듈에서도 사용될 수 있을 정도라면 display 모듈에 의존하는 코드를 초기화 혹은 실행 인수로 바꾸고, marker 모듈로 분리하는 것이 좋다. 혹은 e_marker도 괜찮을 것이다.
서브 모듈이 모듈의 기능에 부가적인 경우 서브 모듈은 메인(핵심) 모듈에 의존하지만 반대의 경우는 그렇지 않도록 하는 것이 좋다. 즉 서브 모듈은 메인(핵심) 모듈에 의존할 수 있지만, 가능하면 메인(핵심) 모듈은 서브 모듈에 의존하지 않는 것이 좋다.
예를 들어 search 모듈의 search_print 모듈이 기능적으로 적절하게 위 원칙을 지켜 설계되고 구현되었다면 인쇄기능이 필요없는 시스템에 최적화할때, search_print 모듈을 Makefile 에서 제외하고 GUI에서 약간의 수정만 가하면 된다. 또한 cupsys 관련 패키지도 모두 삭제할 수 있게 된다.

관련되어 관심가져야할 것은 지속적인 통합(continuous integration) 이 필요하다는 점이다. 통합 이후에는 반드시 복귀 테스트(regression test) 를 통해 통합된 내용이 문제가 없음을 확인해야 한다. 이러한 테스트는 TDD(Test-Driven Development) 및 단위 테스트(Unit Test) 등을 통해 이룰 수 있다.
 
 

코드 성능 최적화 (Code Optimization)

Code Performance

코드의 성능을 향상시키기 전에 반드시 명심해야 할 점은 프로그램의 성능보다 우선하는 것은 프로그램이 정확하게 동작하느냐는 점이다. 프로그램이 정확하게 동작하고 아무 버그도 없다는 걸 확신한 다음에 성능 향상을 위한 최적화를 해야한다. 느리지만 정확하게 동작하는 프로그램이 버그 투성이의 빠른 프로그램보다 낫다는 건 분명하다.
If you want to optimize your program, the first step is to profile the program running with real life data and collect profiling information. This will help you pinpoint the actual hot spots that need optimizing. It is important to never optimize ahead of time if you do not have a clear idea on what the problem is. You might end up losing time in speeding up a routine that is not causing the speed bottleneck and you might obfuscate the routine as a result. This could reduce code readability and maintainability with no visible gain in speed.
Simple code is good because it is easy to understand and easy to maintain. If you can write simple code that is also powerful and fast, all the better. If you have a smart piece of code that is not easy tofollow, please document it so that people do not break it accidentally.
Do not write code that is hard to read and maintain if it is only to make the code faster. Look for a nice and clean algorithm instead, and implement it cleanly.
Doing a good job in the general case is often better than having many special cases as well. Only provide special cases when you have identified weak spots in the program.

List Management in Glib

Avoid using constructs that will lead to slow algorithms. If you use g_list_insert_sorted() or g_list_append() carelessly, you can easily get an algorithm that runs in time proportional to O(n2). Usually you can create the list backwards, using g_list_prepend(), and reverse it when you are done by using g_list_reverse(). This is a O(n) operation. And if you need a sorted list, you can create it by prepending elements and then run g_list_sort() when you are done.
If you need a list to be sorted at all times, you may be better served by a tree structure or a tree/list hybrid. If you need to construct a list by appending nodes to it, keep a pointer to the tail of the list and update it as appropriate; this will let you append or prepend a node in constant time.

커널 수준 프로그래밍

응용 프로그램이 아닌 커널이나 디바이스 드라이버 개발시 유의해야 할 점 중에 첫번째로 가장 중요한 것은 스택의 크기이다. 커널은 제한된 양의 스택만을 쓰레드나 프로세스에 할당하기 문에 이를 염두에 두지 않으면 문제를 일으킬 수 있다. 이를 위해 지켜야 할 항목을 정리하면 다음과 같다.
  • 재귀(recursion)는 반드시 한계가 있어야 한다.(bounded)
  • 가능하면 재귀(recursion)는 반복 루틴으로 재작성해야 한다.
  • 큰 스택 변수, 즉 함수의 지역(local) 변수는 위험하니, 사용하면 안된다. 필요한 경우 malloc 과 같은 방법을 통한 동적할당 변수를 사용해야 한다.
  • 함수는 가능하면 인수의 개수가 적어야 한다.
  • 구조체의 포인터를 넘겨라. 요소를 분리해서 넘기지 말라.
  • 전역 변수를 피하기 위해 인수를 사용하지 말라. 이 경우 [C Coding Style] 에서 제안하는 이름을 따른다.
  • C++ 함수는 static 으로 선언해야 한다.
위 항목들 중에 몇몇은 일반 응용 프로그램에서도 지키면 좋다.

관련 프로젝트

주석 작성하기 (Code Commenting)

원칙

주석(comment)은 좋은 것이지만, 과다한 주석은 오히려 해가 된다. 절대로 코드가 어떻게(How) 동작하는지 설명하려고 애쓰지 말라. 하는 일이 분명한 코드를 작성하는 것이 더 중요하다. 별로 좋지 않은 코드를 설명하는데 시간을 쓰는 건 낭비일 뿐이다.
일반적으로 주석은 코드가 어떻게(How) 동작하는 지가 아닌 무엇을(What) 하는 지를 적는다. 또한 함수 몸체 안에 주석을 적는 건 가능하면 피한다. 만일 함수 안에 주석을 달아야 할 정도로 코드가 길게 나뉘어진다면 [C Coding Style] 의 함수에서 말한 것처럼 함수를 분리하는 것을 고려하는 것이 좋다. 물론 알려두어야 할 경고나 노트 등을 적는 건 괜찮다. 하지만, 그것도 가능하다면 함수 머리 앞에 왜(Why) 그렇게 작성했는 지 적는게 더 좋다.
이름짓기가 잘 되어 있더라도 내부 구조를 이해하기 위해서는 별도의 지식이 필요한 경우가 있다. 이럴 경우에도 별도의 문서를 유지하는 것보다 가능하면 텍스트 형식으로 파일의 처음이나 함수 앞에 주석으로 유지하는 것이 좋다.

설계 및 구현 문서

일반적인 소프트웨어 방법론에서는 요구사항 및 설계, 구현에 대한 내용을 별도의 문서로 관리하고 작성한다. 하지만 그보다 더 좋은 것은, 요구사항과 설계, 구현 내용이 소스 코드 안에 함께 포함되어 계속 갱신되고 작성되는 것이다. Doxygen과 같은 문서화 도구를 이용하면 매우 다양한 설계 문서를 작성할 수 있으며, 구현에 관한 문서 역시 적절한 주석 내용이 그대로 반영되므로 중복 작업을 덜 수 있다. 대부분의 프로그래머는 한 번 작성한 문서는 다시 되돌아 보지 않기 때문에 유지보수 및 디버깅, 기능 추가를 위해 항상 건드리는 소스 안에 관련 문서를 함께 유지하는 것이 개발자의 수고를 덜 수 있는 방법이다.
소프트웨어 요구사항(Software Requirement Spec) 혹은 사용자 시나리오(User Stories or Scenario) 및 설계 문서는 최초 위키 시스템을 등을 이용하여 작성하고 실제 구현에 들어가면 이를 Doxygen 문법 형태로 변환하여 소스 코드 안에서 계속 작성하고 관리해 나가는 것도 좋은 방법이다.

형식

주석은 JavaDoc 스타일을 원칙으로 한다.
원칙적으로 모든 JavaDoc 형식의 주석은 선언(declaration) 부분(헤더)이 아닌 정의(definition) 부분에 코드와 함께 작성하고 변경하는 것이 좋다. 이는 코드가 변경되었을 경우 헤더파일에 있는 주석 갱신을 잊어버리는 실수를 줄일 수 있고, 변경과 동시에 문서화를 할 수 있다는 장점이 있다. 예외적으로, 클래스 같은 경우 클래스 구조가 헤더 파일에 있을 경우 헤더 파일에 작성한다.
모든 함수와 변수에 주석을 달 필요는 없지만, 모듈의 인터페이스만 다룰 것인지, 내부 구조를 서술하는데 필요한 함수나 변수까지 문서화할지에 대한 부분은 프로젝트마다 다를 수 있다. 하지만, 일반적으로 JavaDoc 스타일로 반드시 주석을 작성해 주어야 하는 부분은 다음과 같다.
  • 라이브러리 : 외부로 공개되는 (C의 경우 static으로 선언되지 않은) API만 기술
  • 그외 모듈 : 외부 공개 API + 내부 구조를 이해하는데 반드시 필요한 API
주석을 작성하는데 사용하는 JavaDoc 스타일을 사용하더라도, 더욱 세부적인 문법은 UsingDoxygen 을 참고하기 바란다. 단, 함수 앞에 작성하는 주석의 경우 반드시 아래 예와 같은 형식을 따른다.
/** 
* Get the address of the latest captured image. This buffer locks not to
* update any more. If there is no latest captured video, it delays until
* capturing one image.
* 
* @param IN_dwChannel Channel number that is not disable among SCHEDULE SIZE
*      set in COMART_dw_BoardOpen().
* @param dwImageAddr Image buffer address saved in UYVY type.
* 
* @return NOTBOOL_FUNCTION_SUCCESS on success.
*/
DWORD COMART_dw_LockWait4GetImageAddr(DWORD IN_dwChannel, DWORD *dwImageAddr)
{
       ....
}

소스 파일 헤더

모든 소스 파일의 맨 앞에는 다음과 같은 주석을 이용해 Doxygen에 관련된 문서화가 자동으로 이루어지도록 한다.
/**
 * @file
 *
 * Library or Project Name - Module Description
 *
 * Copyright (c) 2006 EMSTONE, All rights reserved.
 *
 */
Copyright 에 들어가는 년도는 실제로 모듈이 작성되어 유지보수된 기간을 적는다. 무조건 2001-2004가 아니라 만일 2004 년부터 시작되었다면 2004 하나만 적는다.
filename.c 부분에는 실제 파일 이름을 넣어야 Doxygen이 파일 헤더 주석으로 인식한다는 점에 유의하자. 모듈에 대한 자세한 설명을 기술할 경우, 아래와 같은 형식을 이용한다.
/**
 * @file
 *
 * EMSTONE Common Library - 순환 큐
 *
 * Copyright (c) 2006 EMSTONE, All rights reserved.
 *
 * 이 모듈은 순환방식으로 동작하는 @a EQueue 를 다루기 위한 API를 제공한다. 
 * @a EQueue 는 항상 현재 쓰고 있는 버퍼를 가리키는 포인터를 가지고 있어서,
 * e_queue_get_recent_buf()를 이용해 항상 가장 최근의 버퍼, 즉 현재 쓰고 있는
 * 버퍼 바로 이전 버퍼를 얻어올 수 있다.
 */ 

C 코딩 기법 (C Coding Techniques)

메모리 누수 피하기(Memory Leaks)

동적 메모리 할당은 없다고 생각하기
  • malloc() 이 없는 시스템이라고 가정하고 프로그래밍한다. 정말로 어쩔 수 없는 경우에만 동적 메모리 할당을 이용한다.
  • 모듈별로 유지하는 모든 문자열(이름, 장치 이름 등)은 고정 크기를 지정하여 사용한다.
  • 설정값이라면, 항상 설정 모듈의 내용을 사용하고 편의를 위해 따로 유지하지 않는다.
불필요한 메모리 할당 사용하지 않기
  • *_strdup_*() 등과 같은 메모리 할당 방식 API는 사용하지 않도록 노력한다. 지역 변수와 snprintf() 로 대부분 대체 가능하다.
  • API 형식 역시 할당해서 넘겨주는 방식 (char *foo(int bar)) 대신 이미 할당되었거나 보장된 영역에 지정한 크기 이하만큼 복사하는 방식으로 만들기 (int foo(char *str, size_t len, int bar))으로 안전하게 설계한다.
  • e_dir_list() 등과 같이 넘겨줄 용량이 확정적이지 않아 할당하여 넘겨주는 경우도 메모리 할당 없이 최대값만큼만 동작하도록 수정해야 한다. 그렇지 않다면 반복 호출 방식으로 API를 재설계한다.
임시 문자열 및 배열은 스택에 할당하기
  • alloca() 등과 같은 API를 이용한다.

사용자 메시지 관리

에러 메시지 관리
  • 디버그 메시지 (RETURN_IF_FAIL(), e_warning()) 같은 메시지는 얼마나 많은 용량을 차지할까?

  • 시스템에서 발생하는 모든 에러 메시지를 코드로 정의한다.

  • 모든 함수에서는 에러 메시지를 바로 출력하지 않고 해당 코드를 특정 에러 출력 함수에게 전달하는 방식을 이용한다. 예:

  • #define E_OK 0
    #define E_ERROR_SYSTEM 1~255
    #define E_ERROR_DISK 1000
    #define E_ERROR_DISK_NOT_FOUND (E_ERROR_DISK + 1)
    #define E_ERROR_DISK_DUPLICATE (E_ERROR_DISK + 2)
    #define E_ERROR_RECORD 2000
    #define E_ERROR_RECORD_OPEN_FAIL (E_ERROR_RECORD + 1)
    //
    if (record_failed) {
        e_error(E_ERROR_RECORD_OPEN_FAIL);
        return -E_ERROR_RECORD_OPEN_FAIL;
    }
    //    
    if (record_failed) {
        return e_error(E_ERROR_RECORD_OPEN_FAIL);
    }
    
  • 실제 표시하는 문자열은 반드시 코드가 아닌 DB나 텍스트 파일로 관리가 가능하다.

    • 멀티쓰레드

      멀티 쓰레드 방식 프로그래밍은 강력한 효율을 제공하지만 그만큼 프로그래머를 힘들게 하기도 한다. 그렇다면, 멀티 쓰레드 방식을 대체할 수 있는 방법은 무엇이 있을까?
      뮤텍스(mutex)를 사용해야 하는 구조로 설계하지 말기
      뮤텍스를 이용하는 근본적인 이유는 하나의 리소스에 대해 동시에 접근할 경우 문제가 발생할 수 있을 경우 이에 대한 상호 배제(Mutual Exclusion)를 하기 위함이다. 하지만 하나의 리소스 또는 기능에 대한 처리를 전담하는 쓰레드를 만들고, 원하는 작업을 이 쓰레드(또는 프로세스)에게 메시지로 전달하여 처리하는 방식을 이용하면 뮤텍스를 사용해야 하는 경우는 사라지게 된다. 즉, 메시지 처리 방식(message-driven)을 이용하는 것이다. 물론 이 방식은 비동기적인 메시지일 경우 별로 문제가 없지만, 동기화가 필요한 경우 복잡해진다는 단점이 있다. 또한 성능 문제도 무시할 수 없다.
      폴링(polling) 방식을 적극적으로 이용하고 쓰레드로 분리하지 않기
      멀 티쓰레드 프로그래밍시 발생하는 대부분의 문제는 한번 쓰레드 방식을 이용하게 되면 이후 추가되는 모든 구현에 적극적으로 쓰레드를 도입하게 되는 습관에서 시작한다. 특히 IO(비디오,디스크,네트워크) 관련 처리에 대한 비동기 처리를 폴링 방식으로 프로그래밍하면 느려지고 복잡하기 때문에 쓰레드로 분리해서 쉽게 해결하지만, 바로 이때부터가 재앙의 시작이다. 경험상으로 보면 가장 큰 기능별로 쓰레드를 분리하는 것이 가장 바람직하며, 정말로 성능에 별로 지장을 주지 않는다면 조금 수고가 들더라도 비동기 방식으로 제작하여 한 쓰레드에서 수행되도록 하는 것이 데드락(deadlock)과 같은 저주를 피하면서 뇌세포에게 더 많은 휴식을 줄 수 있는 방법일 것이다.
      주기적인 작업 쓰레드 분리
      쓰레드로 분리해야 하는 작업 중에 주기적으로 동작하는 쓰레드는 모든 루틴을 쓰레드 안에서 동작하도록 하지 않고 메인 이벤트 쓰레드에서(GTK+ 기반일 경우 g_main) 타이머(예: g_timeout_add()) API를 이용하여 주기적으로 실행한뒤 실제로 쓰레드 처리가 필요한 부분만 쓰레드로 처리하도록 한다. 이 방식은 불필요한 쓰레드가 증가하는 것을 막고 멀티쓰레드시 고려해야 하는 동기화 문제도 감소할 수 있다. 쓰레드풀(thread pool) 등을 이용하면 쓰레드 생성/소멸시 지연시간을 줄일 수 있지만, 리눅스 커널 2.6의 쓰레드는 성능이 좋아 이에 대한 오버헤드가 별로 없다.
      이미 잘설계된 라이브러리를 이용하기
      GLib 같은 라이브러리를 살펴보면 위의 원칙대로 프로그래밍하기 위한 API가 너무나 잘 지원되고 있다. 욕심이 나지 않을 수가 없다. 다만, 임베디드 시스템에도 이용할 수 있도록 다른 라이브러리에 대한 의존성이 적고, WinCE 등과 같은 플랫폼도 문제없이 지원한다면 현재 진행중인 모든 프로젝트의 기본 라이브러리로 채택하지 않을 이유가 없을텐데 말이다.

      C 코딩 스타일 (C Coding Style)

      코딩 스타일(Conding Style)이란

      소스 코드가 작성되는 형식을 말한다. C를 예로들면 중괄호({})의 위치, 들여쓰기, 괄호가 사용되는 방법들을 의미하는 것이다.
      대부분의 오픈 소스 프로젝트에서는 가능하면 프로그래머 개인의 취향이 강하게 반영되는 코딩 스타일에 그리 많은 제약을 하지 않지만, 코드의 '일관성'은 매우 중요하게 여긴다. 즉, 하나의 모듈이나 프로젝트에서는 가능하면 우선적으로 작성된 방식의 코딩 스타일을 따르는 것은 최소한의 예의면서 동시에 프로그램의 일관성을 지키도록 도와 준다.
      여기서 기술하는 코딩 스타일은 많이 알고 있는 리눅스 커널 코딩 스타일과 Glib, Gtk+등을 포함하는 GNOME 관련 프로그래밍에서 사용되는 방식을 뼈대로 하지만, 필요한 부분만 발췌하고 나머지는 새로 추가된 것이다.
      GNU에서 기본적으로 제안하는 GNU 방식의 코딩 스타일의 특징은 들여쓰기 단위가 2칸, 모든 중괄호는 새로운 행에서 시작한다는 점이다. 그래서 어떤 면에서는 코드 내부의 블럭별 구분이 쉬워지고, Gtk+ 프로그래밍과 같이 API 이름이 대부분 긴 프로그래밍을 할때 웬만해선 하나의 명령어가 한 줄을 넘어가지 않아서 보기 좋다는 장점이 있다. 하지만, 중괄호 위치를 놓는 방식은 실제 코드보다 중괄호에 할당되는 여백이 더 많고, 로직의 깊이에 신경쓰지 않는 프로그래밍 습관을 낳을 우려가 있으며, 결정적으로 이맥스가 아닌 에디터에서는 배보다 배꼽이 더 큰 코딩스타일이 되버린다.
      리눅스 커널 코딩 스타일은 위에서 말한 장점과 단점이 그대로 반대가 되는 경우이다. (리눅스 커널 코딩 스타일 문서에 있는 리눅스의 농담을 무시하라. GNU의 코딩 스타일은 개인적인 선호와는 별개로 소프트웨어 공학 관점에서는 코드의 유지보수와 디버깅, 그리고 버그 없는 코드를 만드는데 매우 효율적인 것으로 알려져 있다)

      들여쓰기 및 텍스트 너비

      들여쓰기는 리눅스 커널 코딩 스타일과 같은 8 칸을 기준으로 하며, 공백문자(space)가 아닌 탭(tab) 문자를 사용하는 것을 원칙으로 한다.
      8칸 탭을 사용하면 좋은 가장 큰 이유는 코드 읽기가 쉬워진다는 점이다. 또한 리눅스코딩스타일 에서도 지적한 바와 같이 들여쓰기 깊이가 3단계를 넘어갈 경우 (이런 경우 80칸 기준의 터미널에서는 반드시 행이 넘어가서 코드가 못생겨진다) 함수 설계가 잘못 되었다는 걸 의미한다. 즉, 그런 경우 다른 함수로 모듈화하거나 다른 방법을 검토해 보아야 한다.
      소스코드가 씌어지는 텍스트의 너비는 80칸 을 기준으로 한다. 넘어가게 될 경우 다음 줄에 한 단계 더 들여쓰기를 해 이어 작성한다. 하지만 만일 이어지는 코드가 의미적으로 반드시 같은 라인에 있는게 코드의 가독성을 높일 경우 한 라인에 작성해도 된다. 80칸 기준에 너무 얽매이기 보다는 적당한 여유를 두되 전체적으로 일관성을 유지하도록 하는게 좋다.
      함수 호출 인수 같은 파라미터는 다음과 같이 괄호 다음 첫번째 인수에 맞춰 정렬한다. 하지만 그리 많지 않다면 한 줄에 모두 넣는 것을 권장한다.
      loving_you_with_or_without_me_forever(first_param, second_param,
                                            third_parameter, fourth_param);
      
      함수 이름이 너무 길어 인수를 적을 공간이 적을 경우 다음과 같은 형태도 가능하다.
      loving_you_with_or_without_me_forever(
              first_parameter,        
              second_parameter,       
              third_parameter,        
              fourth_parameter);
      
      만일 이맥스(Emacs) 사용자라면 리눅스 커널 들여쓰기 스타일을 다음과 같이 ~/.emacs 파일에 저장하면 편하게 사용할 수 있다.
      (add-hook 'c-mode-common-hook
               (lambda ()
                 (c-set-style "k&r")
                 (setq c-basic-offset 8)))
      
      21.x 버전 이후의 ''이맥스''에서는 다음과 같은 설정으로 충분하다.
      (add-hook 'c-mode-common-hook
               (lambda ()
                 (c-set-style "linux")))
      
      Vi(m) 을 사용한다면 다음과 같은 설정을 ~/.vimrc 파일에 넣는 것 만으로 충분하다.
      set ts=8
      if !exists("autocommands_loaded")
       let autocommands_loaded = 1
       augroup C
           autocmd BufRead *.c set cindent
       augroup END
      endif
      
      하지만 가독성을 위해 한 줄을 여러 줄로 나눌 경우 CVS나 SubVersion 같은 버전관리 시스템을 사용할때 버전별 차이점(diff)을 확인할때는 오히려 가독성이 떨어진다. 예를 들어 의미적으로 한 줄만 변경되었는데 코드 상으로 여러 줄이 변경될 경우 그 차이점을 추적할때 매우 피곤해지는 걸 느낄 수 있다.

      indent 프로그램

      이맥스Vim 같은 에디터가 아니더라도 좋은 에디터는 대부분 코딩 스타일에 따라 커스터마이징할 수 있는 기능이 있다. 하지만, 그런 기능이 없더라도 코드의 내용은 건드리지 않으면서 코드의 모양만 다시 가공해주는 프로그램이 indent 이다.
      매뉴얼을 찾아보면 더 많고 자세한 옵션을 알수 있지만, 대개 -kr 옵션만 주어도 예쁘고 이 문서에 비슷한 코딩 스타일을 얻을 수 있다. 하지만, 조금 더 세밀하게 조정된 옵션은 다음과 같다.
      -kr -i8 -ts8 -br -nce -bap -sob -l80 -npcs -ncs -nss -bs -di1 -nbc -lp -npsl
      
      위 내용을 가지는 .indent.pro 라는 이름의 파일을 자신의 홈디렉토리나 실행중인 디렉토리에 넣으면 indent 는 자동으로 옵션을 읽어들이게 된다.
      하지만 명심할 것은, indent 가 반드시 나쁜 프로그래밍 습관을 고쳐주지는 않는다는 점이다.

      중괄호 위치

      들여쓰기와는 달리, 하나의 중괄호 치는 방법을 선택해야 할 이유는 별로 없지만, 여기서 선택한 방법은 C와 C++의 저자들이 즐겨 쓰던 방법인 여는 괄호를 줄 마지막에 놓고, 닫는 괄호는 맨 앞에 놓는 것이다.
      if (x is true) {
              we do y;
      } else {
              wo do z;
      }
      
      for (i = 0; i < max; i++) {
              ;
      }
      
      switch (prev->state) {
      case TASK_INTERRUPTIBLE:
              if (unlikely(signal_pending(prev))) {
                      prev->state = TASK_RUNNING;
                      break;
              }
      default:
              deactivate_task(prev, rq);
      case TASK_RUNNING:
              ;
      }
      
      코드의 라인 수로 프로그래머의 생산량을 판단하던 시절이라면 이런 코딩 스타일은 분명히 환영받지 못할 것이다. 하지만 반대로 이 방법은 중괄호를 넣기 위해 한 줄을 낭비할 필요가 없으며 유용한 주석을 코드에 더 넣을 수 있는 여지를 준다.
      하지만 분명히 함수만은 다른 규칙을 따른다. 중괄호를 다음 라인의 시작에 넣는다. 이는 C뿐 아니라, C++과 같은 클래스 내부 함수를 직접 코딩할때도 해당한다.
      int function(int x)
      {
             body of function
      }
      
      닫는 괄호에 대한 예외 는 문장에 이어지는 부분이 있을 경우이다. 예를 들어 do ~ while 절과 연속되는 else if 절이다. 이 역시 K&R을 따른 것이다.
      do {
              body of do-loop
      } while (condition);
      
      if (x == y) {
              ..
      } else if (x > y) {
              ...
      } else {
              ....
      }
      

      이름짓기

      이름 짓기, 즉 명명법은 코딩 스타일에서 가장 반드시 지켜야 할 만큼 중요하다. 특히 공동 작업으로 이루어지는 개발 프로젝트에서는 함수나 변수의 이름을 통해서 많은 정보를 추가 노력 없이 얻을 수 있기 때문에 코딩은 물론 코드 이해에 드는 노력도 줄어든다.
      물론 C++과 달리 C 언어에서는 별다른 이름 공간(Name Space)를 구분하지 않기 때문에 전체적인 이름 공간을 오염시키지 않기 위해서도 반드시 필요하다. 다시 말하면, 서로 다른 모듈에 같은 이름의 함수가 있을 경우 컴파일 후 링크시 이름 충돌로 인해 에러가 발생한다. 또한 규칙 없이 사용된 이름은 코드의 재사용 및 추적을 매우 어렵게 한다.
      함수 이름 은 모두 소문자로 이루어지며, 다음과 같은 규칙을 따른다.
      {프로젝트}_모듈{_하위모듈}_동사/형용사{_목적어}
      
      구성한다. 여기서 중괄호({})로 둘러 쌓인 부분, 즉 프로젝트 이름과 하위모듈은 경우에 따라 생략될 수 있다는 것을 의미한다. 프로젝트 이름은 약자를 사용할 수도 있고, 프로젝트 이름을 그대로 사용할 수도 있다.
      전역 변수 이름 역시 함수와 같은 규칙으로 구성된다. 이는 static 이 붙는 모듈 내부의 전역 변수도 포함된다.
      {프로젝트}_모듈{_하위모듈}_명사/형용사
      
      짧은 약어 같은 이름을 사용하면 안된다. 전역변수나 구조체, 클래스에 종속된 변수는 반드시 길고 서술적인 이름을 사용해야 한다. 리누스가 얘기한 것처럼 이름은 설명적이어야 한다. cntusr() 같은 이름 대신 count_active_users() 와 같은 이름을 사용한다. 이는 코드를 더욱 읽기 쉽게 하며 그 자체가 문서 역할을 하게 된다.
      하지만, 가능하면 모듈 외부로 전역 변수를 공개하는 것은 피해야 한다. 성능이나 구조적인 이유 때문에 어쩔 수 없는 경우라 하더라도 반드시 데이터에 접근하기 위한 인터페이스 함수를 제공하는 것이 좋다.
      지역 변수(local variables) 는 "i"나 "tmp"처럼 짧은 이름을 사용할 수 있다. 이는 물론 모든 전역 변수가 네이밍룰을 완전히 지킨다는 가정하에서만 옳다. 즉, 약어로 표현된 변수는 지역변수임을 알아채고, 같은 블럭의 시작이나 함수의 시작 부분을 검사하면 바로 확인할 수 있기 때문에 가능하다. 이는 휠씬 쓰기 쉽고 이해하기 어렵지 않으며, 오히려 길게 쓰는 건 비생산적이다. 하지만, 그 이름 역시 오해의 여지가 있으면 안된다.
      전체적으로 이러한 규칙은 다음과 항목에도 사용된다.
      • 함수 이름은 소문자이며, 밑줄(underscore)로 단어를 구분한다. (e_video_set_property(), viewport_switch_next())
      • 매크로(macro)와 열거형(enum)은 대문자이며, 역시 밑줄로 단어를 구분한다. (E_VIDEO_GET_DEVICE(), E_VIDEO_NTSC)
      • 클래스(class)나 타입(type), 구조체(struct) 이름은 EVideoDevice 와 같이 대소문자를 섞어쓴다. 구조체의 경우 typedef 문으로 정의하여 사용하는 경우 이름 앞에 '_' 문자를 붙여 타입(type)과 구조체(struct)를 구분한다.
      • 객체 안에 포함되는 함수(member functions)도 위의 함수와 같은 규칙을 따른다. 단 유의할 점은, 이름을 지을때 의미 중복을 피해야 한다는 점이다. 이는 아래에서 다시 논한다.
      파일 이름 은 약간 다른 규칙을 따른다.
      • 대문자 없이 밑줄로 단어를 구분하는 이름을 사용한다.
      • 파일 이름은 동일한 이름의 모듈, 구조체, 클래스 이름을 따른다. 즉, 누군가 avmania_ 로 시작하는 함수나 변수를 참고하고 싶을 경우, avmania.h``나 ``avmania.c, avmania.cpp 파일을 열면 바로 찾아볼 수 있도록 해야 한다.
      • 파일이 라이브러리의 일부일 경우 라이브러리 이름을 접두어로 항상 붙인다. (예: libpantilt 라이브러리의 파일은 pantilt_ 로 시작)
      단어 구분에 밑줄(_)을 사용하면 좋은 점은 코드에 사용할 수 있는 단어를 좀 더 자유롭게 사용할 수 있고, 단위 이동을 지원하는 대부분의 에디터에서 더 쉽고 빠르게 이동하고 편집할 수 있다는 점이다.

      일관된 이름

      변수 이름을 일관적으로 지어야 한다는 사실은 매우 중요하다. 한 예로, 리스트를 다루는 모듈이 리스트 포인터 변수의 이름을 "l"이라고 지었다. 물론 이는 간결함과 간편함을 위해서다. 하지만, 위젯과 크기를 다루는 모듈이 위젯(widgets)과 가로(widths)를 모두 "w"라고 이름을 지으면 안된다. 이는 코드를 매우 읽기 어렵게 하고 코드의 일관성을 떨어뜨린다.
      좋은 변수나 함수 이름을 지을때 일반적 의미의 단어를 피하는 것도 좋은 방법이다. 또한 이름을 지을때 가능한 기술적인 용어를 피하는 것도 좋은 방법이다.

      의미 중복 피하기

      대부분의 경우 하나의 이름에는 같은 단어가 두 개 이상 들어갈 필요가 없다. 또한 단어가 중복되면 타이핑에 드는 노력도 더 많이 든다. 반드시 의미상으로 구분해야할 필요가 있을 경우에만 같은 단어를 사용하는 것을 원칙으로 한다.
      특히 구조체나 클래스의 함수 이름 같은 경우 구조체나 클래스의 이름이 이미 포함하고 있는 내용을 함수 이름에 다시 넣는 경우가 많은데, 이는 피해야 한다.

      함수

      함수는 짧고 예뻐야 하며 단지 한가지 일만 해야 한다. 특히 switch ~ case``문이 아닌 ``if ~ else if 가 길게 반복되는 코드에서 각 블럭이 길어질 경우 하나의 함수로 분리하는 것을 고려한다. 성능을 고려해야 된다면 static inline 을 함수 앞에 붙이는 것과 같은 컴파일러의 인라인(in-line) 기능을 이용하면 된다. 컴파일러는 프로그래머가 원하는 코드를 생성해 줄 것이다.
      특히 복잡한 함수일 경우, 영어를 할 줄 아는 고등학교 1학년 학생이 읽어도 코드를 이해할 수 있을만큼의 코드를 작성하기는 힘들다. 이를 위해서는 실행 단위별로 주석을 다는 방법도 괜찮지만, 각각의 실행 단위를 충분히 설명적인 보조 함수로 나누는 것도 좋은 방법이다.
      또 하나의 고려사항은 함수에서 사용하는 지역변수의 개수이다. 지역변수는 5~10개를 넘지 말아야 한다. 만일 그렇다면 뭔가 잘못하고 있는 것이다. 다시 생각해보고, 더 작은 단위로 나눈다. 일반적으로 사람의 두뇌는 서로 다른 7가지 일은 동시에 유지할 수 있지만, 그 이상이 되면 힘들어지는 것으로 알려져 있다. 당신이 천재라 할 지라도, 2주 전에 자신이 작성한 코드를 이해하고 싶을 때가 올 것이다.

      static과 const 사용

      들여쓰기와 이름의 일관성만 지켜도 코드는 거의 명료해진다. 하지만 이 두가지는 프로그래머, 즉 사람의 뇌를 피곤하지 않게 하기 위한 방법일 뿐이다. 하지만, 컴파일러의 도움을 받으면 프로그래머는 더욱 즐거워진다.
      모든 함수와 변수는 항상 static 으로 선언한다. 그리고 현재 모듈 바깥에서 사용하는 것만 진짜 전역(global) 함수로 선언한다.
      이 방법의 장점은 첫째 static 키워드가 붙은 키워드는 모듈 밖에서 참조할 수 없도록 컴파일러가 막아주기 때문에, 다시 말해 전역으로 취급되지 않기 때문에(export) 전체적인 소프트웨어 코드의 이름 공간(name space)을 오염시키지 않는다. 두번째 장점은 이름 공간이 현재 모듈로만 제한되기 때문에 다른 모듈에서 내부에서 사용할지 모르는 어떤 이름이라도 마음 놓고 사용할 수 있다. 따라서 모듈 안에서만 사용되는 간단한 함수에 이름짓기 규칙을 반영할 필요가 없어져 두뇌 노동을 덜 수 있다. 그리고, 마지막으로 GCC와 같은 컴파일러는 -Wall -Wunused 옵션을 주어 컴파일하면 static 으로 선언이 되었으면서도 한 번도 사용된 적이 없으면 경고해 준다. 따라서 복잡한 로직에서 빠뜨리기 쉬운 점을 다시 한 번 검토할 수 있다.
      const 키워드 역시 남용해 주길 바란다. 이를 통해 컴파일러는 더욱 많은 숨겨진 버그를 찾아줄 것이다.
      사용자가 해제(free)하면 안되는 내부 데이터 포인터를 돌려주는 함수가 있다면, const 를 사용해야 한다. 이는 사용자가 그런 작업을 하려 하면 자동으로 경고해 줄 것이다. 다음 예제에서 컴파일러는 함수가 돌려준 문자열을 해제하는 코드가 있을 경우 사용자에게 경고할 것이다.
      const char *man_get_name_by_id(int id);
      

      매크로 및 enum

      규칙이 없는 마법수(magic values) 는 코드에 숫자 그대로 적지 말고 항상 매크로를 사용해 정의한다.
      #define GNOME_PAD       8
      #define GNOME_PAD_SMALL 4
      #define GNOME_PAD_BIG   12
      
      한 변수에 가능한 값이 여러개일 경우 대부분의 경우 매크로를 이용해 그 값을 정의해 사용한다. 하지만 그렇게 하지 말라. 반드시 enum 을 사용해 정의하라. 컴파일러는 물론 디버거까지도 그 값에 대한 심볼 정보를 알게 되기 때문에 컴파일시 뿐 아니라 디버깅 시에도 암호 같은 숫자 대신 enum 으로 정의한 심볼을 볼 수 있을 것이다. 물론 이런식으로 선언된 열거형 변수를 int형 으로 선언하지 말고 반드시 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;
      

      헤더 파일

      한 모듈의 헤더 파일은 그 모듈의 인터페이스 역할을 한다. 즉, 외부에서 모듈에 접근하기 위한 API와 그 API를 사용하는데 필요한 자료구조나 정의만 포함해야 한다.
      모듈 안에서 헤더 파일을 포함(include)할때는 다음과 같은 순서로 정렬한다.
      1. 시스템 라이브러리 헤더 파일
      2. 다른 모듈 헤더 파일
      3. 자신의 인터페이스 헤더 파일

      좋은 코딩 습관

      위에서 적은 코딩 스타일 외에 다음 내용은 머리와 손이 습관처럼 따라야할 프로그래밍 습관이다.

      • 특별한 예외가 아닌 이상 파일 이름도 위의 이름 규칙을 따른다.
      • 파일 이름과 모듈 이름은 가능하면 일치시켜라. 예를 들어 e_video_* 형태의 API는 e_video.c 파일에 위치하는 것이 좋다.
      • 헤더파일에는 그 모듈의 인터페이스만 두어라. 반드시 extern 을 앞에 붙이고, 다른 모듈에서 사용할 수 있는 그 모듈의 API만 두어라. 선언이나 형도 API를 사용하는데 필요한 것만 헤더파일에 둔다.
      • 절대로 애매한 코드는 작성하지 말고, 항상 엄격하게 작성하라.
      • 수식을 명확하게 표현하는 것 이상의 괄호를 사용하지 말라.
      • 모든 키워드의 앞뒤, 콤마(,) 다음, 이진연산자(+, -, ...) 앞뒤에 공백을 넣어라.
      • 어설프게 코드를 수정하려 하지 말고, 명료하고 확장가능하며 유지보수가 쉬운 코드를 다시 만들어라.
      • 컴파일시 단 하나의 경고(warnings) 메시지도 무시하지 말고, 먼저 처리해서 없애라. 이는 헤더파일에 있는 함수 프로토타입 일치성 등과 같은 흘리기 쉬운 버그들을 잡도록 도와 준다.
      • 주석을 달아라. 각 함수 앞부분에 무엇을 하는 녀석인지 설명해 두는 것을 잊지 말라. 절대로 필요한 경우가 아니면, 어떻게(HOW) 동작하는지는 적지 말고 무엇을(WHAT) 하는지 기술하라. 그런 건 코드를 읽어보면 안다. 그렇지 않다면, 코드를 이해하기 쉽도록 다시 작성하라. 하지만, 라이브러리나 운영체제, 환경 상의 이유로 일부러 그렇게 작성한 코드는 반드시 주석을 달아야 한다.
       

      C++ 코딩 스타일 (C++ Coding Style)

      C++ 를 이용하여 프로그래밍시 지켜야 할 코딩 스타일에 대해서 기술한다.

      기본적인 들여쓰기, 텍스트 너비 그리고 중괄호 위치는 'C 코딩 스타일'과 동일하게 사용한다.

      /**
      * A test class. 
      * 
      * A more elaborate class description.
      */
      class Test
      {
      public:
      
             /** 
              * An enum.
              *
              * More detailed enum description.
              */
             enum TEnum { 
                     TVal1,   
                     TVal2,   
                     TVal3    
             } 
             *enumPtr, 
             enumVar;  
            
             /**
              * A constructor.
              *
              * A more elaborate description of the constructor.
              */
             Test();
      
             /**
              * A destructor.
              *
              * A more elaborate description of the destructor.
              */
             ~Test();
         
             /**
              * a normal member taking two arguments and returning an integer value.
              *
              * @see Test()
              * @see ~Test()
              * @see testMeToo()
              * @see publicVar()
              *
              * @param a an integer argument.
              * @param s a constant character pointer.
              *
              * @return The test results
              */
             int testMe(int a, const char *s);
            
             /**
              * A pure virtual member.
              *
              * @see testMe()
              *
              * @param c1 the first argument.
              * @param c2 the second argument.
              */
             virtual void testMeToo(char c1, char c2) = 0;
        
             /** 
              * a public variable.
              *
              * Details.
              */
             int publicVar;
            
             /**
              * a function variable.
              *
              * Details.
              */
             int (*handler)(int a, int b);
      };
      

      클래스 이름은 각 단어의 시작을 대문자로 한다. MainWindow , SubWindow 처럼 만들어 사용한다.

      클래스 멤버 이름은 각 단어의 시작을 대문자로 하되, 처음 단어만 소문자로 시작한다. testMeToo 처럼 만들어 사용한다. 멤버 변수는 명사 형태로 멤버 함수는 동사 형태로 만든다. 예를 들면 errorHandler 는 멤버 변수이고 handleError 는 멤버 함수의 이름으로 볼 수 있다.

      모질라 코딩 스타일 및 포터블 C++ 프로그래밍 가이드
       

      파이썬 코딩 스타일 (Python Coding Style)

      코드 레이아웃 (Code Lay-outs)

      들여쓰기

      들여쓰기(indentation)에는 4글자 공백(spaces)를 사용하며, 탭(tab)과 공백(space) 문자를 섞어서 사용하면 안됩니다. 항상 공백(space)만 사용하기를 권장하며, 파이썬 인터프리터 사용시 -tt 옵션을 주면 섞어 사용된 경우를 알려주므로 항상 켜두기를 권장합니다.

      행 최대 길이

      모든 라인은 최대 80 글자 이내로 제한하기를 권장합니다. 문서화나 주석을 달 경우에는 최대 72 글자가 적당합니다. 이 길이가 넘어가는 긴 행을 작성할때는 괄호나 백슬래시 문자를 이용하여 적절하게 표현합니다:
      class Rectangle(Blob):
      
          def __init__(self, width, height,
                       color='black', emphasis=None, highlight=0):
              if width == 0 and height == 0 and \
                 color == 'red' and emphasis == 'strong' or \
                 highlight > 100:
                  raise ValueError("sorry, you lose")
              if width == 0 and height == 0 and (color == 'red' or
                                                 emphasis is None):
                  raise ValueError("I don't think so")
              Blob.__init__(self, width, height,
                            color, emphasis, highlight)
      
      하지만 두 라인 정도로 나뉘어지거나, 의미상 한 라인에 코딩하는 것이 더 나을 경우에는 한 라인에 이어서 코딩하는 것이 더 좋습니다. (요즘 에디터는 80 글자 이상에서도 잘 보입니다) 즉, 위의 코드를 실제 권장하는 방식으로 작성하면 대략 다음과 같습니다:
      class Rectangle(Blob):
      
          def __init__(self, width, height, color='black', emphasis=None, highlight=0):
              if width == 0 and height == 0 and \
                 color == 'red' and emphasis == 'strong' or \
                 highlight > 100:
                  raise ValueError("sorry, you lose")
              if width == 0 and height == 0 and (color == 'red' or emphasis is None):
                  raise ValueError("I don't think so")
              Blob.__init__(self, width, height, color, emphasis, highlight)
      

      빈 행 삽입하기

      일반 함수나 클래스 정의 앞에 두개의 빈 행을 두어 구분합니다. 클래스 내부 메쏘드 정의는 한개의 빈 행을 두어 구분합니다. 이외에도 논리적으로 구분되는 코드 단위는 빈 행으로 구분하는 것이 좋습니다.

      인코딩

      한글을 사용하지 않을 경우 기본 ASCII(Latin-1, ISO-8859-1) 인코딩을 이용합니다. 하지만, 한글 주석이나 문자열을 사용할 경우 유니코드(UTF-8) 인코딩을 이용합니다. 유니코드 인코딩을 이용할 경우에는 파일 맨 앞에 다음과 같은 내용을 추가해야 합니다.:
      #!/usr/bin/env python
      # -*- coding: utf-8 -*-
      

      불러오기 (Imports)

      임포트는 항상 모듈별로 따로 분리하여 사용합니다. 예를 들면:
      Yes: import os
           import sys
      
      No:  import sys, os
      
      모듈의 세부 정의를 임포트할때는 이런식으로 해도 됩니다:
      from subprocess import Popen, PIPE
      
      임포트는 항상 파일의 맨 위에 둡니다. 모듈에 대한 주석 이후, 전역 변수와 상수 이전에 둡니다.
      모듈을 임포트할 때는 다음과 같은 순서를 따릅니다.
      1. 표준 라이브러리
      2. 관련 써드파티 라이브러리
      3. 어플리케이션/라이브러리 내부 모듈
      모듈 그룹별로 공백을 두어 구분하는 것도 좋습니다.
      클래스를 담고 있는 모듈에서 클래스를 임포트할때는 다음과 같이 사용하는 것도 좋습니다.:
      from myclass import MyClass
      from foo.bar.yourclass import YourClass
      
      이름이 충돌한다면 어쩔 수 없이 다음과 같이 임포트하고:
      import myclass
      import foo.bar.yourclass
      
      직접 "myclass.MyClass", "foo.bar.yourclass.YourClass" 클래스를 호출하는 방법도 좋습니다.

      표현식과 문장에서 공백문자

      다음과 같은 상황에서 부수적인 공백 문자는 사용하지 않습니다.
      • 괄호 / 대괄호 안에서:

      Yes: spam(ham[1], {eggs: 2})
      No:  spam( ham[ 1 ], { eggs: 2 } )
      
    • 따옴표(,), 세미콜론(;), 콜론(:) 앞에서:

      Yes: if x == 4: print x, y; x, y = y, x
      No:  if x == 4 : print x , y ; x , y = y , x
      
    • 함수 호출시 사용하는 괄호 앞에서:

      Yes: spam(1)
      No:  spam (1)
      
    • 색인이나 잘라내기에 사용하는 대괄호 앞에서:

      Yes: dict['key'] = list[index]
      No:  dict ['key'] = list [index]
      
    • 할당(=) 연산자 앞뒤로 한 개 이상의 공백:

      Yes:
      
          x = 1
          y = 2
          long_variable = 3
      
      No:
      
          x             = 1
          y             = 2
          long_variable = 3
      
        이항 연산자(binary operators) 앞뒤에는 반드시 한 개씩 공백을 둡니다.:
        assignment (=), augmented assignment (+=, -= etc.),
        comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not),
        Booleans (and, or, not).
        
        산술 연산자 앞뒤에도 반드시 한 개씩 공백을 둡니다.:
        Yes:
        
            i = i + 1
            submitted += 1
            x = x * 2 - 1
            hypot2 = x * x + y * y
            c = (a + b) * (a - b)
        
        No:
        
            i=i+1
            submitted +=1
            x = x*2 - 1
            hypot2 = x*x + y*y
            c = (a+b) * (a-b)
        
        키워드나 기본값으로 사용하는 대입(=) 기호 앞뒤에는 공백을 두지 않습니다.:
        Yes:
        
            def complex(real, imag=0.0):
                return magic(r=real, i=imag)
        
        No:
        
            def complex(real, imag = 0.0):
                return magic(r = real, i = imag)
        
        복합문(한줄에 여러 문장)을 사용하는 것은 별로 안 좋습니다.:
        Yes:
        
            if foo == 'blah':
                do_blah_thing()
            do_one()
            do_two()
            do_three()
        
        Rather not:
        
            if foo == 'blah': do_blah_thing()
            do_one(); do_two(); do_three()
        
        때로는 if/for/while 문 뒤에 짧은 문장을 한 줄에 두는 것은 괜찮지만, 여러 줄에 걸치는 것은 안됩니다.:
        Rather not:
        
          if foo == 'blah': do_blah_thing()
          for x in lst: total += x
          while t < 10: t = delay()
        
        Definitely not:
        
          if foo == 'blah': do_blah_thing()
          else: do_non_blah_thing()
        
          try: something()
          finally: cleanup()
        
          do_one(); do_two(); do_three(long, argument,
                                       list, like, this)
        
          if foo == 'blah': one(); two(); three()
        

        주석달기 (Comments)

        코드와 상관없거나 전혀 다른 내용의 주석은 없는게 차라리 낫습니다. 주석은 항상 코드 변경사항을 반영해야 합니다.
        주석은 언제나 하나의 완전한 문장이어야 합니다. 영문일 경우 항상 대문자로 시작하며, 코드 상의 변수/함수 이름이 소문자일 경우는 그대로 사용합니다.
        주석이 짧다면, 마지막 점(.)은 생략해도 되지만, 점(.) 뒤에 문장이 더 있을 경우 공백을 두어야 합니다.
        그리고 가능한, 콩글리쉬라도 좋으니 영어로 작성하는 것을 권장합니다. 미래를 위해...

        블럭 주석

        블럭 주석은 대개 다음에 오는 코드에 적용되므로, 코드와 같은 수준의 들여쓰기를 가져야 합니다. 각 라인의 주석은 # 문자와 하나의 공백으로 시작합니다. 블럭 주석 안에서 문단을 나누기 위해 줄을 나눌때도 # 문자를 가져야 합니다.

        인라인 주석

        가능한 인라인 주석은 사용하지 않는 것이 좋습니다. 인라인 주석이란 코드와 같은 줄에 위치하는 주석을 말하는데, 최소 두개 이상의 공백으로 구분되어야 합니다. 어쩔 수 없이 사용하더라도, 주석 작성의 기본 규칙인 'How'가 아니라, 'What'을 기술해야 합니다. 다음은 그 예입니다:
        x = x + 1                 # Increment x
        
        다음과 같은 주석은 적절하게 사용된 경우입니다.:
        x = x + 1                 # Compensate for border
        

        문서화 문자열

        흔히 줄여서 'docstrings' 라고 하는 문서화 문자열(documentation strings)은 Docstring Conventions 에 자세히 설명되어 있습니다. 여기서는 일반적인 경우의 예만 다룹니다.
        • 외부에 공개된 모든 모듈, 함수, 클래스, 메쏘드는 문서화 문자열이 있어야 합니다. 비공개 메쏘드에는 의무적이지 않지만, 메쏘드가 무엇을 하는지 주석으로 다는 것이 좋습니다. 이 주석은 "def" 행 바로 다음부터 시작합니다.

      • 다행(multiline) 문서화 문자열의 마지막을 나타내는 """ 표시는 반드시 한 행으로 표시해야 합니다. 그러므로, 공백 라인을 앞에 두는 것이 좋습니다. 다음은 그 예입니다:

        """Return a foobang
        
        Optional plotz says to frobnicate the bizbaz first.
        
        """
        
      • 한줄짜리 문서화 문자열에서는 맨끝에 """ 표시를 하는 것이 좋습니다.

          버전 표시하기

          서브버전(Subverion) 등을 이용해 소스 파일을 버전 관리하고 있다면 다음과 같은 사용해야 모듈의 문서화 문자열에 자동으로 포함됩니다.:
          __version__ = "$Revision: 43264 $"
          # $Source$
          

          모듈 파일 템플릿

          모든 모듈 파일은 다음과 같은 문자열로 시작해야 합니다.:
          #!/usr/bin/env python
          # -*- coding: utf-8 -*-
          #
          # Project Name or Module Name
          #
          # Copyright (c) 2005-2006 EMSTONE, All rights reserved.
          #
          # Module Description...
          #
          
          이후부터 import 문장, 전역 상수, 전역 변수 등을 지정합니다.

          이름짓기 규칙 (Naming Conventions)

          이름짓기 기본

          이름짓기 방법은 여러가지가 있는데, 대략 다음과 같이 나눌 수 있습니다.
          • b (단일 소문자)
          • B (단일 대문자)
          • lowercase (소문자)
          • lower_case_with_underscores (밑줄과 소문자)
          • UPPERCASE (대문자)
          • UPPER_CASE_WITH_UNDERSCORES (밑줄과 대문자)
          • CapitalizedWords 또는 CapWords, CamelCase (단어 첫글자만 대문자) 축약어를 사용할때는 축약어의 모든 문자를 대문자로 적는게 좋습니다. 예를 들면 HttpServerError 보다 HTTPServerError 방식이 더 좋습니다.
          • mixedCase (단어 첫글자는 대문자, 시작은 소문자)
          • Capitalized_Words_With_Underscores (ugly!)
          다음 문법은 파이썬에게 특별하게 취급되므로 유의해야 합니다.
          • _single_leading_underscore : 내부에서만 사용됨을 표시. 예를 들어, "from M import *" 문장은 밑줄(_)로 시작하는 객체는 임포트하지 않습니다.
          • single_trailing_underscore_ : 파이썬 키워드와 충돌을 피하기 위해 사용되는 트릭. 예를 들면 "Tkinter.Toplevel(master, class_='ClassName')"
          • __double_leading_underscore : 클래스 속성을 이름짓기할때 사용
          • __double_leading_and_trailing_underscore__ : 특수한 객체나 속성. 예를 들면, __init__, __import__, __file__).

          피해야 할 이름

          'l'(소문자 L), 'O'(대문자 O), 'I' (대문자 I) 등을 단일 문자 변수로 사용하면, 많은 에디터의 다양한 폰트에서 구분하기 어려운 경우가 많습니다.

          클래스 이름

          예외가 없는한 CapWords 방식으로 이름을 짓는다. 내부적으로만 사용하는 클래스일 경우 앞에 밑줄(_)을 둡니다.

          모듈 이름

          클래스 이름과 동일한 규칙을 따릅니다. 모듈이 하나의 클래스만 가질 경우, 클래스와 동일한 이름으로 짓습니다.

          예외 이름

          예외(execeptions) 역시 클래스이므로, 클래스 이름과 동일한 규칙을 따릅니다.

          상수 이름

          C의 enum형이나 #define과 같은 의미이므로 UPPER_CASE_WITH_UNDERSCORES 방식을 따릅니다.

          함수/메쏘드 이름

          mixedCase 방식으로 이름을 짓습니다.

          전역 변수 이름

          함수와 동일한 규칙을 따릅니다. 내부에서만 사용하는 전역 변수일 경우 앞에 밑줄(_)을 둡니다.

          함수/메쏘드의 지역 변수

          가능한 축약하여 lowercase 또는 lower_case_with_underscores 형식으로 이름을 짓습니다.

          함수/메쏘드의 인수

          함수/메쏘드 이름과 마찬가지로 mixedCase 방식으로 이름을 짓습니다.
          인스턴스 메쏘드의 첫번째 인수는 항상 'self' 를 이용하고, 클래스 메쏘드의 첫번째 인수는 항상 'cls' 를 이용합니다.

          클래스 상속 디자인하기

          클래스 메쏘드와 속성(인스턴스 변수)가 공개(public)인지 비공개(non-public)인지를 결정해야 합니다. 의심스럽다면 비공개로 하십시오. 나중에 공개된 것을 비공개로 만드는 것보다 비공개된 것을 공개하는 일이 더 쉽습니다. 개인(private)이라는 용어를 사용하지 않는 이유는, 파이썬에서는 실제로 개인 속성이 없기 때문이다.
          때로는 상속받아 확장하는 방식을 염두에 두고 클래스를 설계하는 경우도 있는데, 이러한 클래스를 설계할때는 어떤 속성이나 API를 공개하고, 어느 부분이 하위 클래스의 일부로 구현되어 기본 클래스에서 사용될지에 대해 조심스럽게 결정해야 합니다.
          이러한 점을 염두에 둔 파이썬 방식의 가이드라인은 다음과 같습니다.
          • 공개 속성(public attributes)은 밑줄(_)로 시작하면 안된다.
          • 공개 속성 이름이 예약어와 충돌한다면 이름 끝에 밑줄(_) 하나를 추가한다. 이름을 축약하거나 변경하는 것보다 더 나은 방법이 될 것이다. (하지만, 첫번째 인수로 사용되는 클래스를 가리키는 'cls' 는 예외다)
          • 간단한 공개 데이터 속성은 그냥 공개하는 것이 낫다. 복잡한 접근자 메쏘드를 만드는 것은 바람직하다. 정말로 이런 식의 인터페이스가 필요하다면 명시적으로 프로퍼티(properties) 기능을 이용하는 것이 좋다.
          • 클래스가 상속되는 경우라면, 하위 클래스가 사용할 수 없는 속성이 있을텐데 이런 경우 두개의 밑줄(_)로 이름을 시작하면 된다.

          프로그래밍 권장 사항

          • 코드는 여러 파이썬 구현(PyPy, Jython, IronPython, Pyrex, Psyco 등)에서 정상적으로 동작하는 방식으로 작성되어야 합니다. 예를 들어, 문자열을 합칠때 CPython에서 효율적인 구현이라고 하는 a+=b 또는 a=a+b 대신 더 일반적이고 성능 좋은 ''.join() 을 이용합니다.

        • None 등과 같은 싱글레톤(singletons)과 비교할때는 'is''is not' 을 사용해야 합니다. 절대 등호(==)를 사용하지 마세요. 또한 "if x" 라고 적는 것은 실제로 "if x is not None" 이라고 적는 것과 동일하다는 점도 명심하길 바랍니다.

        • 클래스 기반 예외처리를 사용하세요. 문자열 예외처리(string exceptions)는 더 이상 지원되지 않을 것입니다. 모듈이나 패키지는 자체적인 기본 예외처리 클래스(base exception class)를 정의하여 사용하는 것이 좋습니다. 예를 들면 다음과 같습니다.:

          class MessageError(Exception):
              """Base class for errors in the email package."""
          
          예외처리가 에러일 경우 클래스 이름 끝에 반드시 "Error" 를 붙여주어야 합니다.
        • 예외를 일으킬때 옛날 방식인 "raise ValueError, 'message'" 대신 "raise ValueError('message')" 방식을 사용하기 바랍니다. 괄호를 사용하는 방식이 여러가지 이유로 더 좋고, 앞으로는 지원되지 않게 됩니다.

        • string 모듈 대신 문자열(string) 메쏘드를 이용하세요. 문자열 메쏘드가 더 빠르고 유니코드 처리도 동시에 가능합니다.

        • 문자열의 시작과 끝 문자를 검사할때 ''.startswith()''.endswith() 방법을 이용하기 바랍니다. 이 방법이 더 깔끔하고 에러가 적습니다. 예를 들면 다음과 같습니다.:

          Yes: if foo.startswith('bar'):
          
          No:  if foo[:3] == 'bar':
          
        • 객체 타입 비교는 항상 isinstance() 를 이용하세요:

          Yes: if isinstance(obj, int):
          
          No:  if type(obj) is type(1):
          
          객체가 문자열인지 검사할때는 유니코드 문자열일수도 있다는 점을 명심해야 합니다. Python 2.3에서는 문자열과 유니코드는 같은 클래스를 상속받기 때문에 다음과 같이 사용할 수 있습니다.:
          if isinstance(obj, basestring):
          
          파이썬 2.2에서는 다음과 같이 사용해야 합니다.:
          from types import StringTypes
          if isinstance(obj, StringTypes):
          
          파이썬 2.0과 2.1에서는 이렇게 사용합니다.:
          from types import StringType, UnicodeType
          if isinstance(obj, StringType) or \
             isinstance(obj, UnicodeType) :
          
        • 시퀀스(strings, lists, tuples)가 비었는지 검사할때는 다음과 같이 합니다.:

          Yes: if not seq:
               if seq:
          
          No: if len(seq)
              if not len(seq)
          
        • 불리언 값에 등호(==)를 사용하여 비교하지 마십시오:

          Yes:   if greeting:
          
          No:    if greeting == True:
          
          Worse: if greeting is True:
          

            참고