C语言 在重构过程代码时处理错误



我得到了一些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. 验证参数数是否正确
  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语言中你通常如何处理这类事情?

方法很简单。

参数解析和相关的错误检查是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;
    ...
}

然后将指向该结构体实例的指针传递给所有"一次只做一件事"的函数,并返回错误代码。

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

相关内容

  • 没有找到相关文章

最新更新