分类目录归档:架构和远方

DeepSeek 和腾讯元宝都选择在用的SSE 到底是什么?

在我们和 AI 聊天中,AI Chat 都采用了一种「打字机」式效果的实时响应方式,AI 的回答逐字逐句地呈现在我们眼前。

在实现这个功能的技术方案选择上,不管是 DeepSeek ,还是腾讯元宝都在这个对话逻辑中选择了使用 SSE,如下面 4 张图:

这是为啥,它有什么优势,以及如何实现的。

SSE 的优势

因为它在该场景下的优势非常明显,主要是以下 4 点:

1.场景的高度匹配。

AI 对话的核心交互模式是:

  1. 用户发起一次请求(发送问题)。
  2. AI 进行一次持续的、单向的响应输出(生成回答)。

SSE 的单向通信(服务器 → 客户端)模型与这个场景高度切尔西。它就像一条专门为服务器向客户端输送数据的「单行道」,不多一分功能,也不少一毫关键。

相比之下,WebSocket 提供的是全双工通信,即客户端和服务器可以随时互相发送消息,这是一条「双向八车道高速公路」。为了实现 AI 的流式回答,我们只需要其中一个方向的车道,而另一方向的车道(客户端 → 服务器)在 AI 回答期间是闲置的。在这种场景下使用 WebSocket,无异于「杀鸡用牛刀」,引入了不必要的复杂性。用户的提问完全可以通过另一个独立的、常规的 HTTP POST 请求来完成,这让整个系统的架构更加清晰和解耦。

2.HTTP 原生支持,与生俱来的优势

SSE 是建立在标准 HTTP 协议之上的。这意味着:

  • 无需协议升级:SSE 连接的建立就是一个普通的 HTTP GET 请求,服务器以 Content-Type: text/event-stream 响应。而 WebSocket 则需要一个特殊的「协议升级」(Upgrade)握手过程,从 HTTP 切换到 ws://wss:// 协议,过程相对复杂。
  • 兼容性极佳:由于它就是 HTTP,所以它能天然地穿透现有的网络基础设施,包括防火墙、企业代理、负载均衡器等,几乎不会遇到兼容性问题。WebSocket 有时则会因为代理服务器不支持其协议升级而被阻断。并且云服务商对于 Websocket 的支持并不是很完善。
  • 实现轻量:无论是前端还是后端,实现 SSE 都非常简单。前端一个 EventSource API 即可搞定,后端也只需遵循简单的文本格式返回数据流。这大大降低了开发和维护的成本。

3.断网自动重连,原生容错

这是 SSE 的「王牌特性」,尤其在网络不稳定的移动端至关重要。

想象一下,当 AI 正在为我们生成一篇长文时,我们的手机网络突然从 Wi-Fi 切换到 5G,造成了瞬间的网络中断。

  • 如果使用 WebSocket:连接会断开,我们需要手动编写复杂的 JavaScript 代码来监听断开事件、设置定时器、尝试重连、并在重连成功后告知服务器从哪里继续,实现起来非常繁琐。
  • 如果使用 SSE浏览器会自动处理这一切EventSource API 在检测到连接中断后,会自动在几秒后(这个间隔可以通过 retry 字段由服务器建议)发起重连。更棒的是,它还会自动将最后收到的消息 id 通过 Last-Event-ID 请求头发送给服务器,让服务器可以从中断的地方继续推送数据,实现无缝的「断点续传」。当然,Last-Event-ID 的处理逻辑需要服务端来处理。

这种由浏览器原生提供的、可靠的容错机制,为我们省去了大量心力,并极大地提升了用户体验。

4. 易于调试

因为 SSE 的数据流是纯文本并通过标准 HTTP 传输,调试起来异常方便:

  • 我们可以直接在浏览器地址栏输入 SSE 端点的 URL,就能在页面上看到服务器推送的实时文本流。
  • 我偿可以使用任何 HTTP 调试工具,如 curl 命令行或者 Chrome 开发者工具的「网络」面板,清晰地看到每一次数据推送的内容。

而 WebSocket 的数据传输基于帧,格式更复杂,通常需要专门的工具来调试和分析。

