2010년 12월 30일 단상들

단상들 2010. 12. 31. 00:37 posted by whiterock
  • 회사 사무실의 내 자리 이동 완료. 올해 들어 이게 몇 번째인지 기억도 안난다. ㅡ,.ㅡ;; 이번 자리 컨셉은 집중할 수 밖에 없는 환경을 만드는 것. 잡다한 것들을 눈에 보이지 않게 치운다. ㅡ,.ㅡ;(자리이동) 2010-12-30 13:40:43
  • 저자분의 내공이 책에서 자연스럽게 흘러 내린다. C 언어를 오래동안 사용해왔지만, 몰랐던게 많았다는 것을 느끼게 해주는 책.(me2book C 언어 펀더멘탈 : 견고한 프로그램을 위한 기본 원리) 2010-12-30 19:16:29
    C 언어 펀더멘탈 : 견고한 프로그램을 위한 기본 원리
    C 언어 펀더멘탈 : 견고한 프로그램을 위한 기본 원리

이 글은 whiterock님의 2010년 12월 30일의 미투데이 내용입니다.

사내 Review 시스템 상에서 if 문 관련된 코딩 스타일에 대해서 의견을 확인하는 기회가 있었다. "처리할 것이 없는 데, 불필요하게 코드가 추가 되는 것이 아니냐?"라는 의견이 있어 다른 분들의 의견을 묻는 것이었다.


if ( a == b ) 
{
    a++;
} 
else
{ 
    /* do nothing */ 
}

이런 스타일에 부정적인 의견들은 다음처럼 나왔습니다.
  • 필요성을 못 느낀다. 따라서, 불필요한 코드가 많이 차지한다.
  • 신입 개발자들이 엄격한 코딩 스타일에 대한 거부 반응이 있다.
  • 엄격한 코딩 스타일때문에 자유로운 생각에 방해가 된다.
  • 깊은 수준의 indentation을 유발한다.

다음과 같은 긍정적인 의견들이 나왔습니다.
  • 실수를 줄이는 데 도움이 된다.
  • 가독성을 높여준다.
  • else { /* do nothing */ }을 보면 다시 코드를 의심하고 본다.

인원 수로 보면 저를 포함해서 긍정적으로 생각하시는 분들이 많았습니다.

어떻게들 생각하시나요?

C에서 실수를 줄이는 코딩 스타일

프로그래밍 2010. 1. 14. 22:42 posted by whiterock
프로그램의 소스 코드는 사람이 작성을 한다. 다른 작업들도 그렇겠지만, 사람들은 상황에 따라서 실수들을 종종한다. 따라서 실수를 가능한 하지 않게 여러가지 방법들을 이용하여 보완을 하는 게 현실이다. 경험적으로 C 언어를 종종 사소한 실수를 많이 했던 것들에 대해서 보완하는 방법 중 한가지가 코딩 스타일이다. 그 중 몇 가지를 정리를 해본다.

조건절 비교

조건절에서 상수 값과 변수의 값이 일치하는지 비교할 때, 대부분의 예제 코드들 보면 이런 스타일로 나온다.

[code c] if (a == 1) { a++; } [/code]


간혹 실수로 "==" 대신 "="를 쓰는 경우가 있다. 컴파일 옵션으로 경고 수준을 높이지 않으면 경고로도 나오지 않는다. 경고 수준을 높이고 가능한 모든 경고는 제거하는 습관은 말 안해도 기본이다.

이 경우는 다음과 같은 스타일로 코딩을 하면 실수를 문법 에러로 잡아낼 수 있다.

[code c] if (1 == a) { } [/code]

if .. else .. 문

디버깅을 하다 보면 심심치 않게 실수를 하게 되는 부분이 if ... else ... 관련된 부분이다. C언어 메뉴얼에서도 if else 관련 주의 사항으로 {} 없는 경우에 대한 얘기가 나온다. 수 많은 코드를 눈으로 그냥 훑고 지나가다 보면 모양만 유심히 보다가 놓치게 되는 경우가 많다. 다음 경우 같은 경우가 되겠다.

[code c] if (1 == a) a++; a++; [/code]

