61 Write focused, single-purpose functions 编写有针对性的、单一用途的函数
创建执行一项特定任务的函数。
编写专注、单一用途的函数可让您的代码更易于阅读、测试和维护。每个函数都应该只做一件事,并做好它。
Good
// Single-purpose function to add two numbers
func Add(a, b int) int {
return a + b
}
// Single-purpose function to check if a number is even
func IsEven(n int) bool {
return n % 2 == 0
}
Bad
// Multi-purpose function doing multiple tasks
func ProcessNumbers(a, b int) (int, bool) {
sum := a + b
isEven := sum % 2 == 0
return sum, isEven
}
在好的例子中,每个函数都只有一个职责,这让每个函数的作用变得清晰。在坏的例子中,函数 Process Numbers 做了两件事:将两个数字相加,然后检查结果是否为偶数。这使得该函数更难理解和维护。
单一职责原则(SRP)是面向对象设计的 SOLID 原则之一,该原则规定一个类或函数应该只有一个改变的原因。
62 Avoid large, monolithic functions 避免使用庞大而单一的功能
将大型功能分解为较小的、易于管理的部分。
大而单一的函数难以阅读、理解和维护。将它们分解成较小的函数,可以提高代码的可读性和可重用性。
Good
// Breaking down a large function into smaller ones
func ProcessData(data []int) {
cleanedData := CleanData(data)
analyzedData := AnalyzeData(cleanedData)
SaveData(analyzedData)
}
func CleanData(data []int) []int {
// Cleaning logic here
return data
}
func AnalyzeData(data []int) []int {
// Analysis logic here
return data
}
func SaveData(data []int) {
// Saving logic here
}
Bad
// Large, monolithic function
func ProcessData(data []int) {
// Cleaning logic here
for i := range data {
if data[i] < 0 {
data[i] = 0
}
}
// Analysis logic here
sum := 0
for _, v := range data {
sum += v
}
// Saving logic here
fmt.Println("Data saved:", data)
}
在好的例子中,处理数据功能被分解为三个较小的功能:清理数据、分析数据和保存数据。这使得每个功能更易于理解和维护。在坏的例子中,所有逻辑都塞进一个函数中,这使得它更难理解和修改。
重构是在不改变代码外部行为的情况下重构现有代码的过程。它通常用于提高代码的可读性并降低复杂性。
63 Break up complex logic into smaller, reusable functions 将复杂逻辑分解为更小的可重用函数
将大型函数拆分为执行特定任务的较小的模块化函数,提高代码的可读性和可维护性。
具有复杂逻辑的大型函数可能难以理解和维护。通过将它们分解为更小、可重复使用的函数,每个函数都有特定的职责,代码变得更易读且更易于修改。
Good
// calculateTotal calculates the total cost of an order
func calculateTotal(items []Item, discount float64) float64 {
total := calculateSubTotal(items)
discountedTotal := applyDiscount(total, discount)
return discountedTotal
}
// calculateSubTotal calculates the subtotal of an order
func calculateSubTotal(items []Item) float64 {
var subTotal float64
for _, item := range items {
subTotal += item.Price * float64(item.Quantity)
}
return subTotal
}
// applyDiscount applies a discount to a given amount
func applyDiscount(amount, discount float64) float64 {
return amount * (1 - discount)
}
Bad
// calculateTotal calculates the total cost of an order
func calculateTotal(items []Item, discount float64) float64 {
var subTotal float64
for _, item := range items {
subTotal += item.Price * float64(item.Quantity)
}
if discount > 0 {
subTotal = subTotal * (1 - discount)
}
return subTotal
}
在好的代码示例中,计算总计函数被分解为三个较小的函数:计算小计、应用折扣和计算总计本身。每个函数都有特定的职责,使代码更易于阅读、理解和维护。另一方面,糟糕的代码示例将所有逻辑合并到一个函数中,使其更难理解和修改。
单一职责原则(SRP)是面向对象设计的 SOLID 原则之一,该原则规定一个类或函数应该只有一个改变的原因。
64 Limit the number of function parameters
将函数参数的数量保持在最低限度,最好不超过三个,以提高代码的可读性和可维护性。
参数过多的函数可能难以理解和使用,因为很难跟踪每个参数的顺序和用途。通过限制参数数量,函数变得更易读且更易于维护。
Good
// calculateArea calculates the area of a rectangle
func calculateArea(length, width float64) float64 {
return length * width
}
// createUser creates a new user with the provides name and email
func createUser(name, email string) (*User, error) {
// Validate input
if name == "" || email == "" {
return nil, errors.New("name and email are required")
}
// Create user
user := &User{
Name: name,
Email: email,
}
return user, nil
}
Bad
func calculateSomething(a, b, c, d, e, f, g, h, i, j float64) float64 {
// Complex calculations involving all parameters
return result
}
在好的代码示例中,计算面积函数只有两个参数(长度和宽度),创建用户函数有两个参数(名称和电子邮件)。这些函数易于理解和使用,因为参数数量有限。另一方面,坏的代码示例中有一个包含十个参数的函数,很难记住每个参数的顺序和用途。
函数参数的理想数量经常引起争议,但一个常见的指导原则是将其保持在 3 或 4 个参数以下。参数太多可能表示函数执行的操作过多,应进行重构。
65 Use meaningful and descriptive function names 使用有意义且描述性的函数名称
使用有意义且描述性的函数名称可以提高代码的可读性和可维护性。
为函数选择清晰且描述性的名称有助于其他开发人员理解代码的目的和功能,而无需大量的注释或文档。
Good
// CalculateTotalPrice calculates the total price of items in a shopping cart.
func CalculateTotalPrice(items []Item) float64 {
var total float64
for _, item := range items {
total += item.Price
}
return total
}
Bad
func Calc(i []Item) float64 {
var t float64
for _, x := range i {
t += x.Price
}
}
在好的例子中,函数名 CalculateTotalPrice 清楚地表明了该函数的作用,使代码更易于理解。在坏的例子中,函数名 Calc 含糊不清,没有提供任何有关其用途的上下文,使代码更难阅读和维护。
描述性函数名称是自文档代码这一更广泛概念的一部分,旨在使代码无需大量注释即可被理解。
66 Utilize Go's built-in testing framework for writing unit tests 利用 Go 的内置测试框架编写单元测试
Go 提供了一个内置的测试框架,可以轻松编写和运行单元测试。
使用 Go 的测试框架有助于确保代码的可靠性和正确性,因为开发人员可以编写可自动执行的测试来验证其代码的行为。
Good
// TestCalculateTotalPrice tests the CalculateTotalPrice function.
func TestCalculateTotalPrice(t *testing.T) {
items := []Item{
{Price: 10.0},
{Price: 20.0},
}
expected := 30.0
result := CalculateTotalPrice(items)
if result != expected {
t.Errorf("expected %f, got %f", expected, result)
}
}
Bad
func TestCalc(t *testing.T) {
i := []Item{
{Price: 10.0},
{Price: 20.0},
}
e := 30.0
r := Calc(i)
if r != e {
t.Errorf("expected %f, got %f", e, r)
}
}
在好的例子中,测试函数 TestCalculateTotalPrice 的命名很明确,表明它测试的是 CalculateTotalPrice 函数。测试很简单,检查函数是否返回预期结果。在坏的例子中,测试函数 TestCalc 不具描述性,因此不清楚测试的是什么。变量名称也没有意义,降低了可读性。
Go 的测试框架是标准库的一部分,可以通过导入测试包来使用。可以使用 go test 命令运行测试。
67 Leverage Go's interface system for code reusability and abstraction 利用 Go 的接口系统实现代码重用和抽象
在 Go 中使用接口可以通过抽象实现细节来实现灵活且可重用的代码。
Go 中的接口使您可以定义任何类型必须实现的方法,从而提高代码的可重用性和抽象性。这使您的代码更加模块化且更易于维护。
Good
// Define an interface
type Speaker interface {
Speak() string
}
// Implement the interface with a struct
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
// Implement the interface with another struct
type Cat struct{}
func (c Cat) Speak() string {
return "Meow!"
}
func main() {
var animals []Speaker
animals = append(animals, Dog{}, Cat{})
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
Bad
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct{}
func (c Cat) Speak() string {
return "Meow!"
}
func main() {
dogs := []Dog{Dog{}}
cats := []Cat{Cat{}}
for _, dog := range dogs {
fmt.Println(dog.Speak())
}
for _, cat := range cats {
fmt.Println(cat.Speak())
}
}
在好的例子中,Speaker 接口用于抽象 Speak 方法,允许统一处理 Dog 和 Cat 类型。这使得代码更加灵活,并且更容易使用新类型进行扩展。在坏的例子中,对 Dog 和 Cat 使用了单独的切片,导致代码重复,灵活性降低。
Go 接口是隐式满足的,这意味着类型不需要显式声明它实现了接口。此功能促进了松散耦合并增强了代码灵活性。
68 Utilize Go's powerful reflection capabilities judiciously 明智地利用 Go 强大的反射功能
Go 中的反射允许动态类型检查和操作,但由于其复杂性和潜在的性能成本,应谨慎使用。
Go 中的反射提供了在运行时检查类型并动态操作对象的能力。虽然功能强大,但它会使代码更难理解和维护,因此应谨慎使用。
Good
func PrintFields(v interface{}) {
var := reflect.ValueOf(v)
typ := reflect.TypeOf(v)
for i := 0; i < val.NumField(); i++ {
fmt.Printf("Field: %s, Value: %v\n", typ.Field(i).Name, val.Field(i).Interface())
}
}
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
PrintFields(p)
}
Bad
func PrintFields(v interface{}) {
val := reflect.ValueOf(v)
for i := 0; i < val.NumField(); i++ {
fmt.Printf("Field: %v\n", val.Field(i).Interface())
}
}
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
PrintFields(p)
}
在好的例子中,反射用于打印字段名称及其值,使输出更具信息量。坏的例子只打印字段值,如果没有字段名称的上下文,这可能会令人困惑。应谨慎使用反射,以避免使代码过于复杂和难以维护。
Go 中的反射是 reflect 包的一部分,允许在运行时检查对象的类型、访问其字段和方法以及修改其值等操作。但是,由于它会影响性能和代码可读性,因此应谨慎使用。
69 Task advantage of Go's simple and efficient memory management Go 简单高效的内存管理任务优势
Go 的垃圾收集器和内存管理功能可帮助开发人员编写高效、干净的代码。
Go 的内存管理(包括垃圾收集)使开发人员可以专注于编写代码,而无需担心手动内存分配和释放。
Good
func main() {
// Using slices which are managed by Go's garbage collector
numbers := []int{1, 2, 3, 4 ,5}
fmt.Println(numbers)
}
Bad
func main() {
numbers := new([5]int)
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
numbers[3] = 4
numbers[4] = 5
fmt.Println(*numbers)
}
在好的例子中,使用了切片,它们由 Go 的垃圾收集器自动管理,从而使代码更干净、更高效。在坏的例子中,尝试手动管理内存,这不符合 Go 的习惯,并且会导致代码更复杂且容易出错。
Go 的垃圾收集器设计为高效和低延迟,使其适用于高性能应用程序。
70 Employ Go's built-in profiling tools for performance optimization 使用 Go 的内置分析工具进行性能优化
Go 提供了诸如 pprof 之类的内置工具来分析和优化应用程序的性能。
使用 Go 的 pprof 包,开发人员可以分析他们的应用程序以识别性能瓶颈并优化他们的代码。
Good
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
f, err := os.Create("cpu.pprof")
if err != nil {
log.Fatal("count not create CPU profile: ", err)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("count not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
go func() {
log.Println(http.ListenAndServe("localhost:7070", nil))
}()
// Your application code here
}
Bad
func main() {
// No profiling setup
fmt.Println("Running application without profiling")
// Your application code here
}
在好的例子中,pprof 包用于设置分析服务器,允许开发人员分析其应用程序的性能。在坏的例子中,没有设置分析,这使得识别和解决性能问题变得困难。
pprof 工具可以生成各种报告,包括 CPU 和内存配置文件,可以使用 go tool pprof 等工具进行可视化。
71 Break up long expressions into smaller, more readable parts 将长表达式分解为更小、更易读的部分
将复杂的表达式拆分为多行或变量以提高可读性。
复杂的表达式会使代码更难阅读和理解。将它们分解成较小的部分可以提高代码的清晰度。
Good
// Calculate the area of a rectangle
length := 10
width := 5
area := length * width
fmt.Printf("Area: %d", area) // Output: Area: 50
Bad
// Calculate the area of a rectangle
area := 10 * 5
fmt.Printf("Area: %d", area) // Output: Area: 50
在好的代码示例中,长度和宽度变量使计算更加明确且更易于理解。坏的代码示例只有一个表达式,乍一看很难理解。拆分复杂的表达式可以提高可读性,尤其是对于较长或嵌套的表达式。
这种技术在软件工程中被称为 "提取变量" "Extract Variable" 重构。
72 Use intermediate variables to store subexpressions 使用中间变量存储子表达式
将子表达式的结果存储在中间变量中,以提高可读性和可重用性。
复杂表达式可以分解为较小的子表达式,这些子表达式可以存储在中间变量中。这提高了代码的可读性,并允许在需要时重复使用子表达式。
Good
// Calculate the volume of a sphere
radius := 5.0
pi := 3.14159
surfaceArea := 4 * pi * radius * radius
volume := (4.0 / 3.0) * pi * radius * radius * radius
fmt.Printf("Surface Area: %.2f, Volume: %.2f", sufaceArea, volume)
// Output: Surface Area: 314.16, Volume: 523.60
Bad
// Calculate the volume of a sphere
radius := 5.0
fmt.Printf("Surface Area: %.2f, Volume: %.2f", 4 * 3.14159 * radius * radius, (4.0 / 3.0) * 3.14159 * radius * radius * radius)
// Output: Surface Area: 314.16, Volume: 523.60
在好的代码示例中,表面积和体积的子表达式存储在单独的变量中,使代码更易读,并允许在需要时重复使用。而在糟糕的代码示例中,子表达式是内联的,使得代码更难阅读和理解。
该技术与“提取方法”重构相关,其中将复杂的代码提取到单独的方法或函数中,以便更好地组织和重用。
73 Employ parentheses to clarify operator precedence 使用括号来明确运算符优先级
使用括号可以使代码中的运算顺序更清晰。
在编写包含多个运算符的表达式时,括号可以帮助阐明预期的运算顺序,使代码更易于阅读和理解。
Good
// Using parantheses to clarify precedence
result := (a + b) * (c-d) // Clearly shows the intended order of operations
Bad
No parentheses, unclear precedence
result := a + b * c - d // Unclear which operations are intended to be preformed first
在好的例子中,使用括号明确说明应在乘法之前执行加法和减法。这使代码更具可读性并降低了出错的风险。在坏的例子中,缺少括号会让人不清楚应该先执行哪些运算,从而可能导致误解和错误。
在 Go 中,运算顺序遵循标准数学规则:先乘除,后加减。但是,使用括号可以帮助避免错误并使代码更易于维护。
74 Use line breaks and indentation to improve readability 使用换行符和缩进来提高可读性
正确使用换行和缩进来构造代码,以提高可读性。
一致使用换行和缩进有助于在视觉上分离代码的不同部分,从而更容易遵循逻辑和结构。
Good
// Proper use of line breaks and indentation
if condition {
doSomething()
if anotherCondition {
doSomethingElse()
}
} else {
handleElseCase()
}
Bad
// Poor use of line breaks and indentation
if condition {
doSomething() if anotherCondition {
doSomethingElse() }} else { handleElseCase()
}
}
在好的例子中,每个代码块都正确缩进并用换行符分隔,从而可以轻松查看程序的结构和流程。在坏的例子中,代码全部在一行上,缩进不一致,难以阅读和理解。
Go 使用制表符来缩进,而不是空格。这是由 gofmt 工具强制执行的,该工具会根据语言的样式指南自动格式化 Go 代码。
75 Consider refactoring complex expressions into separate functions 考虑将复杂表达式重构为单独的函数
通过将复杂表达式提取为单独的、命名良好的函数来提高代码的可读性。
复杂的表达式会使代码更难阅读和理解。通过将它们提取到具有描述性名称的单独函数中,您可以提高代码的清晰度和可维护性。
Good
func calculateArea(length, width float64) float64 {
return length * width
}
func calculateVolume(length, width, height float64) float64 {
area := calculateArea(length, width)
return area * height
}
Bad
func calculateVolume(length, width, height float64) float64 {
return (length * width) * height
}
在好的代码示例中,复杂的表达式长度宽度被提取到单独的函数计算面积中。这使得计算体积函数更易于阅读和理解,因为它清楚地区分了计算面积和体积的关注点。在坏的代码示例中,复杂的表达式(长度宽度)高度嵌入在计算体积函数中,使得乍一看很难理解其中的逻辑。
将复杂表达式重构为单独的函数是“提取方法”重构技术的一种形式,这是软件开发中的常见做法。
76 Identify and extract independent concerns into separate functions 识别并提取独立关注点到单独的函数中
通过识别独立的关注点并将其提取到单独的函数中来改善代码组织和可重用性。
代码通常包含多个独立的关注点或职责。通过识别这些关注点并将其提取到单独的函数中,您可以改善代码组织、可重用性和可维护性。
Good
func calculateArea(length, width float64) float64 {
return length * width
}
func calculateVolume(length, width, height float64) float64 {
area := calculateArea(length, width)
return area * height
}
func printResult(volume float64) {
fmt.Printf("The volume is: %.2f", volume)
}
Bad
func calculateVolume(length, width, height float64) {
area := length * width
volume := area * height
fmt.Printf("The volume is: %.2f", volume)
}
在好的代码示例中,计算面积、计算体积和打印结果的关注点被分为三个不同的函数:计算面积、计算体积和打印结果。这种关注点分离改善了代码组织、可重用性和可测试性。在坏的代码示例中,所有三个关注点被合并为一个函数计算体积,使得代码更难阅读、维护和重用。
关注点分离原则是软件设计和架构中的一个基本概念,称为“单一责任原则”(SRP),它规定一个类或函数应该只有一个改变的原因。
77 Encapsulate related functionality into reusable modules or packages 将相关功能封装到可重用的模块或包中
将相关的函数和类型分组到包中,以促进代码的重用和可维护性。
将相关功能封装到可重用的模块或包中有助于逻辑地组织代码,使其更易于理解、维护和重用。这种做法还可以促进关注点分离并减少代码重复。
Good
// Package mathutil provides utility functions for mathematical operations.
package mathutil
// Add adds two integers and returns the result.
func Add(a, b int) int {
return a + b
}
// Subtract subtracts the second integer from the first and returns the result.
func Subtract(a, b int) int {
return a - b
}
// main.go
package main
import (
"fmt"
"path/to/your/projecdt/mathutil"
)
func main() {
sum := mathutil.Add(3, 4)
diff := mathutil.Subtract(10, 5)
fmt.Println("Sum: ", sum)
fmt.Println("Difference: ", diff)
}
Bad
package main
import (
"fmt"
)
func add(a, b int) int {
return a + b
}
func subtract(a, b int) int {
return a - b
}
func main() {
sum := add(3, 4)
diff := subtract(10, 5)
fmt.Println("Sum: ", sum)
fmt.Println("Difference: ", diff)
}
在好的例子中,相关函数 Add 和 Subtract 被封装在 mathutil 包中,从而提高了可重用性和更好的组织性。在坏的例子中,函数被定义在主包中,使得代码缺乏模块化,更难重用。
Go 的软件包系统旨在鼓励模块化和代码重用。按照惯例,软件包名称应该简短且有意义,目录结构应该反映软件包层次结构。
78 Separate pure logic from side effects or I/O operations 将纯逻辑与副作用或 I/O 操作分开
将纯函数与执行 I/O 操作的函数隔离,以增强可测试性和可维护性。
将纯逻辑与副作用或 I/O 操作分开有助于编写更易于测试和维护的代码。纯函数具有确定性且更易于测试,而副作用可以单独隔离和管理。
Good
// Package mathutil provides utility functions for mathematical operations.
package mathutil
// Multiply multiplies two integers and returns the result.
func Multiply(a, b int) int {
return a * b
}
// main.go
package main
import (
"fmt"
"path/to/your/project/mathutil"
)
func main() {
result := mathutil.Multiply(3, 4)
printResult(result)
}
// printResult prints the result to the console.
func printResult(result int) {
fmt.Println("Result: ", result)
}
Bad
package main
import "fmt"
func multiplyAndPrint(a, b int) {
result := a * b
fmt.Println("Result: ", result)
}
func main() {
multiplyAndPrint(3, 4)
}
在好的例子中,乘法的纯逻辑被分离到 mathutil 包中的 Multiply 函数中,而 I/O 操作则由 print Result 函数处理。这种分离使逻辑更易于测试。在坏的例子中,逻辑和 I/O 操作混在一起,使函数更难测试和维护。
Go 中的纯函数是指那些对于相同的输入始终产生相同的输出且没有副作用的函数。将此类函数与 I/O 操作隔离是提高代码质量和可测试性的常见做法。
79 Avoid mixing unrelated concerns in the same function or module 避免在同一个函数或模块中混合不相关的关注点
保持功能和模块专注于单一职责,以增强可读性和可维护性。
编写 Go 代码时,确保每个函数或模块都只解决一个问题至关重要。这种做法有助于使代码更易于理解和维护。
Good
// Separate concerns into different functions
package main
import (
"fmt"
"net/http"
)
// Function to handle HTTP requests
func handleRequest(w http.ResponseWriter, r *http.Request) {
data := fetchData()
renderResponse(w, data)
}
// Function to fetch data
func fetchData() string {
// Simulate data fetching
return "Hello, World!"
}
// Function to render response
func renderResponse(w http.ResponseWriter, data string) {
fmt.Fprintf(w, data)
}
func main() {
http.HandleFunc("/", handleRequest)
http.ListenAndServe(":8080", nil)
}
Bad
// Mixing concerns in a single function
package main
import (
"fmt"
"net/http"
)
func handleRequest(w http.ResponseWriter, r *http.Request) {
// Fetch data
data := "Hello, World!"
// Render response
fmt.Fprintf(w, data)
}
func main() {
http.HandleFunc("/", handleRequest)
http.ListenAndServe(":8080", nil)
}
在好的例子中,获取数据和呈现响应的关注点被分成不同的函数。这使得代码更加模块化,更易于测试。在坏的例子中,这两个关注点混合在一个函数中,使其更难理解和维护。
单一职责原则(SRP)是面向对象设计的 SOLID 原则之一,该原则规定一个类或函数应该只有一个改变的原因。
80 Strive for cohesive and loosely coupled code 努力实现内聚且松散耦合的代码
确保您的代码模块高度内聚且松散耦合,以提高灵活性和可重用性。
内聚性是指单个模块的职责关联性,耦合性是指模块间的依赖性。高内聚性和低耦合性是软件设计的理想特征。
Good
// High cohesion and low coupling
package main
import (
"fmt"
"net/http"
)
// DataFetcher interface for fetching data
type DataFetcher interface {
Fetch() string
}
// SimpleDataFetcher implements DataFetcher
type SimpleDataFetcher struct{}
func (sdf SimpleDataFetcher) Fetch() string {
return "Hello, World!"
}
// Function to handle HTTP requests
func handleRequest(w http.ResponseWriter, r *http.Requset, fetcher DataFetcher) {
data := fetcher.Fetch()
renderResponse(w, data)
}
// Function to render response
func renderResponse(w http.ResponseWriter, data string) {
fmt.Fprintf(w, data)
}
func main() {
fetcher := SimpleDataFetcher{}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handleRequest(w, r, fetcher)
})
http.ListenAndServe(":8080", nil)
}
Bad
// Low cohesion and high coupling
package main
import (
"fmt"
"net/http"
)
func handleRequest(w http.ResponseWriter, r *http.Request) {
// Fetch data
data := fetchData()
// Render response
fmt.Fprintf(w, data)
}
func fetchData() string {
return "Hello, World!"
}
func main() {
http.HandleFunc("/", handleRequest)
http.ListenAndServe(":8080", nil)
}
在好的例子中,handleRequest 函数依赖于接口 DataFetcher,从而促进了松耦合。SimpleDataFetcher 实现了这个接口,确保了模块内的高内聚性。在坏的例子中,handleRequest 函数直接调用 fetchData,导致紧耦合和较低的内聚性(只能用于一种数据获取,如果更换一种类型可能就不支持了,但是依赖于接口 DataFetcher,那么只要实现了该接口的数据类型都可以复用这个函数)。
松耦合和高内聚是软件工程中的关键原则,有助于系统的可维护性和可扩展性。它们通常通过使用接口和依赖注入来实现。