大家好,我是程序员鬼才。MartinFowler在他的书[1]中将重构定义为*“对软件的内部结构所做的更改,以便在不更改其可观察到的行为的情况下更容易理解和降低制造成本Revise”*。本书包含大量重构技术,旨在应用于特定情况并旨在消除代码异味[2]。重构是一个非常广泛的话题,我发现重构在软件开发过程中扮演着重要的角色。它们的相关性很高,因此它们是TDD[3]生命周期的重要组成部分。由于它们的重要性,在这篇文章中,我想分享软件开发人员中最常用的4种重构技术。但在你开始之前,因为重构技术可以自动应用(即一些IDE通过应用重构工具来帮助你,只需点击几下鼠标和选择就可以让你的生活更轻松),在这里,我将通过Go中的手动重构来描述它语言并尝试将其用作参考指南。我们的开发团队意识到可观察的功能应该包含在单元测试中,并在应用任何重构技术之前通过所有测试。01Extractmethod这是我经常应用到代码中的一个技巧。它涉及将一段代码按意图分组并将其移动到一个新方法中。抽取让您可以将一个长方法或函数拆分成更小的方法,将逻辑放在一起。通常,小方法或函数的名称可以更好地说明该逻辑是什么。下面的示例显示了应用此重构技术之前和之后的情况。我的主要目标是通过将复杂性分成不同的功能来抽象它。funcStringCalculator(expstring)int{ifexp==""{return0}varsumintfor_,number:=rangestrings.Split(exp,","){n,err:=strconv.Atoi(number)iferr!=nil{return0}sum+=n}returnsum}重构为:funcStringCalculator(expstring)int{ifexp==""{return0}returnsumAllNumberInExpression(exp)}funcsumAllNumberInExpression(expstring)int{varsumintfor_,number:=rangestrings.Split(exp,","){sum+=toInt(number)}returnsum}functoInt(expstring)int{n,err:=strconv.Atoi(exp)iferr!=nil{return0}returnn}StringCalculator函数更简单,但是当添加了两个新函数时,增加了复杂。这是一种我愿意做出深思熟虑的决定的牺牲,我将其作为准则而不是规则,因为了解应用重构技术的结果可以很好地指示是否应用重构技术.02MoveMethod有时候,使用ExtractMethod之后,又发现一个问题:这个方法应该属于这个结构体还是包?移动方法是一种简单的技术,包括将方法从一个结构移动到另一个结构。我发现了一个技巧来确定一个方法是否应该属于该结构:弄清楚该方法是否访问另一个结构依赖项的内部。请参见以下示例:typeBookstruct{IDintTitlestring}typeBooks[]BooktypeUserstruct{IDintNamestringBooksBooks}func(uUser)Info(){fmt.Printf("ID:%d-Name:%s",u.ID,u.Name)fmt。Printf("Books:%d",len(u.Books))fmt.Printf("Bookstitles:%s",u.BooksTitles())}func(uUser)BooksTitles()string{vartitles[]stringfor_,book:=rangeu.Books{titles=append(titles,book.Title)}returnstrings.Join(titles,",")}如您所见,User方法BooksTitles使用的书籍内部字段(特别是Title)多于User,这指示该方法应属于Books。应用此重构技术将方法移至Books类型,然后由用户的Info方法调用。func(bBooks)Titles()string{vartitles[]stringfor_,book:=rangeb{titles=append(titles,book.Title)}returnstrings.Join(titles,",")}func(uUser)Info(){fmt.Printf("ID:%d-Name:%s",u.ID,u.Name)fmt.Printf("Books:%d",len(u.Books))fmt.Printf("Bookstitles:%s",u.Books.Titles())}应用此方法后,Books类型更具内聚性,因为它是唯一可以控制和访问其字段和内部属性的类型。同样,这是在深入思考应用重构的后果之前发生的思考过程。03引入参数对象你见过多少参数像下面这个方法:看到函数内部的代码,当我们看到大量这样的参数时,我们也可以考虑它执行的大量操作。有时,我发现这些参数高度相关,并且稍后在定义它们的方法中一起使用。这提供了一种重构方式,使这个场景更面向对象,并建议将这些参数分组到一个结构中,替换方法签名以使用对象作为参数,并在方法内部使用对象。typeOrderFilterstruct{StartDatetime.TimeEndDatetime.TimeCountrystringStatestringCitystringStatusstring}func(om*OrderManager)Filter(ofOrderFilter)(Orders,error){//useof.StartDate,of.EndDate,of.Country,of.State,of.City,of.Status。看起来更清晰,并且可以确定这些参数的身份,但这需要我更改所有调用此方法的引用,并且需要OrderFilter在将其传递给该方法之前创建一个新类型的对象作为参数。同样,在尝试进行此重构之前,我尽量认真思考并考虑后果。我认为当代码中的影响级别较低时,这种技术非常有效。04用符号常量替换幻数这种技术涉及用常量变量替换硬编码值,以赋予它们意图和意义。funcAdd(inputstring)int{ifinput==""{return0}ifstrings.Contains(input,";"){n1:=toNumber(input[:strings.Index(input,";")])n2:=toNumber(输入[strings.Index(input,";")+1:])returnn1+n2}returntoNumber(input)}functoNumber(inputstring)int{n,err:=strconv.Atoi(input)iferr!=nil{return0}返回哪里;这个角色是什么意思?如果我不清楚答案,我可以创建一个临时变量并使用硬编码字符设置值以赋予其意义。funcAdd(inputstring)int{ifinput==""{return0}numberSeparator:=";"ifstrings.Contains(input,numberSeparator){n1:=toNumber(input[:strings.Index(input,numberSeparator)])n2:=toNumber(input[strings.Index(input,numberSeparator)+1:])returnn1+n2}returntoNumber(input)}functoNumber(inputstring)int{n,err:=strconv.Atoi(input)iferr!=nil{return0}returnn}总结感谢您的阅读,希望对您有所帮助。重构是一个非常广泛的主题,本文举例说明了四种最常用的重构。不要认为这里提到的是理所当然的,你自己试试吧。此处描述的重构技术旨在用作指南,而不是作为规则来遵循,这意味着它们可以在需要时有目的地进行调整。最后,我想说的是,我们要对我们写的所有代码和我们使用的所有工具负责,我们的经验和知识可以指导我们在每种情况下使用最适合的技能,我认为重构技术真的很值得.原文链接:https://wawand.co/blog/posts/four-most-refactoring-techniques-i-use/References[1]书籍:https://martinfowler.com/books/refactoring.html[2]不好气味代码:https://en.wikipedia.org/wiki/Code_smell[3]TDD:https://en.wikipedia.org/wiki/Test-driven_development#/media/File:TDD_Global_Lifecycle.png本文转载自微信公众号「幽鬼」,可通过以下二维码关注。转载本文请联系有鬼公众号。
