代码之家  ›  专栏  ›  技术社区  ›  devoured elysium

如何在C程序中分离日志逻辑和业务逻辑?在C++中呢?

  •  7
  • devoured elysium  · 技术社区  · 14 年前

    我目前正在用C编写代码,我有很多printfs,这样我可以在某些时候跟踪我的应用程序的流程。问题是,有时我比其他人需要更多的细节,所以我通常花时间注释/取消注释我的C代码,这样我就可以得到适当的输出。

    当使用Java或C#时,我通常可以使用方面将实现代码与日志逻辑分开。

    你在C语言中有没有类似的技巧来解决这个问题?

    我知道我可以设置一个名为DEBUG的标志,它可以是on或off,这样我就不必在每次想显示或隐藏printfs时到处注释/取消注释我的整个代码。问题是我还想去掉代码中的日志逻辑。

    如果不是C,我在C++中编码,会更好吗?

    编辑

    似乎有一个APETCT+++,所以对于C++来说似乎有一个解决方案。C呢?

    谢谢

    6 回复  |  直到 14 年前
        1
  •  10
  •   sbi    14 年前

    输入法你不能真的把日志和你想记录的算法分开。战略性地放置日志语句 需要时间和经验 . 通常,代码在其整个生命周期内(尽管是渐进的)一直在组装日志语句。通常 日志记录随代码发展 . 如果算法经常更改,那么日志代码也会经常更改。

    你能做的就是把日志作为 不引人注目 尽可能的。也就是说,确保日志语句始终是不干扰读取算法的一个内衬,使得其他人可以在不必完全理解日志库的情况下,将附加日志语句插入到现有算法中。

    简而言之, 对待日志就像对待字符串处理一样 :将它包装在一个很好的小lib中,这个小lib将包含在几乎所有地方并在其中使用,使这个lib变得快速,并且易于使用。

        2
  •  5
  •   Steve Jessop    14 年前

    不是真的。

    如果有可变宏可用,则可以轻松地玩以下游戏:

    #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声称提供了一个支持AOP的语言扩展的C和C++编译器。我不知道它是什么状态,如果你使用它,当然你就不再真正编写C(或C++)了。

    请记住,C++具有多重继承性,这有时有助于横切关注点。有了足够的模板,您可以做一些了不起的事情,甚至可以实现您自己的方法分派系统,允许某种类型的连接点,但这是一件大事。

        3
  •  4
  •   ruslik    14 年前

    在GCC上,可以使用可变宏: http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html . 有可能定义 dprintf() 具有任意数量的参数。

    使用附加隐藏 verbose_level 参数可以筛选消息。

    在这种情况下,日志loggic将只包含

    dprintf_cond(flags_or_verbose_level, msg, param1, param2);
    

    不需要将它与其他代码分离。

        4
  •  1
  •   winwaed    14 年前

    标记和适当的逻辑可能是更安全的方法,但是在编译类型上也可以这样做。即使用define和ifdef来包含/排除printfs。

        5
  •  1
  •   asdfjklqwer    14 年前

    嗯,这听起来像去年夏天我在C++项目上遇到的一个问题。它是一个分布式应用程序,必须是绝对防弹的,这导致了烦人的异常处理膨胀负载。当您添加一两个异常时,一个10行函数的大小将增加一倍,因为每个函数都涉及从loong异常字符串加上任何相关参数构建stringstream,然后在5行之后实际抛出异常。

    因此,我最终构建了一个小型异常处理框架,这意味着我可以将所有异常消息集中在一个类中。我会在启动时用我的(可能是参数化的)消息初始化该类,这允许我编写如下内容 throw CommunicationException(28, param1, param2) (可变参数)。我想我会为此受到一些批评,但它使代码可读性大大提高。例如,唯一的危险是,您可能会无意中抛出带有消息27而不是28的异常。

        6
  •  0
  •   nategoose    14 年前
    #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) 只是为了让你 ; 在语句之后使用它们,就像调用常规函数一样。

    如果你不想那么花哨,你可以不用调试级的部分。这就使得如果你想的话,你可以改变你想要的调试/跟踪日期的级别。

    您可以将整个print语句转换为一个单独的函数(内联或常规函数),不管调试级别如何,都将调用该函数,并决定是否在内部进行打印。

    #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 .

    您可能还需要搜索一些跟踪库。有一些人做了与我所描述的类似的事情。