Управление ресурсами и обработка ошибок

Единая ответственность функций и управление ресурсами

Последнее обновление: 04.09.2023

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

Рассмотрим небольшой пример, в котором выполняется поиск в файле некоторого слова:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void find(char*, char*, size_t);

int main(void){
    char* filename ="test.txt";
    char word[] = "hello";
    size_t size = sizeof(word);
    find(filename, word, size);
}

void find(char* file_name, char* word, size_t word_size)
{
    FILE* file_pointer = 0;
    char* buffer = 0;

    if(file_name && word && (word_size>0)) // если указано имя файла и слово для поиска и его размер больше 0
    {
        if((file_pointer=fopen(file_name, "r")))    // если файл открыт для чтения
        {
            if((buffer=malloc(word_size)))      // если память выделена
            {
                int found = 0; // индикатор, что слово найдено
                // собственно действия функции - поиск ключевых слов в файле
                while(fgets(buffer, word_size, file_pointer))
                {
                    if(strcmp(word, buffer)==0)
                    {
                        found = 1;
                        break;
                    }
                }
                if(found) {
                    printf("Success! Text found!\n");
                }
                else{
                    printf("Text not found...\n");
                }
                free(buffer);
            }
            else{
                printf("Error! Unable to locate memory!\n");
            }
            fclose(file_pointer);
        }
        else{
            printf("Error! Unable to open file!\n");
        }
    }
    else
    {
        printf("Error! Params are incorrect!\n");
    }
}

Здесь основые действия выполняются в функции find(). Она получает имя файла для считывания, искомое слово и размер слова. Вначале проверяется корректность входных параметров. В идеале, конечно, для каждого параметра можно было сделать отдельную проверку и выводить на консоль специфичное для каждого параметра сообщение об ошибки. Но чтобы упростить пример, в данном случае все три параметра проверяются разом.

После проверки параметров открываем файл на чтение и проверяем, что он открыт.

Затем выделяем память для хранения считанных из файла данных и проверяем, что память выделена.

Далее идет собственно считывание файла и проверка, что считанное слово аналогично искомому. И собственно вывод результата.

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

Прежде всего надо выделить функциональные блоки, которые можно отделить от остального функционала. Одним из индикаторов, что функцию надо разделить, является сочетание управление ресурсами и собственно основного кода функции, ради которого она и задумывалась. В этом случае нередко управление ресурсами выносится в одну функцию, которая выступает оберткой, типа следующего:

// функция-обертка, которая управляет ресурсами
void someFunction()
{
    char* buffer = malloc(SOME_SIZE);
    if(buffer)
    {
        // вызов основной функции
        mainFunctionality(buffer);
    }
    free(buffer);
}
// основная функция
void mainFunctionality()
{
    // основные действия
}

Подобный механизм иногда называются Function Wrapper. Так, изменим программу, отделив управление ресурсами от собственно поиска в файле:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void find(char*, char*, size_t);
void find_word(char*, char*, size_t, FILE*);

int main(void){
    char* filename ="test.txt";
    char word[] = "hello";
    size_t size = sizeof(word);

    find(filename, word, size);
}

void find_word(char* buffer, char* word, size_t word_size, FILE* file_pointer){
    int found = 0; // индикатор, что слово найдено
    while(fgets(buffer, word_size, file_pointer))
    {
        if(strcmp(word, buffer)==0)
        {
            found = 1;
            break;
        }
    }
    if(found) 
    {
        printf("Success! Word found!\n");
    }
    else
    {
        printf("Word not found...\n");
    }
}

void find(char* file_name, char* word, size_t word_size)
{
    FILE* file_pointer = 0;
    char* buffer = 0;

    if(file_name && word && (word_size>0)) // если указано имя файла и слово для поиска и его размер больше 0
    {
        if((file_pointer=fopen(file_name, "r")))    // если файл открыт для чтения
        {
            if((buffer=malloc(word_size)))      // если память выделена
            {
                // собственно поиск слова в файле
                find_word(buffer, word, word_size, file_pointer);
                free(buffer);
            }
            else
            {
                printf("Error! Unable to locate memory!\n");
            }
            fclose(file_pointer);
        }
        else{
            printf("Error! Unable to open file!\n");
        }
    }
    else{
        printf("Error! Params are incorrect!\n");
    }
}

Теперь поиск вынесен в функцию find_word(), которая вызывается в функции find. Хотя данная программа не идеальна, но нам уже проще управлять по отдельности как выделением-освобождением ресурсов, так и поиском в файле. И при необходимости мы можем пойти дальше. Например, вынести отображение результатов поиска в отдельную функцию и т.д. Или избавиться от многочисленных else-выражений и вынести обработку ошибок в отдельную функцию:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef enum{
    WORD_FOUND,
    WORD_NOTFOUND,
    PARAMS_ERROR,
    MEMORY_ERROR,
    FILE_ERROR
} status;

void find(char*, char*, size_t);
status find_word(char*, char*, size_t, FILE*);
void process_result(status);

int main(void){
    char* filename ="test.txt";
    char word[] = "hello";
    size_t size = sizeof(word);

    find(filename, word, size);
}

status find_word(char* buffer, char* word, size_t word_size, FILE* file_pointer)
{
    while(fgets(buffer, word_size, file_pointer))
    {
        if(strcmp(word, buffer)==0)
        {
            return WORD_FOUND;
        }
    }
    return WORD_NOTFOUND;
}


void find(char* file_name, char* word, size_t word_size)
{
    FILE* file_pointer = 0;
    char* buffer = 0;

    status status_code = PARAMS_ERROR;  // если некорректные параметры
    if(file_name && word && (word_size>0)) // если указано имя файла и слово для поиска и его размер больше 0
    {
        status_code = FILE_ERROR;       // если ошибка при открытии файла
        if((file_pointer=fopen(file_name, "r")))    // если файл открыт для чтения
        {
            status_code = MEMORY_ERROR;         // если ошибка при выделении памяти
            if((buffer=malloc(word_size))) 
            {
                // получаем, найдено ли слово или нет
                status_code = find_word(buffer, word, word_size, file_pointer);
                free(buffer);
            }
            fclose(file_pointer);
        }
    }
    process_result(status_code);  // выводим результат
}

void process_result(status status_code)
{
    switch(status_code)
    {
        case WORD_FOUND:
            printf("Success! Word found!\n");
            break;
        case WORD_NOTFOUND:
            printf("Word not found...\n");
            break;
        case PARAMS_ERROR:
            printf("Error! Params are incorrect!\n");
            break;
        case MEMORY_ERROR:
            printf("Error! Unable to locate memory!\n");
            break;
        case FILE_ERROR:
            printf("Error! Unable to open file!\n");
            break;
    }
}

В данном случае для хранения результата определено перечисление status с 5-ю возможными состояниями. В функции find и find_word в зависимости от условий устанавливаем результат и с помощью дополнительной функции process_result выводим то или иное сообщение.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850