Linux Tutorial #3 리눅스 커널 코딩 스타일 2장

문연수·2021년 5월 15일
1

Linux Tutorial

목록 보기
4/25
post-thumbnail

5. 작명 (Naming)

C 는 스파르타 언어이기에 너는 작명 관습을 따라야 한다. Modula-2Pascal 프로그래밍 언어와 달리, C 프로그래머는 ThisVariableIsATemporaryCounter 와 같은 뽑내는 이름을 사용하지 않는다. C 프로그래머는 이러한 변수를 tmp 로 부를 것이며, 이는 더 작성하기 쉽고 조금도 이해하는 것이 어렵지 않다.

그러나, 전역 변수에 대한 서술적으로 묘사된 이름은 필수적이나, 대소문-혼합 이름은 눈살을 찌푸리게 만든다. 전역 함수를 foo 로 부르는 것은 총격 사건(shooting offense)이다.

전역 변수(네가 정말 이를 필요로 할때만 사용되어져야 하는)는 전역 함수가 그러하듯, 서술적인 이름이 필요하다. 만일 네가 활성화된 유저를 세는 함수를 필요로 한다면, 너는 이를 count_active_users() 혹은 그와 비슷한 것으로 불러야지, cntusr() 와 같이 불러선 안된다.

기능의 종류를 이름 안에 부호화(헝가리안 표기법이라 불리우는)하는 것은 터무니없는(asinine) 짓이다. 컴파일러는 어쨌든 그 자료형을 알고 이를 확인할 수 있기에, 이는 오직 프로그래머만을 혼란스럽게 한다.

지역 변수 이름은 짧아야 하며, 특징적이어야 한다. 만일 네가 어떤 난수 반복 카운터가 필요하다면, 이는 아마도 i 로 불릴 것이다. 여기에 이것이 오해되어질 어떠한 가능성이 있지 않다면, 이를 loop_counter 라 부르는 것은 비-생산적이다. 유사하게, tmp 는 임시의 값을 유지하는, 거의 모든 자료형의 변수가 될 수 있다.

만일 네가 너의 지역 변수 이름을 혼합해서 사용하는 것이 두렵다면, 너는 함수-성장-호르몬-불균형 증후군(function-growth-hormone-imbalance syndrome) 이라 불리는 또 다른 문제를 가진다. 7장 (함수들(Functions))을 보라.

기호 이름과 문서에서, master / slave (혹은 master에 독립적인 slave) 그리고 blacklist / whitelist 의 새로운 사용 도입을 피해야 한다.

master / slave 를 위해 추천되는 대체재:
`{primary, main} / {secondary, replica, subordinate}`
`{initiator, requester} / {target, responder}`
`{controller, host} / {device, worker, proxy}`
`leader / follower`
`director / performer`

blacklist / whitelist 를 위해 추천되는 대체재:
`denylist / allowlist`
`blocklist / passlist`

새로운 사용 도입의 예외는 ABI/API 를 유지하는 것과, 혹은 기존 하드웨어(2020년의)나 이러한 용어를 요구하는 규약 설명서(protocol specification)에 대한 코드를 업데이트할 때이다. 새로운 설명서에 대해서는 가능하면 용어에 대한 명세서의 사용을 커널 코딩 표준으로 옮겨라.

6. 자료형 재정의 (Typedefs)

제발 vps_t 와 같이 사용하지 마라. 이는 구조체와 포인터의 자료형 재정의에 대한 오류이다. 네가 만일

vps_t a;

를 코드 내에서 보았다면, 이게 무엇을 의미하는가? 대조적으로, 만일 아래와 같이 작성했다면

struct virtual_container *a;

너는 실제로 a 가 무엇을 말하는지 알 수 있다.

수 많은 사람들은 자료형 재정의가 가독성에 도움을 준다고 생각한다. 그렇지 않다. 그들은 오직 다음의 상황에서만 유효하다:

  • a. 전적으로 불투명한 객체 (재정의가 전적으로 오브젝트가 무엇인지 숨기기 위해 이용될 때)
    예를 들어: pte_t 등. 네가 오직 적합한 접근자 함수(accessor function)를 사용해야만 접근 가능한 불투명 객체.

    주의
    불투명성과 접근자 함수는 그 자체는 좋은 생각이 아니다. 우리가 pte_t 등을 위해 그들을 사용해야 하는 이유는 여기엔 호환성있게 접근 가능한 정보가 전혀 없기 때문이다.

  • b. 추상화가 이것이 int 혹은 long 인지에 대한 혼란을 피하는 것을 도와주는 명확한 정수 자료형들.
    비록 이는 여기보단 범주 (d) 에 더 가깝지만, u8/u16/u32 는 완벽하게 괜찮은 타입 재정의이다.

    주의
    다시 말하자면, 여기에는 그것에 대한 어떠한 이유가 필요하다. 만일 무엇이 unsgined long 이라면, 거기에는 그래야만 하는 어떠한 이유도 없다.

    typedef unsigned long myflags_t;

