干净可读的代码与快速难以读取的代码。什么时候越界?


67

当我编写代码时,我总是试图使我的代码尽可能的干净和可读。

偶尔会有一段时间,您需要越界,从漂亮的干净代码变为稍微难看的代码,以使其更快。

什么时候可以越过那条线?


69
您回答了自己的问题,需要
就越

6
另外,从现在开始六个月后,您的“脏代码”可能与“干净代码”一样快。但是,不要像Windows那样过度。:)
Mateen Ulhaq

21
难以理解的算法与难以理解的代码之间存在显着差异。有时,您需要实现的算法很复杂,并且代码一定会令人困惑,仅仅是因为它表达了一个复杂的想法。但是,如果代码本身就是难点,那么应该固定代码。
tylerl 2011年

8
在很多情况下,智能编译器/解释器可以优化干净易读的代码,因此其性能与“丑陋的”代码相同。因此,没有什么借口,除非剖析另有说明。
Dan Diplo

1
这些天来涉及编译器时,您的丑陋代码很可能与您的干净代码相同(假定您没有做任何真正奇怪的事情)。特别是在.NET中,与C ++ / MFC时代不同,如何定义变量会影响性能。编写可维护的代码。一些代码最终将变得很复杂,但这并不意味着它很丑。
DustinDavis

Answers:


118

当你越界

  • 您已经测量出代码对于预期用途而言太慢了
  • 您尝试了不需要修改代码的替代改进。

这是一个真实的示例:我正在运行的实验系统生成数据的速度太慢,每次运行耗时超过9个小时,并且仅使用40%的CPU。我没有将代码过多地弄乱,而是将所有临时文件都移到了内存中的文件系统中。添加了8行新的非丑陋代码,现在CPU利用率超过98%。问题解决了; 无需丑陋。


2
您还应确保保留原始的,较慢的,较干净的代码,以作为参考实现,并在硬件发生变化并且更快,更骇人的代码不再起作用时重新使用。
Paul R

4
@PaulR您如何保留该代码?以评论的形式?imo,这是错误的-注释已过时,没有人阅读它们,如果我自己看到注释掉的代码,我通常会删除它-这就是源代码管理的目的。imo对一种解释其作用的方法进行了评论。
Evgeni 2013年

5
@Eugene:我通常会保留例程的原始版本foo并将其重命名foo_ref-通常它foo位于源文件的上方。在我的测试工具我打电话foo,并foo_ref进行验证和相关性能测量。
Paul R

5
如果这样做,@Paul可能是一个好主意,如果优化版本的速度比ref函数慢,则测试失败。如果您为了使其运行更快而做出的假设不再成立,则可能会发生这种情况。
user1852503 2014年

57

这是错误的二分法。您可以使代码快速易于维护。

做到这一点的方法是将其编写得干净整洁,尤其是使用尽可能简单的数据结构。

然后,找出时间消耗在哪里(通过运行它,编写之后,而不是在之前),并逐一修复它们。 (这是一个例子。)

补充:我们总是听到有关权衡的问题,例如时间和内存之间的权衡,还是速度和可维护性之间的权衡?尽管这样的曲线很可能存在,但是不应假定任何给定的程序在曲线上,甚至在它附近的任何地方。

曲线上的任何程序都可以很容易地(通过提供给某种类型的程序员)变得既慢得多,又难以维护,那么它将离曲线很远。这样,这样的程序就有很大的空间可以更快,更易于维护。

以我的经验,这就是许多程序开始的地方。


我同意。确实,不干净的快速代码最终会变慢,因为您无法正确修改它。
edA-qa mort-ora-y

22
同意这是错误的二分法。IMO有一些场景,尤其是在库代码中(在应用程序代码中不是很多),其中拆分非常真实。看到我的答案更多。
Marc Gravell

1
Marc,您可以使用“链接” URL链接到评论中的答案。programmers.stackexchange.com/questions/89620/...

