[C\C++] God Function, вывод ошибокв лог
От: Abyx Россия  
Дата: 20.07.11 19:09
Оценка:
Есть код с кучей больших функций которые делают много разной работы (God Function antipattern), с дублированием кода и прочими радостями.

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

допустим сейчас код выглядит так:
void SomeFunction()
{
   ...
   if(state1)
   {
       ...

       // do something

       ... begin do something
       if(success)
       {
            log("[SomeFunction] begin do something failed while in state1");
       }
       else
       {
           ... continue do something
           if(success)
           {
                log("[SomeFunction] continue do something failed while in state1");
           }
           ...
       }

   }
   else ...
}


хочется вынести do something в отдельную функцию bool do_something(),

однако по-первых надо будет писать log("[do_something] ...") — т.е. вместо нескольких огромных функций, названия которых просто запомнить будут десятки функций которые будут вызываться из разных функций — ценность логирования имени функции сразу падает

а во-вторых, если do_something() предполагается быть реюзабельной, надо убрать "... while in state1"

-----

итак, получается что разбиение большой функции со сложной условной логикой на маленькие функции имеет недостаток — теряется контекст в котором работает код.

очевидное решение — писать в лог стек вызовов:
[SomeFunction->on_state1->do_something->do_subtask] failed

однако это вопрос про С++, а не пайтон или C#, так что стектрейса "из коробки" нету

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

нужна кроссплатформенность в пределах WinCE 6+, компилятор — от msvc2005.

-----

на ум приходят такие решения:

1. в каждую функцию добавить код который будет формировать стек вызовов.
struct Frame
{
    const char* fnName;
    Frame* prev;

    Frame(const char* fnName) : fnName(fnName), prev(TlsGetValue(tlsIdx)) { TlsSetValue(tlsIdx, this); }
    ~Frame() { TlsSetValue(tlsIdx, prev); }

    static DWORD tlsIdx;
};

#define ADD_TRACE Frame frame_##__LINE__(__FUNCTION__)

...

bool do_something()
{
    ADD_TRACE;

    ...
}


вызовы TlsGetValue\TlsSetValue могут создавать весьма большой оверхед

их количество можно сократить с трех до одного, если сделать обертку для функции каждого потока, и хранить в TLS адрес локальной переменной, которая уже будет хранить указатель на вершину стека.
    Frame* prev;
    Frame** top;

    Frame(const char* fnName) : fnName(fnName), top(TlsGetValue(tlsIdx)),  prev(*top) { *top = this; }
    ~Frame() { *top = prev; }


-----

2. использовать исключения

исключения не подходят в случае если надо просто написать в лог и продолжить работу.

в этом случае может быть помогло бы RaiseException(code, 0, ...) и EXCEPTION_CONTINUE_EXECUTION, хотя я не уверен что это сработает

-----

3. хранить символы и использовать какой-то анализатор стека вызовов.

если делать это самому — это сложно.


-----

как можно решить эту проблему?
In Zen We Trust
Re: [C\C++] God Function, вывод ошибокв лог
От: dfbag7 Россия  
Дата: 21.07.11 06:37
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Есть код с кучей больших функций которые делают много разной работы (God Function antipattern), с дублированием кода и прочими радостями.


A>Я хотел было взять и для начала разбить на код на кучу маленьких реюзабельных функций, но столкнулся со следующей проблемой:

A>в коде много отладочного вывода, и после рефакторинга он не должен стать менее полезным.

[skipped]

A>как можно решить эту проблему?


В данном конкретном случае напрашивается просто передача признака состояния в функцию. Тогда можно будет логировать только из одного места — из самой функции do_something(). Если смущает необходимость логирования из функции do_something(), то можно сделать специальную прослойку с логированием — do_something_and_log_result().

В более общем виде проблема решается с использованием паттерна Context (если мне не изменяет память). Суть в том, что нужно ввести специальный объект (или структуру в C), указатель на который будет передаваться в каждую функцию, участвующую в обработке. В этой структуре будет храниться вся информация о текущем состоянии обработки. Указатель на контекст будет передаваться и в функции логирования. Конечно, потребность в специальном объекте может и не появиться, если все эти функции объединить в один класс, но тогда будет опасность в конце концов прийти к антипаттерну God Class .
Re: [C\C++] God Function, вывод ошибокв лог
От: uzhas Ниоткуда  
Дата: 21.07.11 09:02
Оценка:
Здравствуйте, Abyx, Вы писали:

A>эти логи предназначены для программиста который знаком с кодом, но у которого под рукой нет никаких средств отладки.

