Есть код с кучей больших функций которые делают много разной работы (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. хранить символы и использовать какой-то анализатор стека вызовов.
если делать это самому — это сложно.
-----
как можно решить эту проблему?
Здравствуйте, Abyx, Вы писали:
A>Есть код с кучей больших функций которые делают много разной работы (God Function antipattern), с дублированием кода и прочими радостями.
A>Я хотел было взять и для начала разбить на код на кучу маленьких реюзабельных функций, но столкнулся со следующей проблемой:
A>в коде много отладочного вывода, и после рефакторинга он не должен стать менее полезным.
[skipped]
A>как можно решить эту проблему?
В данном конкретном случае напрашивается просто передача признака состояния в функцию. Тогда можно будет логировать только из одного места — из самой функции do_something(). Если смущает необходимость логирования из функции do_something(), то можно сделать специальную прослойку с логированием — do_something_and_log_result().
В более общем виде проблема решается с использованием паттерна Context (если мне не изменяет память). Суть в том, что нужно ввести специальный объект (или структуру в C), указатель на который будет передаваться в каждую функцию, участвующую в обработке. В этой структуре будет храниться
вся информация о текущем состоянии обработки. Указатель на контекст будет передаваться и в функции логирования. Конечно, потребность в специальном объекте может и не появиться, если все эти функции объединить в один класс, но тогда будет опасность в конце концов прийти к антипаттерну God Class
![](/Forum/Images/smile.gif)
.
Финальный вариант с формированием 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