Go遗留代码重构:逐步指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Go 遗留代码重构入门



处理遗留代码是每位 Go 开发者职业生涯中不可避免的一部分。随着时间的推移,项目会积累技术债务:出现单体函数、代码重复、缺乏测试和错误处理。Go 遗留代码的重构不仅仅是重写,而是一个在不改变系统外部行为的前提下改善架构的战略性过程。



在本文中,我们将分析 Go 项目重构的主要方法,提供具体的代码示例,并展示如何让旧的代码库变得更干净、更安全、性能更高。



为什么 Go 遗留代码需要特殊方法?



Go 以其简洁和严谨著称,但即使如此,它也会积累问题:



  • 缺少接口 — 组件之间高度耦合;
  • 忽略错误 — 直接 return 而不检查 err;
  • 全局变量 — 隐式依赖;
  • 逻辑重复 — 复制粘贴而非组合;
  • 长函数 — 超过 50 行且承担多项职责。


没有测试的重构就像没有麻醉的手术。因此,第一步是确保关键部分的测试覆盖率。



重构策略:从单体到模块



1. 提取接口与依赖反转



我们从典型的遗留代码开始,其中业务逻辑与存储实现紧密耦合:



// 重构前type UserService struct {    db *sql.DB}

func (s *UserService) GetUser(id int) (*User, error) { row := s.db.QueryRow("SELECT id, name FROM users WHERE id = ?", id) var u User err := row.Scan(&u.ID, &u.Name) return &u, err}


问题:没有真实的数据库就无法测试这样的代码。解决方案是引入接口:



// 重构后type Storage interface {    GetUser(id int) (*User, error)}

type UserService struct { storage Storage}

func NewUserService(storage Storage) *UserService { return &UserService{storage: storage}}

func (s *UserService) GetUser(id int) (*User, error) { return s.storage.GetUser(id)}


现在我们可以轻松地在测试中将实现替换为 mock。



2. 拆分大函数(单一职责原则)



典型的遗留代码——一个函数完成所有事情:



// 重构前func processOrder(order Order) error {    // 验证    if order.Amount <= 0 {        return errors.New("invalid amount")    }    // 计算折扣    discount := 0.0    if order.CustomerType == "vip" {        discount = 0.1    }    // 保存到数据库    _, err := db.Exec("INSERT INTO orders ...")    if err != nil {        return err    }    // 发送邮件    return sendEmail(order.Email, "Order confirmed")}


重构后,我们将其拆分为小的、可测试的函数:



// 重构后type OrderProcessor struct {    db      Database    notifier Notifier}

func (p *OrderProcessor) Process(order Order) error { if err := validateOrder(order); err != nil { return err } order = applyDiscount(order) if err := p.db.SaveOrder(order); err != nil { return fmt.Errorf("save order: %w", err) } return p.notifier.SendConfirmation(order.Email)}

func validateOrder(order Order) error { if order.Amount <= 0 { return errors.New("amount must be positive") } return nil}

func applyDiscount(order Order) Order { if order.CustomerType == "vip" { order.Amount *= 0.9 } return order}


3. 用依赖注入替换全局变量



全局变量是测试的头号敌人。

博客

书籍推荐