Go 中常用的四大重构技术

开发 后端
这是我常应用于代码的技术。它包括提取一段按意图分组的代码,并转移到新方法中。通过提取可以将一个长方法或函数拆分为一些小方法,这些小方法将逻辑组合在一起。通常,小方法或函数的名称可以更好地了解该逻辑是什么。

[[433087]]

大家好,我是程序员幽鬼。

Martin Fowler 在他的书中[1]将重构定义为*“对软件的内部结构进行的更改,以使其更易于理解,并且在不更改其可观察到的行为的情况下更低廉地进行修改”*。本书包含大量重构技术,这些重构技术旨在在某些情况下应用,并旨在消除代码坏味道[2]。

重构是一个非常广泛的话题,我发现重构在软件开发过程中起着重要的作用。它们的相关性很高,因此它们是TDD[3]生命周期的重要组成部分。

由于它们的重要性,在这篇文章中,我想分享一下软件开发人员中使用最多的 4 种重构技术。但是在开始之前,因为可以自动应用重构技术(即某些 IDE 为你提供了帮助,通过应用重构工具,只需单击几下鼠标和进行选择,即可使你的生活更轻松),在这里,我将通过使用 Go 语言手动重构进行描述,并尝试将其作为参考指南。我们的开发团队意识到,在应用任何重构技术之前,应将可观察到的功能包含在单元测试中,并通过所有测试。

01 提取方法

这是我常应用于代码的技术。它包括提取一段按意图分组的代码,并转移到新方法中。通过提取可以将一个长方法或函数拆分为一些小方法,这些小方法将逻辑组合在一起。通常,小方法或函数的名称可以更好地了解该逻辑是什么。