使用 SSE 实现「打字机」效果

1.后端——调用大模型并开启「流式」开关

当后端服务器收到用户的问题后,它并不等待大语言模型生成完整的答案。相反,它在调用 LLM 的 API 时,会传递一个关键参数:stream=True

这个参数告诉 LLM:「请不要等全部内容生成完再给我,而是每生成一小部分(通常是一个或几个‘词元’/Token),就立刻通过数据流发给我。」

下面是一个使用 Python 和 OpenAI API 的后端伪代码示例:

python

代码解读
复制代码
from flask import Flask, Response, request
import openai
import json

app = Flask(__name__)

# 假设 OpenAI 的 API Key 已经配置好
# openai.api_key = "YOUR_API_KEY"

@app.route('/chat-stream')
def chat_stream():
    prompt = request.args.get('prompt')

    def generate_events():
        try:
            # 关键:设置 stream=True
            response_stream = openai.ChatCompletion.create(
                model="gpt-4", # 或其他模型
                messages=[{"role": "user", "content": prompt}],
                stream=True 
            )

            # 遍历从大模型返回的数据流
            for chunk in response_stream:
                # 提取内容部分
                content = chunk.choices[0].delta.get('content', '')
                if content:
                    # 关键:将每个内容块封装成 SSE 格式并 yield 出去
                    # 使用 json.dumps 保证数据格式正确
                    sse_data = f"data: {json.dumps({'token': content})}\n\n"
                    yield sse_data

            # (可选) 发送一个结束信号
            yield "event: done\ndata: [STREAM_END]\n\n"

        except Exception as e:
            # 错误处理
            error_message = f"event: error\ndata: {json.dumps({'error': str(e)})}\n\n"
            yield error_message

    # 返回一个流式响应,并设置正确的 MIME 类型
    return Response(generate_events(), mimetype='text/event-stream')

if __name__ == '__main__':
    app.run(threaded=True)