그러나 만일 여기에 왜 이러한 상황 아래에서 이것이 unsgined int 이어야 하는지 그리고 왜 이러한 설정 아래에서 unsigned long 이어야 하는지 대한 명백한 이유가 있다면, 무슨 수를 써서라도 진행해라, 그리고 자료형 재정의를 사용해라.

  • c. 만일 네가 말 그대로 자료형 확인을 위한 새로운 자료형을 만들어서 드문 드문 사용할 때.
  • d. 특정한 예외적 상황에서, C99 표준 자료형과 동일한 새로운 자료형.
    비록 눈과 뇌가 uint32_t 와 같은 표준 자료형에 익숙해지는데 짧은 시간이 소요될지라도, 일부 사람들은 이러한 사용에 반대한다.
    따라서 비록 너의 새 코드에서는 의무적이지 않지만 리눅스는 표준 자료형과 동일한, 지정된 u8/u16/u32/u64 자료형과 그들의 부호있는 등가물을 허용한다.
    만일 이미 다른 집합을 사용하는 기존 코드를 수헝할 때에는, 기존 코드의 선택에 순응해야 한다.
  • e. 사용자 공간에서 안전해야 하는 자료형.
    사용자 공간에서 보이는 특정 구조체의 경우, 우리는 C99 자료형을 필요로 하지 않고 위에서 사용한 u32 를 사용할 수 없다. 따라서, 우리는 __u32 그리고 모든 구조체에서 사용자 공간에서 공유되는 유사한 자료형을 사용한다.

아마 여기에는 또 다른 경우가 있을 수 있으나, 규칙은 기본적으로 네가 명백하게 이러한 규칙들 중 하나와 일치시키지 않는한 자료형 재정의를 무조건 절대 사용하지 않는 것이다.

일반적으로, 합리적으로 직접 접근되어질 수 있는 원소를 가지는 포인터, 혹은 구조체에 대해 절대 자료형 재정의를 하지 말아야 한다.

7. 함수들 (Functions)

함수들은 짧고 어떠한 불순물도 함유하지 않아야(sweet) 하며, 오직 하나의 일만 해야 한다.
그들은 하나 혹은 두 문자 화면(우리 모두가 알다시피, ISO/ANSI 화면 크기는 80x24 이다)에 맞아야 하며, 하나의 일만을 하며 그 일을 제대로 수행해야 한다.

함수의 최대 길이는 복잡성과 함수의 들여쓰기 수준에 역으로 비례한다. 따라서, 만일 네가 하나의 단순히 긴(그러나 간단한) 조건 구문을 가지는 개념적으로 간단한 함수를 가진다면, 네가 매우 만흥ㄴ 작은 일들을 가각의 경우에 따라 수행한다면, 함수가 길어도 괜찮다.

그러나, 만일 네가 복잡한 함수를 가진다면, 그리고 재능이 없는(less-than-gifted) 고등학교 1학년 학생(first-year-high-school student)이 이 함수가 무엇에 대한 것인 못 알아챌 것 같다고 느낀다면, 너는 모든 것을 최대 제한에 더 가깝게 준수해야 한다. 서술적인 이름을 가지는 도움말 함수(만일 네가 이것이 수행 속도에 치명적이라는 생각이 든다면 너는 컴파일러에게 이를 인라인할 수 있는지 물을 수 있으며, 이는 아마도 기존에 해왔던 것보다 더 잘 해낼 것이다)를 사용하라.

또 다른 함수의 측정은 지역 변수의 개수이다. 그들은 절대 5-10 을 넘어선 안되며, 만일 그렇다면 너는 무언가 잘못된 것이다. 함수를 다시 생각하라, 그리고 더 작은 조각으로 쪼개라.

사람의 뇌는 일반적으로 7개의 다른 것들을 쉽게 추적할 수 있으나 그 이상은 혼란을 가져온다. 너는 네가 훌륭하다는 것을 알지만, 아마 2주 전에 네가 무엇을 했는지를 이해하고 싶을 수도 있다.

소스 파일 내에서, 함수들은 하나의 공백 행으로 분리된다. 만일 함수가 수출된다면, 이를 위한 EXPORT 매크로는 함수의 닫는 중괄호 앞에 바로 나타나야 한다. 예를 들자면:

int system_is_up(void)
{
	return system_state == SYSTEM_RUNNING;
}

함수 프로토타입에서, 그들의 자료형과 함께 매개변수 이름을 포함해야 한다. 비록 이를 C 언어에서 필요치 않을 지라도, 이는 리눅스에서 선호 되어진다.

