方法的本质是接收一些数据作为输入,进行处理然后产生输出,如果方法内部不会对任何外部状态进行改变(例如改变了某个全局变量的值,数据库数据,或是文件内容等),那么这个方法可以称之为纯净的方法(Pure function)。
go 基础库中有许多这样的纯净方法:
math/pow.go
func Pow(x, y float64) float64
strings/strings.go
func Replace(s, old, new string, n int) string
下面示例中 generateKeywords 函数有输入但没有输出,可以发现其功能完全依赖副作用实现。通过函数定义我们只能猜测 keywords 的生成依赖 Book 的 Content 和 Title,至于是其中一者还是两者则不得而知。
package book
type Book struct {
Content string
Title string
Keywords string
}
func (s *Service) Create(title, content string) (*Book, error) {
// ...
bk := new(Book)
bk.Content = content
bk.Title = title
generateKeywords(bk)
// ...
}
func generateKeywords(bk *Book) { // ... }
下面的代码将 generateKeywords 函数改为纯净方法,现在我们通过函数定义我们能一眼看出 keywords 的生成仅依赖 Content。
// ...
func (s *Service) Create(content string) (*Book, error) {
// ...
bk := new(Book)
bk.Content = content
bk.Keywords = generateKeywords(content)
// ...
}
func generateKeywords(content string) []string { // ... }
上面的优化可以带来一些好处:
- 维护人员不再被迫使阅读 generateKeywords 函数的实现细节,因为其函数定义已经很好的说明其功能,依赖以及输出。
- generateKeywords 只做了一件事,我们可以放心进行复用,而不用担心使用前是否需要提前设置好 Title,函数内部是否会改变 Title,Content 的值等。
- 纯净方法容易为其编写单元测试,只需要设计输入和校验输出,因为其没有隐藏逻辑(副作用)。
参考:The Single Biggest Mistake Programmers Make Every Day
文章中提到的 KISS (Keep It Simple, Stupid) 原则很值得参考。