功能应该有多小?

时间:2017-12-29 22:46:54

标签: performance function readability

我应该做多少小功能?例如,如果我有蛋糕烘焙程序。

bakeCake(){
  if(cakeType == "chocolate") 
     fetchIngredients("chocolate")
  else

  if(cakeType == "plain")
     fetchIngredients("plain")
  else

  if(cakeType == "Red velvet")
     fetchIngredients("Red Velvet")

  //Rest of program

我的问题是,虽然这个东西本身很简单,当我向bakeCake函数添加更多东西时,它变得杂乱无章。但是我们可以说这个程序每秒必须烘焙数千个蛋糕。从我所听到的情况来看,与仅使用当前函数中的语句相比,使用另一个函数需要更长的时间(相对于计算机时间)。所以像这样的东西应该很容易阅读,如果效率很重要,我不想把它留在那里吗?

基本上,我在什么时候牺牲了可读性以提高效率。还有一个快速的奖励问题,在什么时候有太多的功能会降低可读性?这是Apple快速教程的一个例子。

func isCandyAmountAcceptable(bandMemberCount: Int, candyCount: Int) -> Bool {
  return candyCount % bandMemberCount == 0

他们说,因为函数名isCandyAmountAcceptablecandyCount % bandMemberCount == 0更易于阅读,因此为此制作函数会更好。但是从我的角度来看,可能需要几秒钟来弄清楚第二个选项的含义,但是当知道它是如何工作时它也更具可读性。

很抱歉,到处都是,有点问两个问题。只是总结一下我的问题:

  1. 使用功能是否会影响效率(速度)?如果确实如此,我怎样才能弄清楚可读性和效率之间的界限是什么?

  2. 我应该为什么小而简单的功能?显然,如果我不得不重复这个功能,我会制作它们,但是一次使用功能呢?

  3. 谢谢你们,对不起,如果这些问题无知或者其他什么,我会非常感谢你的回答。

2 个答案:

答案 0 :(得分:2)

  

使用功能是否会影响效率(速度)?如果   它怎么能弄清楚可读性和可读性之间的截止点   效率是?

对于性能,我通常不会考虑针对任何体面优化器的直接函数调用的任何开销,因为它甚至可以使它们免费。如果没有,那么在99.9%的情景中它仍然可以忽略不计。这甚至适用于性能关键领域。我在光线跟踪,网格处理和图像处理等领域工作,而且函数调用的成本通常位于优先级列表的底部,而不是参考的位置,高效的数据结构,并行化和矢量化。即使您进行微优化,也有比直接函数调用更高的优先级,即使您进行微优化,您通常也希望为优化器留下大量优化执行而不是试图与它作斗争并且手动完成(除非你实际上正在编写汇编代码)。

当然,对于某些编译器,您可能会处理那些从不内联函数调用并且每个函数调用都有一点开销的编译器。但在这种情况下,我仍然说它相对可以忽略不计,因为在使用这些语言和解释器/编译器时,你可能不应该担心这种微观级别的优化。即便如此,相对而言,它可能经常位于优先级列表的底部,而不是更具影响力的事情,例如改进引用的局部性和线程效率。

如果您使用的编译器具有非常简单的寄存器分配,对您使用的每个变量都有堆栈溢出,这并不意味着您应该尝试使用和重用尽可能少的变量来解决其趋势。这意味着在那些不可忽视的开销(例如:将一些C代码写入dylib并将其用于最关键的性能部分)的情况下用于新编译器,或者专注于更高级别的优化,例如让一切都并行运行。

  

我应该为什么小而简单的功能?我显然是在做   如果我不得不重复这个功能,那么它们有一次   使用功能?

这是我稍微偏离的地方,并且实际建议您考虑出于可维护性原因而避免使用最少的功能。这无疑是一个有争议的观点,尽管至少John Carmack似乎有点同意(特别是在内联代码和避免过多函数调用的情况下,副作用发生以使副作用更容易理解)。

  

但是,如果要进行大量的状态更改,请使用它们   所有发生在线确实有优势;你应该经常做   意识到你正在做的事情的完全恐怖。

我认为有时候在更加强大的功能方面犯错是有好处的原因是因为通常更容易理解而不是简单的功能来理解做出改变或修复所需的所有信息。问题

哪个更容易理解,这个函数的逻辑由80行内联代码组成,或者一个分布在几十个函数中的函数,以及可能导致整个代码库中不同位置的函数?

答案不是那么明确。当然,如果广泛使用了很少的函数,比如说sqrtabs,那么读者可以简单地浏览函数调用,完全了解它的后半部分。但是如果有很多奇怪的外来功能只使用一次,那么整体理解操作的能力需要查找它们并理解它们各自单独做的事情,然后才能正确理解这些功能。从大局出发。

我实际上不同意那个Apple Swift教程有点使用单行函数,因为虽然它比理解算术和比较应该做什么更容易理解,但作为交换它可能需要查找它看看它是什么在您不能说的情况下,isCandyAmountAcceptable对我来说是足够的信息,需要弄清楚究竟是什么使得金额可以接受。相反,我实际上更喜欢简单的评论:

// Determine if candy amount is acceptable.
if (candyCount % bandMemberCount == 0)
    ...

...因为那时你不必跳到代码中的不同位置(类似于书中将读者引用到书中的其他页面,导致读者经常不得不在页面之间来回切换)想出来。当然,isCandyAmountAcceptable这种功能背后的想法是你不应该关心什么使得糖果量可以接受,但在实践中,我们最终不得不比我们最佳地调试代码或对代码进行更改更经常地理解细节。如果代码永远不需要调试或更改,那么它的编写方式并不重要。它甚至可以用我们关心的所有二进制代码编写。但是,如果它被编写为维护,如将来调试和更改,那么有时避免让读者不得不跳过很多圈子是有帮助的。在这些情况下,细节通常很重要。

因此,有时通过将其分解为最神奇的拼图来帮助理解大局是没有帮助的。这是一种平衡行为,但是某些类型的开发人员可能会错误地将他们的系统划分为最精细的部分并以这种方式发现维护问题。这些类型仍然是有前途的工程师 - 他们只需找到他们的平衡。另一个极端是编写500行函数并且甚至不考虑重构 - 那些有点无望。但是我认为你适合前一类,而且对于你来说,我实际上建议在更加强大的功能方面犯错误 - 只是为了保持拼图的健康尺寸(不是太小,不是太大) )。

我甚至在代码重复和最小化依赖关系之间看到了平衡行为。如果交换是对具有800,000行代码的复杂数学库的依赖性以及如何使用它的史诗手册,那么通过削减几十行重复数学代码,图像库不一定变得更容易理解。在这种情况下,如果图像库选择在这里和那里复制一些数学函数以避免外部依赖性,隔离其复杂性而不是将其分发到其他地方,那么图像库可能更容易理解以及在新项目中使用和部署。

  

基本上,我在什么时候牺牲了可读性以提高效率。

如上所述,我不认为小图片的可读性和大图片的可理解性是同义词。读取两行函数并知道它的功能非常简单,距离理解您需要理解的内容还需要几英里才能进行必要的更改。拥有许多那些极小的一次性双线人甚至可以延迟理解大局的能力。

但是,如果我使用"可理解性与效率"相反,我会在设计层面提前预测处理大量输入的情况。例如,具有自定义过滤器的视频处理应用程序知道它将每帧多次循环数百万像素。应该利用这些知识来提出一种有效的设计,重复循环数百万像素。但是在设计方面 - 面向系统的核心方面,许多其他地方将依赖于这一点,因为大的中央设计变更成本太高,无法在后见之明中应用。

这并不意味着它必须立即开始应用难以理解的SIMD代码。这是一个实施细节,为设计留下了足够的喘息空间,可以在后见之明中探索这种优化。这样的设计意味着在Image级别抽象,在一百万+像素的水平上,而不是在一个IPixel的水平上。这是值得考虑的事情。

然后,您可以优化热点,并可能在这里和那里使用一些难以理解的算法和微优化,以确保那些真正具有强烈感知业务需求的操作更快,并希望有好的工具(分析器,即)。用户案例会根据用户最常做的事情来指导您要优化的操作,并希望花更少的时间等待。分析器可以准确地指导您需要优化该操作中涉及的代码的哪些部分。

答案 1 :(得分:0)

可读性,性能和可维护性是三回事。可读性将使您的代码看起来简单易懂,不一定是最好的方法。性能总是很重要,除非您在非生产环境中运行此代码,其中最终结果比实现方式更重要。进入企业应用的世界,可维护性突然变得更加重要。您今天工作的内容将在6个月后交给其他人,他们将修复/更改您的代码。这就是突然标准设计模式变得如此重要的原因。在某种程度上,可读性是更大规模可维护性的一部分。如果上面的蛋糕烘焙程序比看起来更复杂,那么首先要注意的是代码气味是否存在,如果if-else。它必须被多态性取代。开关案例类型的构造也是如此。 在什么时候你决定牺牲一个为另一个?这纯粹取决于您的代码实现了哪些业务。这是学术性的吗?它必须是一个完美的解决方案,即使它意味着90%的开发人员挣扎着乍看之下到底发生了什么。它是属于零售店的网站,由来自2个或更多不同地理位置的50个开发者的分布式团队维护吗?遵循传统的设计模式。 我一直认为在几乎所有情况下都遵循的经验法则是,如果一个函数超出屏幕的一半,它就是重构的候选者。你有功能,最终你有你的编辑器长卷轴?重构!