方法是否应该原谅传入的参数?[关闭]


21

假设我们有一个foo(String bar)仅对满足特定条件的字符串进行操作的方法;例如,它必须为小写字母,不能为空或只有空格,并且必须与pattern匹配[a-z0-9-_./@]+。该方法的文档说明了这些条件。

该方法应该拒绝任何和所有与该标准的偏差,还是该方法对某些标准更为宽容?例如,如果初始方法是

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
    }
    this.bar = bar;
}

第二种宽容方法是

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        bar = bar.toLowerCase().trim().replaceAll(" ", "_");
        if (!bar.matches(BAR_PATTERN_STRING) {
            throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
        }
    }
    this.bar = bar;
}

是应该将文档更改为声明将进行转换并将其设置为转换后的值(如果可能的话),还是应将方法保持尽可能简单并拒绝任何和所有偏差?在这种情况下,bar可以由应用程序的用户设置。

主要用例是用户通过特定的字符串标识符从存储库访问对象。存储库中的每个对象都应具有唯一的字符串来标识它。这些存储库可以通过各种方式(SQL Server,JSON,XML,二进制文件等)存储对象,因此我尝试确定与大多数命名约定匹配的最低公分母。


1
这可能在很大程度上取决于您的用例。任何一种都可能是合理的,而且我什至看到过提供两种方法并由用户决定的类。您能否详细说明该方法/类/字段应该做什么,以便我们提供一些实际建议?
Ixrec 2015年

1
您知道每个调用该方法的人吗?同样,如果您进行更改,是否可以可靠地标识所有客户?如果是这样,我会在性能问题允许的情况下尽可能宽容和宽容。我也可能会删除文档。如果不是,并且它是库API的一部分,则我将确保代码完全实现了所宣传的API,否则将来更改代码以匹配文档会易于生成错误报告。
乔恩·切斯特菲尔德

7
您可能会争辩说,“关注分离”说,如果有必要,您应该具有一个严格的foo函数,该函数严格接受所接受的参数,并具有第二个辅助函数,可以尝试“清除”要与一起使用的参数foo。这样,每种方法只需要做很少的工作,就可以更干净地管理和集成它们。如果沿着这条路线走,摆脱异常繁重的设计可能也会有所帮助。您可以改用类似的方法Optional,然后foo在必要时具有使用抛出异常的功能。
gntskn

1
这就像在问“有人冤me我,我应该原谅他们吗?” 显然,有些情况下,一方另一方是合适的。编程可能不像人际关系那么复杂,但是绝对足够复杂,以至于像这样的笼统处方无法奏效。
Kilian Foth,2015年

2
@Boggin我还要指出“重新考虑的稳健性原则”。当您需要扩展实现时,困难就来了,原谅的实现导致扩展实现的情况变得模棱两可。

Answers:


47

您的方法应该按照它说的去做。

这样可以防止错误的使用和维护人员以后更改行为。这样可以节省时间,因为维护人员不需要花费太多时间来弄清楚发生了什么。

也就是说,如果定义的逻辑对用户不友好,则可能应该对其进行改进。


8
这是关键。如果您的方法完全按照其说的做,则使用您的方法的编码器将补偿其特定的用例。永远不要仅仅因为您认为该方法有用而做未记录的事情。如果需要更改,请编写一个容器或更改文档。
尼尔森

我要补充@Nelson的意见,即该方法不应该在真空中设计。如果编码人员说他们会使用它,但会补偿并且其补偿具有通用价值,请考虑使它们成为类的一部分。(例如,具有foofooForUncleanString方法,后者在将其传递给前者之前进行了更正。)
Blrfl 2015年

20

有几点:

  1. 您的实现必须执行书面合同规定的内容,并且不应做其他任何事情。
  2. 对于合同和实施而言,简单性都是重要的,尽管对于合同制和实施而言更为重要。
  3. 试图纠正错误的输入会增加复杂性,不仅违背合同和实施,还会使使用背道而驰。
  4. 只有在能够提高可调试性并且不会过多地影响效率的情况下,才应尽早发现错误。
    请记住,在调试模式下有一些用于诊断逻辑错误的调试断言,这可以在很大程度上缓解任何性能问题。
  5. 尽可能地在不浪费太多简单性的情况下,在可用时间和金钱允许的范围内始终保持效率是目标。