我们已经找到了所有时间,需要尝试使代码运行更快。但是,在使用探查器进行实验以找到最佳解决方案(代码变得丑陋)之后,这并不意味着代码需要保持丑陋。寻找最佳解决方案是一个问题,乍一看似乎并不明显,但是一旦找到通常就可以对其进行清晰地编码。因此,我认为这是一种错误的二分法,只是在您玩弄玩具后不整理房间的借口。我说把它吸干净并整理房间。
马丁·约克

我不同意这是错误的二分法。因为我做了很多图形工作,所以对我来说,最明显的例子是在紧密的图形循环中:我不知道这样做多久进行一次,但是对于用C语言编写的游戏引擎来说,使用Assembly进行核心渲染是很常见的。循环以挤出最后的速度下降。这也让我想到了使用Python进行编程但使用C ++编写的模块的情况。“难以阅读”总是相对的;每当您为了提高速度而使用低级语言时,该代码都比其他语言难读。
2011年

31

在OSS的存在中,我做了很多针对性能的库工作,这些工作与调用者的数据结构(即库外部)紧密相关,(根据设计)对传入类型没有任何要求。在这里,使这种性能最佳的最好方法是元编程,因为我在.NET领域(因为我在.NET领域)所以意味着IL发射。那是一些丑陋的代码,但是非常快。

这样,我很高兴地接受到代码可能比应用程序代码“丑陋” ,只是因为它对输入的控制较少(或没有控制),所以需要通过不同的机制来完成一些任务。或者就像我前几天所说的那样:

“在精神错乱的悬崖上编码,因此您不必

现在,应用程序代码略有不同,因为这是“常规”(健全)开发人员通常将大量的协作/专业时间投入的地方。每个人的目标和期望(IMO)略有不同。

IMO,答案上面表明,它可以快速易于维护指的是应用程序代码,其中开发人员拥有对数据结构的更多的控制,而不是使用像元编程工具。也就是说,执行元编程的方式不同,疯狂程度不同,开销也不同。即使在那个舞台上,您也需要选择适当的抽象级别。但是,当您积极,积极地,真正地希望它以绝对最快的方式处理意外数据时;它可能会变得丑陋。处理; p


4
仅仅因为代码丑陋,并不意味着它就必须无法维护。注释和缩进是免费的,通常可以将丑陋的代码封装到一个可管理的实体(类,模块,包,函数,具体取决于语言)中。该代码可能同样丑陋,但至少人们将能够判断他们将对其进行更改的影响。
tdammers

6
@tdammers确实如此,我会尽力做到这一点;但这有点像在猪上涂口红。
Marc Gravell

1
好吧,也许应该区分丑陋的语法和丑陋的算法-有时需要丑陋的算法,但丑陋的语法通常是无法原谅的IMO。
tdammers 2011年

4
@IMO丑陋的语法是不可避免的,如果您要执行的操作本质上常规语言级别的几个抽象级别。
Marc Gravell

1
@马克...多数民众赞成在有趣。我对meta / abstract丑陋的第一反应是怀疑特定的语言/平台不利于meta编码,而不是将两者联系在一起的一些基本法则。让我相信的是,以集论结束的数学中渐进元级的例子,其表达几乎比代数甚至具体的数学式更丑陋。但随后设置符号可能是一个不同的语言完全和每abstration水平之下有自己的语言....
explorest

26

当您分析了代码并验证了它实际上引起了显着的减速。


3
什么是“重要的”?
鲁克

2
@ hotpaw2:这是一个明智的答案-假设开发人员至少具有一定的竞争力。否则,使用比冒泡排序更快的方法(通常)是个好主意。但是,经常有人会(为了跟上排序)将quicksort换成heapsort的差异为1%,却发现六个月后出于同样的原因别人将其交换回了。

1
从来没有一个理由让不干净的代码。如果您不能使您的高效代码干净且易于维护,那您在做错什么。
edA-qa mort-ora-y

2
@SF。- 如果可以更快,客户总是会发现它太慢。他不在乎代码的“干净”。
鲁克

