<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>JRNitre&#039;s Blog - CleanCode</title>
<link>https://blog.atoery.cn/index.php/tag/clean_code/</link>
<atom:link href="https://blog.atoery.cn/index.php/feed/tag/clean_code/" rel="self" type="application/rss+xml" />
<language>zh-CN</language>
<description></description>
<lastBuildDate>Wed, 26 Nov 2025 16:58:18 +0800</lastBuildDate>
<pubDate>Wed, 26 Nov 2025 16:58:18 +0800</pubDate>
<item>
<title>软件工程中的显式依赖与隐式依赖</title>
<link>https://blog.atoery.cn/index.php/2025/11/26/171.html</link>
<guid>https://blog.atoery.cn/index.php/2025/11/26/171.html</guid>
<pubDate>Wed, 26 Nov 2025 16:58:18 +0800</pubDate>
<dc:creator>JRNitre</dc:creator>
<description><![CDATA[0.0 前言许多项目在开发初期，为了快速实现功能、看到效果从而在编码的过程中加入了大量的隐式依赖；如果不对其处理在编码工作进行到后期时可能遇到：难以测试、调试、代码架构升级等，从而造出一大坨屎山...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<h1>0.0 前言</h1><p>许多项目在开发初期，为了快速实现功能、看到效果从而在编码的过程中加入了大量的<strong>隐式依赖</strong>；如果不对其处理在编码工作进行到后期时可能遇到：难以测试、调试、代码架构升级等，从而造出一大坨屎山。</p><p>大量隐式依赖的存在导致后期对代码结构进行修改时极其困难，比如你请了一位大厨来到家里为你做饭，来之前大厨用电话跟你沟通，说是需要你准备面粉，鸡蛋。。。等等一大堆“依赖”。结果厨师到你家开始做饭了，做一半突然告诉你：哎呀！我家祖传的大铁锅没拿啊！没这口锅我不会做饭了！没办法还得让人家跑回家拿锅；这时想到为什么你不打电话的时候就告诉我你有“铁锅”这一依赖啊？总不能明天我让你给我做炒菜的时候再告诉我祖传的锅铲没拿吧。</p><p>所以隐式依赖对项目开发过程中可能导致没法编写单元测试，耦合度高等问题所在。</p><h1>1.0 隐式依赖</h1><p>我们给出一段 Go 的业务代码，显而易见的标出了隐式依赖的位置：</p><pre><code>// 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)
        // ...
    }
}</code></pre><p>这样的代码存在很多问题：</p><ul><li>不可测试</li></ul><p>单元测试时无法替换 GetUserSrv()，必须启动真实数据库，导致测试慢、不稳定。</p><ul><li>行为不可控</li></ul><p>测试无法模拟“用户已存在”“网络超时”等异常场景。</p><ul><li>耦合度高</li></ul><p>Handler 与具体 Service 实现强绑定，违反“依赖倒置原则”。</p><ul><li>文档缺失</li></ul><p>从函数签名无法得知其真实依赖，增加理解成本。</p><blockquote>这类代码常被称为“黑盒”——你知道输入，但不知道它背后偷偷用了什么。</blockquote><h1>2.0 显式依赖</h1><p>现在我们对上述有问题的代码进行改进：</p><pre><code>// 定义接口契约
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)))</code></pre><p>这样修改后，对其编写单元测试，不依赖后续 service 层提供的函数，不需要启动实际的数据库，这种设计不仅提升了代码的可测试性，更增强了系统的弹性。</p><h1>3.0 常见隐式依赖陷阱及规避策略</h1><table><thead><tr><th align="center">隐式依赖形式</th><th align="center">风险</th><th align="center">显式变化方案</th></tr></thead><tbody><tr><td align="center">全局变量(<code>var db *sql.DB</code>)</td><td align="center">测试污染、并发问题</td><td align="center">通过构造函数注入</td></tr><tr><td align="center">单例函数(<code>service.GetInstance()</code>)</td><td align="center">无法 mock</td><td align="center">改为接口 + 工厂函数</td></tr><tr><td align="center"><code>time.Now()</code></td><td align="center">时间不可控</td><td align="center">注入<code>func() time.Time</code></td></tr><tr><td align="center"><code>os.Getenv(&quot;DB_URL&quot;)</code></td><td align="center">配置硬编码</td><td align="center">抽象为<code>Config</code>接口</td></tr><tr><td align="center">http.Get(...)</td><td align="center">网络不可控</td><td align="center">封装为 <code>HTTPClient</code> 接口</td></tr></tbody></table><blockquote>黄金法则：任何外部依赖都应通过接口抽象，并由上层注入。</blockquote>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://blog.atoery.cn/index.php/2025/11/26/171.html#comments</comments>
<wfw:commentRss>https://blog.atoery.cn/index.php/feed/tag/clean_code/</wfw:commentRss>
</item>
</channel>
</rss>