그리고, 디버깅하다가 간혹 보게되는 실수인 경우가 조건을 만족하지 않았을 때, 해줘야 할 것들을 빠뜨리는 경우가 있는 것을 보게 된다. 그래서 거짓일 때 처리할 게 없다는 것을 명확히 표시를 해 주는 것도 실수를 줄이는 데 도움이 된다.

그래서 if 문 관련 코딩 스타일로 다음 형태로 명확하게 블럭으로 묶어 주고, 처리할 게 없다는 것을 명확하게 표시를 해준다.

[code c] if (1 == a) { a++; } else { /* do nothing */ } [/code]

Return 값 처리 무시 표시

라이브러리들의 함수들을 보면 대부분 처리 결과에 따라 Return 값을 넘겨준다. 이런 결과들을 제대로 처리를 해주는 것 또한 튼튼한 프로그램을 만드는 것에 대한 기본이다. 그러나 때로는 실행에 별다른 영향이 없어서 이 Return 값을 무시할 수 도 있다. 그래서 의도적으로 무시를 한다는 표시로 (void)를 함수 앞에 붙여준다.

[code c] (void)close(fd); [/code]

( )를 이용하여 명확한 연산자 우선 순위 표시

10년 넘게 C 언어를 사용해 왔지만, 아직도 연산자 우선 순위가 아리까리 하다. 간혹 이런 연산자 우선 순위를 잘 못 알고 있어, 디버깅 하다 보면 의도하지 않게 동작하는 코드들을 종종 만나기도 한다. 그래서 가장 높은 우선 순위를 가진 ( )를 이용하여 명확하게 한다.

[code c] a = (a + (a * 2)) / 2; [/code]


그 외에도 더 있겠지만, 지금 생각나는 것들만 정리를 해보았다. 핵심은 명확하게 표현을 하는 것이다.
"C 함수에서 2개의 return 문을 사용하게 제약한 코딩 스타일" 얘기한 함수 구조를 갖추기 위해서 예외처리 관련 매크로를 사용하고 있다.

이 예외처리 매크로는 goto 문을 이용하여 만들어 졌다. C로 구현된 매크로들을 Google에서 검색해보면 setjmp(), longjmp() 같은 것을 이용한 것들도 보이나, C 언어에서 제공되는 goto 만으로 만들어 사용하는 것이 이해하기 쉽고, 다양한 환경에서 무난하게 돌아가는 장점이 있다.

회사에서 사용하는 것보다 좀더 단순화시켜서 비슷하게 만들어 보겠다. C++이나 Java에서 사용하는 try { } catch { } 같은 스타일은 아니고, 크게 예외 판단 매크로들, 예외를 받는 매크로들로 구성이 된다.

직접 보는게 제일 이해가 빠를 것이다. 다음 프로그램은 "hello.txt"라는 파일에 "hello world"를 출력하는 프로그램이다.
[code c]/* * http://blog2.thewhiterock.net/ */ #include <stdio.h> #include <errno.h> #include "exception.h" int main(int argc, char **argv) { FILE *fp = NULL; int result = 0; fp = fopen("hello.txt", "w"); EXCEPTION_TEST_RAISE(fp == NULL, LABEL_OPEN_FAIL); result = fprintf(fp, "hello world\n"); EXCEPTION_TEST_RAISE(result < 0, LABEL_WRITE_FAIL); result = fclose(fp); EXCEPTION_TEST_RAISE(result != 0, LABEL_CLOSE_FAIL); return 0; EXCEPTION(LABEL_OPEN_FAIL) { (void)printf("Fail to open file 'hello.txt' ( %d )\n", errno); } EXCEPTION(LABEL_WRITE_FAIL) { (void)printf("Fail to write file 'hello.txt' ( %d )\n", errno); } EXCEPTION(LABEL_CLOSE_FAIL) { (void)printf("Fail to close file 'hello.txt' ( %d )\n", errno); } EXCEPTION_END; return -1; }[/code]

예외를 검사하는 EXCEPTION_TEST_RAISE() 매크로와 예외를 받는 EXCEPTION(), EXCEPTION_END 매크로들이 보인다. main() 함수의 구조를 보면 상단에는 주요 로직이 위치해 있고, 하단에는 예외 처리 로직이 위치한 형태를 가진다.

