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

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

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

开始课程

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



处理遗留 Go 代码是开发者生活中不可避免的一部分。这类代码通常匆忙编写,缺少测试,充斥着全局变量和庞大函数。然而,重构遗留 Go 代码不仅仅是重写,更是一个需要谨慎和系统化方法的战略过程。在本文中,我们将分解如何在不破坏产品的前提下安全、有效地改进现有代码。



为什么重构遗留 Go 代码很困难?



遗留 Go 代码通常具有以下典型问题:

  • 全局变量 —— 使测试和并行执行变得复杂。
  • 庞大函数 —— 数百行长的函数,同时处理所有事情。
  • 缺乏接口 —— 与具体实现紧密耦合。
  • 没有测试 —— 在没有测试的情况下重构,如同在雷区行走。


但 Go 提供了强大的工具来逐步改进代码。关键在于小步前进,在每个阶段保持系统正常运行。



第一步:准备与测试



在更改代码之前,需要创建一张“安全网”——即测试。即使代码未经测试,你也可以编写特征测试。它们捕获系统的当前行为,以便你在重构后验证没有破坏任何东西。



package main

import "testing"

func TestCalculateTotal(t *testing.T) { // 假设我们有一个遗留函数 CalculateTotal result := CalculateTotal(100, 20) expected := 120.0

if result != expected { t.Errorf("CalculateTotal(100, 20) = %f; want %f", result, expected) }}


运行测试并确保它们通过。现在你有了进行安全更改的基线。



第二步:提取接口



遗留 Go 代码的主要问题之一是组件之间的紧密耦合。解决方案是提取接口。这允许你在测试期间用模拟替换真实的依赖关系,也简化了将来替换实现的步骤。



看一个例子。假设我们有一个函数,直接将数据保存到数据库:



package main

import "database/sql"

type User struct { ID int Name string}

func SaveUser(db *sql.DB, user User) error { _, err := db.Exec("INSERT INTO users (name) VALUES ($1)", user.Name) return err}


问题在于:如果没有真实的数据库,我们无法测试这个函数。解决方案是提取一个接口:



package main

// UserStorage 定义了存储用户的契约type UserStorage interface { Save(user User) error}

type UserSaver struct { storage UserStorage}

func NewUserSaver(storage UserStorage) *UserSaver { return &UserSaver{storage: storage}}

func (s *UserSaver) SaveUser(user User) error { return s.storage.Save(user)}


现在我们可以为测试创建一个模拟:



package main

import "testing"

type mockStorage struct{}

func (m *mockStorage) Save(user User) error { return nil // 总是成功}

func TestUserSaver_SaveUser(t *testing.T) { saver := NewUserSaver(&mockStorage{}) err := saver.SaveUser(User{ID: 1, Name: "Alice"}) if err != nil { t.Errorf("unexpected error: %v", err) }}


第三步:依赖注入



提取接口后的下一步是依赖注入(DI)。不要在函数内部创建依赖项(例如,打开数据库连接),而是从外部传入它们。



考虑典型的遗留代码:



package                

博客

书籍推荐