2011-11-14 2 views
1

기본적으로 큰 main() 함수로 구성된 일부 C 코드가 전달되었습니다. 나는 이제 메소드를 더 작은 함수로 전개하여 코드의 의도를 명확하게하려고 노력 중이다. 그래도 몇 가지 문제가 있어요 :절차 코드 리팩터링시 오류 처리

void main(int argc, char *argv[]) 
{ 
    if(argc != 3) 
    { 
     printf("Usage: table-server <port> <n_lists>\n"); 
     return; 
    } 
    int port = atoi(argv[1]), n_lists = atoi(argv[2]); 
    if(port < 1024 || port > 49151 || n_lists < 1) 
    { 
     printf("Invalid args.\n"); 
     return; 
    } 
    signal(SIGPIPE, SIG_IGN); 
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    struct sockaddr_in s_addr; 
    s_addr.sin_family = AF_INET; 
    s_addr.sin_port = htons(port); 
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    if(bind(sockfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0) 
    { 
     printf("(bind).\n"); 
     return; 
    } 
    if(listen(sockfd, SOMAXCONN) < 0) 
    { 
     printf("(listen).\n"); 
     return; 
    } 

이 코드의 기능에 4 주 문제를 식별 할 수 있습니다

  1. 는 인수의 수를 확인하는 것은 맞습니다.
  2. 명령 줄 인수에서 포트를 가져옵니다.
  3. 신호 호출 중 (SIGPIPE, SIG_IGN).
  4. 실제로 소켓과의 연결을 시도하십시오.

작은 함수로 리팩토링하려고 할 때의 문제는 주로 오류 처리와 관련이 있습니다.

int verify_number_of_args(int argc) { 
    if (argc != 3) { 
     printf("..."); 
     return -1; 
    } 
    return 0; 
} 

를하고 실제로 그렇게 나쁜되지 않습니다

if (verify_number_of_args(argc) == -1) return; 

같은 것입니다 전화 : 예를 들어, 다음과 같을 것 (1)의 논리를 추출하려고 r에. 이제, 소켓, 그 모두 sockfds_addr가 반환 될 필요로하는 방법이 더 귀찮은 것, 플러스 상태 반환 값 : 가지로 내 주요 방법을 유지하기 위해 노력의 목적을 패배

int sockfd; 
struct sockaddr_in* s_addr; 
if (create_socket(port, &sockfd, s_addr) == -1) 
    return; 

가능한 간단하고 명확합니다. 나는 물론 .c 파일의 전역 변수에 의존 할 수는 있지만 그렇게 좋은 아이디어는 아닙니다.

일반적으로 C에서 이런 종류의 작업을 어떻게 처리합니까?

+0

"오류 처리"태그를 추가하고 제목을 편집했습니다. '[error-handling] [c]'에 대한 StackOverflow를 검색하십시오. –

+0

@Catcall : 감사합니다! –

답변

3

다음은 간단한 접근 방식입니다.

인수 구문 분석 및 관련 오류 검사는 main 님의 관심사이므로 main이 극도로 긴 경우를 제외하고는 그 부분을 분리하지 않았습니다. 오류로

int main(int argc, char *argv[]) 
{ 
    // handle arguments 

    return serve(port, n_lists); 
} 

int serve(int port, int n_lists) 
{ 
    // do actual work 
} 

:

실제 작업은, 프로그램의 네트워킹 부분 즉, 제대로 구문 분석 및 검증 소요 인수를 제외하고, main과 매우 유사 함수에 떨어져 분할 할 수 있습니다 handling :이 코드가 라이브러리가 아닌 경우, 호출 체인이 아무리 깊숙이 있더라도 함수가 잘못되었을 때 호출 프로세스를 죽이는 것만으로 도망 갈 수 있습니다. 그것은 실제로 권장되는 연습입니다 (Kernighan & Pike, 실무 프로그래밍). 일관된 오류 메시지를 내기 위해

void error(char const *details) 
{ 
    extern char const *progname; // preferably, put this in a header 

    fprintf(stderr, "%s: error (%s): %s\n", progname, details, strerror(errno)); 
    exit(1); 
} 

과 같은 실제 오류 인쇄 루틴을 제외하십시오. (리눅스와 BSD에서 err(3)을 확인하고 다른 플랫폼에서이 인터페이스를 에뮬레이션 할 수 있습니다.)

간단히 잘못 될 수없는 작업을 제외 시키거나 쉽게 재사용 할 수있는 구성 요소를 만들기 때문에 바보 같은 설정으로 몇 가지 시스템 호출을 호출 할 수도 있습니다.

1

그대로 둘 수 있습니까? 메인 시작시 약간의 설정이 문제가되지 않습니다. IMO. 일이 설정되면 리팩토링을 시작하십시오.

1

리팩토링을 위해 리팩터링하는 신호가 아닙니까? 대한 어쨌든

, 당신은 항상 구조를 만들고, 그것의 포인터 전달할 수 있습니다, "이제 한 번에 sockfd와와의 s_addr를 초기화하자"그런

struct app_ctx { 
    int init_stage; 
    int sock_fd; 
    struct sockaddr_in myaddr; 
    ... 
} 

당신의 인스턴스에 대한 포인터를 전달을 이 구조는 모든 "한 번에 하나씩"기능을 수행하고 오류 코드를 반환합니다.

정리 시간에는 동일한 작업을 수행하고 같은 구조를 전달합니다.

+0

리팩토링을위한 것이 아닙니다. 이 함수는 기본적으로 150 줄의 if와 elses를 엮어서 만든 것과 같습니다. –

+0

@devouredelysium이 맞을 수도 있습니다. 때로는 프로그램 세그먼트에 이름을 부여하기 위해 몇 가지 기능을 도입하는 것이 유용합니다. –