下面的示例显示了应用此重构技术之前和之后的情况。我的主要目标是通过将复杂度分为不同的功能,这样来抽象其复杂度。

  1. func StringCalculator(exp string) int { 
  2.     if exp == "" { 
  3.         return 0 
  4.     } 
  5.      
  6.     var sum int 
  7.     for _, number := range strings.Split(exp, ",") { 
  8.         n, err := strconv.Atoi(number) 
  9.         if err != nil { 
  10.             return 0 
  11.         } 
  12.         sum += n 
  13.     } 
  14.     return sum 

重构为:

  1. func StringCalculator(exp string) int { 
  2.     if exp == "" { 
  3.         return 0 
  4.     } 
  5.  return sumAllNumberInExpression(exp) 
  6.  
  7. func sumAllNumberInExpression(exp string) int { 
  8.     var sum int 
  9.     for _, number := range strings.Split(exp, ",") { 
  10.         sum += toInt(number) 
  11.     } 
  12.     return sum 
  13.  
  14. func toInt(exp string) int { 
  15.     n, err := strconv.Atoi(exp) 
  16.     if err != nil { 
  17.         return 0 
  18.     } 
  19.     return n 

StringCalculator 函数更简单了,但是当添加了两个新的函数时,它会增加复杂性。这是一个我愿意做出慎重决定的牺牲,我将此作为参考而不是规则,从某种意义上说,了解应用重构技术的结果可以很好地判断是否应用重构技术。

02 移动方法

有时,在使用提取方法后,我发现了另一个问题:此方法应该属于此结构或包吗?Move Method 是一种简单的技术,包括将方法从一个结构移动到另一个结构。我发现一个技巧,来确定某个方法是否应该属于该结构:弄清楚该方法是否访问了另一个结构依赖项的内部。看下面的例子:

  1. type Book struct { 
  2.     ID    int 
  3.     Title string 
  4.  
  5. type Books []Book 
  6.  
  7. type User struct { 
  8.     ID    int 
  9.     Name  string 
  10.     Books Books 
  11.  
  12. func (u User) Info() { 
  13.     fmt.Printf("ID:%d - Name:%s", u.ID, u.Name
  14.     fmt.Printf("Books:%d", len(u.Books)) 
  15.     fmt.Printf("Books titles: %s", u.BooksTitles()) 
  16.  
  17. func (u User) BooksTitles() string { 
  18.     var titles []string 
  19.     for _, book := range u.Books { 
  20.         titles = append(titles, book.Title) 
  21.     } 
  22.     return strings.Join(titles, ","

如你所见,User 的方法BooksTitles 使用了 books(具体是 Title)中的内部字段多于User,这表明该方法应归于Books。应用这种重构技术将该方法移动到Books类型上,然后由用户的Info方法调用。

  1. func (b Books) Titles() string { 
  2.     var titles []string 
  3.     for _, book := range b { 
  4.         titles = append(titles, book.Title) 
  5.     } 
  6.     return strings.Join(titles, ","
  7.  
  8. func (u User) Info() { 
  9.     fmt.Printf("ID:%d - Name:%s", u.ID, u.Name
  10.     fmt.Printf("Books:%d", len(u.Books)) 
  11.     fmt.Printf("Books titles: %s", u.Books.Titles()) 

应用此方法后,Books类型会更内聚,因为它是唯一拥有控制权和对它的字段和内部属性访问权的人。同样,这是在深思熟虑之前进行的思考过程,知道应用重构会带来什么结果。

03 引入参数对象

你见过多少像下面方法一样,有很多参数的:

  1. func (om *OrderManager) Filter(startDate, endDate time.Time, country, state, city, status string) (Orders, error) { 
  2.     ... 

即使我们看不到函数内部的代码,当我们看到大量这样的参数时,我们也可以考虑它执行的大量操作。

有时,我发现这些参数之间高度相关,并在以后定义它们的方法中一起使用。这为重构提供了一种使该场景更加面向对象的方式进行处理的方法,并且建议将这些参数分组为一个结构,替换方法签名以将该对象用作参数,并在方法内部使用该对象。

  1. type OrderFilter struct { 
  2.     StartDate time.Time 
  3.     EndDate   time.Time 
  4.     Country   string 
  5.     State     string 
  6.     City      string 
  7.     Status    string 
  8.  
  9. func (om *OrderManager) Filter(of OrderFilter) (Orders, error) { 
  10.     // use of.StartDate, of.EndDate, of.Country, of.State, of.City, of.Status. 

看起来更干净,并且可以确定这些参数的身份,但是这将要求我更改调用此方法的所有引用,并且需要OrderFilter在传递给该方法之前创建一个新类型的对象作为参数。同样,在尝试进行此重构之前,我会尽力思考并考虑后果。当你的代码中的影响程度很低时,我认为此技术非常有效。

04 用符号常量替换魔数

该技术包括用常数变量替换硬编码值以赋予其意图和意义。

  1. func Add(input string) int { 
  2.     if input == "" { 
  3.         return 0 
  4.     } 
  5.  
  6.     if strings.Contains(input, ";") { 
  7.         n1 := toNumber(input[:strings.Index(input, ";")]) 
  8.         n2 := toNumber(input[strings.Index(input, ";")+1:]) 
  9.  
  10.         return n1 + n2 
  11.     } 
  12.  
  13.     return toNumber(input) 
  14.  
  15. func toNumber(input string) int { 
  16.     n, err := strconv.Atoi(input) 
  17.     if err != nil { 
  18.         return 0 
  19.     } 
  20.     return n 

其中 ; 字符是什么意思?如果答案对我来说不太明确,我可以创建一个临时变量,并使用硬编码字符设置该值,以赋予其意义。

  1. func Add(input string) int { 
  2.     if input == "" { 
  3.         return 0 
  4.     } 
  5.  
  6.     numberSeparator := ";" 
  7.     if strings.Contains(input, numberSeparator) { 
  8.         n1 := toNumber(input[:strings.Index(input, numberSeparator)]) 
  9.         n2 := toNumber(input[strings.Index(input, numberSeparator)+1:]) 
  10.  
  11.         return n1 + n2 
  12.     } 
  13.  
  14.     return toNumber(input) 
  15.  
  16.  
  17. func toNumber(input string) int { 
  18.     n, err := strconv.Atoi(input) 
  19.     if err != nil { 
  20.         return 0 
  21.     } 
  22.     return n 

总结

感谢阅读,希望对你有所帮助。重构是一个非常广泛的话题,本文举例说明了重构中使用最多的四个。不要将此处提到的内容视为理所当然,自己尝试一下。此处描述的重构技术仅用作指导原则,而未作为规则遵循,意味着它们在需要时可以有针对性地进行调整。最后,我想说我们对所编写的所有代码和所使用的所有工具负责,我们的经验和知识可以指导我们掌握在每种情况下最适合的技能,我认为重构技术确实值得。

原文链接:https://wawand.co/blog/posts/four-most-refactoring-techniques-i-use/

参考资料

[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

本文转载自微信公众号「幽鬼」,可以通过以下二维码关注。转载本文请联系幽鬼公众号。

 

责任编辑:武晓燕 来源: 幽鬼
相关推荐

2015-04-02 15:50:47

数据仓库数据挖掘

2021-09-17 10:50:14

容器 Linux

2010-09-15 13:35:25

SwingHibernateStruts

2020-02-29 09:09:34

物联网定位技术IOT

2020-04-06 20:11:26

区块链分布式核心技术

2020-06-29 14:52:31

隐私增强技术PET加密

2017-03-17 19:48:01

人脸识别

2015-12-04 13:46:31

技术人创业创业项目

2011-03-21 09:01:49

CSS框架

2015-07-17 09:50:16

Carthage优劣比较

2012-07-11 10:41:38

虚拟化

2014-03-27 15:57:45

Android组件Activity

2011-06-22 08:38:35

Java EE

2013-09-02 10:17:05

虚拟化技术

2012-05-29 09:23:23

HTML5移动应用框架jQTouch

2020-09-22 15:22:04

物联网智慧城市技术

2020-07-28 08:48:49

Python绘图工具

2016-03-30 11:51:55

2009-12-09 10:15:08

2013-01-06 10:44:43

微软Windows 8云计算
点赞
收藏

51CTO技术栈公众号