이 매크들이 구현된 것 또한 소스 코드를 직접 보는 것이 이해가 빠를 것이다. 다음은 exception.h 소스 이다.
[code c]/* * http://blog2.thewhiterock.net/ */ #ifndef __EXCEPTION_H__ #define __EXCEPTION_H__ #define EXCEPTION(label_name) \ goto EXCEPTION_END_LABEL; \ label_name : #define EXCEPTION_END \ EXCEPTION_END_LABEL: \ do \ { \ } while (0) #define EXCEPTION_TEST(expression) \ do \ { \ if (expression) \ { \ goto EXCEPTION_END_LABEL; \ } \ else \ { \ } \ } while (0) #define EXCEPTION_TEST_RAISE(expression, label_name) \ do \ { \ if (expression) \ { \ goto label_name; \ } \ else \ { \ } \ } while(0) #define EXCEPTION_RAISE(label_name) \ do \ { \ goto label_name; \ } while(0) #endif /* __EXCEPTION_H__ */[/code]

지금 회사에서는 함수 안에서 return 문을 딱 2개만 쓸 수 있게 코딩 스타일로제한을 했다. 그래서 대부분 구현된 함수 구조를 보면 다음과 같다.

function( ) 
{ 
    /* 로직 구현 부분 */ 

    return 성공을 나타내는 코드; 

    /* 예외 처리 부분 */ 

    return 에러를 나타내는 코드; 
}

이런 형태로 제약을 하면서 실행 흐름을 단순하게 하여 가독성을 높이고, 버그를 줄이는 효과가 있다.

이런 형태로 구현될 수 있게 도와주는 예외 처리를 위한 매크로들이 있는데, 자세한 내용은 다른 포스트로 정리를 하겠다. 예외 상황 조건을 충족하면 예외 처리 부분으로 넘어가게 goto 문을 이용한 매크로라는 것만 알면 된다.
간만에 시간을 투자하여 꼼꼼히 보는 책이다.  구입은 KLDP 공동구매를 이용해 한달 전 쯤에 했지만, 3, 4월 어학원을 가지 않음으로 시간이 생겨 이제서야 보고 있다. 간만에 제대로 보는 전공 기술 서적이라 재미있게 잘 보고 있다.

Linux 내부 동작에 대한 부연 설명을 통해, Linux 상에서 System Programming을 할 때 보다 효율적으로 코딩을 할 수 있게 도와 준다. 보통 Linux 상에서 시스템 프로그래밍을 할 때, W. Richard Stevens의 책 들을 주로 참고를 했었다. "UNIX Network Programming", "Advanced Programming in the UNIX environment" 책을 항상 옆에 두고 참고 하였다. 필요할 때에 몇몇 부분을 참고하는 방식이었고, 또한 이 책들에서는 Linux 환경에 대한 내용은 없다.

코딩 하면서 흔하게 자주 사용하는 함수 였지만, 책을 보다 보면 아하! 그래서 그랬구나 하는 것들도 많이 눈에 들어온다. 예를 들면 fgetc() 함수의 return 타입이 int 인 이유 같은 것을 알게 되었다. C에서 문자열은 보통 char 타입으로 사용이 되는데, fgetc() 함수의 결과인 읽은 문자의 return 타입이 int 타입이다. 그 이유는 문자를 읽다가 발생하는 오류 상황에 대한 값을 넘겨 주기 위함이다. char 타입은 0x0 ~ 0xff 까지 이기 때문에, char 타입으로는 그 외 오류 상황을 나타내는 값을 추가적으로 정의 할 수 없다. 그래서 char 타입보다 더 많은 값을 저장할 수 있는 int 타입으로 함수를 정의했다고 한다.

"아는 만큼 보인다"고 하는 말 처럼, 같은 내용을 보더라도 예전에는 눈에 잘 안들어 오던 것들이 눈에 잘 들어 온다. 때로는 몇 년전에는 이해 못 했을 내용도 이해가 되기도 한다. 그러나, 어떤 것들은 알면 알수록 모르는 것들이 꼬리를 물고 나오기도 한다 ㅡ,.ㅡ;;;

여럿이 같이 보면서 얘기를 하고 싶지만, 상황이 그렇지 못해 좀 아쉽다. Linux 기반에서 시스템 프로그래밍을 하는 사람이면 한 번쯤 읽어 보라고 권하고 싶은 책이다.