我得到了一些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个主要关注点:
- 验证参数数是否正确
- 从命令行参数获取端口。
- 呼叫信号(SIGPIPE, SIG_IGN)。
- 实际尝试与套接字建立连接
尝试将其重构为小函数时的问题主要与错误处理有关。例如,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;
其实没那么糟。现在,对于套接字,这将更加麻烦,因为sockfd
和s_addr
都需要返回,加上状态返回值:
int sockfd;
struct sockaddr_in* s_addr;
if (create_socket(port, &sockfd, s_addr) == -1)
return;
这有点违背了我试图保持主方法尽可能简单和清晰的目的。当然,我可以在.c
文件中使用全局变量,但这似乎不是一个好主意。
在C语言中你通常如何处理这类事情?
方法很简单。
参数解析和相关的错误检查是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 &Pike, 编程实践)。只要确保将实际的错误打印例程排除在诸如
之类的地方即可。void error(char const *details)
{
extern char const *progname; // preferably, put this in a header
fprintf(stderr, "%s: error (%s): %sn", progname, details, strerror(errno));
exit(1);
}
以获得一致的错误消息。(您可能想要在Linux和BSD上检查err(3)
,并可能在其他平台上模拟该接口。)
您还可以尝试找出那些根本不会出错的操作,或者只是调用一些带有一些简单设置的系统调用,因为这些操作可以轻松地创建可重用的组件。
保持原样?在我看来,main开头的一些设置并不构成问题。
这难道不是为了重构而重构的信号吗?
无论如何,对于"让我们一次初始化sockfd和s_addr",您总是可以创建一个结构体,并传递一个指向它的指针:
struct app_ctx {
int init_stage;
int sock_fd;
struct sockaddr_in myaddr;
...
}
然后将指向该结构体实例的指针传递给所有"一次只做一件事"的函数,并返回错误代码。
在清理时,执行相同的操作并传递相同的结构