如果您实现用户界面,则友好的错误消息(包括建议和其他帮助)是良好设计的一部分。
但是请记住,API是针对程序员的,而不是针对最终用户的。


HTML是对输入进行模糊和宽容的真实实验。
这就导致每个人的操作都略有不同,现在已被记录的规范是一个充满特殊情况的庞然大物。
参见Postel的定律(“ 对您的工作保守态度,对他人接受的态度持开放态度 ”)以及对此进行评论的评论家或者说MichaelT使我意识到更好)。


sendmail作者的另一个重要文章:重新考虑了稳健性原则

15

方法的行为应明确,直观,可预测和简单。通常,我们应该非常犹豫是否要对调用者的输入进行额外的处理。这种关于调用者意图的猜测总是具有很多产生不良行为的极端情况。考虑像文件路径联接一样简单的操作。如果要连接的路径之一似乎已植根,则许多(或什至大多数)文件路径连接功能将静默丢弃任何先前的路径!例如,/abc/xyz与一起加入/evil将导致/evil。当我加入文件路径时,这几乎从来不是我想要的,但是由于没有接口不能按照这种方式运行,所以我被迫有bug或编写涵盖这些情况的额外代码。

也就是说,在极少数情况下,使一种方法“宽容”是有意义的,但始终应由调用者确定这些处理步骤何时以及是否适用于其情况。因此,当您确定了要在各种情况下应用于参数的通用预处理步骤时,应公开以下接口:

  • 原始功能,无需任何预处理。
  • 预处理本身
  • 原始功能和预处理的结合。

最后一个是可选的;仅当大量呼叫会使用它时,才应提供它。

公开原始功能使调用者可以在需要时使用其而无需预处理步骤。暴露预处理程序本身可以使调用者在甚至不调用函数的情况下,或者调用函数之前要对其进行预处理的情况下使用它(例如,当他们想先将其传递给另一个函数时)。提供组合可以使呼叫者轻松地调用两者,这在大多数呼叫者都将以这种方式使用时非常有用。


2
+1可预测。还有一个+1(我希望)简单。我更希望您能帮助我发现并纠正错误,而不是试图隐藏它们。
John M Gant 2015年

4

正如其他人所说,使字符串匹配为“宽容”意味着引入了额外的复杂性。这意味着要实现匹配需要做更多的工作。例如,您现在有更多的测试用例。您必须做其他工作以确保名称空间中没有语义上相等的名称。更高的复杂性也意味着将来还会有更多出错。较简单的机制(如自行车)比复杂的机制(如汽车)所需的维护更少。

那么宽大的字符串匹配是否值得所有这些额外费用?正如其他人指出的,它确实取决于用例。如果字符串是某种外部输入,则您无法控制,并且宽容匹配具有明显的优势,它可能是值得的。可能输入的内容来自最终用户,他们可能对空格字符和大小写不太认真,并且您强烈希望使产品易于使用。

另一方面,如果输入来自例如技术人员汇编的属性文件,那么他们应该了解 "Fred Mertz" != "FredMertz",那么我将更倾向于使匹配更加严格并节省开发成本。

我确实认为,在任何情况下,修剪和忽略前导和尾随的空格都是有价值的,尽管如此-我已经看到在调试这类问题上浪费了太多时间。


3

您提到了这个问题的背景。

鉴于此,我将让方法只做一件事,它声明对字符串的要求,然后基于该字符串执行-我不会在这里尝试对其进行转换。保持简单明了;对其进行文档化,并努力使文档和代码彼此同步。

如果您希望以更宽容的方式转换来自用户数据库的数据,请将该功能放入单独的转换方法中并记录与该功能绑定的功能

在某些时候,需要对功能的要求进行计量,清楚地记录下来,并且必须继续执行。在他看来,“宽恕”有点无言,这是一个设计决定,我会为功能辩称不要改变其论点。使函数发生变化会使输入隐藏一些客户端所需的验证。具有执行变异的功能可以帮助客户正确处理。

这里最大的重点是清晰度,并记录代码的作用


-1
  1. 您可以根据操作命名方法,例如doSomething(),takeBackUp()。
  2. 为了便于维护,您可以保留不同程序上的通用合同和验证。根据用例调用它们。
  3. 防御性编程:您的过程处理各种输入,包括(无论如何都必须覆盖用例的最低限度要求)
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.