As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center提供指导。




7年前关闭。




我记得一段时间(大概几年前),我在Stackoverflow上阅读了有关使用尽可能少的if-tests进行编程的魅力。 This question在某种程度上是相关的,但我认为重点在于使用许多小函数,这些小函数根据它们接收的参数返回由测试确定的值。一个非常简单的示例将使用以下代码:
int i = 5;
bool iIsSmall = isSmall(i);
isSmall()看起来像这样:
private bool isSmall(int number)
{
    return (i < 10);
}

而不是仅仅这样做:
int i = 5;
bool isSmall;
if (i < 10) {
    isSmall = true;
} else {
    isSmall = false;
}

(逻辑上,此代码只是示例代码。它不是我正在编写的程序的一部分。)

我相信这样做的原因是因为它看起来更好,并且使程序员不易出现逻辑错误。如果正确地应用了此编码约定,则除了在其唯一目的是执行该测试的函数中,几乎看不到任何if测试。

现在,我的问题是:是否有关于该公约的任何文档?在任何地方都可以看到这种风格的支持者和反对者之间的激烈争论?我尝试搜索将其介绍给我的Stackoverflow帖子,但现在找不到了。

最后,我希望这个问题不会被拒绝,因为我不是在寻求解决问题的方法。我只是希望听到更多关于这种编码风格的信息,也许会提高我将来将要进行的所有编码的质量。

最佳答案

整个“if”与“no if”的对比让我想到了Expression Problem1。基本上,这是一种观察,即使用if语句或不使用if语句进行编程都是封装和可扩展性的问题,有时使用if语句2和if更好。有时最好将动态分配与方法/函数指针一起使用。

当我们要建模时,有两个轴需要考虑:

  • 我们需要处理的输入的不同情况(或类型)。
  • 我们想要对这些输入执行的不同操作。

  • 实现这种事情的一种方法是使用if语句/模式匹配/访问者模式:
    data List = Nil | Cons Int List
    
    length xs = case xs of
      Nil -> 0
      Cons a as -> 1 + length x
    
    concat xs ys = case ii of
      Nil -> jj
      Cons a as -> Cons a (concat as ys)
    

    另一种方法是使用面向对象:
    data List = {
        length :: Int
        concat :: (List -> List)
    }
    
    nil = List {
        length = 0,
        concat = (\ys -> ys)
    }
    
    cons x xs = List {
        length = 1 + length xs,
        concat = (\ys -> cons x (concat xs ys))
    }
    

    不难看出,使用if语句的第一个版本可以轻松地在我们的数据类型上添加新操作:只需创建一个新函数并在其中进行案例分析即可。另一方面,这使得很难向我们的数据类型添加新的案例,因为这意味着要遍历程序并修改所有分支语句。

    第二个版本则相反。向数据类型添加新案例非常容易:只需创建一个新的“类”,并告诉我们需要实现的每种方法要做什么。但是,现在很难在接口(interface)上添加新操作,因为这意味着为实现该接口(interface)的所有旧类添加新方法。

    语言使用许多不同的方法来尝试解决表达问题,并使向模型中添加新案例和新操作变得容易。但是,这些解决方案各有利弊,因此总的来说,我认为在OO和if语句之间进行选择是一个很好的经验法则,具体取决于要使扩展对象更容易的轴。

    无论如何,回到您的问题,我想指出几件事:

    第一个是,我认为摆脱所有if语句并将其替换为方法分派(dispatch)的OO“口头禅”与大多数OO语言不具有类型安全的代数数据类型有关,而与“if statemsnts”不利于封装。由于确保类型安全的唯一方法是使用方法调用,因此建议您使用if语句将程序转换为使用Visitor Pattern 4或更糟糕的程序:将应使用访问者模式的程序转换为使用简单方法分派(dispatch)的程序,因此具有可扩展性容易朝错误的方向前进。

    第二件事是,我并不是仅仅因为可以就将其制动为功能的忠实拥护者。特别是,我发现所有功能只有5行并且调用大量其他功能的样式很难读懂。

    最后,我认为您的示例并未真正摆脱if语句。本质上,您正在做的是具有从Integers到新数据类型的功能(有两种情况,一种用于Big,一种用于Small),然后在处理数据类型时仍需要使用if语句:
    data Size = Big | Small
    
    toSize :: Int -> Size
    toSize n = if n < 10 then Small else Big
    
    someOp :: Size -> String
    someOp Small = "Wow, its small"
    someOp Big   = "Wow, its big"
    

    回到表达式问题的角度来看,定义toSize/isSmall函数的好处是,我们将选择数字适合的大小写放在一个地方的逻辑,然后我们的函数只能在此大小写上进行操作。但是,这并不意味着我们已经从代码中删除了if语句!如果我们的toSize是工厂函数,而Big和Small是共享接口(interface)的类,那么可以,我们将从代码中删除了if语句。但是,如果out isSmall仅返回 bool 值或枚举,则if语句的数量将与以前一样多。 (并且您应该选择是否要使用哪种实现,如果您想在将来使添加新方法或新案例(例如“中等”)变得更加容易)

    1-问题的名称来自于您具有“表达式”数据类型(数字,变量,子表达式的加/乘等)并且想要实现诸如评估函数之类的事物的问题。

    2-或通过代数数据类型进行模式匹配,如果您希望更加安全的话...

    3-例如,您可能必须在“调度程序”可以看到的“顶层”上定义所有多方法。与一般情况相比,这是一个限制,因为您可以使用if语句(和lambda)深深地嵌套在其他代码中。

    4-本质上是代数数据类型的“教会编码”

    09-19 02:05