1
@Rook:客户可能会发现(简单的)接口代码太慢。一些非常简单的心理技巧可以在不实际加速代码的情况下改善用户体验-将按钮事件推迟到后台例程中,而不是动态执行操作,显示进度栏或类似的内容,在后台执行活动时问一些无关紧要的问题...当这些还不够时,您可以考虑实际的优化。
SF。

13

干净的代码不一定是快速执行代码所独有的。编写通常难以阅读的代码是因为它编写起来更快,而不是因为它执行得更快。

试图编写“肮脏的”代码以使其更快,这无疑是不明智的,因为您不确定您所做的更改实际上可以改善任何事情。Knuth说得最好:

“我们应该忘掉效率低下的情况,大约有97%的时间是这样:过早的优化是万恶之源。但是,我们不应该在那3%的临界水平上放弃自己的机会。优秀的程序员不会因此而感到自满推理时,明智的做法是仔细查看关键代码;但是只有在确定了该代码之后,

换句话说,首先编写干净的代码。然后,分析生成的程序,并查看该段实际上是否是性能瓶颈。如果是这样,请根据需要对本节进行优化,并确保包含大量文档注释(可能包括原始代码)以解释优化。然后分析结果以验证您确实进行了改进。


10

由于问题是“ 难以阅读的快速代码”,因此永远不会提供简单的答案。编写难读的代码从来没有任何借口。为什么?有两个原因。

  1. 如果您今晚回家时被公交车撞到怎么办?还是(更乐观地,更通常地)取消该项目并重新分配给其他项目?想象中的混乱代码带来的小好处完全被没有人理解的事实所抵消。这给软件项目带来的风险很难夸大。我曾经在一家大型PBX工作过制造商(如果您在办公室工作,则您的桌子上可能有他们的一部手机)。他们的项目经理告诉我,有一天他们的核心产品(将标准Linux盒子变成功能齐全的电话交换机的专有软件)在公司内部被称为“斑点”。没人明白了。每次他们实施新功能时。他们会先进行编译,然后站起来,闭上眼睛,数到二十,然后通过手指窥视一下是否有效。没有业务需要不再控制的核心产品,但这是一个可怕的普遍情况。
  2. 但是我需要保持乐观!好的,因此您在回答该问题的其他答案时都遵循了所有出色的建议:您的代码未通过其性能测试用例,已仔细分析了它,确定了瓶颈,提出了解决方案...涉及一些纠结。很好:现在继续乐观。但这是个秘密(您可能想坐下来):优化和减少源代码大小不是一回事。注释,空格,方括号和有意义的变量名都是提高可读性的巨大帮助,因为编译器会将它们丢弃,因此绝对不会花费任何费用。(或者,如果您正在编写JavaScript之类的非编译语言-是的,则有非常合理的理由来优化JavaScript-可以通过压缩程序来处理它们。)局促的,极简的代码行(例如一个Muntoo拥有张贴在此处)与优化无关:这是程序员试图通过将尽可能多的代码打包到尽可能少的字符中来展示它们的聪明之处。那不聪明,这很愚蠢。一个真正聪明的程序员是可以与他人清晰地交流思想的程序员。

2
我不同意答案是“从不”。一些算法本质上很难理解和/或有效地实现。无论注释多少,都必须阅读代码,这可能是一个非常困难的过程。
Rex Kerr

4

当它是一次性代码时。我的意思是,从字面上:当你写一个脚本来执行一次性计算或任务,这样的肯定知道你将永远不会再这样做那个动作,你可以“RM源文件”没有犹豫,那么你可以选择丑陋的路线。

否则,这是错误的二分法-如果您认为需要使其变得丑陋以使其更快地执行,则说明您做错了。(或者您需要修改关于什么是好的代码的原则。实际上,当goto是解决问题的适当方法时,使用goto确实很优雅。但是很少这样做。)


5
没有扔掉的代码这样的东西。如果每次因为“扔掉的代码”投入生产而每次都花一分钱,因为“它在起作用,我们没有时间重写它”,那么我将成为百万富翁。您编写的每一行代码都应该编写成这样,以便让另一位称职的程序员明天晚上被雷电击中后才能取用。否则不要写它。
马克·惠特克

