第 41 章 - Go语言 软件工程原则

news/2024/12/4 18:56:28/

软件工程中,有一些广泛接受的原则和最佳实践,它们帮助开发者构建更易于维护、扩展和理解的代码。本章将介绍几个重要的原则:SOLID、DRY(Don’t Repeat Yourself)、KISS(Keep It Simple, Stupid)等,并通过Go语言的例子来展示如何应用这些原则。

SOLID 原则

SOLID 是五个面向对象设计原则的首字母缩写,它包括:

  • Single Responsibility Principle (单一职责原则)
  • Open/Closed Principle (开闭原则)
  • Liskov Substitution Principle (里氏替换原则)
  • Interface Segregation Principle (接口隔离原则)
  • Dependency Inversion Principle (依赖倒置原则)
单一职责原则

一个类应该只有一个引起它变化的原因。也就是说,一个类或模块应该负责一项功能,而不是多项功能。

示例代码

// 不好的例子
type User struct {ID   intName string
}func (u *User) Save() error { /* ... */ }
func (u *User) Validate() bool { /* ... */ }// 更好的例子
type UserRepository interface {Save(user *User) error
}type UserService struct {repo UserRepository
}func (s *UserService) Validate(user *User) bool { /* ... */ }
开闭原则

软件实体(类、模块、函数等)应该是开放扩展的,但对修改是封闭的。这意味着可以通过添加新代码来扩展行为,而不需要修改现有的代码。

示例代码

type DiscountCalculator interface {Calculate(price float64) float64
}type BasicDiscount struct{}func (b *BasicDiscount) Calculate(price float64) float64 {return price * 0.9 // 10% discount
}// 扩展新的折扣类型
type SpecialDiscount struct{}func (s *SpecialDiscount) Calculate(price float64) float64 {return price * 0.85 // 15% discount
}
里氏替换原则

子类型必须能够替代其基类型。即任何基类可以出现的地方,子类一定可以出现。

示例代码

type Bird interface {Fly()
}type Duck struct{}func (d *Duck) Fly() {fmt.Println("Duck is flying")
}// 遵循里氏替换原则
type FlyingBird struct {Bird
}func (f *FlyingBird) Fly() {f.Bird.Fly()
}
接口隔离原则

不应该强迫客户端依赖于它们不使用的方法。应当创建小的、具体的接口,而不是大的、通用的接口。

示例代码

// 不好的例子
type Printer interface {Print()Scan()
}// 更好的例子
type PrinterOnly interface {Print()
}type ScannerOnly interface {Scan()
}
依赖倒置原则

高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

示例代码

type Reader interface {Read(p []byte) (n int, err error)
}type FileReader struct{}func (r *FileReader) Read(p []byte) (int, error) {// 实现读取文件逻辑return 0, nil
}type Service struct {reader Reader
}func (s *Service) ProcessData() {data := make([]byte, 1024)s.reader.Read(data)// 处理数据
}

DRY 原则

不要重复自己。尽量减少代码中的重复,通过抽象公共部分来提高代码的可重用性。

示例代码

