冗余检查条件是否不好?


10

我经常在代码中找到一些位置,一遍又一遍地检查特定条件。

我想举一个小例子:假设有一个文本文件,其中包含以“ a”开头的行,以“ b”开头的行以及其他行,而我实际上只想使用前两种行。我的代码看起来像这样(使用python,但将其读取为伪代码):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a")):
        # do stuff
    elif (line.startsWith("b")):
        # magic
    else:
        # this else is redundant, I already made sure there is no else-case
        # by using clear_lines()
# ...

您可以想象我不仅会在这里检查这种情况,而且可能还会在其他功能中等等。

您认为它是噪声还是给我的代码增加了一些价值?


5
基本上是关于您是否在进行防御性编码。您看到此代码经常被编辑吗?这很可能会成为系统的一部分,而该系统必须极其可靠?我认为插入assert()其中以进行测试不会有多大危害,但除此之外,这可能是过度的。也就是说,它会根据情况而有所不同。
Latty 2012年

您的“其他”情况本质上是无效/无法访问的代码。请检查是否存在禁止该限制的系统范围要求。
NWS 2012年

@NWS:您是说我应该保留其他情况吗?对不起,我不太了解你。
marktani 2012年

2
并非与问题特别相关-但我会将“断言”转化为不变式-这将需要一个新的“ Line”类(也许具有A和B的派生类),而不是将这些行视为字符串并告诉它们他们代表从外面。我很高兴在这个过阐述代码审查
MattDavey

你的意思是elif (line.startsWith("b"))?顺便说一句,您可以安全地删除条件上的那些括号,它们在Python中不是惯用语言。
tokland 2012年

Answers:


14

这是一种超乎寻常的惯例,处理它的方法是通过高阶滤波器

本质上,您将一个函数以及要过滤的列表/序列传递给filter方法,结果列表/序列仅包含所需的那些元素。

我不熟悉python语法(尽管它确实包含上面链接中所示的功能),但是在c#/ f#中看起来像这样:

C#:

var linesWithAB = lines.Where(l => l.StartsWith("a") || l.StartsWith("b"));
foreach (var line in linesWithAB)
{
    /* line is guaranteed to ONLY start with a or b */
}

f#(假设为ienumerable,否则将使用List.filter):

let linesWithAB = lines
    |> Seq.filter (fun l -> l.StartsWith("a") || l.StartsWith("b"))

for line in linesWithAB do
    /* line is guaranteed to ONLY start with a or b */

因此,要明确一点:如果使用经过验证的代码/模式,这是不好的样式。那样,通过clear_lines()在内存中更改列表的方式将失去线程安全性,并失去您本可以拥有的并行性的希望。


3
请注意,用于此的python语法将是生成器表达式:(line for line in lines if line.startswith("a") or line.startswith("b"))
Latty

1
+1指出,(不必要的)命令式实现clear_lines确实是个坏主意。在Python中,您可能会使用生成器来避免将整个文件加载到内存中。
tokland 2012年

当输入文件大于可用内存时会发生什么?
Blrfl 2012年

@Blrfl:好吧,如果术语生成器在c#/ f#/ python之间是一致的,那么@tokland和@Lattyware会转换为c#/ f#yield和/或yield!陈述。在我的f#示例中,它更加明显,因为Seq.filter只能应用于IEnumerable <T>的集合,但是如果lines是生成的集合,则两个代码示例都可以使用。
史蒂文·埃弗斯

@mcwise:当您开始查看以这种方式操作的所有其他可用函数时,它开始变得非常性感和令人难以置信的表现力,因为它们都可以链接并组合在一起。看看skiptakereduceaggregate在.NET), mapselect在.NET),并有更多的,但这是一个真正良好的开端。
史蒂文·埃弗斯

14

最近,我不得不使用Motorola S-record格式实现固件编程器,与您所描述的非常相似。由于时间紧张,我的初稿忽略了冗余,并根据我实际需要在应用程序中使用的子集进行了简化。它很容易通过我的测试,但是一旦有人尝试过,它就失败了。没有任何问题的线索。它贯穿了整个过程,但最终失败了。

