【问题标题】:Debugging a command-line program调试命令行程序
【发布时间】:2016-04-12 08:43:00
【问题描述】:

如果我有一个用作命令行工具的程序,我有哪些调试选项?

为了这个例子,假设程序看起来像这样。

do_stuff.pl列表:

main :-
    current_prolog_flag(argv, Argv),
    do_stuff(Argv),
    halt.
main :-
    halt(1).

使用 SWI-Prolog,我可以编译:

swipl --goal=main -o do_stuff -c do_stuff.pl

我可以通过调用来运行它

$ ./do_stuff foo bar baz

就目前而言,如果do_stuff/1 失败,它将以 1 退出。我怎样才能看到失败的第一个(最早、最深的)目标?或者更好的是,整个回溯?我假设我应该可以使用debugleash,例如:

main :-
    current_prolog_flag(argv, Argv),
    debug, leash(+fail),
    do_stuff(Argv),
    halt.

...但是我没有尝试过任何有效的方法。

我唯一半途而废的想法是为每个我希望确定性成功但没有成功的谓词抛出一个错误。这当然可行,但似乎有点过分?

动机

用作命令行工具的程序(通常)意味着运行一次,获取其参数,读取其输入,写入输出。在这种情况下,失败意味着什么?我的解释是,意外失败是程序中的错误。

单元测试可能会有所帮助(单独测试谓词);但是,根据定义,这对于由于程序员对问题、范围或工具缺乏了解而导致的错误没有帮助。只有使用真实输入运行程序才能捕获此类错误。

那么,给定上面的例子,如果某个用例导致do_stuff/1 失败,并且程序以非零代码退出,程序员有哪些选择来确定哪个谓词失败?

answer linked in the comments 提供了一种解决方案。但是(如果我理解正确的话)这确实需要程序员系统地检查执行流程,直到找到有问题的谓词调用。

这正是我希望避免的。

【问题讨论】:

  • 你看过this answer吗?
  • @false 我之前已经看过这个问题的两个答案。当我读到它时,你的绝对比使用format 更干净,但没有根本的不同:​​我仍然需要自己寻找失败的根源。我错过了什么?
  • @false 但是我当然可以为“没有选择点的成功”定义一个符号/元谓词,并慷慨地使用它(也就是说,对于我知道的每个谓词都应该在没有选择的情况下成功观点);这会是一个合理的解决方案吗?
  • 我不确定这个“完全”确定性的属性是否有用,您应该一直对其进行测试。通过偶尔测试,我发现这个属性相对容易维护。
  • @false 查看我的问题的编辑,我试图解释我的动机。

标签: debugging prolog swi-prolog failure-slice


【解决方案1】:

与更多面向命令的语言相比,失败在 Prolog 中是非常不寻常的事情。它从第一天起就引起了人们的兴趣。事实上,即使在 Prolog 0(Prolog I 之前的版本)中,除了跟踪选项 ECRIRE 之外,还有一个特殊选项 IMPASSES,它只显示了失败。

后来,Mirelle Ducassé 进行了一项特别的工作,试图自动找出失败的原因。

失败的奇怪之处在于,它们不一定表明出了问题。但有时,他们是。

我想说,有两个不同的方向可以理解失败。第一个是程序性的,第二个是声明性的。

注释

在许多程序中,我使用(@)/1 来表示我希望目标永远成功。感谢操作符声明,这只是一个额外的字符:

   ...,
   @goal_aux_togoalaux_spec(OQuery, FVect0, Query, Spec),
   ...

如果目标失败,则会发出错误。记录嵌套异常也很重要。如果有一些时间紧迫的事情,这些@ 必须被删除。但是,我只计算了 120kLOP 中的约 400 个。

请注意,@ 也适用于具有多个答案的目标。喜欢 @member(1,[X,Y]).

这种技术适用于 de facto 模式的程序。想想准备一个(就是上面的例子)。在那里,你主要在想:这是一个程序,什么是合适的切片?在这种情况下,答案:“不,没有切片”将不是答案。你真的希望它永远成功。如果您没有这样的模式程序,您通常可以通过强制稳定来转换现有的未模式程序:

p(X, Y) :-
   wellformed(X),
   @p_old(X, Yc),
   Yc = Y.

该技术在纯关系、声明性代码中迅速失去吸引力。取recent example 中的。在那里,几乎不可能添加@ - 除了第一个目标。在这种情况下,需要一种更具声明性的方法,如下所示。

概括

对于更复杂的问题,@ 效果不佳。相反,需要程序修改/切片。需要通过添加前缀* 来概括程序。请参阅this answer,以获取手动使用此技术的 SO 上的此类调试会话的集合。 这种技术的主要观点是,在确定最大泛化时,您不必理解程序的真正含义。您只需要留意失败的目标。

理想情况下,此类概括会自动生成。然而,有很多障碍。一方面,它们只适用于纯单调的代码(事实上,这是一个很好的动机,为什么人们应该坚持这样的代码)。因此,首先必须对现有代码进行分析和分类。如果系统不符合并随机改变其行为(如您提到的系统),这将更加困难。

【讨论】:

  • 差不多了,a) 抛出任何应该总是成功一次的谓词,或者 b) 开始在你的程序中寻找你的错误。我没有得到“随机改变他们的行为”的一点:我曾经有幸使用过的几乎任何个软件都存在错误。找出错误是在您自己的代码中还是在其他地方是练习的重点。
  • @Boris:误解:a) 是关于成功,而不是关于成功一次。 @member(1,[1,1]) 也成功了两次。在 b 中,您错过了泛化标准与您对程序的理解无关。将添加更多...
  • 不需要,我想我开始明白了。确实,总是成功和只成功一次不是一回事。关于推广程序:是的,我想我现在看到了。我的困惑(我认为)是由于混淆了“程序失败”和“程序没有做我认为应该做的事情”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多