在这段代码中,有几个关键点:

  1. stream=True:向 LLM 请求流式数据。
  2. 生成器函数(generate_events:使用 yield 关键字,每从 LLM 收到一小块数据,就立即将其处理成 SSE 格式(data: ...\n\n)并发送出去。
  3. Response(..., mimetype='text/event-stream'):告诉浏览器,这是一个 SSE 流,请保持连接并准备接收事件。
2.SSE 格式的约定

后端 yield 的每一条 data: 都像是一个个装着文字的信封,通过 HTTP 长连接这个管道持续不断地寄给前端。

前端收到的原始数据流看起来就像这样:

vbnet

代码解读
复制代码
data: {"token": "当"}

data: {"token": "然"}

data: {"token": ","}

data: {"token": "很"}

data: {"token": "乐"}

data: {"token": "意"}

data: {"token": "为"}

data: {"token": "您"}

data: {"token": "解"}

data: {"token": "答"}

data: {"token": "。"}

event: done
data: [STREAM_END]

看 DeepSeek 和腾讯元宝的数据格式,略有不同,不过有一点,都是直接用的 JSON 格式,且元宝的返回值相对冗余一些。 且都没有 data: 的前缀。

3.前端监听并拼接成「打字机」

前端的工作就是接收这些「信封」,拆开并把里面的文字一个个地追加到聊天框里。

html

代码解读
复制代码
<!-- HTML 结构 -->
<div id="chat-box"></div>
<input id="user-input" type="text">
<button onclick="sendMessage()">发送</button>

<script>
    let eventSource;

    function sendMessage() {
        const input = document.getElementById('user-input');
        const prompt = input.value;
        input.value = '';

        const chatBox = document.getElementById('chat-box');
        // 创建一个新的 p 标签来显示 AI 的回答
        const aiMessageElement = document.createElement('p');
        aiMessageElement.textContent = "AI: ";
        chatBox.appendChild(aiMessageElement);

        // 建立 SSE 连接
        eventSource = new EventSource(`/chat-stream?prompt=${encodeURIComponent(prompt)}`);

        // 监听 message 事件,这是接收所有 "data:" 字段的地方
        eventSource.onmessage = function(event) {
            // 解析 JSON 字符串
            const data = JSON.parse(event.data);
            const token = data.token;

            if (token) {
                // 将新收到的文字追加到 p 标签末尾
                aiMessageElement.textContent += token;
            }
        };

        // 监听自定义的 done 事件,表示数据流结束
        eventSource.addEventListener('done', function(event) {
            console.log('Stream finished:', event.data);
            // 关闭连接,释放资源
            eventSource.close();
        });

        // 监听错误
        eventSource.onerror = function(err) {
            console.error("EventSource failed:", err);
            aiMessageElement.textContent += " [出现错误,连接已断开]";
            eventSource.close();
        };
    }
</script>

这段代码主要有如下的点:

  1. new EventSource(...):发起连接。
  2. eventSource.onmessage:这是主要的处理函数。每当收到一条 data: 消息,它就会被触发。
  3. aiMessageElement.textContent += token;:这就是「打字机」效果的精髓所在——持续地在同一个 DOM 元素上追加内容,而不是创建新的元素。
  4. eventSource.close():在接收到结束信号或发生错误后,务必关闭连接,以避免不必要的资源占用。

EventSource 的来源与发展

在 SSE 标准化之前,Web 的基础是 HTTP 的请求-响应模型:客户端发起请求,服务器给予响应,然后连接关闭。这种模式无法满足服务器主动向客户端推送信息的需求。为了突破这一限制,开发者们创造了多种「模拟」实时通信的技术。

  1. 短轮询:这是最简单直接的方法。客户端通过 JavaScript 定时(如每隔几秒)向服务器发送一次 HTTP 请求,询问是否有新数据。无论有无更新,服务器都会立即返回响应。这种方式实现简单,但缺点显而易见:存在大量无效请求,实时性差,并且对服务器造成了巨大的负载压力。
  2. 长轮询:为了改进短轮询,长轮询应运而生。客户端发送一个请求后,服务器并不会立即响应,而是会保持连接打开,直到有新数据产生或者连接超时。一旦服务器发送了数据并关闭了连接,客户端会立即发起一个新的长轮询请求。这大大减少了无效请求,提高了数据的实时性,但仍然存在 HTTP 连接的开销,并且实现起来相对复杂。
  3. Comet:一个时代的统称:在 HTML5 标准化之前,像长轮询和 HTTP 流(HTTP Streaming)这样的技术被统称为 Comet。 Comet 是一种设计模式,它描述了使用原生 HTTP 协议在服务器和浏览器之间实现持续、双向交互的多种技术集合。 它是对实现实时 Web 应用的早期探索,为后来更成熟的标准化技术(如 SSE 和 WebSockets)奠定了基础。

随着 Web 应用对实时性要求的日益增长,需要一种更高效、更标准的解决方案。

  • WHATWG 的早期工作:SSE 机制最早由 Ian Hickson 作为「WHATWG Web Applications 1.0」提案的一部分,于 2004 年开始进行规范制定。
  • Opera 的先行实践:2006 年 9 月,Opera 浏览器在一项名为“Server-Sent Events”的功能中,率先实验性地实现了这项技术,展示了其可行性。
  • HTML5 标准化:最终,SSE 作为 HTML5 标准的一部分被正式确立。它通过定义一种名为 text/event-stream 的 MIME 类型,让服务器可以通过一个持久化的 HTTP 连接向客户端发送事件流。 客户端一旦与服务器建立连接,就会保持该连接打开,持续接收服务器发送的数据。

SSE 的本质是利用了 HTTP 的流信息机制。服务器向客户端声明接下来要发送的是一个数据流,而不是一次性的数据包,从而实现了一种用时很长的「下载」过程,服务器得以在此期间不断推送新数据。

其返回内容标准大概如下:

event-source 必须编码成 utf8 的格式,消息的每个字段都是用”\n”来做分割,下面 4 个规范定义好的字段:

  1. Event: 事件类型
  2. Data: 发送的数据
  3. ID:每一条事件流的ID
  4. Retry: 告知浏览器在所有的连接丢失之后重新开启新的连接等待的事件,在自动重连连接的过程中,之前收到的最后一个事件流ID会被发送到服务器

在实际中,大概率不一定按这个标准来实现。对于一些重连的逻辑需要自行实现。

现在大部分的浏览器都兼容这个特性,如图:

参考资料:

  1. en.wikipedia.org/wiki/Server…
  2. learn.microsoft.com/zh-cn/azure…
  3. www.cnblogs.com/openmind-in…
  4. javascript.ruanyifeng.com/htmlapi/eve…

以上。

从架构师的角度来看 AI 编程带来的技术债务

在吹水群,聊到 AI 编程,OZ 大佬提到

感觉 AI 会写很多各种可能情况的无用代码?它不会调试,不知道外部模块调用返回的具体格式,就用一堆if else去处理,最后还没有处理对。

GZ 大佬也说到:

ai 写代码感觉太差了,不知道在写什么
会不会制造更难维护的屎山

大家现在都已经在 AI 编程的世界中畅游了,整个软件开发似乎正以一种前所未有的方式悄然发生。 Cursor、Windsutf、Trae、lovable 等等已经完全进入了当前软件开发的世界中。

AI 能够根据自然语言注释或上下文,瞬间生成代码片段、函数甚至整个模块,极大地提升了编码的「表面」效率。我们正迈入一个「人机结对编程」的新时代。

但是大佬们也开始担心其产生的一些可能产生的后遗症。

今天我们从架构师的角度来看 AI 编程可能会给我们带来哪些技术债务。

作为架构师,我们的职责是超越眼前的速度与激情,洞察长期影响和潜在风险。

当团队的开发者们沉浸在「Tab」键带来的快感中时,一种新型的技术债务正在无声无息地累积。这种债务不再仅仅是糟糕的设计或潦草的实现,它更加隐蔽、更具迷惑性,并且直接与我们赖以信任的开发范式相冲突。

1992 年 Ward Cunningham 首次提出了传统的技术债务,指的是为了短期速度而采取了非最优的、有瑕疵的技术方案,从而在未来需要付出额外成本(利息)去修复。

它通常体现在糟糕的代码、过时的架构或缺失的文档上。

然而,AI 技术债务的范畴远超于此。它深深根植于数据、模型、基础设施乃至组织文化之中,其「利息」不仅是未来的重构成本,更可能是业务决策的失误、用户信任的丧失、合规风险的爆发,甚至是整个 AI 战略的崩塌。

从大模型的本质上来看,「 AI 编程是一个基于海量代码数据训练的、概率性的“代码建议引擎”」。它的工作方式决定了其产生的技术债务具有全新的特点。我们可以将其归纳为三个相互关联的维度:微观的代码级债务、宏观的架构级债务,以及深层的组织级债务。

微观的代码级债务 ——「似是而非」的陷阱

这是最直接、也最容易被感知的债务层面,它潜藏在 AI 生成的每一行代码之中。

在传统编程逻辑下,代码是要写的,而在 AI 编程时,代码是生成的,程序员的作用不再是写代码,而更多的是读代码,审核代码。

AI 生成的代码通常语法正确,甚至能够通过单元测试,但其内部逻辑可能存在微妙的、难以察觉的缺陷。例如,它可能选择了一个在特定边界条件下有问题的算法,或者「幻觉」出一个不存在的 API 调用,甚至在一个复杂的业务流程中,遗漏了一个关键的状态检查。这些 Bug 不再是明显的语法错误,而是「看似正确」的逻辑陷阱。

AI 编程减少了写代码的需要的认知成本,但是极大提升了读代码的心智负担。我们不仅仅要检查代码是否符合规范,还需要检查是否满足需求,以及是否在业务逻辑上完备。如果我们没有管这些问题,将来就可能是一个定时炸弹,隐藏在线上,不知道哪天会爆。

我们知道,AI 的知识来源于其训练数据——通常是海量的开源代码。因为 AI 倾向于生成「最常见」或「最流行」的解决方案,而不是针对当前上下文「最合适」的方案。它可能会引入一个庞大的库来解决一个小问题,或者使用一个过时但常见的编程范式,而不是团队正在推广的、更现代的模式。

这是 「设计熵增」的债务。它会持续不断地将外部的、非标准的、可能是平庸的设计模式注入我们的系统。长此以往,系统的技术选型会变得混乱,代码风格会变得不一致,精心设计的架构原则(如轻量级、高内聚)会被一点点侵蚀。我们必须警惕这种「随波逐流」的设计倾向。

每一行 AI 生成的代码,都应被视为一个来源不明的「外部依赖」。因为 AI 生成的代码片段可能悄无声息地引入了新的第三方依赖。更危险的是,它可能复现了其训练数据中包含的、有安全漏洞的代码模式(例如,不正确的加密实现、SQL 注入漏洞等)。此外,它生成的代码可能源自某种具有严格传染性的开源许可证(如 GPL),而团队并未意识到,从而引发法律合规风险。

为此,我们需要建立机制,自动扫描这些代码中的安全漏洞(SAST)和许可证合规问题。我们需要推动一种「零信任」的代码审查文化:开发者对任何由 AI 生成并最终提交的代码,负有 100% 的理解和责任。

宏观的架构级债务 ——「无声」的侵蚀

如果说代码级债务是「树木」的问题,那么架构级债务则是「森林」的水土流失。这种债务更加隐蔽,破坏性也更大。

如过往我们已经有一套优雅的微服务架构,定义了清晰的通信协议(如 gRPC)、统一的错误处理机制和标准的日志格式。然而,在使用 AI 编程时,为了快速实现一个功能,可能会接受一个使用 RESTful API、采用不同错误码、日志格式也千差万别的代码建议。单次来看,这只是一个局部的不一致,但当团队中数十个开发者每天都在这样做时,整个架构的一致性就会被破坏掉。

这是由于 AI 编程缺乏对我们「项目级」或「组织级」架构约定的认知而导致的,AI 编程是一个无状态的建议者,不理解我们系统的顶层设计。偿还这笔债务的成本极高,可能需要大规模的重构。架构师的核心挑战,从「设计」架构,扩展到了如何「捍卫」架构,防止其在日常开发中被无声地侵蚀。

AI 编程非常擅长生成「胶水代码」——那些用于连接不同系统、转换数据格式的脚本。这使得开发者可以轻易地在两个本应解耦的模块或服务之间建立直接的、临时的连接,绕过了设计的网关或事件总线。系统的模块化边界因此变得模糊,耦合度在不知不觉中急剧升高。

这是一种「捷径」。AI 让走「捷径」的成本变得极低,从而放大了人性中寻求最省力路径的倾向。架构师需要提供同样便捷、但符合架构原则的「正道」。例如,提供设计良好、文档清晰的 SDK、脚手架和标准化的 API 客户端,让「走正道」比「走捷径」更轻松。

从领域知识的角度来看,AI 可以从文档和代码中了解到一些,但是可能做不到完整的理解。

软件的核心价值在于其对复杂业务领域的精确建模。我们需要给 AI 以某种方式注入领域知识,如通过维护高质量的、富含领域术语的内部代码库和文档,来「引导」或「微调」AI 模型,使其建议更具上下文感知能力。

深层的组织级债务 ——「温水煮青蛙」的危机

这是最深层、也最关乎未来的债务,它影响的是人与团队。

当我们严重依赖 AI 编程时,会慢慢失去思考力和对代码的掌控力。

如果初级开发者过度依赖 AI,习惯于「提问-接受」的工作模式,而跳过了学习、思考和调试的艰苦过程。他们能够快速「产出」代码,但对底层原理、算法选择、设计权衡的理解却越来越肤浅。知其然不知其所以然,长此以往,团队成员的平均技能水平可能会停滞甚至下降。

这是团队的「未来」债务。我们在用未来的能力,来换取今天的速度。一个团队如果失去了独立解决复杂问题的能力,其创造力和韧性将不复存在。架构师需要倡导一种新的学习文化,将 AI 视为一个「助教」或「陪练」,而不是「枪手」。例如,鼓励开发者不仅要采纳 AI 的建议,更要尝试用自己的方法实现一遍,或者让 AI 解释它为什么这么写,并对解释进行批判性思考。

我们过往会用代码行数或者功能交付速度等指标来衡量团队的生产力,当有 AI 编程后,这些传统指标会得到巨大的提升,一天生产几千行代码是常事了。管理者可能会为此感到满意,但实际上,系统内部的技术债务正在快速累积,维护成本和风险也在同步攀升。

这是「技术管理」债务。我们需要建立新的、更能反映真实工程质量的度量体系。例如,关注代码的「可变性」(修改一个功能需要触碰多少文件)、「圈复杂度」、单元测试覆盖率的「质量」(而不仅仅是数量),以及 Code Review 中发现的深度问题的数量。架构师需要向管理层清晰地阐释 AI 编程的「债务风险」,推动建立更成熟的工程效能度量。

AI 编程助手是这个时代给予软件工程师最强大的杠杆之一,它有潜力将我们从繁琐的样板代码中解放出来,去专注于更具创造性的设计和思考。然而,任何强大的工具都伴随着巨大的责任和风险。

作为架构师,我们不能成为新技术的「勒德分子」,也不能成为盲目的「技术乐观派」。我们的角色,是确保这个强大的杠杆,成为放大我们架构意图和工程卓越的「放大器」,而不是制造技术债务的「复印机」。

这要求我们重新思考架构师的职责:我们不仅是蓝图的绘制者,更是蓝图的守护者;我们不仅要设计优雅的系统,更要设计能让优雅得以延续的「开发体系」;我们不仅要关注技术,更要塑造文化。通过建立清晰的规则、打造坚实的工程护栏、培育健康的开发者文化,我们才能确保,在 AI 赋能的未来,我们构建的软件系统,不仅跑得更快,而且走得更远、更稳。

以上。

AI 编程下的舒适区不能一直呆着

上周和 GZ 大佬在群里吹水,聊到 AI 编程,其中他所在的大厂已经全面推行 Cursor,他提到在 AI 时代下依赖 AI 会导致程序员失去思考力和代码能力。

虽然 GZ 的观点有些尖锐,但其核心表述的:过度依赖这类 AI 工具,可能会导致程序员的独立思考能力和实际编码能力下降。 是有道理的,人都是有惰性的,由简入奢易,由奢入简难

AI 给编程带来了「捷径」和「舒适区」。有即时的满足感,有认知负载的降低,还有我好像什么都会了的错觉。

  • 即时满足感: AI工具能迅速生成代码、解答疑问,这种即时满足感是非常诱人的。就像以前需要自己做饭(从买菜、洗菜、切菜到烹饪),现在可以直接点外卖,省时省力,结果直接呈现。
  • 认知负荷降低: 思考是耗能的。AI 工具在很多时候替我们完成了初步的思考和构建工作,大大降低了认知负荷。大脑天然倾向于节能,所以会不自觉地依赖这种轻松模式。
  • 我好像什么都会了的错觉: 有了AI的辅助,很多以前觉得困难或耗时的任务变得简单,这容易让人产生一种「能力快速提升」的错觉,从而更愿意待在这个由AI构建的「舒适区」里。

美国心理学家 NoelTichy 提出的理论人类对外部世界的认识可分为三个区域:舒适区,学习区,恐慌区。

参考下面这张图:

图片来源:即梦 AI 生成

我们天生是喜欢舒适区的,有在 AI 的加持下慢慢滑向依赖的自然趋势。

  • 习惯的养成: 一旦习惯了 AI 带来的便利,就很难再回到过去那种凡事亲力亲为的「简朴」状态。第一次用 AI 生成复杂函数可能还会仔细研究,第十次可能就直接 Accepted 了。惰性会让我们不自觉地选择最省力的方式。
  • 「温水煮青蛙」效应: 思考能力和编码能力的退化,往往不是一蹴而就的,而是像温水煮青蛙一样,在不知不觉中慢慢发生的。每次都依赖一点点,每次都少思考一点点,日积月累,当真正需要独立面对复杂问题时,才发现「内功」已经荒废了。
  • 对「不便」的容忍度降低: 习惯了 AI 的「秒回」和「全能」,一旦遇到 AI 无法解决或需要自己深入研究的问题,可能会更容易感到沮丧、不耐烦,甚至选择回避。

当我们真的养成了这种依赖的习惯,适应了这种舒服区,就会面对能力退化后的困境

  • 核心技能的生疏: 长期依赖 AI 完成编码和调试,会导致对编程语言特性、底层原理、算法数据结构、系统设计等核心技能的生疏。就像长期开车的人,突然让他走一段远路,可能会觉得非常吃力。
  • 问题解决能力的下降: 独立分析问题、定位问题、解决问题的能力,是在一次次「啃硬骨头」的过程中锻炼出来的。如果这个过程被 AI 替代,那么这种宝贵的实战经验就会缺失。当 AI 「失灵」或给出错误方案时,便会束手无策。
  • 创新能力的抑制: 真正的创新往往源于对问题的深刻理解和多角度的尝试。如果满足于AI给出的「标准答案」,就可能失去探索更优解或全新解决方案的动力和能力。
  • 学习动力的削弱: 「反正有AI」,这种心态可能会削弱一些人主动学习新知识、钻研深层技术的动力。因为「奢华」的生活方式似乎唾手可得,何必再去「简朴」地刻苦修炼呢?

就像我们家包包公主说的,这算不算「没苦硬吃」呢?

小朋友的视角不一样,也很形象。我们再深入思考一下:

从某种程度上说,如果 AI 已经能完美、高效地解决一些重复性的、模式化的、或者我们已经非常熟悉且没有太多新学习价值的问题,我们还非要「绕过」AI,坚持用原始的、低效的方式去「硬磕」,那确实有点「没苦硬吃」的味道。这就像明明有洗衣机,还非要每一件衣服都手洗,只为了「体验劳动的艰辛」,效率上肯定是不划算的。

但是,这里面有个核心的思考:我们「吃苦」的目的是什么?

比如,AI 能快速生成一个标准的 CRUD 代码框架,我们非要一行一行手动敲,而且这个过程对我们来说已经没有新的知识增量了,那这种「苦」可能就真的是「没必要硬吃」。时间应该花在更有价值的地方。

如果是为了「锻炼核心能力」、「深化理解」、「探索未知」而吃苦:

  • 打地基的苦: 对于初学者,或者在学习新技术、新领域时,有些基础的「苦」是必须吃的。比如,亲手搭建环境、理解底层原理、调试简单的错误。这个过程 AI 可以辅助,但不能完全替代,因为这是建立认知框架和培养解决问题直觉的过程。直接跳过,地基不牢。
  • 理解「所以然」的苦: AI 给出了一个方案,我们不满足于「知其然」,而是要去深究「所以然」——它为什么这么写?有没有其他方案?优劣何在?这个思考和验证的过程,可能需要查阅资料、动手实验,是「苦」的,但这种「苦」能让我们真正掌握知识,而不是停留在表面。
  • 攻坚克难的苦: 面对复杂的、AI 也难以完美解决的、或者需要创新性思维的问题时,我们需要自己去分析、设计、试错。这个过程无疑是「苦」的,但正是这种「苦」孕育了核心竞争力和真正的技术突破。
  • 保持「手感」和「警惕性」的苦: 就像运动员需要日常训练来保持状态一样,程序员偶尔也需要「刻意练习」一些基础技能,或者对AI的输出进行严格的审视和重构,以保持对代码的敏感度和对潜在问题的警惕性。这种「苦」是为了防止能力退化。

明明有更优解(AI能完美胜任且无损学习),却固执地选择低效、重复且对能力提升帮助不大的方式。这是一种低效的勤奋,属于没苦硬吃。为了掌握核心技能、深化理解、培养批判性思维、解决复杂问题而进行的有目的的、高价值的努力。这是一种战略性的投入。不是没苦硬吃。

回到「由简入奢易,由奢入简难」和「人的惰性」

正是因为惰性的存在,我们很容易滑向完全依赖 AI 的「奢华」生活,从而不自觉地回避了那些必要的、能提升核心能力的「苦」。这时候,有意识地去「吃一些必要的苦」,就不是「没苦硬吃」,而是对抗惰性、保持清醒、主动投资未来的表现。

举个例子:

  • AI能帮我们写单元测试。如果我们只是为了应付覆盖率,让 AI 生成然后看都不看,那可能会错过很多理解代码逻辑和边界情况的机会。
  • 但如果我们让 AI 生成初步的测试用例,然后再仔细分析这些用例是否覆盖了所有关键逻辑、边界条件、异常情况,并在此基础上进行补充和优化,甚至思考如何设计更健壮的被测试代码——这个过程虽然也「苦」,但价值巨大。

那如何对抗这种「人性」?

正因为「惰性」和「由简入奢易,由奢入简难」是人性的一部分,所以对抗它需要刻意的练习

1.保持警惕意识: 时刻提醒自己,AI 是工具,不是替代品。享受便利的同时,要警惕能力滑坡的风险。

2.刻意练习:

  • 主动「脱离」 AI : 对于一些核心模块或自己希望提升的领域,尝试不使用或少使用 AI,强迫自己独立思考和编码。
  • 深究 AI 的答案: 不满足于AI给出的结果,而是去理解它为什么这么做,它的原理是什么,有没有更好的方式。把 AI 的输出当成学习材料,而不是最终答案。
  • 复盘与总结: 即使使用了 AI,也要对过程和结果进行复盘,总结学到的东西和 AI 的局限性。

3.设定更高的目标: 将 AI 视为达到更高目标的「杠杆」,而不是满足于现有水平的「安乐椅」。比如,利用 AI 节省下来的时间去学习新的架构知识、去钻研更复杂的算法、去思考更有创造性的解决方案。让 AI 帮助我们去追求一种更高层次的、更依赖人类智慧的「奢华」。

4.强化元认知: 思考自己是如何思考的,学习自己是如何学习的。意识到自己可能陷入了惰性思维,并主动调整策略。

5.对「思考能力」和「代码能力」的重新定义

  • 思考能力: 可能从「如何从零开始解决问题」更多地转向「如何清晰地描述问题」、「如何将大问题分解给 AI」、「如何评估和整合 AI 提供的方案」、「如何在更高层面进行架构设计和技术决策」。
  • 代码能力: 可能从「熟练编写每一行具体代码」更多地转向「快速理解和修改 AI 生成的代码」、「保证代码质量、可维护性和安全性」、「进行有效的Code Review(即使是AI生成的代码)」。

AI 编程工具,确实像一把双刃剑。它带来的「即时满足感」和「认知负荷降低」,很容易就把我们拽进那个诱人的「舒适区」。毕竟,谁不爱走捷径呢?可问题也随之而来,长期依赖这种「外挂」,我们自己的「内功」——独立思考和编码能力,真可能在「温水煮青蛙」般的日常中,不知不觉就打了折扣。

当然,这也不是说我们就得跟 AI 划清界限,放着高效的工具不用,非得事事躬亲,那确实有点「低效勤奋」,甚至真成了「没苦硬吃」。关键在于,我们得想明白,哪些「苦」是值得吃的,是能真正提升我们核心竞争力的「战略性投入」。

  • 那些AI能完美胜任、重复性高且对我们知识增量有限的活儿,大胆交给 AI,这叫明智地利用工具,解放生产力
  • 但那些关乎「打地基」、深究「所以然」、需要「攻坚克难」的硬骨头,以及为了保持「手感」和「警惕性」的刻意练习——这些「苦」,恰恰是 AI 时代我们安身立命的本钱。它们能帮助我们构建真正的理解,培养批判性思维,并最终驾驭 AI,而不是被 AI 所定义

所以,面对AI,我们不能简单地「躺平」享受,也不能盲目地「排斥对抗」。更重要的是,要保持那份警惕意识,用刻意的练习去对抗人性的惰性

这不仅仅是关于代码怎么写得更快,更是关于我们如何重新定义自己的「思考能力」和「代码能力」,如何在 AI 的浪潮中,通过主动学习和深度思考,完成一次自我进化。说到底,AI 是工具,方向盘始终还是握在我们自己手里,是选择成为更智慧的「驾驶员」,还是满足于当一个「乘客」,这道题,得我们自己用心作答。