함수 프로토타입에 extern 키워드를 사용하지 마라. 이는 행을 더 늘리며, 꼭 필요하지 않기 때문이다.

8. 함수의 탈출을 중앙화하라. (Centralized exiting of functions)

비록 일부 사람들로부터 비추천되었지만, goto 문의 등가물은 컴파일러에 의해 무조건 분기문의 형태로써 흔히 사용되어진다.

goto 문은 여러 구간에서 함수를 탈출할 때 그리고 cleanup 과 같은 일부 동일한 작업이 이뤄져야 할 때 유용하다. 만일 여기에서 어떠한 cleanup 도 필요치 않다면 그냥 바로 반환하면 된다.

goto 가 무엇을 하는지 그리고 왜 goto 가 존재하는지 알리는 레이블 이름을 선택하라. out_free_buffer 는 하나의 좋은 이름의 예시가 될 수 있다: 만일 gotobuffer 를 해제한다면.
err1: 그리고 err2 와 같은 GW-BASIC(역자 주: GW-BASICMicrosoft에서 개발한 BASIC 언어와 비슷한 프로그래밍 언어이다)이름을 사용하는 것을 피하라. 만일 네가 한번이라도 경로를 더하거나 제거한다면 너는 그들의 번호를 재지정해야만 하고, 어쨌든 이들은 수정을 어렵게 만든다.

goto 사용의 관례는 다음과 같다:

  • 무조건 분기문이 쉽게 이해되어질 수 있고, 따라갈 수 있다.
  • 중첩이 줄어들게 된다.
  • 코드를 수정했을 때 각각의 종단지점을 갱신하지 않아 발생하는 에러를 예방한다.
  • 어쨌든 불필요한 코드를 최적화하는 컴파일러의 할 일을 줄여라.
int fun(int a)
{
	int result = 0;
	char *buffer;
   
	buffer = kmalloc(SIZE, GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	if (condition1) {
		while (loop1) {
			...
		}
		result = 1;
		goto out_free_buffer;
	}
	...
out_free_buffer:
	kfree(buffer);
	return result;
}

알아차려질 수 있는 흔한 형태의 버그는 one err 버그이다. 이는 다음과 같이 생겼다:

err:
	kfree(foo->bar);
	free(foo);
	return ret;

이 코드의 버그는 일부 종단 경로에 대해 fooNULL 인 것이다. 일반적으로 이러한 에러의 수정은 이를 err_free_bar: 그리고 err_free_foo: 두 개의 에러 레이블로 나누는 것이다:

err_free_bar:
	kfree(foo->bar);
err_free_foo:
	kfree(foo);
	return ret;

이상적으로 너는 모든 에러 경로에 대한 에러 모의 실험을 해야 한다.

9. 주석 달기 (Commenting)

주석은 좋으나 과도한-주석달기(over-commenting)는 또한 위험하다. 절대 어떻게 너의 코드가 동작하는지를 주석에서 설명하려 들지 마라: 작동 방식을 명백하게 하여 코드를 작성하는 것이 더 좋고, 나쁘게 작성된 코드를 설명하는 것은 시간의 낭비이다.

일반적으로, 너는 네 코드가 하는 일이 어떻게(HOW) 가 아닌 무엇(WHAT)인지 설명하고 싶을 것이다. 또한, 함수 몸체 안에 주석을 삽입하는 것을 피하라: 만일 함수가 너무 복잡하여 네가 각각에 대해 주석을 달기 원한다면, 너는 아마도 잠시동안 6 장으로 돌아가야 할 것이다.

너는 영리하게 (혹은 추악하게) 특정한 무언가에 작은 주석으로 경고하거나 주의를 줄 수 있지만, 과잉은 금물이다.
대신, 사람들에게 이것이 무엇인지 말하고, 가능하면 왜 이것이 그래야 하는지에 대해 함수의 머리에 주석을 달 수 있다.

커널 API 함수에 주석을 달 때에는, kernel-doc 형식을 사용하길 바란다.

선호되는 긴(다중 행) 주석의 양식은 다음과 같다:

 /*
 * This is the preferred style for multi-line
 * comments in the Linux kernel source code.
 * Please use it consistently.
 *
 * Description: A column of asterisks on the left side,
 * with beginning and ending almost-blank lines.
 */

net/drivers/net/ 파일 내에서 선호되는 긴(다중 행) 주석의 양식은 조금 다르다.

  /* The preferred comment style for files in net/ and drivers/net
 * looks like this.
 *
 * It is nearly the same as the generally preferred comment style,
 * but there is no initial almost-blank line.
 */

기본 자료형이든 파생 자료형이든 무관하게 데이터에 대해 주석을 추가하는 것 역시 중요하다. 이를 위해, 각 행에 하나의 데이터만을 선언하라 (다중 데이터 선언을 위해 콤마를 사용하지 마라).

10. 네가 그걸 엉망으로 만들어 놨어 (You've made a mess of it)

괜찮다. 우리도 그렇게 한다. 너는 너의 오랜 유닉스 사용자 도우미인 GNU emacs 가 너를 위해 자동으로 C 소스를 서식화한다는 것을 들어왔을 것이고, 이것이 그렇게 한다는 것을 눈치 챘을 것이다. 그러나 기본 설정을 사용하는 것은 바람직하지 않다 (사실상, 이는 임의 입력보다 더 나쁠 수 있다 - 셀 수 없이 많은 수의 원숭이들이 GNU emacs 를 통해 작성하는 것은 절대 좋은 프로그램을 만들 수 없다).

따라서, 너는 GNU emacs 를 제거하거나, 제정신인 값으로 변경할 수 있다. 후자의 것을 하기 위해서, 너는 다음의 .emacs 파일을 따라야 한다:

defun c-lineup-arglist-tabs-only (ignored)
  "Line up argument lists by tabs, not spaces"
  (let* ((anchor (c-langelem-pos c-syntactic-element))
         (column (c-langelem-2nd-pos c-syntactic-element))
         (offset (- (1+ column) anchor))
         (steps (floor offset c-basic-offset)))
    (* (max steps 1)
       c-basic-offset)))

(dir-locals-set-class-variables
 'linux-kernel
 '((c-mode . (
        (c-basic-offset . 8)
        (c-label-minimum-indentation . 0)
        (c-offsets-alist . (
                (arglist-close         . c-lineup-arglist-tabs-only)
                (arglist-cont-nonempty .
                    (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only))
                (arglist-intro         . +)
                (brace-list-intro      . +)
                (c                     . c-lineup-C-comments)
                (case-label            . 0)
                (comment-intro         . c-lineup-comment)
                (cpp-define-intro      . +)
                (cpp-macro             . -1000)
                (cpp-macro-cont        . +)
                (defun-block-intro     . +)
                (else-clause           . 0)
                (func-decl-cont        . +)
                (inclass               . +)
                (inher-cont            . c-lineup-multi-inher)
                (knr-argdecl-intro     . 0)
                (label                 . -1000)
                (statement             . 0)
                (statement-block-intro . +)
                (statement-case-intro  . +)
                (statement-cont        . +)
                (substatement          . +)
                ))
        (indent-tabs-mode . t)
        (show-trailing-whitespace . t)
        ))))

