我最近在工作中重构了一些代码,并且我认为自己做得很好。我将980行代码减少到450行,并将类数量减半。
当向我的同事展示时,有些人不同意这是一种进步。
他们说-“更少的代码行不一定更好”
我可以看到,在某些极端情况下,人们会写很长的行和/或将所有内容放在一个方法中以节省几行,但这不是我要做的。在我看来,由于代码只有一半大小,因此其结构合理且易于理解/维护。
我正在努力地弄清楚为什么有人希望将完成工作所需的代码加倍,而且我想知道是否有人会觉得与我的同事一样,并且可以提出一些理由,使事半功倍?
我最近在工作中重构了一些代码,并且我认为自己做得很好。我将980行代码减少到450行,并将类数量减半。
当向我的同事展示时,有些人不同意这是一种进步。
他们说-“更少的代码行不一定更好”
我可以看到,在某些极端情况下,人们会写很长的行和/或将所有内容放在一个方法中以节省几行,但这不是我要做的。在我看来,由于代码只有一半大小,因此其结构合理且易于理解/维护。
我正在努力地弄清楚为什么有人希望将完成工作所需的代码加倍,而且我想知道是否有人会觉得与我的同事一样,并且可以提出一些理由,使事半功倍?
Answers:
瘦的人不一定比超重的人健康。
980行的儿童故事比450行的物理论文更容易阅读。
有许多属性可以决定代码的质量。有些简单地计算出来,例如Cyclomatic Complexity和Halstead Complexity。其他的定义较为宽松,例如内聚性,可读性,可理解性,可扩展性,健壮性,正确性,自文档,清洁度,可测试性等等。
例如,可能是在减少了代码的总长度的同时,又引入了不必要的复杂性,并使代码变得更加神秘。
将一长段代码拆分为多个小方法既有害又有益。
请您的同事向您提供有关他们为什么认为您的重构工作产生了不良结果的具体反馈。
有趣的是,我和一位同事目前正处于重构的中间阶段,尽管代码行将保持不变,但类和函数的数量将增加不到一倍。所以我碰巧有一个很好的例子。
在我们的案例中,我们有一层抽象,实际上应该是两层。一切都塞满了ui层。通过将其分为两层,所有内容都变得更加紧密,而测试和维护各个部分也变得更加简单。
困扰您的同事的不是代码的大小,而是其他。如果他们无法清楚地表达它,请尝试自己看一下代码,好像从未见过旧实现一样,并根据其自身价值进行评估,而不仅仅是进行比较。有时,当我进行长时间的重构时,我有时会看不到最初的目标,而把事情做得太远了。仔细看一下“大局”,然后回到正轨,也许是在一对程序员的帮助下,您对他们的建议很重视。
人们常常想到一个报价,通常是阿尔伯特·爱因斯坦(Albert Einstein):
使一切尽可能简单,但不要简单。
当您精简整修时,它会使代码更难以阅读。因为“易读/难读”可能是一个非常主观的术语,所以我将确切解释我的意思:衡量熟练的开发人员确定“此代码的作用”的难易程度。仅需查看源代码即可,无需专用工具的帮助。
Java和Pascal之类的语言因其冗长而臭名昭著。人们经常指出某些语法元素,并嘲讽地说:“它们只是为了简化编译器的工作”。除了“正义”部分外,这或多或少是正确的。信息越明确,不仅使编译器而且对人来说,代码越容易阅读和理解。
如果我说var x = 2 + 2;,那显然x是一个整数。但是,如果我说的话var foo = value.Response;,那么foo代表什么或它的属性和功能到底是什么,还不清楚。即使编译器可以轻松推断出它,也需要付出更多的认知努力。
请记住,必须编写程序供人们阅读,并且只能偶然地使机器执行。(具有讽刺意味的是,这句话来自一本专门针对以极难阅读而臭名昭著的语言编写的教科书!)删除多余的东西是一个好主意,但是不要带走使人类更容易理解的代码找出正在发生的事情,即使对于编写该程序不是绝对必要的。
var示例不是一种特别好的简化方法,因为大部分时间阅读和理解代码都需要弄清楚特定抽象级别的行为,因此了解特定变量的实际类型通常不会发生任何变化(仅帮助您了解较低的抽象)。一个更好的例子是将多行简单代码压缩成一个复杂的语句-例如,   if ((x = Foo()) != (y = Bar()) && CheckResult(x, y))  花一些时间来研究,并且知道类型x或y根本没有帮助。
                    较长的代码可能更易于阅读。通常情况恰好相反,但是有很多例外-其中一些在其他答案中有所概述。
