<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel rdf:about="https://blog.atoery.cn/index.php/feed/rss/tag/clean_code/">
<title>JRNitre&#039;s Blog - CleanCode</title>
<link>https://blog.atoery.cn/index.php/tag/clean_code/</link>
<description></description>
<items>
<rdf:Seq>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/11/26/171.html"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="https://blog.atoery.cn/index.php/2025/11/26/171.html">
<title>软件工程中的显式依赖与隐式依赖</title>
<link>https://blog.atoery.cn/index.php/2025/11/26/171.html</link>
<dc:date>2025-11-26T16:58:18+08:00</dc:date>
<description>0.0 前言许多项目在开发初期，为了快速实现功能、看到效果从而在编码的过程中加入了大量的隐式依赖；如果不对其处理在编码工作进行到后期时可能遇到：难以测试、调试、代码架构升级等，从而造出一大坨屎山。大量隐式依赖的存在导致后期对代码结构进行修改时极其困难，比如你请了一位大厨来到家里为你做饭，来之前大厨用电话跟你沟通，说是需要你准备面粉，鸡蛋。。。等等一大堆“依赖”。结果厨师到你家开始做饭了，做一半突然告诉你：哎呀！我家祖传的大铁锅没拿啊！没这口锅我不会做饭了！没办法还得让人家跑回家拿锅；这时想到为什么你不打电话的时候就告诉我你有“铁锅”这一依赖啊？总不能明天我让你给我做炒菜的时候再告诉我祖传的锅铲没拿吧。所以隐式依赖对项目开发过程中可能导致没法编写单元测试，耦合度高等问题所在。1.0 隐式依赖我们给出一段 Go 的业务代码，显而易见的标出了隐式依赖的位置：// api/user.go
func UserRegister() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        var req types.UserRegisterReq
        if err := ctx.ShouldBindJSON(&amp;req); err != nil {
            // ...
            return
        }

        // ⚠️ 隐式依赖：直接调用全局单例
        svc := service.GetUserSrv()
        resp, err := svc.UserRegister(ctx.Request.Context(), &amp;req)
        // ...
    }
}这样的代码存在很多问题：不可测试单元测试时无法替换 GetUserSrv()，必须启动真实数据库，导致测试慢、不稳定。行为不可控测试无法模拟“用户已存在”“网络超时”等异常场景。耦合度高Handler 与具体 Service 实现强绑定，违反“依赖倒置原则”。文档缺失从函数签名无法得知其真实依赖，增加理解成本。这类代码常被称为“黑盒”——你知道输入，但不知道它背后偷偷用了什么。2.0 显式依赖现在我们对上述有问题的代码进行改进：// 定义接口契约
type UserRegisterService interface {
    UserRegister(context.Context, *types.UserRegisterReq) (*UserResponse, error)
}

// Handler 工厂函数：依赖通过参数注入
func MakeUserRegisterHandler(svc UserRegisterService) gin.HandlerFunc {
    return func(ctx *gin.Context) {
        var req types.UserRegisterReq
        if err := ctx.ShouldBindJSON(&amp;req); err != nil {
            // ...
            return
        }

        // ✅ 显式使用注入的依赖
        resp, err := svc.UserRegister(ctx.Request.Context(), &amp;req)
        // ...
    }
}

// 生产环境注册
router.POST(&quot;/register&quot;, MakeUserRegisterHandler(service.NewUserSrv(db)))这样修改后，对其编写单元测试，不依赖后续 service 层提供的函数，不需要启动实际的数据库，这种设计不仅提升了代码的可测试性，更增强了系统的弹性。3.0 常见隐式依赖陷阱及规避策略隐式依赖形式风险显式变化方案全局变量(var db *sql.DB)测试污染、并发问题通过构造函数注入单例函数(service.GetInstance())无法 mock改为接口 + 工厂函数time.Now()时间不可控注入func() time.Timeos.Getenv(&quot;DB_URL&quot;)配置硬编码抽象为Config接口http.Get(...)网络不可控封装为 HTTPClient 接口黄金法则：任何外部依赖都应通过接口抽象，并由上层注入。</description>
</item>
</rdf:RDF>