我不同意这是错误的二分法。IMO有一些场景,尤其是在库代码中(在应用程序代码中不是很多),其中拆分非常真实。看到我的答案更多。
Marc Gravell

@mark,如果“另一个称职的程序员”真的很称职,那么扔掉的代码也不应该是一个问题:)

@Mark-简单。只需编写扔掉的代码,这样它就可能无法固定地通过任何生产测试。
2011年

@Mark,如果您的“废弃代码”投入生产,那么那不是废弃代码。请注意,我在回答问题上花了一些时间,以澄清我所谈论的是字面意义上的代码:即,首次使用后删除。否则,我同意你的观点,并在回答中说了很多。
maaku 2011年

3

每当市场上较低性能的估计成本大于所讨论代码模块的代码维护估计成本时。

人们仍然会扭曲手编码的SSE / NEON /等。尝试在今年流行的CPU芯片上击败竞争对手的软件。


从良好的业务角度来看,有时程序员需要超越单纯的技术视野。
this.josh 2011年

3

不要忘记,通过适当的文档和注释,您可以使难以阅读的代码易于理解。

通常,在编写了具有所需功能的易于阅读的代码之后进行概要分析。瓶颈可能需要您做一些使其看起来更复杂的事情,但是您可以通过解释自己来解决。


0

对我来说,这是一定比例的稳定性(例如水泥中的水泥,烤箱中烘烤的粘土,石头中的石头,用永久墨水书写的)。您的代码越不稳定,因为将来需要更改它的可能性就越高,则保持湿润的生产力(如湿粘土)就越需要柔韧性。我还强调柔韧性而不是可读性。对我来说,更改代码比阅读代码更重要。代码很容易阅读,并且需要更改,这是一场噩梦;如果代码需要更改,它们可以读取并轻松理解实现细节有什么用?除非只是学术活动,否则通常容易理解生产代码库中代码的目的是为了能够根据需要更轻松地对其进行更改。如果很难改变 那么可读性的许多好处就荡然无存了。可读性通常仅在柔韧性的情况下有用,而柔韧性仅在不稳定的情况下有用。

自然,即使是最难维护的可想象的代码,无论它有多难读,如果没有理由只更改它,也不会造成问题。而且有可能达到这样的质量,特别是对于性能往往最重要的低级系统代码。我有我经常使用的C代码,自80年代末以来就没有改变过。从那时起,它不需要更改。该代码很笨拙,是在繁琐的日子里写的,我几乎不懂。但是它今天仍然适用,并且我不需要了解它的实现就可以充分利用它。

彻底编写测试是提高稳定性的一种方法。另一个是去耦。如果您的代码不依赖其他任何内容,那么更改它的唯一原因就是它本身是否需要更改。有时,少量的代码重复可以用作一种去耦机制,从而以显着提高稳定性的方式进行折衷,如果作为交换,您获得的代码现在完全独立于其他任何事物,则可以进行折衷。现在,该代码已不受外部环境的影响。同时,依赖于10个不同外部库的代码在将来进行更改的原因是其10倍。

在实践中的另一件有用的事情是将您的库与代码库的不稳定部分分开,甚至可能像单独构建第三方库那样将其分开构建(同样,第三方库也应被使用,不得更改,至少不被您的第三方使用)球队)。只是这种类型的组织可以防止人们对其进行篡改。

另一个是极简主义。您的代码尝试执行的次数越少,就越有可能执行良好的工作。整体设计几乎永久不稳定,因为越来越多的功能被添加到它们中,它们看起来越不完整。

每当您打算编写不可避免地难以更改的代码(例如已微调至死的并行化SIMD代码)时,稳定性就应该是您的主要目标。您可以通过最大化不必更改代码的可能性来抵消维护代码的困难,因此将来不必维护代码。无论代码维护多么困难,这都将维护成本降低到零。

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.