【问题标题】:Reporting information during code execution: best design在代码执行期间报告信息:最佳设计
【发布时间】:2009-04-12 00:37:06
【问题描述】:

在设计正确的执行报告时,我一直有疑问。

假设您有以下(愚蠢的,简单来说)案例。我会用python。

def doStuff():
    doStep1()
    doStep2()
    doStep3()

现在,假设您想要报告各个步骤,如果出现问题等。不是真正的调试:只是应用程序的信息行为。

第一个简单的解决方案是放置打印件

def doStuff():
    print "starting doing stuff"
    print "I am starting to do step 1"
    doStep1()
    print "I did step 1"
    print "I am starting to do step 2"
    doStep2()
    print "I did step 2"
    print "I am starting to do step 3"
    doStep3()
    print "I did step 3"

总的来说,这很糟糕。假设这段代码最终会出现在一个库中。我不希望我的图书馆把东西打印出来。我希望它能够默默地完成这项工作。尽管如此,有时我还是想提供信息,不仅是在调试情况下,而且还要让用户知道某些事情实际上正在完成的过程中。打印也很糟糕,因为您无法控制消息的处理。它只是进入标准输出,除了重定向之外,你无能为力。

另一种解决方案是有一个用于日志记录的模块。

def doStuff():
    Logging.log("starting doing stuff")
    Logging.log("I am starting to do step 1")
    doStep1()
    Logging.log("I did step 1")
    Logging.log("I am starting to do step 2")
    doStep2()
    Logging.log("I did step 2")
    Logging.log("I am starting to do step 3")
    doStep3()
    Logging.log("I did step 3")

这样做的好处是您知道日志服务的独特位置,并且您可以随意修改此服务。您可以将其静音、将其重定向到文件、stdout 甚至网络。缺点是您与 Logging 模块的耦合非常强。基本上你的代码的每一部分都依赖于它,而且你到处都可以调用日志记录。

第三种选择是有一个界面清晰的报表对象,然后你传递它

def doStuff(reporter=NullReporter()):
    reporter.log("starting doing stuff")
    reporter.log("I am starting to do step 1")
    doStep1()
    reporter.log("I did step 1")
    reporter.log("I am starting to do step 2")
    doStep2()
    reporter.log("I did step 2")
    reporter.log("I am starting to do step 3")
    doStep3()
    reporter.log("I did step 3")

最后,如果他们有更多话要说,您也可以将记者对象传递给 doStepX()。 优点:它减少了与模块的耦合,但它引入了与 NullReporter 对象的实例化的耦合。这可以通过使用 None 作为默认值并在调用 log 之前检查来解决,这很笨拙,因为在 python 中你必须每次都编写一个条件(在 C 中你可以定义一个宏)

def doStuff(reporter=None):
    if reporter is not None:
        reporter.log("starting doing stuff")
        # etc...

编辑: 另一种选择是像 Qt 一样工作,并有一个 emit() 信号策略。当您的代码执行时,它会发出带有适当状态代码的信息,任何有兴趣的人都可以订阅信号并提供信息。漂亮干净,非常解耦,但需要一点编码,因为我认为使用随附的 python 电池无法快速完成。

最后,您可以使用有意义的错误消息引发异常,但这当然只能在您退出错误条件时使用。它不适用于偶尔的报告。

编辑:我想澄清一个事实,即情况更普遍,而不仅限于一系列调用的步骤。它还可能涉及控制结构:

 if disconnected:
     print "Trying to connect"
     connect()
 else:
     print "obtaining list of files from remote host"
     getRemoteList()

报告也可以包含在实际的例程中,因此您将在 connect() 和 getRemoteList() 例程中作为第一条语句进行“打印”。

因此问题是:

  • 您认为对于某些代码(尤其是在库的情况下)而言,在噪音可能对客户端造成干扰时保持沉默但在有用时冗长的最佳设计是什么?
  • 如何处理逻辑代码和报告代码之间的平衡混合?
  • 代码和错误检查之间的混合已通过异常解决。如何将报告的“噪音”从代码逻辑中分离出来?

编辑:头脑中的更多想法

我认为这不仅仅是将 Logging 代码与逻辑代码分离的问题。我认为这也是信息生产与信息消费脱钩的问题。类似的技术已经存在,特别是用于处理 UI 事件,但我并没有真正看到相同的模式应用于日志记录问题。


编辑:我接受了 Marcelo 的回答,因为他指出事实证据表明在这种情况下妥协是最好的解决方案,并且没有灵丹妙药。然而,所有其他的答案也很有趣,我真的很高兴为所有答案投票。感谢您的帮助!

【问题讨论】:

  • 我添加了一个赏金,看看是否可以对这个问题进行进一步的讨论。还有,这是 mu 的第一次赏金开放,所以我也很好奇。

标签: debugging logging


【解决方案1】:

我认为图书馆的最佳解决方案是添加例如

Log.Write(...)

Log 的行为是从周围环境中获取的(例如 app.config 或环境变量)。

(我也认为这是一个已经被多次处理和解决的问题,虽然在设计空间中有一些“最佳位置”,但上面的答案是最适合您描述的情况的 IMO。)

我没有看到任何将代码的“正常”部分与“日志记录”部分“分离”的好方法。日志记录往往是相对非侵入性的;我不认为偶尔使用 Log.Write(...) 会分散实际代码的注意力。

【讨论】:

  • 好吧,虽然不是侵入性的,但我认为这不是代码本身的解耦问题,而是向外部实体提供有关进度的信息的概念。
【解决方案2】:

