Обработка ошибок при рефакторинге процедурного кода - PullRequest
1 голос
/ 14 ноября 2011

Мне был передан некоторый 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. Сигнал вызова (SIGPIPE, SIG_IGN).
  3. На самом деле попытайтесь установить соединение с сокетом.

Проблема при попытке реорганизовать это в небольшие функции в основном связана с обработкой ошибок.Например, попытка 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?

Ответы [ 3 ]

3 голосов
/ 14 ноября 2011

Вот простой подход.

Синтаксический анализ аргументов и связанная с ними проверка ошибок являются проблемой 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): %s\n", progname, details, strerror(errno));
    exit(1);
}

, чтобы получить согласованные сообщения об ошибках.(Возможно, вы захотите проверить err(3) в Linux и BSD и, возможно, эмулировать этот интерфейс на других платформах.)

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

1 голос
/ 14 ноября 2011

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

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

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

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

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

1 голос
/ 14 ноября 2011

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

...