因此,我别无选择,只能执行所有冗余检查,以缩小问题所在。之后,我花了大约两秒钟的时间才找到问题。

正确的方法可能要花我两个多小时,但是在排查方面却浪费了别人的时间。很少有几个处理器周期值得浪费一天的故障排除时间。

话虽这么说,在涉及读取文件的地方,将软件设计为可以同时读取和处理一行而不是将整个文件读取到内存中并在内存中进行处理通常是有益的。这样,它仍然可以在非常大的文件上运行。


“很少有几个处理器周期值得浪费一天的故障排除时间。” 感谢您的回答,您的观点很好。
marktani 2012年

5

您可以提出例外else情况。这样就没有多余。异常是指不应该发生但要进行检查的事情。

clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a)):
        # do stuff
    if (line.startsWith("b")):
        # magic
    else:
        throw BadLineException
# ...

我认为后者是个坏主意,因为它不太明确-如果您以后决定添加"c",可能会不太清楚。
Latty

首先建议有可取之处...第二(假设“B”)是一个坏主意
安德鲁·

@Lattyware我改善了答案。感谢您的意见。
图兰斯·科尔多瓦

1
@安德鲁我改善了答案。感谢您的意见。
图兰斯·科尔多瓦

3

按合同设计时,人们猜测每个功能必须按照其文档中所述进行工作。因此,每个功能都有一个先决条件列表,即功能输入的条件,以及后置条件,即功能输出的条件。

该功能必须向其客户保证,如果输入符合前提条件,则输出将如后置条件所描述。如果不满足至少一个前提条件,则该函数可以执行所需的操作(崩溃,返回任何结果,...)。因此,前置条件和后置条件是功能的语义描述。

多亏了合同,一个功能可以确保其客户正确使用了该功能,而一个客户可以确保该功能可以正确完成其工作。

一些语言本机处理或通过专用框架处理合同。对于其他人来说,最好的办法就是使用asserts来检查前置条件和后置条件,如@Lattyware所说。但是我不会称之为防御性编程,因为在我看来,这个概念更侧重于针对(人类)用户输入的保护。

如果您利用合同,则可以避免冗余检查的情况,因为被调用函数可以完美地工作并且您不需要双重检查,或者被调用函数功能失调,并且调用函数可以按需运行。

然后最困难的部分是定义哪个功能负责什么,并严格记录这些角色。


1

实际上,您一开始就不需要clear_lines()。如果行既不是“ a”也不是“ b”,则条件触发器根本不会触发。如果要摆脱这些行,则将else放入clear_line()中。就目前而言,您正在对文档进行两次遍历。如果您在开始时跳过clear_lines()并将其作为foreach循环的一部分进行操作,则可以将处理时间减少一半。

这不仅是糟糕的风格,而且在计算上也很糟糕。


2
这些行可能被用于其他用途,在处理"a"/ "b"行之前需要对其进行处理。并不是说有可能(明确的名称暗示它们已被丢弃),只是有可能需要它。如果将来会重复迭代那组线,那么也有必要事先删除它们,以避免大量无意义的迭代。
Latty

0

如果您发现有无效的字符串(例如输出调试文本)而实际上想做任何事情,那么我说那绝对没问题。当由于某种未知的原因而停止工作时,还有几行额外的代码和几个月的时间,您可以查看输出以找出原因。

但是,如果只是忽略它是安全的,或者可以确定您永远不会获得无效的字符串,那么就不需要额外的分支。

就我个人而言,我始终会为任何意外情况至少输入一个跟踪输出-当您遇到附有输出的错误(确切告诉您出了什么问题)时,这样做会使工作变得更加轻松。


0

……假设有一个文本文件,其中包含以“ a”开头的行,以“ b”开头的行以及其他行,而我实际上只想使用前两种行。我的代码看起来像这样(使用python,但将其读取为伪代码):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if ...

我讨厌if...then...else建筑。我会避免整个问题:

process_lines_by_first_character (lines,  
                                  'a' => { |line| ... a code ... },
                                  'b' => { |line| ... b code ... } )
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.