重构——代码的坏味道

1. Duplicated Code(重复的代码)程序员

臭味行列中首当其冲的就是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将其它函数的算法替换掉。express

若是两个绝不相关的classes内出现Duplicated Code,你应该考虑对其中一个使用Extract Class,将重复代码提炼到一个独立class中,而后在另外一个class内使用这个新class。可是,重复代码所在的函数也可能的确只应该属于某个class,另外一个class只能调用它,抑或这个函数可能属于第三个class,而另两个classes应该引用这第三个class。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其它任何地方出现。编程

2. Long Method(过长函数)设计模式

拥有[短函数](short methods)的对象会活得比较好、比较长。不熟悉面向对象技术的人,经常以为对象程序中只有无穷无尽的delegation(委托),根本没有进行任何计算。和此类程序共同生活数年以后,你才会知道,这些小小函数有多大价值。[间接层]所能带来的所有利益——解释能力、共享能力、选择能力——都是由小型函数支持的。安全

好久之前程序员就已认识到:程序愈长愈难理解。早期的编程语言中,[子程序调用动做]须要额外开销,这使得作大家不太乐意使用small method,现代OO语言几乎已经彻底免除了进程内的[函数调用动做额外开销]。不过代码阅读者仍是得多费力气,由于他必须常常转换上下文去看看子程序作了什么。某些开发环境容许用户同时看到两个函数,这能够帮助你省去部分麻烦,可是让small method容易理解的真正关键在于一个好名字。若是你能给函数起个好名字,读者就能够经过名字了解函数的做用,根本没必要去看其中写了些什么。session

最终的效果是:你应该更积极进取地分解函数。咱们遵循这样一条原则:每当感受须要以注释来讲明点什么的时候,咱们就把须要说明的东西写进一个独立的函数中,并以其用途(而非实现手法)命名。咱们能够对一组或甚至短短一行代码作这件事。哪怕替换后的函数调用动做比函数自身还长,只要函数名称可以解释其用途,咱们也该绝不犹豫地那么作。关键不在于函数的长度,而在于函数[作什么]和[如何作]之间的语义距离。并发

百分之九十九的场合里,要把函数变小,只需使用Extract Method。找到函数中适合集在一块儿的部分,将它们提炼出来造成一个新函数。app

若是函数内有大量的参数和临时变量,它们会对你的函数提炼造成阻碍。若是你尝试运用Extract Method,最终就会把许多这些参数和临时变量看成参数,传递给被提炼出来的新函数,致使可读性几乎没有任何提高。啊是的,你能够常常运用Replace Temp with Query则能够将过长的参数列变得更简洁一些。

若是你已经这么作,仍然有太多临时变量和参数,那就应该拿出咱们的杀手锏:Replace Method with Method Object。

如何肯定该提炼哪一段代码呢?一个很好的技巧是:寻找注释。它们一般是指出[代码用途和实现手法间的语义距离]的信号。若是代码前言有一行注释,就是在提醒你:能够将这段代码替换成一个函数,并且能够在注释的基础上给这个函数命名。就算只有一行代码,若是它须要以注释来讲明,那也值得将它提炼到独立的函数去。

条件式和循环经常也是提炼的信号。你可使用Decompose Conditional处理条件式。至于循环,你应该将循环和其内的代码提炼到一例独立函数中。

3. Large Class(过大类)

若是想利用单一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。这里有个有用技巧:先肯定客户端如何使用它们,而后运用Extract Interface为每一种使用一个接口。这或许能够帮助你看清楚如何分解这个class。

若是你的Large Class是个GUI class,你可能须要把数据和行为移到一个独立的领域对象去。你可能须要两边各保留一些重复数据,并令这些数据同步。Duplicate Observed Data告诉你该怎么作。这种状况下,特别是若是你使用旧式AWT组件,你能够采用这种方式去掉GUI class并代以Swing组件。

4. Long Parameter List(过长参数列)

刚开始学习编程的时候,老师教咱们:把函数所需的全部东西都以参数传递进去。这能够理解,由于除此以外就只能选择全局数据,而全局数据是邪恶的东西。对象技术改变了这一状况,由于若是你手上没有你所须要的东西,总能够叫另外一个对象给你。所以,有了对象,你就没必要把函数须要的全部东西都以参数传递给它了,你只需给它足够的东西、让函数能从中得到本身须要的全部东西就好了。函数须要的东西多半能够在函数的宿主类(host class)中找到。面向对象程序中的函数,其参数列一般比在传统程序中短得多。

这是好现象,由于太长的参数列难以理解,太多参数会形成先后不一致、不易使用,并且一旦你须要更多数据,就不得不修改它。若是将对象传递给函数,大多数修改都将没有必要,由于你极可能只需(在函数内)增长一两条请求,就能获得更多数据。

