我可以在不改变外部行为的情况下将重构推进多远?


27

根据Martin Fowler的说法,代码重构是(强调我的):

重构是一种用于重组现有代码主体,在不更改其外部行为的情况下更改其内部结构的规范技术。它的核心是一系列小的行为保留转换。每个转换(称为“重构”)几乎没有,但是一系列转换可以产生显着的重组。由于每个重构都很小,因此出错的可能性较小。每次进行少量重构后,系统也可以保持完全正常工作,从而减少了系统在重组期间可能严重损坏的机会。

在这种情况下,什么是“外部行为”?例如,如果我应用move方法重构并将某个方法移动到其他类,则看起来我在更改外部行为,不是吗?

因此,我有兴趣弄清楚变更在什么时候停止成为重构并变成更多。“重构”一词可能会被误用于较大的更改:它是否有不同的词?

更新。关于接口的很多有趣的答案,但是不会通过方法重构来改变接口吗?


如果现有行为很糟糕或不完整,请对其进行修改,删除/重写。那时您可能没有进行重构,但是如果系统会因此而变得更好,那么谁在乎这个名字呢?
工作

2
您的主管可能会在乎您是否获得了重构的权限,并且进行了重写。
JeffO 2011年

2
重构的边界是单元测试。如果您有一个由他们制定的规范,那么您所做的任何不破坏测试的更改都可以重构吗?
乔治·席尔瓦


如果您想了解更多,该主题也是活跃的研究领域。关于此主题的科学出版物很多,例如,informatik.uni-trier.de
〜ley / db / indices / a

Answers:


25

在本文中,“外部”表示“用户可观察”。如果是应用程序,则用户可能是人类;如果是公共API,则用户可能是其他程序。

因此,如果将方法M从类A移到类B,并且这两个类都位于应用程序内部,并且由于更改,没有用户可以观察到应用程序行为的任何变化,那么您可以正确地称其为重构。

如果OTOH,某些其他更高级别的子系统/组件由于更改而改变了其行为或损坏,则实际上(通常)是用户(或至少是系统管理员检查日志)可以观察到的。或者,如果您的类是公共API的一部分,则可能存在第三方代码,这取决于M是A类而不是B的一部分。因此,从严格意义上讲,这两种情况都不是重构的。

我倾向于将任何代码返工称为重构,这是不正确的。

确实,这是重构成为时尚的可悲但可预期的结果。多年来,开发人员一直在以特殊的方式进行代码返工,而且学习一个新的流行词当然比分析和改变根深蒂固的习惯要容易得多。

那么,改变外部行为的返工正确的词是什么?

我称之为重新设计

更新资料

关于接口的很多有趣的答案,但是不会通过方法重构来改变接口吗?

什么啊 具体的类,是的。但是,这些类是否以任何方式对外界直接可见?如果没有-因为他们是你的程序中,而不是外部接口(API / GUI)的一部分程序 -没有任何变化作出有外部各方观察到的(除非变化中断的东西,当然)。

我觉得还有一个更深层的问题:特定类本身是否作为独立实体存在?在大多数情况下,答案是否定的:该类仅作为较大的组件,类和对象的生态系统的一部分存在,没有它,就无法实例化和/或不可用。该生态系统不仅包括其(直接/间接)依赖性,还包括依赖于它的其他类/对象。这是因为,如果没有这些更高级别的类,与我们的类相关联的责任对于系统的用户可能是无意义的/无用的。

例如,在我们涉及汽车租赁的项目中, Charge类。该类本身对系统的用户没有用,因为出租站的代理和客户不能单独承担很多费用:他们作为一个整体处理租赁协议合同(包括许多不同的费用) 。用户最关心的是这些费用的总和,他们最终将要支付;代理商对选择的不同合同选项,租赁时间长度,车辆种类,保险套餐,额外物品等感兴趣,这些(通过复杂的业务规则)决定了要收取的费用以及如何计算最终付款在这些之外。国家代表/业务分析师会关注特定的业务规则,它们的协同作用和影响(对公司收入等)。

最近,我重构了该类,重命名了其大多数字段和方法(以遵循标准Java命名约定,而我们的前辈完全忽略了该约定)。我还计划进一步重构更换Stringchar更适当的领域enumboolean类型。所有这些肯定会改变类的界面,但是(如果我做得对,我的应用程序的用户将看不到)。尽管他们肯定知道收费的概念,但他们都不关心单个收费的表示方式。我可以选择一百个不代表任何领域概念的类作为示例,因此对于最终用户在概念上甚至是不可见的,但是我认为选择一个在概念级别上至少具有某些可见性的示例更为有趣。这很好地表明了类接口(最多)只是领域概念的表示,而不是真实的*。可以更改表示形式而不会影响概念。用户只拥有并理解该概念;在概念和表示之间进行映射是我们的任务。

* 并且我们可以轻松地添加一个类,我们的类表示的域模型本身只是某些“真实事物”的近似表示...


3
挑剔,我会说“设计变更”不是重新设计。重新设计听起来实在太多。
user606723 2011年

