2009-04-12 29 views
6

当涉及到设计合适的执行报告时,我总是怀疑。代码执行期间的报告信息:最佳设计

假设你有以下(愚蠢,简单)的情况。我将使用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" 

在一般情况下,这是相当糟糕的。假设这段代码将在一个库中结束。我不希望我的图书馆打印出来。我希望它能够默默地完成这项工作。不过,有时我想提供信息,不仅在调试情况下,而且还要向用户通知实际正在进行的工作。打印也很糟糕,因为您无法控制消息的处理。它只是stdout,除了重定向之外,你无能为力。

另一种解决方案是有一个记录模块。

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") 

这样做的好处是您可以为您的日志记录服务知道一个独特的位置,并且您可以根据需要调整此服务。您可以将其静音,将其重定向到文件,标准输出或甚至网络。缺点是你与记录模块有很强的耦合。基本上你的代码的每个部分都依赖于它,并且你有打电话到处登录。

的第三种选择是有一个清晰的界面报表对象,而且你通过它周围

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对象实例化的耦合。 另:这可以通过使用无作为默认和检查呼叫日志,这是笨拙的,因为在Python中,你必须有条件每次(在C你可以定义一个宏)

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

编辑写之前得到解决选项是类似于Qt,并有一个emit()信号策略。随着代码的执行,它会发出带有适当状态代码的信息,任何感兴趣的人都可以订阅这些信号并提供信息。不错,干净,非常分离,但需要一些编码,因为我认为这可以很快用包含的蟒蛇电池来完成。

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

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

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

该报告还可以进入实际程序,所以你必须在connect()和getRemoteList()程序“打印”作为第一条语句。因此

的问题是:

  • 你认为什么是一些代码(特别是在图书馆的情况下)最好的设计是在同一时间沉默的时候噪音可能是颠覆性的客户端,但是有用时冗长?
  • 如何处理逻辑代码和报告代码之间的平衡混合?
  • 代码和错误检查之间的混合已被解决,但有例外。可以做些什么来划分代码逻辑报告的“噪音”?

编辑:为心灵

更多的想法,我认为这不仅是从逻辑代码去耦日志代码的问题。我认为这也是将信息生产和信息消费分开的问题。类似的技术已经存在,尤其是处理UI事件,但我并没有真正看到应用于日志记录问题的相同模式。


编辑:我接受了来自马塞洛的答案,因为他在事实证据表明,妥协是在这种情况下,最好的解决办法,而且也没有银弹指出。然而,所有其他人都是有意思的答案,我很高兴能够全部赞扬他们。谢谢您的帮助!

+0

我添加了一个赏金,看看是否可以在这个问题上做进一步的讨论。此外,这是mu第一次赏金开放,所以我也很好奇。 – 2009-04-14 01:36:39

回答

1

我认为有一点你必须画一条线并作出妥协。 我看不出将日志从系统中彻底解耦的原因,因为您必须在某处以某种方式发送这些消息。

我会去默认的记录模块,因为...这是默认模块。它有很好的文档记录,并带有默认库,因此这里不存在依赖关系问题。此外,你可以避免重蹈覆辙。

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

你也可以把它放在另一个线程里面并且抓住Qt的日志事件。

4

我认为图书馆的最佳解决方案是沿着添加图书馆的方向。

Log.Write(...) 

其中Log的行为是从周围环境(例如app.config或环境变量)中获取的。 (我也认为这是一个已经接近并解决了很多次的问题,虽然在设计领域有一些“甜蜜点”,但是对于你描述的情况,上面的答案是最好的IMO)。

我没有看到任何好的方法来将'正常'部分的代码从'日志'部分'解耦'。记录往往是相对非侵入性的;我偶尔发现Log.Write(...)是对现实世界代码的分心。

+0

好吧,虽然没有侵入性,但我认为这不是代码本身的解耦问题,而是关于向外部实体提供关于进度信息的概念。 – 2009-04-13 18:40:14

2

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

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

+0

AOP中的一种“编织者”。但o发现很难真正实现类似的东西。它面临着很多实际的障碍。 – 2009-04-12 09:49:41

+0

实现这一点的简单方法是暂时将内联注释更改为函数调用。 – 2009-04-14 17:17:50

1

应该有工具,允许样板日志消息一拉“进带参数的方法A(1,2,3)”,“从回国方法B的值为X,花费10 ms“自动(并有选择地)生成(在运行或部署时进行控制)。用手写这些东西太无聊/重复/容易出错。

不知道是否有,但。

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

+0

我认为所需要的是一种类似异常的系统,其中异常不用于控制执行,只是为了报告事件已经发生。 – 2009-04-12 09:51:16

2

我发现this搜索Python的面向方面编程。我同意其他海报,不应将这种担忧与核心逻辑混为一谈。

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

+0

AO for python?我对去年阅读的AO想法很感兴趣,但是我认为我不得不钻研C#,Java或其他的东西......哇,那是从2003年开始的?凉。 – DarenW 2009-04-12 01:58:23

+0

我对AOP不太了解,但是使用AOP可以帮助您拦截常见的切入点,但并不能解决在代码中放置任意点的问题,您希望为即将发生的错误情况提供有意义的消息发生或信息状态。我对吗 ? – 2009-04-12 10:01:30

1

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

这样,很有可能人们看着你的代码已经知道logging模块是如何工作的。如果他们必须学习,那么至少它有很好的文档记录,并且他们的知识可以转移到也使用logging的其他库。

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

1

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

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

或者你可以尝试暂时将内嵌评论转换为函数调用,但我不确定这是行不通的。

1

作为对您对信息生产/消费的编辑的回应:对于一般情况来说,这是一个有效的关注点,但记录不是一般情况。尤其是,您不应该依赖来自程序某个部分的日志输出来影响另一部分的执行。这确实将消费者与生产者的实施紧密结合在一起。

日志记录应该被认为是附属于您的主要执行。您的系统不应该知道或关心这些日志文件的存在或内容(可能有监视工具除外)。既然如此,将原木与其生产的“消耗”分离的概念是无关紧要的。由于您没有使用日志来做任何有意义的事情,所以耦合不是问题。

2

我经常为此使用DTrace。在OS X上,python和ruby都已经设置了DTrace钩子。在其他平台上,你可能必须自己做。但是能够将调试跟踪附加到正在运行的进程上,太棒了。如上所述,对于库代码(假设您正在编写一个http客户端库),最好的选择是传入一个可选的记录器作为参数。 DTrace适用于在生产环境中(有时在其他地方)添加日志记录功能,但如果其他人可能想要访问日志以调试随后调用您的代码的日志,那么可选日志记录器作为参数绝对是一种方式去。