样板代码
Python和Go之间的案例研究。
在Go中,来自另一种语言的刚开发的地鼠的常见抱怨是错误处理模式:
result, err := DoSomething() if err != nil { return nil, err }
another, err := SomethingElse(result) if err != nil { return nil, err }
// We can be a bit more terse if we don't need to save any // variables outside of the if-scope: if err := MoreWork(another); err != nil { return nil, err } ...
// We can be a bit more terse if we don't need to save any // variables outside of the if-scope: if err := MoreWork(another); err != nil { return nil, err } ...
每当我们一遍又一遍地写相似的代码时,就被认为是样板 。
我们都学习的编程关键原则是不要重复自己 (DRY)。 从某种意义上讲,这就是编程的全部要点:要使我们不断手动重复执行的任务自动化。 谁想要每天早上让我们能有一个程序让文本文件更快,更可靠地进行手工排序,那么谁愿意保持这种技巧? 同样,当我们应该能够将其抽象化为更简短的语法时,为什么还要不断重复编写一些代码模式?
比例工作
在编写Python代码十多年并精通它之后,我将利用任何机会编写简洁的代码。 变得聪明起来感觉很好, 更多的代码意味着更多的错误 ,对吗?
直到我改用Go语言作为主要语言时,我才注意到一件事:Python代码中完成的工作的比例是非常可变的。
比例性是什么意思? 考虑以下两行:
a = some_variable + 42 b = sum((j if j % 2 else 0) for (j, k) in results if k)
第二行则比第一线更多的工作。 第一行是简单的整数加法运算,而第二行则具有循环和变量拆分,以及两个条件分支和生成器上的累加器。
在Python(和许多其他语言)中,很容易在单行内置语法中无意地隐藏大量工作。 有些行几乎不执行任何工作,另一些行则进行中等程度的工作,另一些行我要描述的是数十行值得进行的工作-很多工作。 即使在初学者代码中,它也相差很大。
在Go中,我发现这种可变性大大降低了。 循环始终是循环,错误检查始终是错误检查(而不是隐式异常传播),逻辑分支始终是分支。
这是我可以在Go中编写以上代码的最合理的方式:
b := 0 for _, r := range results { j, k := r[0], r[1] if !k || j % 2 != 0 { continue } b += j }
这个好吗? 我相信是。 我相信这段代码与它正在做的工作的复杂程度成正比。 也可以以相似的比例级别编写Python,为什么不呢?
读取比例代码时,更容易注意到有趣的位在哪里。 更容易注意到大部分工作在何处完成,并将我们的审查重点放在此上。 也许这是主观的,但是我认为代码的形状看起来更像是它的形状 。
我们可以继续争论说,更容易理解工作的实际执行情况,但这不是我想说的。 相反,我想提出一个不同的观点:调整代码的作用更加容易。
纠正的机会
在用Python写了很多代码并在Go中写了一些代码之后,我注意到了一个有趣的现象:我在Go中对代码进行了非常不同的调整。
编程是一个非常反复的过程。 随着我们实施解决方案,我们对问题的心理模型也在不断发展。 我们经常返回并更改一些假设,调整数据结构,删除过时的拐杖和存根,使错误处理更加健壮,等等。
我注意到,当我编写Python代码时,我通常会尽力避免将逻辑移动到自然可能属于的地方以外的地方,从而弄乱了优美的简洁行集。 我会在不知不觉中将复杂性从它属于切向位置的位置移开,该位置使调整变得比原本应该更复杂。
例如:我不是将try / except块重构为单独的适当分层的方法调用或多个try / except阶段,而是引入一个变量来维护在finally块中处理的错误状态。 ( urllib3中此模式的示例 。)
当我写Go时,我注意到我的调整几乎总是准确地归根结底。 我的样板并没有保持不变!
例如:我通常会在第一次迭代中编写很多Go样板,但是当我仔细查看要发布的代码时,我注意到它不再像样板。 到我完成工作时(添加更多的恢复方案,更好的日志记录,使用更多的上下文来增加错误,处理更多的边缘情况),我的大部分样板都变得更加有趣。 我没有意识到好的样板可以为迭代提供沃土。
当一个朋友阅读ssh-chat的源代码时 ,他为我实现了自己的Set类型感到惊讶。 我解释说,尽管Go没有内置的Set,但仅需几行样板就可以在地图上创建自己的模板。 实际上,我注意到我的Set版本演变为非常特定于其使用方式。 回想起来,我很高兴自己能反复调整自己的实现,而不是花时间解决通用库可能存在的任何限制。
现在,我意识到不成比例的代码使我无意间在它周围跳舞,就像微小的复杂黑洞一样。 拥有成比例的样板有助于平衡修改代码的难易程度与代码实际正在执行的工作量—无论是错误处理,循环还是其他任何事情。
比例代码减少了复杂性
我并不是在说比例代码行多于不比例代码行少。
我并不是说所有样板都很好:样板在另一个方向上也可能不成比例,因为它所做的工作比我们预期的要少得多。
我的主张是:对于非平凡的项目, 比例代码累积的行数不超过不比例代码。
当我们编写不成比例的代码(或成比例的代码)时,我们会将自然的复杂性转移到不自然的地方,这是意料之外的牺牲。 我们漂亮的单线确实确实很短,但是该项目的其余部分在其他地方遭受了还清购买的款项。
到我们完成对项目的充实和充实时,我们可能不会像不经意地摆脱样板而失去的收获那样多。 比例样板可以帮助我们以较少约束的方式进行迭代。
翻译自: https://hackernoon.com/code-boilerplate-is-it-always-bad-934827efcfc7
样板代码