但是,让我们从另一个角度来看。我们假设大多数熟练的程序员都将新代码视为高级代码,他们会看到这两段代码,而又不了解公司的文化,代码库或路线图。即使这样,还是有很多理由反对新代码。为简便起见,我称“人们在批评新代码” Pecritenc:
哪种代码更好,可能取决于程序员的专业知识以及他们使用的工具。例如,这就是为什么在某些情况下,通常被认为写得不好的代码比写得好,充分利用继承的面向对象的代码更有效的原因:
(1)有些程序员只是对面向对象的编程没有直观的了解。如果您对软件项目的隐喻是电路,那么您将期望很多代码重复。您可能希望在许多类中看到或多或少相同的方法。他们会让您有宾至如归的感觉。在一个项目中,您必须在父类甚至祖父母类中查找方法以查看正在发生的事情,这可能会感到敌对。您不想了解父类的工作原理,然后了解当前类的不同之处。您想直接了解当前类的工作原理,并且发现信息分散在多个文件中这一事实令人困惑。
同样,当您只想解决特定类中的特定问题时,您可能不喜欢考虑是直接在基类中解决问题还是覆盖当前感兴趣的类中的方法。(没有继承,您不必做出有意识的决定。默认情况是只忽略相似类中的类似问题,直到它们被报告为错误为止。)这最后一个方面并不是一个有效的参数,尽管它可以解释某些原因。反对。
(2)一些程序员经常使用调试器。尽管总的来说我本人坚定地站在代码继承和防止重复方面,但我还是在调试面向对象的代码时遇到了我在(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);
编译器应为两者生成相同的代码,因此使用较短的形式将无济于事。
当更少的代码比更多的代码难以维护/扩展时。为了代码简洁,重构通常会出于LoC的考虑而删除“不必要的”代码结构。麻烦的是,如果此代码需要比当前做更多的事情,或者以不同的方式来做,那么那些代码构造(例如并行接口声明,提取的方法/子类等)是必需的。在极端情况下,如果问题定义稍有变化,则针对特定问题量身定制的某些解决方案可能根本无法工作。
一个例子;您有一个整数列表。这些整数中的每一个在列表中都有一个重复的值(一个除外)。您的算法必须找到未配对的值。一般情况下的解决方案是将每个数字与每个其他数字进行比较,直到您在列表中找到没有重复的数字为止,这是N ^ 2次运算。您还可以使用哈希表来构建直方图,但这在空间上非常低效。但是,可以通过按位XOR运算使它成为线性时间和恒定空间。将每个整数与正在运行的“总数”(从零开始)进行异或运算,最后,运行总和将是未配对整数的值。十分优雅。直到需求更改,列表中的多个数字可能会不成对,或者整数包括零。现在您的程序将返回垃圾或模棱两可的结果(如果返回零,是否意味着所有元素都已配对,或者未配对的元素为零?)。这就是现实编程中“聪明”实现的问题。
计算性能。当优化流水线或并行运行代码的部分时,可能有益于例如不从1循环到400,而是从1循环到50,并在每个循环中放置8个相似代码的实例。我不认为您的情况就是这种情况,但这是一个示例,其中多行更好(从性能角度来看)。