在什么情况下,更少的代码更好?[关闭]


55

我最近在工作中重构了一些代码,并且我认为自己做得很好。我将980行代码减少到450行,并将类数量减半。

当向我的同事展示时,有些人不同意这是一种进步。

他们说-“更少的代码行不一定更好”

我可以看到,在某些极端情况下,人们会写很长的行和/或将所有内容放在一个方法中以节省几行,但这不是我要做的。在我看来,由于代码只有一半大小,因此其结构合理且易于理解/维护。

我正在努力地弄清楚为什么有人希望将完成工作所需的代码加倍,而且我想知道是否有人会觉得与我的同事一样,并且可以提出一些理由,使事半功倍?


145
代码大小是根据您需要阅读和理解它的时间而不是行数或字符数来衡量的。
贝尔吉'17

13
您所写的问题绝对过于广泛。建议撰写有关您所做的特定更改的新文章。
jpmc26

8
考虑快速逆平方根算法。用适当的变量命名来实现完整的Newton方法将更加清晰,并且更容易阅读,即使它可能包含更多的代码行。(请注意,在这种特殊情况下,出于性能考虑,使用智能代码是合理的)。
Maciej Piechotka '17

65
有一个专门用于回答您问题的整个堆栈交换站点:codegolf.stackexchange.com。:)
Federico Poloni's

Answers:


124

瘦的人不一定比超重的人健康。

980行的儿童故事比450行的物理论文更容易阅读。

有许多属性可以决定代码的质量。有些简单地计算出来,例如Cyclomatic ComplexityHalstead Complexity。其他的定义较为宽松,例如内聚性,可读性,可理解性,可扩展性,健壮性,正确性,自文档,清洁度,可测试性等等。

例如,可能是在减少了代码的总长度的同时,又引入了不必要的复杂性,并使代码变得更加神秘。

将一长段代码拆分为多个小方法既有害又有益

请您的同事向您提供有关他们为什么认为您的重构工作产生了不良结果的具体反馈。


1
@PiersyP只是一个FYI,据我所知,关于良好重构的指导原则之一是,我们应该看到圈复杂度降低到了原来的平方根
马哈宁

4
@PiersyP也是,我并不是说您的代码比实际的要糟糕。作为一个局外人,我无法真正分辨。也可能是您的同事过于保守,担心您所做的更改仅仅是因为他们没有付出必要的精力去检查和验证它​​。因此,我建议您要求他们提供其他反馈。
马哈宁

6
伙计们,干得好-您已经确定某个地方的重量正确(确切数字可能有所不同)。甚至@Neil的原始帖子都说“超重”,而不是“一个人较重”,这是因为就像编程一样,这里有一个甜蜜的地方。添加超出该“适当大小”的代码只是很混乱,而为了简洁起见,删除该点以下的行只会牺牲理解力。知道确切的点在哪里……那是困难的一点。
AC

1
仅仅因为没有必要,并不意味着它没有价值。
克里斯·沃勒特

1
@Neil您通常是正确的,但客观地说,您所提到的难以捉摸的“平衡”是一个神话。每个人对什么是“良好的平衡”都有不同的看法。显然,OP认为他做得很好,而他的同事没有做,但是我敢肯定,他们在编写代码时都以为自己拥有“适当的平衡”。
–'code_dredd

35

有趣的是,我和一位同事目前正处于重构的中间阶段,尽管代码行将保持不变,但类和函数的数量将增加不到一倍。所以我碰巧有一个很好的例子。

在我们的案例中,我们有一层抽象,实际上应该是两层。一切都塞满了ui层。通过将其分为两层,所有内容都变得更加紧密,而测试和维护各个部分也变得更加简单。

困扰您的同事的不是代码的大小,而是其他。如果他们无法清楚地表达它,请尝试自己看一下代码,好像从未见过旧实现一样,并根据其自身价值进行评估,而不仅仅是进行比较。有时,当我进行长时间的重构时,我有时会看不到最初的目标,而把事情做得太远了。仔细看一下“大局”,然后回到正轨,也许是在一对程序员的帮助下,您对他们的建议很重视。


1
是的,绝对要将UI与其他东西分开,这总是值得的。关于您看不到最初目标的观点,我有些同意,但是您也可以重新设计一些更好的东西,或者在更好的道路上。就像关于进化论的古老论点(“机翼的一部分有什么优点?”)一样,如果您从不花时间去改进它们,那么事情就不会改善。直到旅途顺利,您并不总是知道要去哪里。我同意尝试找出为什么同事感到不安,但也许这确实是“他们的问题”,而不是您的问题。

18

人们常常想到一个报价,通常是阿尔伯特·爱因斯坦(Albert Einstein):

使一切尽可能简单,但不要简单。

当您精简整修时,它会使代码更难以阅读。因为“易读/难读”可能是一个非常主观的术语,所以我将确切解释我的意思:衡量熟练的开发人员确定“此代码的作用”的难易程度。仅需查看源代码即可,无需专用工具的帮助。

Java和Pascal之类的语言因其冗长而臭名昭著。人们经常指出某些语法元素,并嘲讽地说:“它们只是为了简化编译器的工作”。除了“正义”部分外,这或多或少是正确的。信息越明确,不仅使编译器而且对人来说,代码越容易阅读和理解。

如果我说var x = 2 + 2;,那显然x是一个整数。但是,如果我说的话var foo = value.Response;,那么foo代表什么或它的属性和功能到底是什么,还不清楚。即使编译器可以轻松推断出它,也需要付出更多的认知努力。

请记住,必须编写程序供人们阅读,并且只能偶然地使机器执行。(具有讽刺意味的是,这句话来自一本专门针对以极难阅读而臭名昭著的语言编写的教科书!)删除多余的东西是一个好主意,但是不要带走使人类更容易理解的代码找出正在发生的事情,即使对于编写该程序不是绝对必要的。


7
var示例不是一种特别好的简化方法,因为大部分时间阅读和理解代码都需要弄清楚特定抽象级别的行为,因此了解特定变量的实际类型通常不会发生任何变化(仅帮助您了解较低的抽象)。一个更好的例子是将多行简单代码压缩成一个复杂的语句-例如, if ((x = Foo()) != (y = Bar()) && CheckResult(x, y)) 花一些时间来研究,并且知道类型xy根本没有帮助。
本·科特雷尔

15

较长的代码可能更易于阅读。通常情况恰好相反,但是有很多例外-其中一些在其他答案中有所概述。

但是,让我们从另一个角度来看。我们假设大多数熟练的程序员都将新代码视为高级代码,他们会看到这两段代码,而又不了解公司的文化,代码库或路线图。即使这样,还是有很多理由反对新代码。为简便起见,我称“人们在批评新代码” Pecritenc

  • 稳定性。如果已知旧代码是稳定的,则新代码的稳定性是未知的。在使用新代码之前,仍然需要对其进行测试。如果由于某种原因无法进行适当的测试,则更改将是一个很大的问题。即使可以进行测试,Pecritenc也会认为付出的努力不值得对代码进行(较小的)改进。
  • 性能/扩展。旧代码的伸缩性可能会更好,Pecritenc认为,随着客户和功能的日趋堆积,性能将成为未来的问题。
  • 可扩展性。旧的代码可能允许轻松地介绍Pecritenc假定不久将添加的某些功能*。
  • 熟悉度。旧代码可能具有在公司代码库的其他5个地方使用的重用模式。同时,新代码使用了一种花哨的模式,目前只有一半的公司听说过这种模式。
  • 在猪上的口红。Pecritenc可能认为旧代码和新代码都是垃圾,或者是无关紧要的,因此使它们之间的任何比较都没有意义。
  • 自豪。Pecritenc可能是该代码的原始作者,并且不喜欢人们对其代码进行大量更改。他甚至可能把进步看作是轻蔑的侮辱,因为它们暗示他应该做得更好。

4
+1表示“ Pecritenc”,并很好地总结了在重构之前应该考虑的合理反对。

