Форум программистов, компьютерный форум, киберфорум
CoderHuligan
Войти
Регистрация
Восстановить пароль
Рейтинг: 3.00. Голосов: 2.

Не возвращайте мне ваши ошибки!

Запись от CoderHuligan размещена 08.07.2024 в 14:51
Обновил(-а) CoderHuligan 10.07.2024 в 11:30

Размышления по поводу архитектуры обработки ошибок в коде. Конкретно я использую язык Си и его библиотеку. И примеры будут на чистом Си.
Так вот многие функции стандартной библиотеки кроме полезного значения возвращают код ошибки или NULL.
Но возникает вопрос: а почему вызывающая функция в принципе должна получать что-то иное, кроме того значения которое запрашивается? Ошибка - это вот и есть то, другое значение. Возникает вопрос: а кто должен обрабатывать ошибки - вызывающая функция или вызываемая?
"Какой отец если у него сын попросит хлеба, даст ему камень?"
Если ошибка происходит внутри вызываемой функции, то принято отправлять её вызывающей, наверх. Типа: это не мое дело, пусть кто-то за меня это разгребет. Но ведь всё равно кто-то это должен разгребать.
Если это делает вызывающая функция, то структура программного кода сильно страдает от меньшей читаемости. Обработка ошибок "наверху" а не внизу иногда может полностью скрыть смысл решаемой задачи.
Простейший пример. Функция открытия файла при невозможности открыть файл возвращает нулевой указатель NULL. Но мы ждали файл. При этом обработка ошибки выполняется в вызывающей функции:
C
1
2
3
4
5
6
int main (void)
  FILE *f = fopen(file, mode);
  if(f==NULL)
  {
    exit(EXIT_FAILURE);
  }