若是[向既有对象发出一条请求]就能够取得本来位于参数列上的一份数据,那么你应该激活重构准则Replace Parameter with Method。上述的既有对象多是函数所属class内的一个值域,也多是另外一个参数。你还能够运用Preserve Whole Object未来自同一对象的一堆数据收集起来,并以该对象替换它们。若是某些数据缺少合理的对象归属,可以使用Introduce Parameter Object为它们制造出一个[参数对象]。

此间存在一个重要的例外。有时候你明显不但愿形成[被调用之对象]与[较大对象]间的某种依存关系。这时候将数据从对象中拆解出来单独做为参数,也很合情合理。可是请注意其所引起的代价。若是参数列太长或变化太频繁,你就须要从新考虑本身的依存结构了。

5. Divergent Change(发散式变化)

咱们但愿软件可以更容易被修改——毕竟软件再怎么说原本就该是[软]的。一旦须要修改,咱们但愿可以跌到系统的某一点,只在该处作修改。若是不能作到这点,你就嗅出两种紧密相关的刺鼻味道中的一种了。

若是某个class常常由于不一样的缘由在不一样的方向上发生变化,Divergent Change就出现了。当你看着一个class说:“呃,若是新加入一个数据库,我必须修改这三个函数;若是新出现一种金融工具,我必须修改这四个函数”,那么此时也许将这个对象分红两个会更好,这么一来每一个对象就能够只因一种变化而须要修改。固然,每每只有在加入新数据库或新金融工具后,你才能发现这一点。针对某一外界变化的全部相应修改,都只应该发生在单一class中,而这个新class内的全部内容都应该反应该外界变化。为此,你应该找出因着某特定缘由而形成的全部变化,而后运用Extract Class将它们提炼到另外一个class中。

6. Shotgun Surgery(霰弹式修改)

Shotgun Surgery相似Divergent Change,但偏偏相反。若是每遇到某种变化,你都必须在许多不一样的class内作出许多小修改以响应之,你所面临的坏味道就是Shotgun Surgery。若是须要修改的代码散布四处,你不但很难找到它们,也很容易忘记某个重要的修改。

这种状况下你应该使用Move Method和Move Field把全部须要修改的代码放进同一个class。若是眼下没有合适的class能够安置这些代码,就创造一个。一般你能够运用Inline Class把一系列相关行为放进同一个class。这可能会形成少许Divergent Change,但你能够轻易处理它。

Divergent Change是指[一个class受多种变化的影响],Shotgun Surgery则是指[一种变化引起多个classes相应修改]。这两种状况下你都会但愿整理代码,取得[外界变化]与[待改类]呈现一对一关系的理想境地。

7. Feature Envy(依恋情结)

对象技术的所有要点在于:这是一种[将数据和加诸其上的操做行为包装在一块儿]的技术。有一种经典气味是:函数对某个class的兴趣高过对本身所处之host class的兴趣。这种孺慕之情最一般的焦点即是数据。无数次经验里,咱们看到某个函数为了计算某值,从另外一个对象那儿调用几乎半打的取值函数。疗法显而易见:把这个函数移到另外一个地点。你应该使用Move Method把它移到它该去的地方。有时候函数中只有一部分受这种依恋之苦,这时候你应该使用Extract Method把这一部分提炼到独立函数中,再使用Move Method带它去它的梦中家园。

固然,并不是全部状况都这么简单。一个函数每每会用上数个classes特性,那么它究竟该被置于何处呢?咱们的原则是:判断哪一个class拥有最多[被此函数使用]的数据,而后就把这个函数和那些数据摆在一块儿。若是先以Extract Method将这个函数分解为整个较小函数并分别置放于不一样地点,上述步骤也就比较容易完成了。

有数个复杂精巧的模式破坏了这个规则。提及这个话题,[四巨头]的Streategy和Visitor马上跳入个人脑海,Kent Beck的Self Delegation也丰此列。使用这些模式是为了对抗坏味道Divergent Change。最根本的原则是:将老是一块儿变化的东西放在一起。[数据]和[引用这些数据]的行为老是一块儿变化的,但也有例外。若是例外出现,咱们就搬移那些行为,保持[变化只在一块儿发生]。Strategy和Visitor使你得以轻松修改函数行为,由于它们将少许须要被覆写的行为隔离开来——固然也付出了[多一层间接性]的代价。

8. Data Clumps(数据泥团)

数据项就像小孩子:喜欢三五成群地待在一起。你经常能够在不少地方看到相同的三或四笔数据项:两个classes内的相同值域、许多函数签名式中的相同参数。这些[老是绑在一块儿出现的数据]真应该放进属于它们本身的对象中。首先请找出这些数据的值域形式出现点,运用Extract Class将它们提炼到一个独立对象中。而后将注意力转移到函数签名式上头,运用Introduce Parameter Object或Preserve Whole Object为它减肥。这么作的直接好处是能够将不少参数列缩短,简化函数调用动做。是的,没必要由于Data Clumps只用上新对象的一部分值域而在乎,只要你以新对象取代两个(或更多)值域,你就值回票价了。

一个好的评断办法是:删掉众多数据中的一笔。其它数据有没有于是失去意义?若是它们再也不有问询,这就是个明确信号:你应该为它们产生一个新对象。

缩短值域个数和参数个数,固然能够支队一些坏味道,但更重要的是:一旦拥有新对象,你就有机会让程序散发出一种芳香。获得新对象后,你就能够着手寻找Feature Envy,这能够帮你指出[可移到新class]中的种种程序行为。没必要过久,全部classes都将在它们的小小社会中充分发挥本身的生产力。

9. Primitive Obsession(基本型别偏执)

大多数编程环境都有两种数据:结构型别容许你将数据组织成有意义的形式;基本型别则是构成结构型别的积木块。结构老是会带来必定的额外开销。它们有点像数据库中的表格,或是那些得不偿失的东西。

对象的一个极具价值的东西早到:它们模糊了横亘于基本数据和体积较大的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。

10. Switch Statements(switch惊悚现身)

面向对象程序的一个最明显特征就是:少用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。

11. Parallel Inheritance Hierarchies(平等继承体系)

Parallel Inheritance Hierarchies实际上是Shotgun Surgery的特殊状况。在这种状况下,每当你为某个class增长一个subclass,必须也为另外一个class相应增长一个subclass。若是你发现某个继承体系的class名称前缀和另外一个继承体系的class名称前缀彻底相同,即是闻到了这种坏味道。

消除这种重复性的通常策略是:让一个继承体系的实体指涉另外一个继承体系的实体。若是再接再砺运用Move Method和Move Field,就能够将指涉端的继承体系消弭于无形。

12. Lazy Class(冗赘类)

你所建立的每个class,都得有人去理解它、维护它,这些工做都是要花钱的。若是一个class的所得不值其身份,它就应该消失。项目中常常会出现这样的状况:某个class本来对得起本身的身份,但重檐使它身形缩水,再也不作那么多工做;或开发者事前规划了某些变化,并添加一个class来就会这些变化,但变化实际上没有发生。不论上述哪种缘由,请让这个class庄严赴义吧。若是某些subclass没有作知足够工做,试试Collapse Hierarchy。对于几乎没用的组件,你应该以Inline Class对付它们。

13. Speculative Generality(夸夸其谈将来性)

这个令咱们十分敏感的坏味道,命名者是Brian Foote。当有人说“噢,我想咱们总有一天须要作这事”并于是企图以各式各样的挂勾和特殊状况来处理一些非必要的事情,这种坏味道就出现了。那么作的结果每每形成系统更难理解和维护。若是全部装置都会被用到,那就值得那么作;若是用不到,就不值得。用不上的装置只会挡你的路,因此,把它搬弄吧。

若是你的某个abstract class其实没有太大做用,请运用Collapse Hierarchy。非必要之delegation可运用Inline Class除掉。若是函数的某些参数示被用上,可对它实施Rename Method让它现实一些。

若是函数或class的唯一用户是test cases,这就飘出了坏味道Speculative Generality。若是你发现这样的函数或class,请把它们连同其test cases都删掉。但若是它们的用途是帮助test cases检测正当功能,固然必须刀下留人。

14. Temporary Field(使人迷惑的暂时值域)

有时你会看到这样的对象:其内某个instance 变量仅为某种特定情势而设。这样的代码让人不易理解,由于你一般认为对象在全部时候都须要它的全部变量。在变量未被使用的状况下猜想当初其设置目的,会让你发疯。

请使用Extract Class给这个可怜的孤首创造一个家,而后把全部和这个变量相关的代码都放进这个新家。也许你还可使用Introduce Null Object在[变量不合法]的状况下建立一个Null对象,从而避免写出[条件式代码]。

若是class中有一个复杂算法,须要好几个变量,每每就可能致使坏味道Temporary Field的出现。因为实现者不但愿传递一长串参数,因此他把这些参数都放进值域中。可是这些值域只在使用该算法时才有效,其它状况下只会让人迷惑。这时候你能够利用Extract Class把这些变量和其相关函数提炼到一个独立class中。提炼后的新对象将是一个method object。

