什么是重构,什么仅是修改代码?


81

我知道重构就是“改变程序的结构,以使功能不变”。我正在与一些正在大学里参加我的最后一个项目的家伙交谈,我很惊讶他们对重构的看法更加宽泛(需要一个更好的词)。

我认为重构就是诸如提取方法和重命名类之类的事情。他们还提出了一些建议,例如更改数据结构(例如将JavaLinkedList更改为ArrayList),更改算法(使用合并排序而不是冒泡排序),甚至重写大量代码作为重构。

我非常确定他们错了,但是我不能给出一个很好的理由,因为他们的建议确实改变了程序(并且可能使程序变得更好)而没有改变其行为。我是对的,更重要的是,为什么?

Answers:


77

Martin Fowler的“重构:改进现有代码的设计”可能是以下参考:

重构是一种用于改进现有代码库设计的受控技术。它的本质是应用一系列小的行为保留转换,每个转换“都太小,不值得做”。但是,每个转换的累积效果都非常显着。通过分步进行,可以降低引入错误的风险。您还可以避免在进行重组时损坏系统-这使您可以在较长的时间内逐步重构系统。

重构与单元测试息息相关。在重构之前编写测试,然后对重构有一个置信度(与测试的覆盖范围成比例)。

一个很好的参考是:有关重构的信息


16
Fowler的报价当然是相关的,是的,它与单元测试紧密结合...但是,这确实回答了以下问题:所提到的示例是重构还是只是修改代码?OP或他的同事谁是正确的,为什么?
Jonik

36

Fowler在可以更改的代码和不影响代码的行为之间划清界限。他称那些没有的称为“重构”。这一个重要的区别,因为如果将我们的工作分为重构和非重构代码修改活动(Fowler称其为“戴不同的帽子”),则我们可以应用适合目标的不同技术。

如果我们要进行重构或行为保留代码修改:

  • 我们所有的单元测试应该在修改前后通过
  • 我们不需要修改任何测试或编写任何新测试
  • 我们希望完成后会有更干净的代码
  • 我们不希望有新的行为

如果我们要进行更改行为的代码修改:

  • 我们期待新的行为
  • 我们应该写新的测试
  • 完成后,我们可能会得到更脏的代码(然后应该对其进行重构)

如果我们看不到这种区别,那么我们对任何给定的代码修改任务的期望就会变得混乱和复杂,或者比我们考虑到它时更加混乱和更加复杂。这就是为什么单词及其含义很重要的原因。


3
+1。尤其是您给出的理由;混乱的期望。写我自己的答案时,即使没有像这样整齐地写下来,我也想到了这一点:)
Jonik,2009年

18

提出我的看法:

较小的增量更改使代码处于比发现状态更好的状态

绝对可以:与功能没有直接关系的“化妆品”更改(即,不作为更改请求计费)。

绝对不能:重写大块显然违反了“小增量”部分。重构通常被用作重写的反义词:而不是再做一次,而是改进现有的重构。

绝对可能:替换数据结构和算法在某种程度上是一个边界案例。IMO的决定性差异是很小的步骤:准备交付,准备处理其他案件。


示例:假设您有一个报告随机化器模块,该模块因使用矢量而减慢了速度。您已经剖析了矢量插入是瓶颈,但是不幸的是,该模块在许多地方都依赖连续内存,因此当使用列表时,事情会悄无声息地中断。

重写意味着将模块从头开始扔掉一栋更好,更快的建筑,而只是从旧建筑中挑选一些。或编写一个新的核心,然后将其放入现有对话框中。

重构意味着将采取一些小步骤来删除指针算法,以便进行切换。甚至您甚至可以创建一个实用程序函数来包装指针算术,用对该函数的调用替换直接的指针操作,然后切换到迭代器,以便编译器抱怨仍然使用指针算术的地方,然后切换到list,然后删除多功能功能。


背后的想法是代码本身会变得更糟。修复错误并添加功能时,质量会逐步降低-变量的含义会微妙地改变,函数会获得一个额外的参数来破坏隔离,循环会变得有些复杂等。这些都不是真正的错误,您可以不能说会使循环变得复杂的行数,但会损害可读性和维护性。

同样,更改变量名称或提取函数本身并不是明显的改进。但总的来说,他们与缓慢的侵蚀作斗争。

就像鹅卵石的墙壁,每天都掉在地上。每天都有一个路人把它捡起来放回去。


12

铭记马丁·福勒(Martin Fowler)的定义,

重构是一种用于重组现有代码主体,更改其内部结构而不更改其外部行为的规范技术。