另一种选择是在不记录的情况下编写代码,然后在执行代码之前应用一些转换来插入适当的记录语句。执行此操作的实际技术高度依赖于语言,但与编写调试器的过程非常相似。

虽然这可能不值得增加复杂性......

【讨论】:

  • AOP 中的一种“编织者”。但是发现很难实际实现类似的东西。它面临许多实际障碍。
  • 一种简单的实现方法是将内联 cmets 临时更改为函数调用。
【解决方案3】:

我在搜索面向方面的 Python 编程时找到了this。我同意其他发帖者的观点,即此类担忧不应与核心逻辑混为一谈。

本质上,您想要记录日志的点可能并不总是任意的,可能是诸如“错误之前的所有点”之类的概念,可以通过切入点来识别。其他完全任意的点可以使用简单的记录技术来捕获。

【讨论】:

  • AO for python?我对去年读到的关于 AO 的想法很感兴趣,但我认为我必须深入研究 C#、Java 或其他东西……哇,那是 2003 年的事吗?酷。
  • 我对 AOP 了解不多,但是使用 AOP 可以帮助您拦截常见的切入点,但不能解决在代码中放置任意点的问题即将发生的错误情况或信息状态。我说的对吗?
  • 是的,你是对的。但是我推断您想要放置的点可能并不总是任意的,可能是诸如“错误之前的所有点”之类的概念可以通过切入点来识别。其他完全任意的点可以使用简单的记录技术来捕获。
【解决方案4】:

我经常为此使用 DTrace。在 OS X 上,python 和 ruby​​ 都已经设置了 DTrace 挂钩。在其他平台上,您可能必须自己执行此操作。但是能够将调试跟踪附加到正在运行的进程是非常棒的。

但是,对于库代码(假设您正在编写一个 http 客户端库),最好的选择是传入一个可选的记录器作为参数,正如您所提到的。 DTrace 非常适合将日志记录添加到生产中(有时在其他地方)出现问题的地方,但是如果其他人可能需要访问日志来调试随后调用您的代码,那么可选的记录器作为参数绝对是方法去。

【讨论】:

    【解决方案5】:

    我认为有一点你必须划清界限并做出妥协。 我认为没有办法将日志记录与系统完全分离,因为您必须将这些消息发送到某个地方并且以某人理解的方式发送。

    我会使用默认的日志记录模块,因为...它是默认模块。它有据可查,并带有默认库,因此这里没有依赖性问题。此外,您还可以避免重新发明轮子。

    也就是说,如果您真的想做一些新的事情,您可以创建一个全局报告器对象。您可以在流程开始时实例化和配置它(记录、不记录、重定向流等。即使在每个流程/功能/步骤的基础上)并从任何地方调用它,无需传递它(也许在一个多线程环境,但这将是最小的)。

    您也可以将它放在另一个线程中并像 Qt 一样捕获日志事件。

    【讨论】:

      【解决方案6】:

      应该有工具允许自动(和有选择地)生成样板日志消息,例如“使用参数 (1,2,3) 输入方法 A”、“从方法 B 返回值 X,花费 10 毫秒” (在运行或部署时控制)。手写这些东西太无聊/重复/容易出错。

      但不确定是否有。

      如果您要编写手动日志消息,请确保包含一些有用的上下文信息(用户 ID、正在查看的 URL、搜索查询等),以便在出现问题时获得更多信息不仅仅是方法名称。

      【讨论】:

      • 我认为需要一种类似异常的系统,其中异常不用于控制执行,只是报告发生了一个事件。
      【解决方案7】:

      我会使用标准的 logging 模块,该模块自 Python 2.3 起就成为标准库的一部分。

      这样,查看您的代码的人很有可能已经知道logging 模块的工作原理。如果他们必须学习,那么至少它是有据可查的,并且他们的知识可以转移到也使用 logging 的其他图书馆。

      是否有任何您想要但在标准logging 模块中找不到的功能?

      【讨论】:

      • 据我所知,日志模块非常强大。处理程序可用于定义任何行为。所以原则上,通过“滥用”日志模块,你可以分发事件。这个模块属于我描述的第二类,它与标准库相关。
      【解决方案8】:

      我认为这里最简单的解决方案是最好的。这取决于语言,但只需使用一个非常短的、可全局访问的标识符 - 在 PHP 中我使用自定义函数 trace($msg) - 然后只需实现并重新实现您认为适合特定项目或阶段的代码。

      它的自动编译器版本是标准调试器。如果你想看到有意义的标签,你需要自己写这些标签,不幸的是:)

      或者您可以尝试暂时将内联 cmets 转换为函数调用,但我不确定这是否可行。

      【讨论】:

        【解决方案9】:

        针对您对信息生产/消费的修改:对于一般情况,这是一个有效的问题,但日志记录不是一般情况。特别是,您不应该依赖程序某个部分的日志输出来影响另一部分的执行。这确实会将消费者与生产者的实现紧密耦合。

        日志记录应被视为您的主要执行附带的。您的系统不应该知道或关心此类日志文件的存在或内容(监控工具可能除外)。在这种情况下,将原木的“消费”与其生产脱钩的概念是无关紧要的。由于您没有使用日志来做任何有意义的事情,因此耦合不是问题。

        【讨论】:

        • 确实日志记录是事件调度和处理的一种特殊情况。我不会依赖日志消息,但我可以依赖日志事件来控制我的流程。示例:FTP 客户端库可以报告文件已作为日志事件和控制事件(例如回调)下载
        猜你喜欢
        • 1970-01-01
        • 2012-11-02
        • 2020-02-01
        • 1970-01-01
        • 2012-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-26
        相关资源
        最近更新 更多