4
有趣的-在你的例子A级是“代码现有的身体”,如果方法M是公共那么A的外在行为被改变所以,你也许可以说,A级重新设计,而整个系统正在重构。
saus 2011年

我喜欢观察用户。这就是为什么我不会说破坏单元测试是一个标志,而是端到端或集成测试将是一个标志的原因。
2011年

8

外部只是以其真正的语言含义表示接口。以牛为例。只要您喂一些蔬菜并获得牛奶作为回报,您就不会在乎其内部器官如何工作。现在,如果上帝改变了母牛的内脏,以便其血液变成蓝色,只要入口和出口(口和牛奶)没有变化,就可以认为是重构。


1
我认为这不是一个好答案。重构可能意味着API发生了变化。但这不会影响最终用户或输入/文件的读取/生成方式。
rds

3

对我而言,当通过测试和/或正式规范设定界限时,重构最有效/最舒适。

  • 这些边界足够严格,使我感到安心,即使我偶尔越过,也将很快发现它,这样我就不必回滚很多更改来恢复。另一方面,它们提供了足够的空间来改进代码,而不必担心更改无关的行为。

  • 我特别喜欢的是,可以说这些边界是自适应的。我的意思是:1)我进行更改并验证其是否符合规格/测试。然后,2)将其传递给质量检查或用户测试-注意此处,由于规范/测试中缺少某些内容,它仍可能会失败。好吧,如果3a)测试通过,我就完成了,很好。否则,如果3b)测试失败,那么我4)回滚更改, 5)添加测试或澄清规格,以使下次不会再次出现此错误。请注意,无论测试通过还是失败,我都会有所收获 -代码/测试/规范都得到了改进-我的努力不会变成浪费。

至于其他类型的边界-到目前为止,我运气不高。

如果有人遵循一门学科,那么 “可观察到的用户”是一个安全的选择-对我而言,在某种程度上,在分析现有/创建新测试方面总会花费很多精力-也许会花费太多精力。我不喜欢这种方法的另一件事是,盲目地遵循它可能会变得过于严格。-禁止进行此更改,因为加载数据将花费3秒钟而不是2秒钟。-嗯,如何与用户/ UX专家核对这是否相关?-禁止,禁止任何可观察到的行为改变。安全?你打赌!有生产力吗?并不是的。

我尝试过的另一个方法是保持代码逻辑(阅读时理解的方式)。除了最基本的更改(通常不是非常有成果的更改)之外,此更改始终是一罐蠕虫病毒……还是我应该说一堆bug?我的意思是回归错误。在处理意大利面条式代码时,破坏重要的内容太容易了。


3

重构的书是相当强在它的消息,你可以只执行,当你有单元测试覆盖率重构

因此,您可以说只要不破坏任何单元测试,就可以重构。破坏测试时,您不再需要重构。

但是:如何进行简单的重构,例如更改类或成员的名称?他们不打破测试吗?

是的,他们确实这样做,并且在每种情况下,您都需要考虑该中断是否很重要。如果您的SUT是公共API / SDK,则重命名确实是一项重大更改。如果没有,那可能没问题。

但是,请经常考虑,测试失败并不是因为您更改了您实际上关心的行为,而是因为测试是脆弱测试


3

在这种情况下,定义“外部行为”的最佳方法可能是“测试用例”。

如果您重构代码,并且代码继续通过测试用例(在重构之前定义),则重构不会改变外部行为。如果一个或多个测试用例失败,则说明您更改了外部行为。

至少,这就是我对有关该主题的各种书籍的理解(例如,福勒的书籍)。


2

边界将是区分谁来开发,维护,支持该项目以及谁是项目的用户(而不是支持者,维护者,开发者)之间的分界线。因此,对于外部世界,行为看起来是相同的,而行为背后的内部结构却发生了变化。

因此,可以在类之间移动函数,只要它们不是用户看到的函数就可以。

只要代码返工不会更改外部行为,添加新功能或删除现有功能,我想可以将返工称为重构。


不同意。如果将整个数据访问代码替换为nHibernate,则它不会更改外部行为,但不会遵循Fowler的“纪律性技术”。这将是重新设计,并称其为重构隐藏了所涉及的风险因素。
pdr

1

出于所有应有的尊重,我们必须记住,一个类的用户不是使用该类构建的应用程序的最终用户,而是使用通过重构的类(调用或继承)实现的类。

当您说“外部行为不应改变”时,是指就用户而言,该类的行为与以前完全相同。可能是原始的(未重构的)实现是单个类,而新的(重构的)实现具有一个或多个构建该类的超类,但是用户从不看到它们的内部(实现)只看到界面。

因此,如果类具有称为“ doSomethingAmazing”的方法,则对于用户而言,该方法是由他们所引用的类或由该类所基于的超类实现的,对于用户而言都无关紧要。对用户而言,重要的是新的(重构的)“ doSomethingAmazing”与旧的(未重构的)“ doSomethingAmazing”具有相同的结果。

