Обработка ошибок при рефакторинге процедурного кода

Мне вручили некоторый код 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;

что на самом деле не так уж и плохо. Теперь, для сокета, это было бы намного более проблематично, так как необходимо вернуть как sockfd, так и s_addr, а также возвращаемое значение состояния:

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

что противоречит цели сделать мой основной метод как можно более простым и ясным. Я мог бы, конечно, прибегнуть к глобальным переменным в файле .c, но это не кажется хорошей идеей.

Как вы обычно справляетесь с такими вещами в C?


person devoured elysium    schedule 14.11.2011    source источник
comment
Я добавил тег обработки ошибок и отредактировал заголовок. Найдите в StackOverflow [error-handling] [c].   -  person Mike Sherrill 'Cat Recall'    schedule 14.11.2011


Ответы (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
}

Что касается обработки ошибок: если этот код не предназначен для библиотеки, вы можете просто убить вызывающий процесс, когда что-то пойдет не так в функции, независимо от того, насколько глубоко в цепочке вызовов это находится; на самом деле это рекомендуемая практика (Керниган и Пайк, Практика программирования). Просто убедитесь, что вы учитываете фактические процедуры печати ошибок в чем-то вроде

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 и, возможно, эмулировать этот интерфейс на других платформах.)

Вы также можете попытаться исключить те операции, которые просто не могут пойти не так, или просто вызывают несколько системных вызовов с некоторой надежной настройкой, поскольку они позволяют легко повторно использовать компоненты.

person Fred Foo    schedule 14.11.2011

Оставить как есть? Небольшая настройка в начале main не составляет проблемы, IMO. Начинайте рефакторинг после того, как все настроено.

person Daniel Fischer    schedule 14.11.2011

Разве это не признак того, что вы проводите рефакторинг ради рефакторинга?

В любом случае, что касается «давайте инициализируем sockfd и s_addr за один раз», вы всегда можете создать структуру и передать на нее указатель:

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

Затем вы передаете указатель на экземпляр этой структуры всем вашим функциям, выполняющим одно действие за раз, и возвращаете код ошибки.

Во время очистки вы делаете то же самое и передаете ту же структуру.

person shodanex    schedule 14.11.2011
comment
Это не ради рефакторинга. Функция состоит из 150 строк, состоящих из переплетенных между собой ifs и else. - person devoured elysium; 14.11.2011
comment
Я думаю, что @devouredelysium может быть прав. Иногда бывает полезно ввести некоторые функции просто для того, чтобы дать имена сегментам программы. - person Fred Foo; 15.11.2011