在阅读《100个go语言经典错误》的时候,看到错误:使用文件名作为函数输入。由此思考,这个虽然是入参的设计,但是实际上涉及到了函数的抽象问题。
从函数输入选择与函数抽象的最佳实践 到 思考关注点分离原则 。
通过分析46-function-input中的代码,我们可以总结出关于函数输入选择的重要原则以及函数抽象的深入思考。
代码展示了两个功能相似但设计差异明显的函数:
func countEmptyLinesInFile(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {
return 0, err
}
// 处理文件内容...
return 0, nil
}
问题分析:
countEmptyLinesInFile
指明了输入来源(文件)func countEmptyLines(reader io.Reader) (int, error) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
// ...
}
return 0, nil
}
改进之处:
io.Reader
接口而非具体类型io.Reader
的输入源func TestCountEmptyLines(t *testing.T) {
emptyLines, err := countEmptyLines(strings.NewReader(
`foo
bar
baz
`))
// 测试逻辑...
}
使用改进后的设计,测试变得更加简单:
strings.NewReader
创建测试输入函数抽象应该遵循以下原则:
依赖抽象,不依赖具体实现:
io.Reader
、io.Writer
、io.Closer
等关注点分离:
适当的抽象粒度:
io.Reader
是适当的抽象级别,既通用又易于理解抽象的成本与收益:
示例体现了函数组合的思想:
func main() {
file, err := os.Open("main.go")
if err != nil {
panic(err)
}
_, _ = countEmptyLines(file)
}
这体现了Unix哲学中的管道概念:
*os.File
(实现了io.Reader
)countEmptyLines
函数面向接口编程:
io.Reader
),不需要知道内容来自哪里抽象泄漏的防范:
countEmptyLinesInFile
内部异常处理针对文件系统特定错误,这就是抽象泄漏单一职责原则:
countEmptyLines
专注于计数逻辑,不关心I/O来源函数命名原则:
输入参数选择:
分层设计:
测试友好设计:
适应变化:
通过遵循这些原则,我们可以设计出更加健壮、灵活且易于维护的代码,真正体现Go语言简洁而强大的设计理念。
关注点分离原则是软件工程中的一个基本设计原则,它主张将计算机程序分解成不同的部分,每个部分负责处理一个特定的"关注点"或功能方面。
关注点分离原则的核心思想是:一段代码应该只关注于解决一个特定的问题或实现一个特定的功能。
关注点的定义:
分离的好处:
通过之前的代码示例可以很好地理解关注点分离:
func countEmptyLinesInFile(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {
return 0, err
}
// 文件处理和计数逻辑混合在一起
scanner := bufio.NewScanner(file)
// ...计数逻辑...
return 0, nil
}
这个函数混合了两个关注点:
// 文件操作关注点
func openFile(filename string) (*os.File, error) {
return os.Open(filename)
}
// 业务逻辑关注点
func countEmptyLines(reader io.Reader) (int, error) {
scanner := bufio.NewScanner(reader)
// ...计数逻辑...
return 0, nil
}
// 组合使用
func main() {
file, err := openFile("example.txt")
if err != nil {
// 错误处理
}
defer file.Close()
count, err := countEmptyLines(file)
// ...
}
通过分离,每个函数现在只负责一个关注点:
openFile
只关注文件打开操作countEmptyLines
只关注计数逻辑测试文件main_test.go
展示了关注点分离带来的测试优势:
func TestCountEmptyLines(t *testing.T) {
emptyLines, err := countEmptyLines(strings.NewReader(
`foo
bar
baz
`))
// 测试逻辑
}
由于关注点分离:
MVC架构:
分层架构:
Go语言中的体现:
io.Reader
/io.Writer
等接口识别关注点:
抽象恰当边界:
保持适度:
权衡取舍:
关注点分离是软件设计中的重要原则,它帮助我们创建更加模块化、可维护和可测试的代码。在Go编程中,这一原则与语言的简洁哲学和组合设计理念高度契合。