但是,在很多情况下,所谓的重构并不是真正的重构,而可能是为了使代码更易于修改以添加一些新功能而进行的重新实例化。因此,在稍后的(伪)重构情况下,新的(重构)代码实际上会执行不同的操作,或者可能比旧的执行更多操作。


如果使用Windows窗体弹出对话框“确定要按下OK按钮吗?”怎么办?然后我决定将其删除,因为它的作用很小并且使用户感到烦恼,然后我是否重构了代码,重新设计了代码,对其进行了修改,对它进行了调试,其他?
工作

@job:您已更改程序以符合新规范。
jmoreno

恕我直言,您可能在这里混淆了不同的标准。从头开始重写代码确实不是在重构,但是无论是否更改了外部行为,都是如此。而且,如果不能重构类接口,那么Move Method等人怎么了。是否存在于重构目录中
彼得Török

@PéterTörök-这完全取决于您“更改类接口”的意思,因为在实现继承的OOP语言中,类的接口不仅包括由类的所有祖先自己实现的类。更改类接口意味着将方法删除/添加到接口(或更改方法的签名-即,所传递的参数类型的数量)。重构意味着谁在响应方法,类或超类。
Zeke Hansell,

恕我直言-这整个问题可能太深奥了,以至于对程序员没有任何有用的价值。
Zeke Hansell,

1

他主要是通过“外部行为”来谈论公共接口,但这也包括系统的输出/工件。(即,您有一种生成文件的方法,更改文件格式将更改外部行为)

e:我认为“移动方法”是对外部行为的改变。请记住,Fowler在谈论的是已被广泛使用的现有代码库。根据您的情况,您也许可以验证您的更改不会破坏任何外部客户,并以一种愉悦的方式进行。

e2:“那么改变外部行为的返工正确的词是什么?” -API重构,重大变更等...它仍在重构,只是没有遵循最佳实践来重构已经在客户端中广泛使用的公共接口。


但是,如果他谈论的是公共接口,那么“移动方法”重构又如何呢?
SiberianGuy

@Idsa编辑了我的回复以解决您的问题。

@kekela,但是我仍然不清楚这个“重构”的事情在哪里结束
SiberianGuy

@idsa根据您发布的定义,在您更改公共界面的那一刻,它不再是重构。(将一个公共方法从一个类移到另一个类就是一个例子)

0

“移动方法”是一种重构技术,而不是单独进行重构。重构是将几种重构技术应用于类的过程。在那里,当您说我对一个类应用了“移动方法”时,您实际上并不是在说“我重构了(该类)”,而是您说了“我在该类​​中应用了一种重构技术”。重构在其最纯粹的意义上适用于设计,或更具体地说,适用于应用程序设计的某些部分,可以将其视为黑匣子。

您可以说,在类的上下文中使用的“重构”意味着“重构技术”,因此“移动方法”不会破坏过程重构的定义。在同一页面上,在设计上下文中进行“重构”不会破坏代码中的现有功能,而只会“破坏”设计(无论如何,这就是其目的)。

总而言之,如果您将(mix:D)重构技术与重构过程混淆,就会越过问题中提到的“边界”。

PS:好问题,顺便说一句。


读了几次,但还是听不懂
SiberianGuy

您不了解哪一部分?(在您的示例中,存在适用于您的定义的重构过程或重构技术,而该方法不适用于该定义;因此,move-method不会破坏重构的定义,过程,或者不跨越边界)。我是说您所关心的问题不应该存在于您的示例中。重构的边界并不是模糊的。您只是将某事物的定义应用于其他事物。
贝伦2011年

0

如果您谈论因数分解,那么您将描述一组整数,当它们相乘时等于起始数字。如果我们将此定义用于分解,并将其应用于编程术语“重构”,那么重构将把程序分解为最小的逻辑单元,以便当它们作为程序运行时,产生相同的输出(给定相同的输入) )作为原始程序。


0

重构的边界特定于给定的重构。根本不可能有一个答案,它涵盖了一切。原因之一是术语重构本身是非特定的。

您可以重构:

  • 源代码
  • 程序架构
  • UI设计/用户交互

我相信还有其他几件事。

也许重构可以定义为

“减少特定系统中的熵的一个动作或一组动作”


0

重构的距离肯定有一定范围。请参阅:两种算法何时相同?

人们通常认为算法比实现它们的程序更抽象。形式化此想法的自然方法是,就适当的等价关系而言,算法是程序的等价类。我们认为不存在这样的等价关系。

因此,不要走得太远,否则您将对结果没有信心。另一方面,经验表明,您通常可以将一种算法替换为另一种算法,并获得相同的答案,有时更快。那就是它的美吧?


0

您可以想到“外部”的含义

外在日常起立。

因此,对系统无任何影响的任何变更都可以视为重构。

如果仅由您的团队构建和维护的单个系统使用该类,则更改类接口是没有问题的。但是,如果该类是每个.net程序员使用的.net框架中的公共类,则情况大不相同。

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.