标签:功能 学习 local 包装 res 选择 号码 建立 hiera
臭味行列中首当其冲的就是Duplicated Code。假设你在一个以上的地点看到同样的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好。
最单纯的Duplicated Code就是[同一个class内的两个方法含有同样表达式(expression)]。
这时候你须要做的就是採用Extract Method提炼出反复的代码,然后让这两个地点都调用被提炼出来的那一段代码。
还有一种常见情况就是[两个互为兄弟(sibling)的subclasses内含有同样表达式]。
要避免这样的情况,仅仅须要对两个classes都使用 Extract Method,然后再对被提炼出的代码使用Pull Up Method,将它推入superclass内。假设代码之间仅仅是类似,并不是全然同样。那么就得运用Extract Method将相似部分和差异部切割开。构成单独一个方法。然后你可能发现也许能够运用Form Template Method获得一个Template Method设计模式。假设有些方法以不同的算法做同样的事,你能够择定当中较清晰的一个。并使用Substitute Algorithm将其他方法的算法替换掉。
假设两个毫不相关的classes内出现Duplicated Code。你应该考虑对当中一个使用Extract Class,将反复代码提炼到一个独立class中,然后在还有一个class内使用这个新class。可是,反复代码所在的方法也可能的确仅仅应该属于某个 class,还有一个class仅仅能调用它。抑或这种方法可能属于第三个class,而另两个classes应该引用这第三个class。你必须决定这种方法放在哪儿最合适。并确保它被安置后就不会再在其他不论什么地方出现。
拥有[短方法](short methods)的对象会活得比較好、比較长。
不熟悉面向对象技术的人,经常认为对象程序中仅仅有无穷无尽的delegation(托付)。根本没有进行不论什么计算。
和此类程序共同生活数年之后,你才会知道,这些小小方法有多大价值。[间接层]所能带来的所有利益——解释能力、共享能力、选择能力——都是由小型方法支持的。
非常久曾经程序猿就已认识到:程序愈长愈难理解。早期的编程语言中,[子程序调用动作]须要额外开销。这使得做你们不太乐意使用small method,现代OO语言差点儿已经全然免除了进程内的[方法调用动作额外开销]。
只是代码阅读者还是得多费力气,由于他必须常常转换上下文去看看子程序做了什么。
某些开发环境同意用户同一时候看到两个方法,这能够帮助你省去部分麻烦。可是让small methodeasy理解的真正关键在于一个好名字。假设你能给方法起个好名字,读者就能够通过名字了解方法的作用,根本不必去看当中写了些什么。
终于的效果是:你应该更积极进取地分解方法。
我们遵循这样一条原则:每当感觉须要以凝视来说明点什么的时候。我们就把须要说明的东西写进一个独立的方法中,并以其用途(而非实现手法)命名。
我们可以对一组或甚至短短一行代码做这件事。哪怕替换后的方法调用动作例如法自身还长,仅仅要方法名称可以解释其用途。我们也该毫不犹豫地那么做。关键不在于方法的长度。而在于方法[做什么]和[怎样做]之间的语义距离。
百分之九十九的场合里。要把方法变小,仅仅需使用Extract Method。找到方法中适合集在一起的部分。将它们提炼出来形成一个新方法。
假设方法内有大量的參数和暂时变量。它们会对你的方法提炼形成阻碍。假设你尝试运用Extract Method,终于就会把很多这些參数和暂时变量当作參数,传递给被提炼出来的新方法,导致可读性差点儿没有不论什么提升。
啊是的,你能够常常运用 Replace Temp with Query则能够将过长的參数列变得更简洁一些。
假设你已经这么做,仍然有太多暂时变量和參数,那就应该拿出我们的杀手锏:Replace Method with Method Object。
怎样确定该提炼哪一段代码呢?一个非常好的技巧是:寻找凝视。它们一般是指出[代码用途和实现手法间的语义距离]的信号。假设代码前言有一行凝视。就是在提醒你:能够将这段代码替换成一个方法。并且能够在凝视的基础上给这种方法命名。
就算仅仅有一行代码,假设它须要以凝视来说明。那也值得将它提炼到独立的方法去。
条件式和循环经常也是提炼的信号。你能够使用Decompose Conditional处理条件式。至于循环,你应该将循环和其内的代码提炼到一例独立方法中。
假设想利用单一class做太多事情。其内往往就会出现太多instance变量。
一旦如此。Duplicated Code也就接踵而至了。
你能够运用Extract Class将数个变量一直提炼到新class内。提炼时应该选择class内彼此相关的变量,将它们放在一直。比如”depositAmount” 和”depositCurrency”可能应该隶属同一个class。通常假设class内的数个变量有着同样的前缀或字尾,这就意味有机会把它们提炼到某个组件内。假设这个组件适合作为一个subclass,你会发现Extract Subclass往往比較简单。
有时候class并不是在全部时刻都使用全部instance变量。
果真如此,你也许能够多次使用Extract Class或Extract Subclass。
和[太多instance变量]一样,class内假设有太多代码。也是[]代码反复、混乱、死亡]的绝佳滋生地点。
最简单的解决方式是把赘余的东西消弭于class内部。
假设有五个[百行方法],它们之中非常多代码都同样,那么也许你能够把它们变成五个[十行方法]和十个提炼出来的[双行方法]。
和[拥有太多instance变量]一样。一个class假设拥有太多代码,往往也适合使用Extract Class和Extract Subclass。这里有个实用技巧:先确定client怎样使用它们,然后运用Extract Interface为每一种使用一个接口。这也许能够帮助你看清楚怎样分解这个class。
假设你的Large Class是个GUI class,你可能须要把数据和行为移到一个独立的领域对象去。
你可能须要两边各保留一些反复数据,并令这些数据同步。Duplicate Observed Data告诉你该怎么做。这样的情况下。特别是假设你使用旧式AWT组件,你能够採用这样的方式去掉GUI class并代以Swing组件。
刚開始学习编程的时候,老师教我们:把方法所需的全部东西都以參数传递进去。这能够理解,由于除此之外就仅仅能选择全局数据。而全局数据是邪恶的东西。对象技术改变了这一情况,由于假设你手上没有你所须要的东西,总能够叫还有一个对象给你。因此,有了对象,你就不必把方法须要的全部东西都以參数传递给它了,你仅仅需给它足够的东西、让方法能从中获得自己须要的全部东西即可了。方法须要的东西多半能够在方法的宿主类(host class)中找到。面向对象程序中的方法,其參数列通常比在传统程序中短得多。
这是好现象,由于太长的參数列难以理解,太多參数会造成前后不一致、不易使用,并且一旦你须要很多其它数据。就不得不改动它。假设将对象传递给方法,大多数改动都将没有必要,由于你非常可能仅仅需(在方法内)添加一两条请求,就能得到很多其它数据。
假设[向既有对象发出一条请求]就能够取得原本位于參数列上的一份数据,那么你应该激活重构准则Replace Parameter with Method。上述的既有对象可能是方法所属class内的一个字段。也可能是还有一个參数。你还能够运用Preserve Whole Object将来自同一对象的一堆数据收集起来,并以该对象替换它们。
假设某些数据缺乏合理的对象归属,可使用Introduce Parameter Object为它们制造出一个[參数对象]。
此间存在一个重要的例外。有时候你明显不希望造成[被调用之对象]与[较大对象]间的某种依存关系。
这时候将数据从对象中拆解出来单独作为參数。也非常合情合理。可是请注意其所引发的代价。假设參数列太长或变化太频繁,你就须要又一次考虑自己的依存结构了。
我们希望软件可以更easy被改动——毕竟软件再怎么说本来就该是[软]的。
一旦须要改动,我们希望可以跌到系统的某一点,仅仅在该处做改动。假设不能做到这点,你就嗅出两种紧密相关的刺鼻味道中的一种了。
假设某个class常常由于不同的原因在不同的方向上发生变化,Divergent Change就出现了。
当你看着一个class说:“ 呃,假设新增加一个数据库。我必须改动这三个方法;假设新出现一种金融工具,我必须改动这四个方法”,那么此时或许将这个对象分成两个会更好,这么一来每一个对象就能够仅仅因一种变化而须要改动。
当然,往往仅仅有在增加新数据库或新金融工具后,你才干发现这一点。针对某一外界变化的全部对应改动。都仅仅应该发生在单一class中,而这个新class内的全部内容都应该反应该外界变化。为此,你应该找出因着某特定原因而造成的全部变化,然后运用Extract Class将它们提炼到还有一个class中。
Shotgun Surgery类似Divergent Change。但恰恰相反。假设每遇到某种变化,你都必须在很多不同的class内做出很多小改动以响应之。你所面临的坏味道就是Shotgun Surgery。假设须要改动的代码散布四处。你不但非常难找到它们。也非常easy忘记某个重要的改动。
这样的情况下你应该使用Move Method和Move Field把全部须要改动的代码放进同一个class。
假设眼下没有合适的class能够安置这些代码。就创造一个。
通常你能够运用Inline Class把一系列相关行为放进同一个class。这可能会造成少量Divergent Change,但你能够轻易处理它。
Divergent Change是指[一个class受多种变化的影响],Shotgun Surgery则是指[一种变化引发多个classes对应改动]。
这两种情况下你都会希望整理代码。取得[外界变化]与[待改类]呈现一对一关系的理想境界。
对象技术的所有要点在于:这是一种[将数据和加诸其上的操作行为包装在一起]的技术。有一种经典气味是:方法对某个class的兴趣高过对自己所处之 host class的兴趣。
这样的孺慕之情最通常的焦点便是数据。无数次经验里,我们看到某个方法为了计算某值,从还有一个对象那儿调用差点儿半打的取值方法。疗法显而易见:把这种方法移到还有一个地点。
你应该使用Move Method把它移到它该去的地方。有时候方法中仅仅有一部分受这样的依恋之苦。这时候你应该使用Extract Method把这一部分提炼到独立方法中。再使用Move Method带它去它的梦中家园。
当然。并不是全部情况都这么简单。一个方法往往会用上数个classes特性。那么它到底该被置于何处呢?我们的原则是:推断哪个class拥有最多[被此方法使用]的数据。然后就把这种方法和那些数据摆在一起。
假设先以Extract Method将这种方法分解为整个较小方法并分别置放于不同地点,上述步骤也就比較easy完毕了。
有数个复杂静止的模式破坏了这个规则。
说起这个话题,[四巨头]的Streategy和Visitor立马跳入我的脑海,Kent Beck的Self Delegation也丰此列。使用这些模式是为了对抗坏味道Divergent Change。
最根本的原则是:将总是一起变化的东西放在一块儿。[数据]和[引用这些数据]的行为总是一起变化的,但也有例外。假设例外出现。我们就搬移那些行为。保持[变化仅仅在一起发生]。Strategy和Visitor使你得以轻松改动方法行为。由于它们将少量须要被覆写的行为隔离开来——当然也付出了[多一层间接性]的代价。
数据项就像小孩子:喜欢成群结队地待在一块儿。你经常能够在非常多地方看到同样的三或四笔数据项:两个classes内的同样字段、很多方法签名式中的同样參数。这些[总是绑在一起出现的数据]真应该放进属于它们自己的对象中。
首先请找出这些数据的字段形式出现点,运用Extract Class将它们提炼到一个独立对象中。然后将注意力转移到方法签名式上头。运用Introduce Parameter Object或Preserve Whole Object为它减肥。这么做的直接优点是能够将非常多參数列缩短,简化方法调用动作。是的,不必由于Data Clumps仅仅用上新对象的一部分字段而在意,仅仅要你以新对象代替两个(或很多其它)字段,你就值回票价了。
一个好的评断办法是:删掉众多数据中的一笔。
其他数据有没有因而失去意义?假设它们不再有问询。这就是个明白信号:你应该为它们产生一个新对象。
缩短字段个数和參数个数,当然能够支队一些坏味道,但更重要的是:一旦拥有新对象,你就有机会让程序散发出一种芳香。得到新对象后,你就能够着手寻找 Feature Envy,这能够帮你指出[可移到新class]中的种种程序行为。
不必太久。全部classes都将在它们的小小社会中充分发挥自己的生产力。
大多数编程环境都有两种数据:结构型别同意你将数据组织成有意义的形式;基本型别则是构成结构型别的积木块。结构总是会带来一定的额外开销。它们有点像数据库中的表格,或是那些得不偿失的东西。
对象的一个极具价值的东西早到:它们模糊了横亘于基本数据和体积较大的classes之间的界限。
你能够轻松编写出一些与语言内置型别无异的小型 classes。
比如Java就以基本型别表示数值,而心class表示字符串和日期——这两个型别在其他很多编程环境中都以基本型别表现。
对象技术的新手通常在小任务上运用小对象——像是结合数值和币别的money class、含一个起始值和一个结束值的range class、电话号码或邮政编码等等的特殊strings。你能够运用Replace Data Value with Object将原本单独存在的数据值替换为对象。从而走出传统的洞窟。进入炙手可热的对象世界。假设欲替换之数据值是type code。而它并不影响行为。你能够运用Replace Type Code with Class将它换掉。假设你有相依于此type code的条件式,可运用Replace Type Code with Subclass或Replace Type Code with State/Strategy加以处理。
假设你有一组应该总是被放在一起的字段。可运用Extract Class。假设你在參数列中看到基本型数据,最好还是试试Introduce Parameter Object。假设你发现自己正从array中挑选数据。可运用Replace Array with Object。
面向对象程序的一个最明显特征就是:少用switch(或case)语句。
从本质上说,switch语句的问题在于反复。你常会发现相同的switch语句散布于不同的地点。假设要为它加入一个新的case子句,你必须找到全部switch语句并改动它们。面向的多态概念可为此带来优雅的解决的方法。
大多数时候,一看到switch语句你就应该考虑以多态来替换它。问题是多态该出如今哪儿?switch语句经常依据type code进行选择,你要的是[与该type code相关的方法或class]。
所以你应该使用Extract Method将switch语句提炼到一个独立方法中,再以Move Method将它搬移到须要多态性的那个class里头。
此时你必须决定是否使用Replace Type Code with Subclasses或Replace Type Code with State/Strategy。一旦这样完毕继承结构之后,你就能够运用Replace Conditional with Polymorphism了。
假设你仅仅是在单一方法中髭选择事例,而你并不想修改它们,那么[多态]就有点杀鸡用牛刀了。
这样的情况下Replace Parameter with Explicit Methods是个不错的选择。假设你的选择条件之中的一个是null。能够试试Introduce Null Object。
Parallel Inheritance Hierarchies事实上是Shotgun Surgery的特殊情况。在这样的情况下。每当你为某个class添加一个subclass,必须也为还有一个class对应添加一个subclass。
假设你发现某个继承体系的class名称前缀和还有一个继承体系的class名称前缀全然同样,便是闻到了这样的坏味道。
消除这样的反复性的一般策略是:让一个继承体系的实体指涉还有一个继承体系的实体。假设再接再厉运用Move Method和Move Field,就能够将指涉端的继承体系消弭于无形。
你所创建的每个class,都得有人去理解它、维护它,这些工作都是要花钱的。
假设一个class的所得不值其身份。它就应该消失。
项目中常常会出现这种情况:某个class原本对得起自己的身份。但重檐使它身形缩水,不再做那么多工作;或开发人员事前规划了某些变化。并加入一个class来就会这些变化,但变化实际上没有发生。不论上述哪一种原因。请让这个class庄严赴义吧。假设某些subclass没有做满足够工作。试试Collapse Hierarchy[合并继承]。
对于差点儿无用的组件,你应该以Inline Class对付它们。
这个令我们十分敏感的坏味道,命名者是Brian Foote。当有人说“噢,我想我们总有一天须要做这事”并因而企图以各式各样的挂勾和特殊情况来处理一些非必要的事情,这样的坏味道就出现了。
那么做的结果往往造成系统更难理解和维护。假设全部装置都会被用到。那就值得那么做。假设用不到,就不值得。
用不上的装置仅仅会挡你的路,所以。把它搬弄吧。
假设你的某个abstract class事实上没有太大作用,请运用Collapse Hierarchy。非必要之delegation可运用Inline Class除掉。假设方法的某些參数示被用上。可对它实施Rename Method让它现实一些。
假设方法或class的惟一用户是test cases。这就飘出了坏味道Speculative Generality。
假设你发现这个方案或class,请把它们连同其test cases都删掉。但假设它们的用途是帮助test cases检測正当功能,当然必须刀下留人。
有时你会看到这种对象:其内某个instance 变量仅为某种特定情势而设。这种代码让人不易理解,由于你通常觉得对象在全部时候都须要它的全部变量。在变量未被使用的情况下推測当初其设置目的,会让你发疯。
请使用Extract Class给这个可怜的孤独创造一个家。然后把全部和这个变量相关的代码都放进这个新家。或许你还能够使用Introduce Null Object在[变量不合法]的情况下创建一个Null对象,从而避免写出[条件式代码]。
假设class中有一个复杂算法。须要好几个变量,往往就可能导致坏味道Temporary Field的出现。因为实现者不希望传递一长串參数,所以他把这些參数都放进字段中。
可是这些字段仅仅在使用该算法时才有效,其他情况下仅仅会让人迷惑。这时候你能够利用Extract Class把这些变量和其相关方法提炼到一个独立class中。提炼后的新对象将是一个method object。
假设你看到用户向一个对象索求还有一个对象,然后再向后者索求还有一个对象,然后再索求还有一个对象……这就是Message Chain。实际代码中你看到的可能是一长串getThis()或一长串暂时变量。採取这样的方式,意味客户将与查找过程中的航行结构紧密耦合。
一旦对象间的关系发生不论什么变化,client就不得不做出对应改动。
这时候你应该使用Hide Delegate。你能够在Message Chain的不同位置进行这样的重构手法。
理论上你能够重构Message Chain上的不论什么一个对象,但这么做往往会把全部中介对象都变成Middle Man。通常更好的选择是:先观察Message Chain终于得到的对象是用来干什么的,看看是否能以Extract Method把使用该对象的代码提炼到一个独立方法中,再运用Move Method把这种方法推入Message Chain。假设这条链上的某个对象有多位客户打算航行此航线的剩余部分,就加一个方法来做这件事。
有些人把不论什么方法链都视为坏东西,我们不这样想。
呵呵,我们的总代表镇定是出了名的,起码在这件事情上是这样。
对象的基本特征之中的一个就是封装——对外部世界隐藏其内部细节。封装往往伴随delegation。比方说你问主管是否有时间參加一个会议,他就把这个消息托付给他的记事簿,然后才干回答你。
非常好。你不是必需知道这位主管究竟使用传统记事簿或电子记事簿抑或秘书来记录自己的约会。
可是人们可能过度运用delegation。你或许会看到某个class接口有一半的方法都托付给其他class,这样就是过度运用。
这里你应该使用 Remove Middle Man。直接和负责对象打交道。假设这样[不干实事]的方法仅仅有少数几个。能够运用Inline Method把它们”inlining”,放进调用端。
假设这些Middle Man还有其他行为内销能够运用Replace Delegation with Inheritance把它变成负责对象的subclass。这样你既能够扩展原对象的行为。又不必负担那么多的托付动作。
有时候你会看到两个classes过于亲热,花费太多时间去探究彼此的private成分。假设这发生在两个[人]之间。我们不必做卫道之士;但对于 classes,我们希望它们严守清规。
就像古代恋人一样,过份狎昵的classes必须拆散。你能够採用Move Method和Move Field帮它们划清界线,从而降低狎昵行径。
你也能够看看是否运用Change Bidirectional Association to Unidirectional[将双向关联改为单向]让当中一个class对还有一个斩断情丝。假设两个classes实在情投意合。能够运用Extract Class把两者共同点提炼到一个安全地点。让它们坦荡地使用这个新class。或者也能够尝试运用Hide Delegate让还有一个class来为它们传递相思情。
继承往往造成过度亲热,由于subclass对superclass的了解总是超过superclass的主观愿望。假设你认为该让这个孩子独自生活了,请运用Replace Inheritance with Delegation让它离开继承体系。
假设两个方法做同一件事,却有着不同的签名式,请运用Rename Method依据它们的用途又一次命名。但这往往不够。请重复运用Move Method将某些行为移入classes,直到两者的协议一致为止。假设你必须重复而赘余地移入代码才干完毕这些,也许可运用Extract Superclass为自己赎点罪。
复用常被视为对象的终极目的。我们觉得这实在是过度预计了。可是无可否认,很多编程技术都建立在library classes的基础上,没人敢说是不是我们都把排序算法忘得一干二净了。
Library classes构筑者没有未卜先知的能力,我们不能因此责备他们。毕竟我们自己也差点儿总是在系统快要构筑完毕的时候才干弄清楚它的设计,所以 library构筑者的任务真的非常艰巨。麻烦的是library的形式往往不够好,往往不可能让我们改动当中的classes使它完毕我们希望完毕的工作。这是否意味那些经过实践检验的战术如Move Method等等,现在都派不上用场了?
幸好我们有两个专门就会这样的情况的工具。假设你仅仅想改动library classes内的一两个方法,能够运用Introduce Foreign Method。假设想要加入一大堆额外行为,就得运用Introduce Local Extension。
所谓Data Class是指:它们拥有一些字段,以及用于訪问这些字段的方法,除此之外一无长物。这种classes仅仅是一种[不会说话的数据容器],它们差点儿一定被其他classes过份细琐地操控着。这些classes早期可能拥有public字段。果真如此你应该在别人注意到它们之前。立马运用 Encapsulate Field将它们封装起来。假设这些classes内含容器类的字段。你应该检查它们是不是得到了恰当的封装;假设没有。就运用Encapsulate Collection把它们封装起来。对于那些不该被其他classes改动的字段。请运用Remove Setting Method。
然后,找出这些[取值/设值]方法被其他classes运用的地点。
尝试以Move Method把那些调用行为搬移到Data Class来。假设无法搬移整个方法,就运用Extract Method产生一个可被搬移的方法。不久之后你就能够运用Hide Method把这些[取值/设值]方法隐藏起来了。
Data Class就像小孩子。作为一个起点非常好,但若要让它们像[成年]的对象那样參与整个系统的工作,它们就必须承担一定责任。
Subclasses应该继承superclass的方法和数据。但假设它们不想或不须要继承,又该怎么办呢?它们得到全部礼物。却仅仅从中挑选几样来玩!
按传统说法,这就意味继承体系设计错误。
你须要为这个subclass新建一个兄弟,再运用Push Down Method和Push Down Field把全部用不到的方法下推给那兄弟。这样一来superclass就仅仅持有全部subclasses共享的东西。
经常你会听到这种建议:全部 superclasses都应该是抽象的。
既然使用[传统说法]这个略带贬义的词。你就能够猜到。我们不建议你这么做,起码不建议你每次都这么做。我们常常利用subclassing手法来复用一些行为,并发现这能够非常好地应用于日常工作。
这也是一种坏味道,我们不否认,但气味通常并不强烈。所以我们说:假设Refused Bequest引起困惑和问题,请遵循传统忠告。但不必觉得你每次都得那么做。十有八九这样的坏味道非常淡。不值得理睬。
假设subclass复用了superclass的行为(实现)。却又不愿意支持superclass的接口。Refused Bequest的坏味道就会变得浓烈。拒绝继承superclass的实现。这一点我们不介意。但假设拒绝继承superclass的接口,我们不以为然。
只是即使你不愿意继承接口。也不要胡乱改动继承系。你应该运用Replace Inheritance with Delegation来达到目的。
别操心,我们并非说你不该写凝视。
从嗅觉上说,Comments不是一种坏味道;其实它们还是一种香味呢。
我们之所以要在这里提到Comments,由于人们常把它当作除臭剂来使用。
经常会有这种情况:你看到一段代码有着长长的凝视。然后发现,这些凝视之所以存在乃是由于代码非常糟糕。这种情况的发生次数之多。实在令人惊讶。
Comments能够带我们找到本章先前提到的各种坏味道。
找到坏味道后,我们首先应该以各种重构手法把坏味道去除。完毕之后我们经常会发现:凝视已经变得多余了。由于代码已经清楚说明了一切。
假设你须要凝视来解释一块代码做了什么,试试Extract Method;假设你须要凝视说明某些系统的需求规格。试试Introduce Assertion。
假设你不知道该做什么,这才是凝视的良好运用时机。
除了用来记述将来的打算之外。凝视还能够用来标记你并无十足把握的区域。你能够在凝视里写下自己[为什么做某某事]。
这类信息能够帮助将来的改动者,尤其是那些健忘的家伙。
标签:功能 学习 local 包装 res 选择 号码 建立 hiera
原文地址:http://www.cnblogs.com/zsychanpin/p/6871668.html