<div>안녕하세요. 달빛연구자 입니다.<br />저는 주 언어를 C를 사용하고 있는데요.<br /> 다른 고급언어들과 달리 비교적 단순한 문법을 갖고 있는 C는 스스로 엄격한 규칙을 가지고 짜지 않으면, 소스가 쉽게 엉켜버리는<br />특징을 가지고 있습니다.<br /> 이 글에서는 제가 코드를 짤 때 사용하는 규칙을 몇가지 뽑아서 소개하고자 하는데요.<br /> C언어를 사용하시는 분들께 많은 공감이 되었으면 좋겠습니다.</div> <div> </div> <div><strong>1. 하나의 함수가 오직 하나의 기능만을 수행할 때 까지 분리할 것</strong>.<br /> 함수를 만들 때 조건문을 사용하여, 하나의 함수가 여러가지의 기능을 하도록 만드는 경우가 있습니다.<br />이렇게요</div> <div>int foo(int a, int b,int c)<br />{<br /> if(c == 1){return a+b;}<br /> if(c == 2){return a-b;}<br /> return 0;<br />}</div> <div>이러한 형태는 함수의 재사용성과 가독성을 떨어뜨리게 되므로 사용하지 않는 것이 좋습니다. <br />대신 아래와 같이 고쳐주는 편이 좋습니다.</div> <div>int plus(int a,int b){return a+b;}<br />int min (int a,int b){return a-b;}</div> <div>만약 어떠한 사정으로 반드시 foo함수와 같은 형태로 써야만 한다면 foo라는 함수가 오직 분기의 기능만을 수행하도록<br />아래와 같이 foo를 작성해 주면 됩니다.</div> <div>int foo(int a,int b, int c)<br />{<br /> switch(c)<br /> {<br /> case 1: return plus(a,b);<br /> case 2: return min(a,b);<br /> default: return 0;<br /> }<br />}</div> <div><strong>2. 함수 내부에서 static 변수를 사용하지 말 것.</strong><br />int foo(void)<br />{<br /> static int a = 0;<br /> a++;<br /> return a;<br />}</div> <div>위와 같이 작성된 함수는 한번 호출될 때마다 1씩 증가된 값을 리턴합니다.<br />함수 내부에서 static 변수나 전역변수를 참조하는 것은 멀티쓰레드 동작시 재진입 문제가 발생하기 때문에, 가능한 피하는게 좋습니다.<br />또한 디버깅시 함수의 동작 뿐 아니라 상태까지 고려해 주어야 하는 어려움이 발생합니다.</div> <div>이러한 함수가 필요하다면, 아래와 같이 함수 외부에서 값을 받도록 바꾸어 주는 편이 좋습니다.</div> <div>void foo(int *a){*a = *a + 1;}</div> <div> </div> <div><strong>3. 함수의 출구를 하나로 묶을 것</strong><br />아래는 매우 흔한 형태의 메모리 누출의 예 입니다...<br />int foo(int c)<br />{<br /> int *a=NULL;<br /> a = (int*)malloc(sizeof(int)); <br /> if(c == 2){return -1;}<br /> free(a);<br /> return c;<br />}</div> <div>함수의 마지막에서 free를 잘 호출해 주었으나, c == 2 인 경우에는 free를 호출하지 못하고 함수가 끝나버리는데요..<br />이러한 실수가 실무에서도 상당히 많이 일어나며, goto를 활용하면 아래와 같이 바꿀 수 있습니다.</div> <div>int foo(int c)<br />{<br /> int res = 0;<br /> int *a=NULL;<br /> a = (int*)malloc(sizeof(int)); <br /> if(c == 2)<br /> {<br /> res = -1;<br /> goto END;<br /> }</div> <div> res = c;<br />END:<br /> free(a);<br /> return res;<br />}</div> <div>함수의 출구가 항상 하나로 묶여있기 때문에 함수가 종료될 때는 항상 free를 실행하게 됩니다.</div> <div> </div> <div><strong>4. 옳지 않은 조건을 먼저 소거할 것</strong><br />아래의 코드는 옳지 않은 조건을 소거하지 않는 코드의 예 입니다.<br />int foo(int c,int d)<br />{<br /> if(c == 1 || d == 3)<br /> {<br /> printf("ok");<br /> return 0;<br /> }<br /> return -1;<br />}<br />이것을 아래와 같이 바꾸라는 말 이에요.<br />int foo(int c,int d)<br />{<br /> if(c != 1){return -1;}<br /> if(c != 3){return -1;}<br /> printf("ok");<br /> return 0;<br />}</div> <div>코드를 이렇게 바꾸었을 때 이점이 있는데요. 그것은 바로 블럭의 깊이가 깊어지지 않는다는 점입니다.<br />대신에 코드가 아래로 쭉 길어지지만, 블럭이 깊어지는 것보다는 낫습니다.</div> <div>또한 이렇게 코드가 단순한 형태를 유지하며 아래로 쭉 길어지면 디버깅을 할 때 break point를 잡기가 수월해진다는 장점이 있고요.</div> <div>5. 일반적인 목적의 함수는 가능한 특수한 목적을 갖는 함수의 형태로 감싸서 사용할 것.<br />글로 써 놓으니 의미가 조금 애매할 수 있는데요. 예를 들자면 이런 겁니다.</div> <div>printf("[error message] %s",error_message);</div> <div>이러한 형태의 코드를 직접 사용하는 대신 아래와 같이 감싸서 사용하라는 의미입니다.<br />int error_print(const char *error_message)<br />{<br /> return printf("[error message] %s",error_message);<br />}<br />조금 소소해 보일 수 있지만, 이렇게 감싸놓은 코드는 유지보수에 감초같은 존재가 되어 줍니다.</div> <div> </div> <div><strong>6. 전역범위에서 static 을 적극적으로 활용할것.</strong><br />전역범위에서의 static 예약어는 전역변수와 함수가 해당 파일 밖에서 호출되는 것을 막아줍니다.<br />즉 함수 내부에서만 사용하는 전역변수와 함수들은 모조리 static을 붙여 선언해 주어야합니다.<br />그러면 파일은 아래와 같은 형태를 지니게 되죠..</div> <div>xxx.h<br /> void func_init(void);<br /> void func_run(void);<br /> void func_deinit(void);</div> <div>xxx.c<br />typedef struct { ... }xxx_t;<br /> static void func_a(void);<br /> static void func_b(void);<br /> static void func_c(void);</div> <div>즉 위와 같은 형태로 함수원형이 선언되었다면, 이 모듈을 사용하는 사람은 static으로 선언된 함수들에 대해서는 신경쓰지 않고,<br />오직 헤더에 선언된 함수들만을 사용해서 작업할 수 있게 됩니다. 사용자로에게 필요없는 함수와 변수를 은닉하는 것입니다.</div> <div>이와 같이 소스파일을 하나의 객체(Object)로 보고, 인터페이스 함수를 제외한 모든 부분을 다른 파일로부터 감추는 것이<br />C언어로 코드를 짤 때 가장 기본이 되는 요령 중 하나 입니다. 완전한 OOP는 아니되 비교적 OOP 비슷하게 짜지 않으면, 코드의 복잡도와<br />상호의존도가 급격하게 올라가 프로그램의 규모가 커질수록 고생을 하게 되기 때문이죠.</div> <div> </div> <div><strong>7. 숫자를 하드코딩할 때에는 반드시 enum이나 define으로 치환해서 사용할 것.</strong><br />예는 간단합니다.</div> <div>int a = 5;<br />이렇게 쓰는 대신<br />#define ARRAY_SIZE_MAX 5<br />int a = ARRAY_SIZE_MAX;</div> <div>이렇게 쓰라는 이야기죠. 이렇게 하는 이유는 크게 두 가지 인데요. 하나는 가독성을 위한 부분이 크고요.<br />나머지 하나는 같은 의미의 숫자가 여러 곳에서 쓰일 때 이것 하나만을 고치면 모든 부분에 적용되는 것을 기대하는 것 입니다.</div> <div>다른 사람이 짜 놓은 코드를 분석할 때 가장 곤란한 것 중 하나가 의미를 알수없는 상수입니다.<br />이것을 매크로로 치환해 놓으면, 매크로 자체가 상수의 의미를 설명하는 주석이 되어 줍니다.<br /> </div> <div>이번 글에서 소개할 내용은 이상 7가지 입니다.</div> <div>사실 이런식의 규칙은 제가 사용하는 것만 해도 수십개가 되어서 일단은 7개의 항목만을 정리해서 올렸는데요.</div> <div>개인적인 노하우기 때문에 제가 무조건 옳다는 법도 없고, 이런 규칙이 오히려 독이 되는 경우도 있을 것 입니다.</div> <div>그런 걸 발견하시면 댓글로 피드백을 주시면 감사하겠습니다.</div> <div> </div> <div>감사합니다.</div>