// 不好的例子
func calculateTotal(items []Item) float64 {total := 0.0for _, item := range items {if item.Type == "book" {total += item.Price * 0.9 // 书籍打9折} else {total += item.Price}}return total
}// 更好的例子
func applyDiscount(price float64, itemType string) float64 {if itemType == "book" {return price * 0.9}return price
}func calculateTotal(items []Item) float64 {total := 0.0for _, item := range items {total += applyDiscount(item.Price, item.Type)}return total
}

KISS 原则

保持简单直接。避免不必要的复杂性,以最简单的方式实现功能。

示例代码

// 不好的例子
func complexCalculation(a, b, c, d, e, f int) int {result := a + bif c > 0 {result *= c}if d > 0 && e > 0 {result -= (d + e)}if f > 0 {result /= f}return result
}// 更简单的例子
func simpleCalculation(a, b, c, d, e, f int) int {sum := a + bproduct := sum * max(c, 1) // 避免除以0subtrahend := (d + e) * boolToInt(d>0 && e>0)quotient := product - subtrahendreturn quotient / max(f, 1)
}func max(x, y int) int {if x > y {return x}return y
}func boolToInt(b bool) int {if b {return 1}return 0
}

以上就是关于SOLID原则、DRY原则以及KISS原则的一些基本概念及其在Go语言中的应用案例。遵循这些原则可以帮助我们编写出更加健壮、灵活和易于维护的代码。

更多 SOLID 原则的应用

单一职责原则(SRP) - 进一步的例子

考虑一个更复杂的场景,比如一个订单处理系统。我们希望保持每个类或结构体只负责一项功能。

不好的例子

type Order struct {ID     intItems  []ItemStatus string
}func (o *Order) AddItem(item Item) {o.Items = append(o.Items, item)
}func (o *Order) CalculateTotal() float64 {total := 0.0for _, item := range o.Items {total += item.Price}return total
}func (o *Order) ProcessPayment(payment Payment) bool {// 处理支付逻辑return true
}

在这个例子中,Order 结构体不仅管理订单项,还计算总价和处理支付。这违反了单一职责原则。

更好的例子

type Order struct {ID     intItems  []ItemStatus string
}type OrderService struct {repo OrderRepository
}func (s *OrderService) AddItem(order *Order, item Item) {order.Items = append(order.Items, item)s.repo.Save(order)
}func (s *OrderService) CalculateTotal(order *Order) float64 {var total float64for _, item := range order.Items {total += item.Price}return total
}// 假设有一个支付服务
type PaymentService struct{}func (p *PaymentService) ProcessPayment(payment Payment) bool {// 处理支付逻辑return true
}

这里,Order 只是一个数据容器,而 OrderServicePaymentService 分别处理业务逻辑和支付逻辑。

开闭原则(OCP)- 进一步的例子

假设我们需要为不同的客户类型提供不同的折扣策略。

不好的例子

type Customer struct {Type string
}func CalculateDiscount(customer *Customer, price float64) float64 {if customer.Type == "Regular" {return price * 0.95} else if customer.Type == "VIP" {return price * 0.9}return price
}

每当我们添加新的客户类型时,都需要修改这个函数,这违反了开闭原则。

更好的例子

type DiscountStrategy interface {Calculate(price float64) float64
}type RegularDiscount struct{}func (r *RegularDiscount) Calculate(price float64) float64 {return price * 0.95
}type VIPDiscount struct{}func (v *VIPDiscount) Calculate(price float64) float64 {return price * 0.9
}type Customer struct {Type             stringDiscountStrategy DiscountStrategy
}func NewCustomer(typ string) *Customer {switch typ {case "Regular":return &Customer{Type: typ, DiscountStrategy: &RegularDiscount{}}case "VIP":return &Customer{Type: typ, DiscountStrategy: &VIPDiscount{}}default:return &Customer{Type: typ, DiscountStrategy: nil}}
}func (c *Customer) GetDiscountedPrice(price float64) float64 {if c.DiscountStrategy != nil {return c.DiscountStrategy.Calculate(price)}return price
}

这种方式允许我们轻松地添加新的折扣策略,而无需修改现有的代码。

DRY 原则 - 更多实践

DRY原则强调减少重复代码。例如,在多个地方使用相同的配置加载逻辑。

不好的例子

func loadConfig1() Config {// 配置加载逻辑
}func loadConfig2() Config {// 相同的配置加载逻辑
}

更好的例子

func loadConfig() Config {// 统一的配置加载逻辑
}func loadConfig1() Config {return loadConfig()
}func loadConfig2() Config {return loadConfig()
}

KISS 原则 - 更多实践

KISS原则鼓励保持简单。例如,避免不必要的复杂性。

不好的例子

func complexLogic(a, b, c, d, e, f, g, h int) int {// 复杂的条件判断和计算// ...
}func simpleLogic(a, b int) int {return a + b
}

更好的例子

func simpleLogic(a, b int) int {return a + b
}

通过简化逻辑,代码变得更容易理解和维护。

好的,我们可以继续探讨更多关于软件工程原则的应用,并提供进一步的Go语言示例。接下来,我们将更深入地讨论一些额外的原则和最佳实践,包括:

  • YAGNI(You Aren’t Gonna Need It)原则:不要添加你认为将来可能会需要的功能。
  • Law of Demeter(迪米特法则/最少知识原则):一个对象应当对其他对象有尽可能少的了解。
  • 代码重构:改善现有代码的设计而不改变其行为。

YAGNI 原则

YAGNI原则强调只实现当前确实需要的功能,避免过早优化或添加不必要的功能,因为这些功能可能永远不会被使用。

不好的例子

type User struct {ID   intName string// 添加了未来可能用到但目前不需要的字段Email        stringPhoneNumber  stringRegistration Date
}func (u *User) Save() error {// 保存用户信息,包括未来可能用到的字段
}

更好的例子

type User struct {ID   intName string
}func (u *User) Save() error {// 只保存必需的信息
}

Law of Demeter(迪米特法则)

该原则建议一个对象不应该直接访问另一个对象的属性或方法,而是应该通过自己的属性来间接访问,以减少对象之间的耦合度。

不好的例子

type Order struct {Customer *Customer
}type Customer struct {Address *Address
}type Address struct {City string
}func printCity(order *Order) {fmt.Println(order.Customer.Address.City)
}

更好的例子

type Order struct {Customer *Customer
}type Customer struct {Address *Address
}type Address struct {City string
}func (c *Customer) GetCity() string {return c.Address.City
}func printCity(order *Order) {fmt.Println(order.Customer.GetCity())
}

在这个例子中,printCity 函数不再直接访问 order.Customer.Address.City,而是调用 CustomerGetCity 方法,这样就减少了 OrderAddress 的依赖。

代码重构

代码重构是一种在不改变外部行为的情况下改进代码结构的过程。它可以帮助提高代码质量、可读性和可维护性。

原始代码

func processItems(items []Item) float64 {total := 0.0for _, item := range items {if item.Type == "book" {total += item.Price * 0.9} else if item.Type == "food" {total += item.Price * 0.85} else {total += item.Price}}return total
}

重构后的代码

// 定义折扣策略接口
type DiscountStrategy interface {Apply(price float64) float64
}// 实现具体的折扣策略
type BookDiscount struct{}func (b *BookDiscount) Apply(price float64) float64 {return price * 0.9
}type FoodDiscount struct{}func (f *FoodDiscount) Apply(price float64) float64 {return price * 0.85
}type NoDiscount struct{}func (n *NoDiscount) Apply(price float64) float64 {return price
}// 工厂函数根据类型返回相应的折扣策略
func getDiscountStrategy(itemType string) DiscountStrategy {switch itemType {case "book":return &BookDiscount{}case "food":return &FoodDiscount{}default:return &NoDiscount{}}
}// 使用折扣策略计算总价
func processItems(items []Item) float64 {total := 0.0for _, item := range items {discount := getDiscountStrategy(item.Type)total += discount.Apply(item.Price)}return total
}

通过引入折扣策略模式,我们使 processItems 函数更加清晰和易于扩展。如果需要添加新的折扣类型,只需定义一个新的折扣策略并更新工厂函数即可。

以上是关于YAGNI原则、迪米特法则以及代码重构的一些基本概念及其在Go语言中的应用案例。遵循这些原则和最佳实践可以帮助我们编写出更加健壮、灵活和易于维护的代码。希望这些信息对您有所帮助!


http://www.ppmy.cn/news/1552353.html

相关文章

QT6_UI设计——设置表格

环境&#xff1a;qt6.8 1、放置 双击 2行 、列 设置 3、设置表格内容 读取表格内容 uint16 get_table_value_16_cmd(int row,int column) {if(column<1)return 0;QTableWidgetItem *itemnew QTableWidgetItem;itemui1->tableWidget_2->item(row,column);if(item! nul…

Web安全基础实践

实践目标 &#xff08;1&#xff09;理解常用网络攻击技术的基本原理。&#xff08;2&#xff09;Webgoat实践下相关实验。 WebGoat WebGoat是由著名的OWASP负责维护的一个漏洞百出的J2EE Web应用程序&#xff0c;这些漏洞并非程序中的bug&#xff0c;而是故意设计用来讲授We…

【机器学习】CatBoost 模型实践:回归与分类的全流程解析

一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS&#xff1a;转载自己的文章也算原创吧。 在机器学习领域&#xff0c;CatBoost 是一款强大的梯度提升框架&#xff0c;特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…

基于 LlamaFactory 的 LoRA 微调模型支持 vllm 批量推理的实现

背景 LlamaFactory 的 LoRA 微调功能非常便捷&#xff0c;微调后的模型&#xff0c;没有直接支持 vllm 推理&#xff0c;故导致推理速度不够快。 LlamaFactory 目前支持通过 VLLM API 进行部署&#xff0c;调用 API 时的响应速度&#xff0c;仍然没有vllm批量推理的速度快。 …

FreeRTOS之ARM CR5栈结构操作示意图

FreeRTOS之ARM CR5栈结构操作示意图 1 FreeRTOS源码下载地址2 ARM CR5栈结构操作宏和接口2.1 portSAVE_CONTEXT宏2.1.1 portSAVE_CONTEXT源码2.1.2 portSAVE_CONTEXT宏操作栈结构变化示意图 2.2 portRESTORE_CONTEXT宏2.2.1 portRESTORE_CONTEXT源码2.2.2 portRESTORE_CONTEXT宏…

Nginx管理维护运维规范

Nginx管理维护运维规范 一、版本约束 软件的不同版本&#xff0c;在使用起来都有可能带来不可预知的影响&#xff0c;因此需要统一整理&#xff0c;固定下来&#xff0c;不允许轻易变更。 在Nginx开源版官网&#xff0c;点击右侧download可以看到各个版本的Nginx&#xff0c;…

第六届金盾信安杯Web题解

比赛一共4道Web题,比赛时只做出三道,那道文件上传没有做出来,所以这里是另外三道题的WP 分别是 fillllll_put hoverfly ssrf fillllll_put 涉及: 绕过exit() 死亡函数 php://filter 伪协议配合base64加解密 一句话木马 题目源码&#xff1a; $content参数在开头被…

Linux笔试题(自己整理,已做完,选择题)

详细Linux内容查看&#xff1a;day04【入门】Linux系统操作-CSDN博客 1、部分笔试题 本文的笔试题&#xff0c;主要是为了复习学习的day04【入门】Linux系统操作-CSDN博客的相关知识点。后续还会更新一些面试相关的题目。 欢迎一起学习