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. 验证 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;

这实际上并没有那么糟糕。现在,对于套接字,这将更加麻烦,因为两者都sockfd需要s_addr返回,加上状态返回值:

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

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

您通常如何在 C 中处理此类事情?

4

3 回答 3

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 & Pike,The Practice of Programming)。只需确保您将实际的错误打印例程分解为类似

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

以获得一致的错误消息。(您可能想err(3)在 Linux 和 BSD 上进行检查,并可能在其他平台上模拟该界面。)

您还可以尝试排除那些根本不会出错的操作,或者只是通过一些简单的设置调用一些系统调用,因为这些操作可以轻松重用组件。

于 2011-11-14T15:28:34.453 回答
1

保持原样?IMO,在 main 开始时进行一些设置并不构成问题。设置好后开始重构。

于 2011-11-14T15:27:35.760 回答
1

这不是一个迹象,表明您正在为重构而重构吗?

无论如何,关于“让我们一次性初始化 sockfd 和 s_addr”,您始终可以创建一个结构,并将指针传递给它:

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

然后你将一个指向这个结构实例的指针传递给你所有的“一次做一件事”函数,并返回错误代码。

在清理时,您执行相同的操作并传递相同的结构。

于 2011-11-14T15:29:19.073 回答