1
+1是“可扩展性”-我在想原始代码可能具有打算在将来的项目中使用的函数或类,因此抽象似乎显得多余或不必要,但仅在单个程序的上下文中。
达伦·林格

而且,所讨论的代码可能不是关键代码,因此被认为是浪费大量的工程资源来清理它。
艾瑞克·艾德

@nocomprende您使用合理,预先考虑和预先构造的任何原因吗?类似于Pecritenc的方法?
Milind R

@MilindR可能是先入为主,偏爱还是个人喜好?或者,也许根本没有理由,辅因子的宇宙汇合,混淆了阴谋条件。不知道,真的。你呢?

1

哪种代码更好,可能取决于程序员的专业知识以及他们使用的工具。例如,这就是为什么在某些情况下,通常被认为写得不好的代码比写得好,充分利用继承的面向对象的代码更有效的原因:

(1)有些程序员只是对面向对象的编程没有直观的了解。如果您对软件项目的隐喻是电路,那么您将期望很多代码重复。您可能希望在许多类中看到或多或少相同的方法。他们会让您有宾至如归的感觉。在一个项目中,您必须在父类甚至祖父母类中查找方法以查看正在发生的事情,这可能会感到敌对。您不想了解父类的工作原理,然后了解当前类的不同之处。您想直接了解当前类的工作原理,并且发现信息分散在多个文件中这一事实令人困惑。

同样,当您只想解决特定类中的特定问题时,您可能不喜欢考虑是直接在基类中解决问题还是覆盖当前感兴趣的类中的方法。(没有继承,您不必做出有意识的决定。默认情况是只忽略相似类中的类似问题,直到它们被报告为错误为止。)这最后一个方面并不是一个有效的参数,尽管它可以解释某些原因。反对。

(2)一些程序员经常使用调试器。尽管总的来说我本人坚定地站在代码继承和防止重复方面,但我还是在调试面向对象的代码时遇到了我在(1)中描述的一些挫败感。当您遵循代码执行时,即使它停留在同一对象中,有时它也会在(祖先)类之间不断地跳来跳去。此外,在编写良好的代码中设置断点时,很有可能在无用的时候触发它,因此您可能不得不花一些时间使其成为有条件的(在可行的情况下),或者甚至是在相关触发之前手动继续多次。


3
“祖父母班”!ha!只要注意亚当夏娃课程。(当然还有上帝的阶级)在那之前,它没有形式,而且是虚无的。

1

这完全取决于。我一直在一个项目中工作,该项目不允许将布尔变量用作函数参数,而是需要enum每个选项都专用。

所以,

enum OPTION1 { OPTION1_OFF, OPTION1_ON };
enum OPTION2 { OPTION2_OFF, OPTION2_ON };

void doSomething(OPTION1, OPTION2);

比它详细得多

void doSomething(bool, bool);

然而,

doSomething(OPTION1_ON, OPTION2_OFF);

可读性比

doSomething(true, false);

编译器应为两者生成相同的代码,因此使用较短的形式将无济于事。


0

我会说凝聚力可能是个问题。

例如,在一个Web应用程序中,假设您有一个Admin页面,在其中您可以对所有产品进行索引,而该页面本质上与您在首页情况下使用的代码(索引)相同,以便对产品进行索引。

如果您决定将所有内容均分,以便保持DRY的时尚感,则必须添加很多条件,例如有关用户浏览是否是管理员的信息,并在代码中添加不必要的内容,这会使它变得非常难以理解设计师!

因此,在这样的情况下,即使代码几乎相同,只是因为它可以扩展到其他内容,并且用例可能会略有变化,所以通过添加条件和if来跟踪每个代码将很不好。因此,一个好的策略是放弃DRY概念并将代码分解为可维护的部分。