...我认为您显然是正确的。

他们还提出了一些建议,例如更改数据结构(例如将Java LinkedList更改为ArrayList),更改算法(使用合并排序而不是冒泡排序),甚至重写大量代码作为重构。

将算法更改为更快的速度显然不会进行重构,因为外部行为已更改!(然后再说一次,如果这种影响永远不明显,也许您可​​以称其为重构-也是过早的优化。:-)

这是我的宠儿。当人们草率地使用该术语时,这很烦人-我什至遇到过一些人,他们可能会随便使用重构进行基本上任何类型的更改或修复。是的,这是一个时髦而时髦的流行词,但是使用诸如更改重写性能改进之类的简单旧术语并没有错。我们应该在适当的时候使用它们,并在真正改善软件内部结构的情况下保留重构。在开发团队中,尤其是使用一种通用语言来准确地讨论您的工作确实很重要。


3
我不确定使加快代码的速度是否符合外部行为的改变……
GalacticCowboy

2
是的,我明白您的意思,我想这取决于您从哪个角度看。:)在任何情况下,IMO,在编程时都应注意每时每刻戴着的“帽子”,即您要实现的目标。换句话说,在添加/修复功能时,重构时和优化时(提高性能),您应该有意识地分开。在IIRC中,Fowler在其有关重构的权威著作中也谈到了这一点。
乔尼克(Jonik)

1
对于外部变更部分,如果行为很重要,我们可以改写为:[...]而无需更改其外部行为。如果性能无论以哪种方式都重要(更快或更慢),那么在“重构”阶段不要进行可能影响性能的更改。
Loki

11

如果一段代码的接口发生了变化,那么我认为这不仅仅是重构。

重构的典型情况是

  • “哦,我所有的单元测试都在运行,但是我认为我的代码可以做得更干净”
  • 更改代码以使其更具可读性/更清晰/更高效
  • 重新运行单元测试(不更改测试)并检查它们是否仍然有效

这意味着术语重构是相对于您正在讨论的接口的。也就是说,您可以重构一个接口背后的代码,而在较低级别上更广泛地更改另一个接口的代码(也许这种区别是什么使您和您的同事在这里困惑?)


7

我认为您是对的,但是争论一个词的含义并不是特别有趣或富有成效。


6
我通常会同意这一点,但是讨论是在审阅我们编写的文档时进行的,当我们使用相同的词谈论同一件事时,我认为这是很好的。
David Johnstone 2009年

4

http://en.wikipedia.org/wiki/Code_refactoring

代码重构是更改计算机程序内部结构而不修改其外部功能行为或现有功能的过程,以改善软件的内部非功能属性,例如,提高代码可读性,简化代码结构,更改代码遵守给定的编程范例,提高可维护性,提高性能或提高可扩展性。

我同意重构代码确实包括破坏现有代码。只需确保您具有单元测试,以免引入任何错误,然后其余代码即可编译。使用诸如Resharper的C#重构工具使此操作变得如此简单!

  • 使代码更易于理解
  • 清理代码并使代码整洁
  • 删除代码!冗余,未使用的代码和注释应删除
  • 改善表现
  • 使某些东西更通用。从最简单的事情开始,然后对其进行重构,以使其更易于测试/隔离或泛型,从而可以通过多态以不同方式工作
  • 保持代码干燥-不要重复自己,因此重构会话可能涉及获取一些重复的代码并将其重构为单个组件/类/模块。

3

不同意

在软件工程中,“重构”源代码意味着在不改变其总体结果的情况下对其进行改进[...]

您已经知道用于重构子集的更精确的术语,是的,这是一个非常笼统的术语。


1

我认为没有人会受益于对“重构”一词过于严格的定义。您如何看待它与您的同事之间的界限很模糊,并且取决于许多事实,它们可能更接近他们或您的看法。由于它是动态的,因此我们尝试对其进行定义。首先,定义您要重构的系统或子系统的边界。

如果是方法,请保持名称,输入参数,返回值的类型以及可能抛出的语句固定。在方法内部应用所有更改,而无需更改在外部的查看方式。

如果重构一个类,以其公共API修复并使用重命名变量,提取方法和所有其他可用技术,则该类将变得更易读和/或更高效。

如果您要重构的代码部分是包或模块,请在其中进行重构,从而可能重命名类,删除,引入接口,将代码推入/拉入超类/子类。


0

重构=改善非功能性需求,同时保持功能性需求不变。

非功能性需求=模块化,可测试性,可维护性,可读性,关注点分离,liskov原理等...

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.