15. Message Chains(过分耦合的消息链)

若是你看到用户向一个对象索求另外一个对象,而后再向后者索求另外一个对象,而后再索求另外一个对象……这就是Message Chain。实际代码中你看到的多是一长串getThis()或一长串临时变量。采起这种方式,意味客户将与查找过程当中的航行结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不作出相应修改。

这时候你应该使用Hide Delegate。你能够在Message Chain的不一样位置进行这种重构手法。理论上你能够重构Message Chain上的任何一个对象,但这么作每每会把全部中介对象都变成Middle Man。一般更好的选择是:先观察Message Chain最终获得的对象是用来干什么的,看看可否以Extract Method把使用该对象的代码提炼到一个独立函数中,再运用Move Method把这个函数推入Message Chain。若是这条链上的某个对象有多位客户打算航行此航线的剩余部分,就加一个函数来作这件事。

有些人把任何函数链都视为坏东西,咱们不这样想。呵呵,咱们的总表明镇定是出了名的,起码在这件事情上是这样。

16. Middle Man(中间转手人)

对象的基本特征之一就是封装——对外部世界隐藏其内部细节。封装每每伴随delegation。好比说你问主管是否有时间参加一个会议,他就把这个消息委托给他的记事簿,而后才能回答你。很好,你不必知道这位主管到底使用传统记事簿或电子记事簿抑或秘书来记录本身的约会。

可是人们可能过分运用delegation。你也许会看到某个class接口有一半的函数都委托给其它class,这样就是过分运用。这里你应该使用Remove Middle Man,直接和负责对象打交道。若是这样[不干实事]的函数只有少数几个,能够运用Inline Method把它们”inlining”,放进调用端。若是这些Middle Man还有其它行为内销能够运用Replace Delegation with Inheritance把它变成负责对象的subclass,这样你既能够扩展原对象的行为,又没必要负担那么多的委托动做。

17. Inappropriate  (狎昵关系)

有时候你会看到两个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让它离开继承体系。

18. Alternative Classes with Different Interfaces(殊途同归的类)

若是两个函数作同一件事,却有着不一样的签名式,请运用Rename Method根据它们的用途从新命名。但这每每不够,请反复运用Move Method将某些行为移入classes,直到二者的协议一致为止。若是你必须重复而赘余地移入代码才能完成这些,或许可运用Extract Superclass为本身赎点罪。

19. Incomplete Library Class(不完美的程序库类)

复用常被视为对象的终极目的。咱们认为这实在是过分估计了。可是无能否认,许多编程技术都创建在library classes的基础上,没人敢说是否是咱们都把排序算法忘得一干二净了。

Library classes构筑者没有未卜先知的能力,咱们不能所以责怪他们。毕竟咱们本身也几乎老是在系统快要构筑完成的时候才能弄清楚它的设计,因此library构筑者的任务真的很艰巨。麻烦的是library的形式每每不够好,每每不可能让咱们修改其中的classes使它完成咱们但愿完成的工做。这是否意味那些通过实践检验的战术如Move Method等等,现在都派不上用场了?

幸亏咱们有两个专门就会这种状况的工具。若是你只想修改library classes内的一两个函数,能够运用Introduce Foreign Method;若是想要添加一大堆额外行为,就得运用Introduce Local Extension。

20. Data Class(幼稚的数据类)

所谓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就像小孩子。做为一个起点很好,但若要让它们像[成年]的对象那样参与整个系统的工做,它们就必须承担必定责任。

21. Refused Bequest(被拒绝的遗赠)

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来达到目的。

22. Comments(过多的注释)

别担忧,咱们并非说你不应写注释。从嗅觉上说,Comments不是一种坏味道;事实上它们仍是一种香味呢。咱们之因此要在这里提到Comments,由于人们常把它看成除臭剂来使用。经常会有这样的状况:你看到一段代码有着长长的注释,而后发现,这些注释之因此存在乃是由于代码很糟糕。这种状况的发生次数之多,实在使人吃惊。

Comments能够带咱们找到本章先前提到的各类坏味道。找到坏味道后,咱们首先应该以各类重构手法把坏味道去除。完成以后咱们经常会发现:注释已经变得多余了,由于代码已经清楚说明了一切。

若是你须要注释来解释一块代码作了什么,试试Extract Method;若是你须要注释说明某些系统的需求规格,试试Introduce Assertion。

若是你不知道该作什么,这才是注释的良好运用时机。除了用来记述未来的打算以外,注释还能够用来标记你并没有十足把握的区域。你能够在注释里写下本身[为何作某某事]。这类信息能够帮助未来的修改者,尤为是那些健忘的家伙。

相关文章
相关标签/搜索