(dir-locals-set-directory-class
 (expand-file-name "~/src/linux-trees")
 'linux-kernel)

이는 emacs~/src/linux-trees 아래에 있는 C 파일에 대해 커널 코딩 양식으로 더 좋게 만들 것이다.

그러나 비록 네가 제정신의 양식을 가지는 것에 실패하였더라도, 모든 것을 잃은건 아니다: indent 를 사용하라.

이제, 다시, GNU 들여쓰기는 GNU emacs 처럼 뇌사-상태(brain-dead)의 설정을 가지기에 네가 일부 명령 줄 설정을 제공하는 것이 필요하다. 그러나, 이것이 그렇게 나쁜 것은 아니다. 왜냐하면 GNU indent 의 제작자가 K&R 의 권위를 인정했기 때문에 (GNU 사람들이 악마인 것은 아니다. 그들은 단지 이 문제에 대해 심각하게 잘못 이해하고 있을 뿐이다), 너는 -kr -i8 (K&R8 문자 들여쓰기의 약어) 의 들여쓰기 설정을 줄 수 있고, 혹은 최신의 들여쓰기 양식인 scripts/Lindent 를 사용할 수 있다.

indent 는 수많은 설정을 가지고, 특히 이것이 주석 재-형식화(re-formatting)와 만났을 때, 너는 아마도 메뉴얼 페이지를 보고 싶어질 것이다. 그러나 기억하라: indent 는 잘못된 프로그래밍 습관을 고치지 않는다.

너는 또한 이러한 규칙들로부터 너의 코드 일부를 자동으로 빠르게 재-형식화하고, 전체 파일 안의 코딩 양식 실수를 재검토하고, 오타 그리고 가능한 개선점에 대한 도움을 줄 clang-format 도구를 사용할 수 있다는 것에 주목하라.

이는 또한 #includes 를 정렬하고, 변수와 매크로를 줄 세우고, 화면에서 텍스트가 차지하는 공간을 조정하고 또 다른 유사한 작업들이 편리하다. 더 자세한 사항을 위해 clang-format 파일을 보라.

profile
2000.11.30

0개의 댓글