Обычно используют голобальную переменную errno и функции типа perror(). Я не стал загромождать код.
Итак почти по каждой функции. Люди настолько привыкли к этому что ничего не замечают, никакого подвоха.
Но так лучше не делать.
Конечно можно, но наш код превращается в грязь, в болото, становится нечистым. Чистый код делает только одно дело. Чистая функция возвращает одно значение. Один тип.
Вместо этого обработка ошибок не должна покидать пределы функций. Если ошибка произошла в функции fopen() она там должна и остаться, а верхний код может получить или не получить управление в зависимости от вида ошибок.
При этом верхний код становиться гораздо чище и понятней, так как обработка ошибок происходит ниже. И чем ниже тем интенсивней. наверху практически нет обработки, а есть просто структура основного кода.
Стандартная библиотека Си сделана неправильно. Чтобы обойти это можно заключить функции в обертки:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
void error(int r)
{
  if(r==1)perror("Error in open file in main()");//для простоты
}
FILE *myFopen(const char *file, const char *mode, int er)
{
  errno = 0;
  FILE *f = fopen(file, mode);
  if(f==NULL)
  {
    error(er);
    exit(EXIT_FAILURE);// exit можно сделать в error()
   // или не делать, а сделать предупреждение и продолжить программу
  }
  return f;
}
double mySqrt(double d)
{
  assert(d >= 0.0);//можно иначе обработать
  return sqrt(d);
}
int main(void)
{
  FILE *f = myFopen("w.txt", "r", 1);
  double a = mySqrt(-1.0);
  printf("a = %f", a);
  // дальнейший код
    return 0;
}
При этом все ошибки локализуются внутри функций, которые по идее и ответственны их обработать.
Пример конечно упрощенный, но показывает принцип.
Смотрите: верхний код становится чистым. Мы работаем только через подключения клиентского кода.
То есть: у нас есть некий главный модуль, который задает общую структуру приложения, её архитектуру в общем и целом. Без обработки ошибок на верхнем уровне, структура кода становится более понятной.
То есть никогда не надо перекладывать обработку косяков на кого-то еще: разбираться надо там, где это произошло..
Размещено в Без категории
Показов 1359 Комментарии 35
Всего комментариев 35
Комментарии
  1. Старый комментарий
    Аватар для Croessmah
    Цитата:
    Если ошибка произошла в функции fopen() она там должна и остаться
    Вы же в курсе, что это глупость?
    Есть правило получше: ваша библиотека при правильном использовании не должна крашить или завершать клиентское приложение.
    Если, вдруг, там где-то есть exit - это должно быть жирным выделено в документации.

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

    Цитата:
    Стандартная библиотека Си сделана неправильно.
    Ну да, ну да, ты то знаешь как надо.


    Обработкой ошибок занимается та функция, которая в состоянии принять решение о дальнейших действиях.
    fopen вообще не в состоянии принять решение при ошибке открытия файла.
    Запись от Croessmah размещена 08.07.2024 в 15:02 Croessmah вне форума
    Обновил(-а) Croessmah 08.07.2024 в 15:08
  2. Старый комментарий
    Аватар для CoderHuligan
    Цитата:
    Ваша библиотека при правильном использовании не должна крашить или завершать клиентское приложение.
    А вы заметили, что я не про либы писал, показывая пример?
    Я про обертки над стандартной либой. Если бы я реализовывал стандартную либу, то функция fopen получила третьим параметром указатель на функцию. А функцию пишет клиент. Или как то иначе. Главное принцип.
    Запись от CoderHuligan размещена 08.07.2024 в 15:08 CoderHuligan вне форума
    Обновил(-а) CoderHuligan 08.07.2024 в 15:10
  3. Старый комментарий
    Аватар для CoderHuligan
    Цитата:
    Обработкой ошибок занимается та функция, которая в состоянии принять решение о дальнейших действиях.
    fopen вообще не в состоянии принять решение при ошибке открытия файла.
    Дальнейшие действия можно заключить в функции error(). Там может быть switch case, который в зависимости от кода ошибки вызовет те или иные действия. А fopen() ничего об этом знать не должна, так как принимает решения не она, а error()
    Запись от CoderHuligan размещена 08.07.2024 в 15:21 CoderHuligan вне форума
  4. Старый комментарий
    Аватар для CoderHuligan
    Цитата:
    А в Вашем мире клиентское приложение тупо закроется, а нужно это делать или нет вашу функцию не колышет.
    Еще раз перечитайте: это УППРОЩЕННЫЙ пример. Закрыть клиента решает не fopen. В комментарии об этом сказано намеком, что exit() может быть в error().
    Запись от CoderHuligan размещена 08.07.2024 в 15:24 CoderHuligan вне форума
  5. Старый комментарий
    Аватар для CoderHuligan
    Цитата:
    Вот надо мне открыть файл для сохранения настроек. Не получилось - ну и хрен с ним, скажем пользователю об этом или промолчим. Ну да, не сохранятся настройки какие-то, и что?
    Просто передаем в аргументе код обработки данного состояния. А функция error() её обработает и вернет управление в fopen(), а та в main().
    Запись от CoderHuligan размещена 08.07.2024 в 15:28 CoderHuligan вне форума
  6. Старый комментарий
    Аватар для CoderHuligan
    Ну и вся обработка ошибок собрана в одном месте: в функции error(). Это явное преимущество.Обычно обработка размазана по всему коду. В error() находится переключатель. Функция error() может обрабатывать все возможные ошибки приложения.
    Запись от CoderHuligan размещена 08.07.2024 в 16:11 CoderHuligan вне форума
    Обновил(-а) CoderHuligan 08.07.2024 в 16:45
  7. Старый комментарий
    Аватар для XLAT
    правильно так:
    C++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
    #include <stdio.h>
    #include <string>
     
    ///----------------------------------------------------------------------------|
    /// Класс для работы с файлом.
    ///----------------------------------------------------------------------------:
    struct  File
    {       File(std::string_view filename, std::string_view mode)
            {   f = fopen(filename.data(), mode.data());
            }
           ~File(){if(f != NULL) fclose(f); };
     
        bool is_open(){ return f != NULL; }
     
    private:
        FILE* f;
    };
     
    ///----------------------------------------------------------------------------|
    /// Тест.
    ///----------------------------------------------------------------------------:
    int main()
    {
        {   ///--------------------|
            /// Захват ресурса...  |
            ///--------------------:
            File file("w.txt", "r");
            if(file.is_open())
            {
                /// ...
            }
            else
            {   perror("Error in open file in main()");
                exit(EXIT_FAILURE);
            }
            ///--------------------|
            /// Освобождаем ресурс.|
            ///--------------------.
        }
        
        ///---------------------------------------|
        /// Ресурс w.txt готов для нового захвата.|
        ///---------------------------------------:
        /// ...
     
        return 0;
    }
    Запись от XLAT размещена 08.07.2024 в 17:07 XLAT вне форума
    Обновил(-а) XLAT 08.07.2024 в 17:12
  8. Старый комментарий
    Аватар для XLAT
    или от логики применения можно так:
    C++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    #include "myfile.hpp" /// Код класса смотрите выше.
     
    ///----------------------------------------------------------------------------|
    /// Тест.
    ///----------------------------------------------------------------------------:
    int main()
    {
        ///--------------------|
        /// Захват ресурса...  |
        ///--------------------:
        if(File file("w.txt", "r"); file.is_open())
        {
            /// Доступ к ресурсу только здесь!
            /// ...
        }
        else
        {   perror("Error in open file in main()");
            exit(EXIT_FAILURE);
        }
     
        /// ...
        
        ///---------------------------------------|
        /// Ресурс w.txt готов для нового захвата.|
        ///---------------------------------------:
        /// ...
     
        return 0;
    }
    Запись от XLAT размещена 08.07.2024 в 17:32 XLAT вне форума
  9. Старый комментарий
    -- delete it.


    Спутал темы
    Запись от voral размещена 08.07.2024 в 18:16 voral вне форума
  10. Старый комментарий
    Аватар для XLAT
    Цитата:
    Сообщение от voral Просмотреть комментарий
    Спутал темы
    КодерХулиган крутит-вертит темы - нас запутать хочет))
    Запись от XLAT размещена 08.07.2024 в 18:21 XLAT вне форума
  11. Старый комментарий
    Аватар для CoderHuligan
    Цитата:
    правильно так
    Ничего нового. Опять обработчик ошибок в самой верхней функции. А смысл в создании объекта? Тогда уж при ошибке создавайте объект ошибки, например в конструкторе.
    Запись от CoderHuligan размещена 08.07.2024 в 18:45 CoderHuligan вне форума
  12. Старый комментарий
    Аватар для XLAT
    Цитата:
    Сообщение от CoderHuligan Просмотреть комментарий
    Опять обработчик ошибок в самой верхней функции. А смысл в создании объекта? Тогда уж при ошибке создавайте объект ошибки, например в конструкторе.
    C++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    
    #include <stdio.h>
    #include <string>
     
    ///----------------------------------------------------------------------------|
    /// Класс для работы с файлом.
    ///----------------------------------------------------------------------------:
    struct  File
    {       File(std::string_view filename, std::string_view mode)
            {   f = fopen(filename.data(), mode.data());
            }
           ~File(){if(f != NULL) fclose(f); };
     
        bool is_open(){ return f != NULL; }
     
        void read(std::string& s)
        {   for (  char c; (c = getc(f)) != EOF;) s.push_back(c);
        }
     
    private:
        FILE* f;
    };
     
    ///----------------------------------------------------------------------------|
    /// Обработка ошибки класса File зависит от логики работы проги.
    ///----------------------------------------------------------------------------:
    struct  Config   : protected File, std::string
    {       Config() : File("cfg.txt", "r")
            {   reserve(1024);
                read   (    );
            }
     
    private:
        void read()
        {
            if(is_open())
            {
                File::read(*this);
            }
            else
            {   ///--------------------------|
                /// Файл конфига не найден!  |
                /// Грузим дефолтный...      |
                ///--------------------------:
                (std::string&)(*this) = "val  = 1001\n"
                                        "year = 2024\n";
            }
        }
    };
     
     
    ///----------------------------------------------------------------------------|
    /// Тест.
    ///----------------------------------------------------------------------------:
    int main()
    {
        Config cfg;
     
        printf(cfg.c_str());
     
        return 0;
    }
    Цитата:
    Сообщение от CoderHuligan Просмотреть комментарий
    Ничего нового
    всего то нужно взять и заюзать на ляме кода...
    Запись от XLAT размещена 08.07.2024 в 19:23 XLAT вне форума
    Обновил(-а) XLAT 08.07.2024 в 19:44
  13. Старый комментарий
    Аватар для Croessmah
    CoderHuligan, прочитай еще раз что я написал, может, дойдет.
    Запись от Croessmah размещена 08.07.2024 в 21:22 Croessmah вне форума
    Обновил(-а) Croessmah 08.07.2024 в 21:45
  14. Старый комментарий
    Цитата:
    Сообщение от CoderHuligan Просмотреть комментарий
    функция error() её обработает и вернет управление в fopen(), а та в main().
    И что жё вернёт fopen() (или myFopen() ) в main(), если не удастся открыть файл?
    Тот же 0?
    Запись от politoto размещена 09.07.2024 в 07:41 politoto вне форума
  15. Старый комментарий
    Аватар для CoderHuligan
    XLAT уже лучше..
    Цитата:
    прочитай еще раз что я написал, может, дойдет.
    Я не абсолютист. Где-то лучше на месте проверять, а где-то переложить на нижние уровни.
    Самое главное чтобы обработка ошибок оказалась в одном месте.
    Цитата:
    И что жё вернёт fopen() (или myFopen() ) в main(), если не удастся открыть файл?
    Тот же 0?
    Если ошибка критическая ничего не вернет. error() обработает ошибку. Номер ошибки можно через глобальную переменную передавать.
    Если ошибка не критическая то варианты:
    1. обрабатываем ошибку тут же на месте.
    2. функция error() установит флаг и вернет управление.
    Вы или не хотите читать код, или не хотите вникать. Я не настаиваю. Просто высказываю свое мнение. Никого не призываю действовать подобным образом. Пишите как привыкли.
    Запись от CoderHuligan размещена 09.07.2024 в 11:45 CoderHuligan вне форума
    Обновил(-а) CoderHuligan 09.07.2024 в 17:39
  16. Старый комментарий
    Цитата:
    Сообщение от CoderHuligan Просмотреть комментарий
    Я не абсолютист.
    Ваши послания воспринимаются наоборот.

    Цитата:
    Сообщение от CoderHuligan Просмотреть комментарий
    Самое главное чтобы обработка ошибок оказалась в одном месте.
    Это тоже не всегда верно и правильно. Все зависит от архитектуры приложения - обработка должна производиться в соответствии с распределением зоны ответственности. Иначе придется повышать взаимосвязанность модулей/слоев. Снабжать модули знаниями, которыми им обладать нет объективной необходимости - размазывая таким образом логику по всему приложению. (а обработка ошибок - тоже часть логики)
    Запись от voral размещена 09.07.2024 в 11:56 voral вне форума
  17. Старый комментарий
    Аватар для CoderHuligan
    Цитата:
    Все зависит от архитектуры приложения - обработка должна производиться в соответствии с распределением зоны ответственности.
    Согласен. Каждый модуль может иметь свой обработчик общий для модуля.
    Запись от CoderHuligan размещена 09.07.2024 в 13:56 CoderHuligan вне форума
  18. Старый комментарий
    Аватар для CoderHuligan
    Цитата:
    Ну да, ну да, ты то знаешь как надо
    На самом деле никто не знает как надо. Кто-то сказал, что искусство программирования все еще не вышло из пещерного века. А махание объектами сродни маханию дубинами.
    Запись от CoderHuligan размещена 09.07.2024 в 17:42 CoderHuligan вне форума
  19. Старый комментарий
    Цитата:
    Сообщение от CoderHuligan Просмотреть комментарий
    На самом деле никто не знает как надо. Кто-то сказал, что искусство программирования все еще не вышло из пещерного века. А махание объектами сродни маханию дубинами.
    Вы просто пытаетесь рассуждать (по всем вопросам) ставя во главу угла программирование как таковое: "Как будет правильнее для программирования".. ТОлько в этом случае оправдана "никто не знает как надо". И это естественно.

    А надо рассуждать с точки зрения конкретного проекта, задачи, предметной области (но не забывая про хорошие практики программирования - как раз что бы быть понятным).

    И пока у программиста нет восприятия проекта: в его руках хоть функции хоть объекты будут "сродни маханию дубинами".
    Запись от voral размещена 09.07.2024 в 20:00 voral вне форума
  20. Старый комментарий
    А так, лично я, если рассуждать "в общем" случае эдакого примера в вакууме для вашего примера в функции myFopen бросил бы исключение. Исхожу из логики нормальная задача функции открыть файл и вернуть. если не удалось значит, что то не так. При этом в функции могут быть разные исключения. Но точно ни каких прерываний выполнения всей программы. Т.е. здесь будет первый уровень обработки ошибок. Т.к. эта функция осуществляет взаимодействие с файловой системой, знает "язык ФС" и переводит случившееся на язык приложения.

    Далее уже по иерархии это исключение обрабатывается на том уровне (возхможно даже на нескольких) в соответствии с бизнес логикой. И в одном и том же приложении она может, в итоге, и показать пользователю ошибку, а может промолчать.

    Скажем на уровне логирования я могу собирать все ошибки и, в соответствии с настройками и уровнем логирования - выполнять соответствующие действия, на уровне взаимодействия с пользователем - принимать решение какое сообщение показать (или не показать пользователю)

    Но, повторюсь, это не рецепт для всех проектов единственно верный и безальтернативный.
    Запись от voral размещена 09.07.2024 в 20:18 voral вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru