优化一个类以进行单元测试是好的实践吗,还是过早了?

5
我在StackOverflow上看到(并搜索)了很多关于过早优化的问题 - 大家普遍认为,这是万恶之源。:P 我承认我经常会犯这种错误; 我不会为了速度而牺牲代码的可读性,但我会以更合适的方式重写我的代码,使用更适合任务的数据类型和方法(例如,在Actionscript 3中,使用一个有类型的Vector而不是无类型的Array进行迭代),如果可以让我的代码更优雅,我就会这么做。这通常有助于我理解我的代码,我通常知道为什么要进行这些更改。
无论如何,我今天想到了一个问题 - 在面向对象编程中,我们提倡封装,试图隐藏实现并促进接口,使类松散耦合。这样做的目的是创建一个可以在不必知道内部发生的情况下工作的东西 - 黑盒子思想。
因此,我的问题是这样的 - 在类级别上深度优化代码是否明智,因为OOP促进了模块化?还是这属于过早优化的范畴?我想,如果你使用一种容易支持单元测试的语言,你可以测试、基准测试和优化类,因为它本身就是一个接受输入并生成输出的模块。但是,作为一个单独的编写代码的人,我不知道等到项目完全完成后开始优化是否更明智。
供参考:我以前从未在团队中工作过,所以对于有这种经验的开发人员来说显而易见的事情可能对我来说是陌生的。
希望这个问题适合StackOverflow - 我没有找到另一个直接回答我的问题的问题。
谢谢!
编辑:思考这个问题,我意识到“分析”可能是正确的术语,而不是“单元测试”;单元测试检查模块是否按预期工作,而分析则检查性能。另外,在之前应该先问一个问题 - 在创建完单个模块后分析它是否会减少应用程序完成后的分析时间?
我的问题源于我正在尝试进行的游戏开发 - 我必须创建模块,例如图形引擎,它们应该执行最佳(它们是否会是另一回事:D)。在性能不那么重要的应用程序中,我可能不会担心这个问题。

这个问题是否更适合作为 CW 问题,因为有几个好答案,而且它并不是一个特定的主题? - user677526
5个回答

2
“我不会为了速度而牺牲代码可读性进行优化,但我会以更符合任务需求的数据类型和方法逻辑重写我的代码[...]如果我能让我的代码更加优雅,我也会这样做。这通常有助于我理解我的代码。”这并不是真正的“优化”,而是为了更清晰的代码和更好的设计而进行的“重构”。因此,这是一件好事,并且应该不断地进行小幅度的练习。Bob Martin(在他的书《Clean Code》中)提出了男童军规则,在软件开发中得到了广泛的推广:让代码比你发现时更干净。
所以为了回答你的标题问题,重新说一下,是的,重构代码以使其可进行单元测试是一个好的实践。其中的“极端”做法是 测试驱动开发,即先编写测试,然后添加使测试通过的代码。这样,代码从一开始就可以进行单元测试。
*不是要挑刺,只是澄清常见术语并确保我们使用相同的术语来表示相同的含义。

1

我认为优化应该留到最后(虽然在写第一稿时意识到可能需要回头优化是好的)。这并不是说你不应该迭代地重构代码以保持代码的整洁性和可读性。而是说,如果某个东西目前已经达到了其目的,并且没有破坏应用程序的要求,那么首先应该解决需求,因为最终你要交付的是需求(除非需求包括最大请求时间或类似的具体要求)。我也同意Korin的方法论,如果时间允许,首先构建功能,然后再进行优化(或者达到理论极限,以先到者为准)。


1

过早地进行优化是不好的原因在于:它可能会花费很多时间,而你事先并不知道最好利用时间的地方在哪里。

例如,你可能会花费大量时间来优化一个类,但最终发现你应用程序中的瓶颈是网络延迟或类似的因素,这些因素在执行时间方面更加昂贵。由于一开始你没有完整的图片,过早的优化会导致你时间使用不够优化。在这种情况下,你可能更愿意解决延迟问题而不是优化类代码。


优化也可能会让你的代码变得复杂和晦涩,从而使添加或修改功能变得更加困难。 - don
那通常是优化问题,而不是过早优化的特定情况。 - Hamish

1

我坚信,为了性能优化而降低代码可读性和良好设计是绝对不可取的

如果你正在编写性能至关重要的代码,可能会降低代码的风格和清晰度,但这并不适用于普通企业应用程序。硬件快速发展,每天都在变得更便宜。最终,你编写的代码将被其他开发人员阅读,因此你最好做好它!

令人愉悦的是,阅读经过精心制作的代码,每个路径都有一个测试,帮助你理解如何使用它。我真的不在乎它比执行许多疯狂操作的意大利面替代方案慢50毫秒。


1

是的,您应该跳过为单元测试进行优化。通常情况下,当需要优化时,代码会变得更加复杂。追求简单。如果您为单元测试进行优化,实际上可能会使生产环境的性能下降。

如果单元测试中的性能真的很差,您可能需要查看设计。在应用程序中进行测试,以查看性能是否同样糟糕,然后再进行优化。

编辑:当处理的数据大小不同时,很可能会发生反优化。这最有可能发生在处理数据集的类中。响应可能是线性的,但最初很慢,与几何级别和最初快速相比较。如果单元测试使用小数据集,则可能选择几何解决方案。当生产环境使用大数据集命中该类时,性能会下降。

排序算法是这种行为和导致反优化的典型案例。许多其他算法具有类似的特征。

编辑2:我的最成功的优化是针对一份报告的排序程序,其中数据存储在内存映射文件中。在适度的数据大小下,排序时间合理,不需要访问磁盘。但当数据集大小更大时,处理数据可能需要数天。报告的初始时间显示:数据选择3分钟,数据排序3天,报告3分钟。调查发现,这是一个未经过全面优化的冒泡排序(对于大小为n的数据集,需要n-1次完整遍历),在大O符号表示法中约为n平方。将排序算法更改后,此报告的排序时间缩短至3分钟。我没有预料到单元测试会涵盖这种情况,而原始代码对于小型数据集来说非常简单(快速)。替代方案更加复杂,对于非常小的数据集来说更慢,但处理大型数据集更快,具有更线性的曲线,在大O符号表示法中为n log n。(注:在获得指标之前没有进行任何优化。)

实际上,我致力于将运行时间占模块总运行时间50%以上的例程提高十倍。对于使用55%运行时的程序,达到这种水平的优化将节省50%的总运行时间。


我希望这不是一个愚蠢的问题,但是如何为单元测试进行优化会导致在生产环境中变慢呢?假设你进行了大量的优化(也许使用了这个主题中的一些技术),并且你能够将执行时间缩短50%或更多。这难道不会转化为应用程序环境中更快的执行时间吗?我不确定我是否理解正确。 - user677526
啊,我明白了……不过,以不同的方式对类进行单元测试是很合理的,不是吗?如果一个类需要处理多种类型或大小的数据,那么测试这些情况是很有意义的。至少,这是我会做的(但我有点极客,所以我喜欢这样的东西)。然而,我也能理解时间限制是个问题。 - user677526
1
@lunchmeat317:对于任何相当大的数据集,这很快变得不切实际。我希望单元测试使用小数据集(少于10个)。 - BillThor
我花了一段时间(很长的一段时间)才真正理解这个。我明白你在说什么,但只有在看到大O符号后,我才真正理解你的意思。可以(我想应该)使用不同范围的数据进行单元测试-10、100、1000、10000等……但一旦超过这些限制,测试就变得昂贵了。好答案。 - user677526

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接