0
  • 当更少的代码无法完成与更多代码相同的工作时。为了简单起见,重构是好的,但是您必须注意不要过度简化此解决方案所遇到的问题空间。980行代码可能比450行处理更多的极端情况。
  • 当更少的代码不会像更多代码那样优雅地失败时。我已经看到了一些在代码上完成的“引用”工作,以删除“不必要的” try-catch和其他错误情况处理。不可避免的结果是,没有显示一个对话框,其中包含有关错误以及用户可以执行的操作的漂亮消息,导致应用程序崩溃或损坏。
  • 当更少的代码比更多的代码难以维护/扩展时。为了代码简洁,重构通常会出于LoC的考虑而删除“不必要的”代码结构。麻烦的是,如果此代码需要比当前做更多的事情,或者以不同的方式来做,那么那些代码构造(例如并行接口声明,提取的方法/子类等)是必需的。在极端情况下,如果问题定义稍有变化,则针对特定问题量身定制的某些解决方案可能根本无法工作。

    一个例子;您有一个整数列表。这些整数中的每一个在列表中都有一个重复的值(一个除外)。您的算法必须找到未配对的值。一般情况下的解决方案是将每个数字与每个其他数字进行比较,直到您在列表中找到没有重复的数字为止,这是N ^ 2次运算。您还可以使用哈希表来构建直方图,但这在空间上非常低效。但是,可以通过按位XOR运算使它成为线性时间和恒定空间。将每个整数与正在运行的“总数”(从零开始)进行异或运算,最后,运行总和将是未配对整数的值。十分优雅。直到需求更改,列表中的多个数字可能会不成对,或者整数包括零。现在您的程序将返回垃圾或模棱两可的结果(如果返回零,是否意味着所有元素都已配对,或者未配对的元素为零?)。这就是现实编程中“聪明”实现的问题。

  • 当更少的代码比更多的代码更少的自我记录。能够读取代码本身并确定代码在做什么对于团队开发至关重要。将您编写的Brain-f ***算法提供给初级开发人员,使其表现出色,并要求他对其进行微调以修改输出,这不会使您走得太远。许多高级开发人员也会遇到这种情况。在任何给定的时间都能理解代码在做什么,以及代码可能出什么毛病,这是工作团队开发环境的关键(甚至是单人环境;我向您保证,当您编写5当您再次使用该功能以治疗帕金森氏症时,癌症的在线方法将早已消失。)

0

计算机代码需要做很多事情。不执行这些操作的“极简主义”代码不是好的代码。

例如,计算机程序应涵盖所有可能的情况(或至少涵盖所有可能的情况)。如果一段代码仅涵盖一个“基本情况”而忽略其他代码,则即使是简短的代码也不是好代码。

计算机代码应该是“可扩展的”。密码只能用于一个专门的应用程序,而更长的,更开放的程序可能使添加新应用程序更容易。

计算机代码应该清楚。正如另一个回答者所演示的那样,对于一个硬编码器来说,可能会产生一个单行的“算法”型函数来完成任务。但是,对于普通程序员来说,单行代码必须分为五个不同的“句子”。


情人眼中的情人。

-2

计算性能。当优化流水线或并行运行代码的部分时,可能有益于例如不从1循环到400,而是从1循环到50,并在每个循环中放置8个相似代码的实例。我不认为您的情况就是这种情况,但这是一个示例,其中多行更好(从性能角度来看)。


4
一个好的编译器比一般的程序员更应该知道如何展开特定计算机体系结构的循环,但是总的来说是有效的。我曾经看过Cray高性能库中矩阵乘法例程的源代码。矩阵乘法是三个嵌套循环和总共约6行代码,对吗?错误-该库例程运行了大约1100行代码,外加类似数量的注释行,说明了为什么这么长!
alephzero

1
@alephzero哇,我很想看看那个代码,它一定是Cray Cray。

@alephzero,好的编译器可以做很多事情,但可惜不是所有的事情。好的一面是,这些都是使编程变得有趣的事情!
汉斯·詹森

2
@alephzero的确,好的矩阵乘法代码不仅会节省一点时间(即减少一个常数因子),而且会使用完全不同的算法,具有不同的渐近复杂度,例如Strassen算法大约为O(n ^ 2.8)而不是O(n ^ 3)。
亚瑟塔卡
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.