Как отделить логику логирования от бизнес-логики в C-программе? А в С ++ один? - PullRequest
7 голосов
/ 14 ноября 2010

В настоящее время я пишу код на C, и у меня есть много printfs, чтобы я мог иногда отслеживать поток моего приложения. Проблема в том, что иногда мне нужно больше деталей, чем другим, поэтому я обычно трачу свое время на комментирование / раскомментирование своего кода на C, чтобы получить соответствующий вывод.

При использовании Java или C # я обычно могу отделить оба кода реализации от логики ведения журнала, используя Аспекты.

Есть ли подобная техника, которую вы используете в C, чтобы обойти эту проблему?

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

Если бы вместо C я кодировал на C ++, было бы лучше?

Редактировать

Кажется, что есть AspectC ++, поэтому для C ++, похоже, есть решение. А как насчет C?

Спасибо

Ответы [ 6 ]

10 голосов
/ 14 ноября 2010

IME, вы не можете отделить ведение журнала от алгоритмов, по которым вы хотите войти. Стратегическое размещение выписок из журнала требует времени и опыта . Обычно код продолжает собирать логирующие операторы в течение всего времени жизни (хотя он асимптотический). Обычно логирование развивается с кодом . Если алгоритм часто меняется, то, как правило, будет и код регистрации.

Что вы можете сделать, это сделать регистрацию как ненавязчивой , насколько это возможно. То есть, убедитесь, что операторы журналирования всегда являются однострочными, которые не нарушают чтение алгоритма, сделайте так, чтобы другие могли вставлять дополнительные операторы журналирования в существующий алгоритм без полного понимания вашей библиотеки журналирования и т. Д.

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

5 голосов
/ 14 ноября 2010

Не совсем.

Если у вас есть различные макросы, вы можете легко играть в такие игры:

#ifdef NDEBUG
    #define log(...) (void)0
#else
    #define log(...) do {printf("%s:%d: ", __FILE__, __LINE__); printf(__VA_ARGS__);} while(0)
#endif

Вы также можете включить или отключить ведение журналаболее тонкая гранулярность:

#define LOG_FLAGS <something>;

#define maybe_log(FLAG, ...) do { if (FLAG&LOG_FLAGS) printf(__VA_ARGS__);} while(0)

int some_function(int x, int y) {
    maybe_log(FUNCTION_ENTRY, "x=%d;y=%d\n", x, y);
    ... do something ...
    maybe_log(FUNCTION_EXIT, "result=%d\n", result);
    return result;
}

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

Любой изэти макросы и вызовы printf могут быть заменены чем-то (другие макросы или вызовы переменных функций), что позволяет отделить фактический формат ведения журнала и цель от бизнес-логики, но факт выполнения какого-либо вида ведения журнала может 'На самом деле.

aspectc.org заявляет, что предлагает компилятор C и C ++ с языковыми расширениями, поддерживающими AOP.Я понятия не имею, в каком состоянии он находится, и если вы используете его, то, конечно, вы больше не пишете C (или C ++).

Помните, что C ++ имеет множественное наследование, что иногда полезно при перекрестномпроблемы.Имея достаточное количество шаблонов, вы можете делать замечательные вещи, возможно, даже реализуя собственную систему диспетчеризации методов, которая допускает какие-то точки соединения, но это очень важно.

4 голосов
/ 14 ноября 2010

В GCC вы можете использовать переменные макросы: http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html.Это позволяет определить dprintf() с любым количеством параметров.

Используя дополнительный скрытый параметр verbose_level, вы можете фильтровать сообщения.

В этом случае логика ведения журнала будет содержать только

dprintf_cond(flags_or_verbose_level, msg, param1, param2);

и отделять его от остального кода не нужно.

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

Хм, это звучит похоже на проблему, с которой я столкнулся, работая над проектом C ++ прошлым летом.Это было распределенное приложение, которое должно было быть абсолютно пуленепробиваемым, и это приводило к множеству раздражающих операций по обработке исключений.Функция из 10 строк удвоится к тому времени, когда вы добавите исключение или две, потому что каждая из них включает создание потока строк из более длинной строки исключения и любых соответствующих параметров, а затем фактически генерирование исключения, может быть, через пять строк.1002 * Итак, я закончил создание мини-среды обработки исключений, что означало, что я мог централизовать все свои сообщения об исключениях в одном классе.Я инициализировал бы этот класс моими (возможно параметризованными) сообщениями при запуске, и это позволило мне писать такие вещи, как throw CommunicationException(28, param1, param2) (переменные аргументы).Я думаю, что поймаю немного за это, но это сделало код бесконечно более читабельным.Например, единственная опасность заключалась в том, что вы могли непреднамеренно вызвать это исключение с помощью сообщения № 27, а не № 28.

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

Флаг и правильная логика, вероятно, являются более безопасным способом сделать это, но вы можете сделать то же самое при типе компиляции. То есть. Используйте #define и #ifdef для включения / исключения printfs.

0 голосов
/ 15 ноября 2010
#ifndef DEBUG_OUT

# define DBG_MGS(level, format, ...) 
# define DBG_SET_LEVEL(x) do{}while(0)

#else

extern int dbg_level;
# define DBG_MSG(level, format, ...)              \
   do {                                           \
      if ((level) >= dbg_level) {                 \
          fprintf(stderr, (format), ## __VA_ARGS__); \
      }                                           \
   } while (0)
# define DBG_SET_LEVEL(X) do { dbg_level = (X); } while (0)

#endif

## перед __VA_ARGS__ - это особенность GCC, которая заставляет , __VA_ARGS__ фактически превращаться в правильный код, когда нет фактических дополнительных аргументов.

Материал do { ... } while (0) предназначен только для того, чтобы вы ставили ; после операторов при их использовании, как вы делаете, когда вызываете обычные функции.

Если вы не хотите придумывать что-то необычное, вы можете покончить с частью уровня отладки. Это просто делает так, что при желании вы можете изменить желаемый уровень отладки / трассировки.

Вы можете превратить весь оператор печати в отдельную функцию (встроенную или обычную), которая будет вызываться независимо от уровня отладки и принимать решение о печати или без нее.

#include <stdarg.h>
#include <stdio.h>

int dbg_level = 0;

void DBG_MGS(int level, const char *format, ...) {
    va_list ap;
    va_start(ap, format);
    if (level >= dbg_level) {
        vfprintf(stderr, format, ap);
    }
    va_end(ap);
}

Если вы используете систему * nix, вам следует взглянуть на syslog.

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

...