2011-11-14 93 views
1

时,有人递给我一些C代码,基本上是由一个大的main()函数的。我现在试图将该方法展开成更小的函数,以更清晰地表示代码的意图。我有一些麻烦,但:处理错误重构程序代码

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. 验证args来数是正确的。
  2. 从命令行获取端口参数。
  3. 调用信号(SIGPIPE,SIG_IGN)。
  4. 其实尝试与插座连接。

试图重构为小功能时,这个问题主要是与错误处理相关。例如,R试图提取1.逻辑是这样的:

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

,并呼吁它会是这样的

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

。它实际上不是那么糟糕。现在,对于插座,那会是这样比较麻烦既是sockfds_addr需要返回,再加上状态返回值:

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

哪一种失败的尝试让我的主要方法为目的尽可能简单明了。当然,我可以使用.c文件中的全局变量,但这似乎不是一个好主意。

你怎么一般处理这种用C的东西?

+0

我添加了一个“错误处理”标签并编辑了标题。在[错误处理] [c]'中搜索StackOverflow。 –

+0

@Catcall:谢谢! –

回答

3

下面是简单的方法。

参数解析和相关的错误检查main的关注,所以我不会分裂出来,除非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 
} 

至于错误处理:如果这段代码并不是一个库,那么当函数中出现错误时,不管它在调用链中有多深,都可以在调用过程中停止工作。这实际上是在推荐的做法(Kernighan的&派克,编程的实践)。只要确保你的东西分解出实际误差打印程序像

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); 
} 

得到一致的错误消息。 (您可能希望在Linux和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和else。 –

+0

我认为@devouredelysium可能是对的。有时引入一些功能只是为了给程序段赋予名字是有用的。 –