들어가기
기존 c에서 사용하는 에러 반환은 errno을 사용하고 있다. 이는 시스템 내에서 사용하는 구조이다.
개발하는 프로그램에서 errno같은 에러처리 구조를 만들려고 한다.
작성자: http://ospace.tistory.com/ ,2011.11.08 (ospace114@empal.com)
요구 사항
- thread-safe
- 새로운 에러 코드 유지보수 쉽게함
- 에러 코드를 문자열로 변환
- 다국어 지원
Thread-safe
사용 방안으로 기존 errno을 재활용과 TLS(Thread Local Storage)을 사용 가능하다. 그리고, 반환 값 자체를 에러 코드로 사용한다.
기존 errno 사용
errno은 리눅스와 윈도우 둘 다 지원한다. 그러나 오래된 운영체제 버전에서는 thread-safe하지 않을 수 있다.
TLS 사용
Thread-safe 지원한다. 그러나 thread에 대한 구현이 되어 있어야 하며, 이식성을 위해서라면 같은 API을 제공해야함. 즉, 기존 thread 구현에 따른 영향을 받으며, 독립적으로 사용할 수 없다.
반환값 사용
반환 값에 에러 코드를 포함한다. 예를 들어 양의 정수라면 0은 성공이고 나머지는 에러 코드를 표시하는 형태이다. 혹은 그냥 정수라면, 0이상 양수이면 정상, 음수이면 에러 코드로 사용할 수 있다.
에러 코드 유지보수
유지보수를 위해 에러 코드 추가 및 변경이 쉽게 되어야 한다.
자신만의 에러 데이터 형을 정의해야 한다.
typedef return\_t int;
일반적으로 매크로를 사용하여 정의할 수 있다.
#define ERR_BASE 0
#define ERR_MEMORY (ERR_BASE+1)
이는 디버깅시 숫자로 표시되어 읽기 어렵다.
다른 방법으로 enum을 사용하여 정의한다.
enum ERR_TYPE { ERR_BASE = 0, ERR_MEMORY = ERR_BASE+1 };
추가로 "ERR_BASE+1"을 하는데 이는 에러 숫자 값이 언제든지 변경할 수 있게, 처음 시작 점을 별도로 사용. 그럼 시작점이 변경되면 다른 에러 값도 모두 변경이 된다.
이를 응용하면 여러 에러 코드 범위를 가질 수 있다. 예를 들어 네트워크, 운영체제, 그리고 자체 에러가 있다면, 아래와 같이 사용 가능하다.
enum ERR_TYPE { ERR_BASE_OS = 0, ERR_MEMORY = (ERR_BASE_OS+1), ... ERR_BASE_NET = 1000, ERR_CONNECT_LOSS = (ERR_BASE_NET+1), ... ERR_BASE_APP = 3000, ERR_FUNCTION_FAIL = (ERR_BASE_APP+1), ... };
물론 매크로를 이용할 수도 있다. 물론 주의할 부분은 에러코드가 배포 후에 변경은 신중해야 한다.
에러 코드를 문자열로 변환
에러 모드가 연속적인 숫자로 되어 있다면, 문자열 구조체로 간단하에 에러 메시지 출력 가능하다.
즉, 배열 인덱스를 에러 코드로 사용할 수 있다.
static const char * const err_msg_list[] = { "no error", "memory fail", .... };
char * error_str(return_t err) { return err_msg_list[err]; }
함수 구현이 간단하고, 검색하는데 상수시간이 걸린다.
이 방식은 에러 코드가 연속적이고, 값이 별로 없다면 관리하는데는 쉽다. 그러나 에러 값이 많아지기 시작하면, 중간에 에러 메시지 하나가 없어지거나 위치가 잘못되면, 모든 에러 메시지 결과가 틀려질 수 있다.
다름 방법으로 에러코드를 식별자로 같이 사용한다.
typedef struct err_msg {
const return_t err;
const char * const msg;
} err_msg_t;
구조체 초기화처럼 간단히 처리할 수 있다. 내부 변수가 상수형이기에 생성할 때 추가된다..
만약, 동적으로 에러코드를 추가하고 싶다면, 상수형은 제외하고, 에러 메시지 목록을 가변 메모리 영역으로 할당해서 사용하면 된다.
err_msg_t err_msg_list[] = { {ERR_MEMORY, "memory fail"}, ... {ERR_MAX, NULL} };
const char * error_str ( return_t err ) {
int i = 0;
while ( err_msg_list[i].err < ERR_MAX ) {
if ( err_msg_list[i].err == err )
return err_msg_list[i].msg;
++i;
}
return NULL; // 반환값은 임의 "unknown error"도 가능.
}
추가로 다른 에러 메시지도 같이 처리하는 경우, 예를 들어 앞의 운영체제, 네트워크, 자체 에러도 포함되어 있다면 아래와 같이 사용 가능하다.
const char* error_str ( return_t err ) {
if ( ERR_BASE_APP < err ) {
//자체 에러 메시지 검색
} else if ( ERR_BASE_NET < err ) {
//네크워크 에러 메시지 검색
} else {
//운영체제 에러 메시지 검색
}
}
다국어 지원
gettext를 사용을 추천한다.
결론
간단하게 에러처리는 구조를 살펴보았다. 다양한 형태의 에러 처리가 가능하고, 현대 프로그래밍 언어에서는 예외라는 에러 처리도 있다. 에러 처리가 아무렇지 않게 생각하고 처리할 수 있지만, 이 부분이 전체 프로그램에 대한 철학이 드러날 수 있는 부분이기도 한다. 생각하면 할 수록 생각할게 너무 많아진다. ^^;;; 모드 즐프하세요. ospace.
참조
[1] 박재성, 쓰레드별 전역변수 사용하기, http://ospace.tistory.com/213
'2.분석 및 설계' 카테고리의 다른 글
Mina로 본 네트웍 프레임워크 (0) | 2012.07.27 |
---|---|
MFC 메시지맵 구조 (0) | 2012.07.27 |
함수 호출과정 분석 (0) | 2011.01.12 |
코드 문서화 (0) | 2011.01.07 |
설치 패키지 작성시 고려사항 (0) | 2009.07.31 |