A>лог должен быть текстовым и читабельным, причем читать его будут во время работы программы
я сейчас в подобном проекте работаю, где все сделано на функциях в С-стиле
основное направление все же использовать высокоуровневые средства, которые признаны best-практиками и предоставляются языком:
1) ошибки репортятся исключениями. для сбора контекста выставлены catch на более высоких уровнях для прикрепления доп. инфы
2) задача разбивается на ортогональные составляющие
3) трассировки приходится лепить во всех местах — при входе в функцию, при выходе, при кидании исключений
в трассировки надо вставлять все доступные данные : имя файла, строчка, входные параметры
для всех этих вещей можно написать вспомогательные макросы
не стОит передавать в функцию данные о контексте, которые вставляются только в лог и не используются в логике
еще считаю, что надо обдумать смысл трассировки и ее качество, а не превращать C++ в другой язык, т.к. это усложнит сопровождение и квалификацию C++ разработчиков, членов команды. если нужны стек-трейсы, то лучше в сторону джавы смотреть
Re: [C\C++] God Function, вывод ошибокв лог
От: Аноним  
Дата: 21.07.11 09:35
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Есть код с кучей больших функций которые делают много разной работы (God Function antipattern), с дублированием кода и прочими радостями.

...
A>как можно решить эту проблему?
На мой взгляд проблему можно решить при помощи паттерна Command и CompositeCommand и Strategy. Те для решения задачи собирается CompositeCommand состоящий из Command (ну дерево команд может быть конечно гораздо сложнее описанной) по определенной стратегии. Соответственно CompositeCommand может собрать лог.

Ну или по тупинькому в начале и в конце писать в лог вход в метод и выход из метода, ну и плюс идентификатор потока. В дальнейшем такие логи весьма читабельные, только при написание получается куча оверхеда с написанием логов.
Re[2]: [C\C++] God Function, вывод ошибокв лог
От: Abyx Россия  
Дата: 21.07.11 17:08
Оценка:
Здравствуйте, uzhas, Вы писали:

U>основное направление все же использовать высокоуровневые средства, которые признаны best-практиками и предоставляются языком:

U>1) ошибки репортятся исключениями. для сбора контекста выставлены catch на более высоких уровнях для прикрепления доп. инфы
исключения могут существенно усложнить восприятие кода, и изза кучи try {} catch {} — примерно +10 строчек кода
к тому же исключения С++ прерывают выполнение кода, а это не всегда нужно

U>2) задача разбивается на ортогональные составляющие

т.е.?

U>3) трассировки приходится лепить во всех местах — при входе в функцию, при выходе, при кидании исключений

U>в трассировки надо вставлять все доступные данные : имя файла, строчка, входные параметры
U>для всех этих вещей можно написать вспомогательные макросы
трассировка неудобна тем что в лог идет спам сообщениями об успешном выполнении кода и нужно какое-то средство фильтрации чтобы прочитать лог

U>не стОит передавать в функцию данные о контексте, которые вставляются только в лог и не используются в логике

+1, вводить контекст только для логов — это перебор
In Zen We Trust
Re: [C\C++] God Function, вывод ошибок в лог - stacktrace
От: Abyx Россия  
Дата: 21.07.11 20:08
Оценка:
Финальный вариант с формированием stacktrace

использование __declspec(thread) вместо Tls[Get|Set]Value позволяет создать ссылку на переменную в TLS, и тем самым ускорить доступ к ней

#pragma once

#include <Windows.h>

namespace aux {

/*  Calls stack support

    Creates linked list of local variables which keeps names of called functions (or code block)

    Typical usage:

    // prints "foo <- block <- main <- "

    void foo()
    {
        // 1. Create local variables in functions
        AUX_ADD_CALL_STACK_FRAME; // expands to ::aux::Frame frame__("foo");

        // 2. Use Frame::top() to access frames stack
        using aux::Frame;
        for(const Frame* frame = Frame::top(); frame != NULL; frame = frame->next())
            std::cout << frame->name() << " <- ";
    }

    int main()
    {
        AUX_ADD_CALL_STACK_FRAME; // expands to ::aux::Frame frame__("main");

        // or add frame for block
        {
            aux::Frame frame("block")
            foo();
        }
    }
*/

#define AUX_ADD_CALL_STACK_FRAME ::aux::Frame frame__(__FUNCTION__)

class Frame
{
public:
    static const Frame* top()
    {
        return tlsVarRef();
    }

    const Frame* next() const
    {
        return prevFrame;
    }

    const char* name() const
    {
        return fnName;
    }

private:
    static const Frame*& tlsVarRef()
    {
        __declspec(thread) static const Frame* tlsVar = NULL;
        return tlsVar;
    }

    const char* fnName; // current function name
    const Frame*& topPtr; // cached TLS value - pointer to top (last) frame
    const Frame* prevFrame; // previous frame

public:
    Frame(const char* fnName)
        : fnName(fnName)
        , topPtr(tlsVarRef())
        , prevFrame(topPtr)
    {
        topPtr = this;
    }
    ~Frame()
    {
        topPtr = prevFrame;
    }

private:
    Frame(const Frame&); // noncopyable
    void operator=(const Frame&); // noncopyable
};

} // namespace aux
In Zen We Trust
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.