在字节搬砖五年后,我用AI重塑了自己的开发工作流
上周四晚上十一点半,我盯着屏幕上第37个报错的单元测试,突然意识到一件事:我在这个工位上坐了快五年了。
从19年校招进字节,到现在在基础架构组搬砖,经历了三次组织架构调整,换了两个leader,唯一没变的是我桌上那台从大学用到现在的MacBook Pro——当然,中间换过两块电池和一次键盘。Windows?不存在的,我连虚拟机都懒得开,最多用Parallels跑个IE给前端同事测兼容性。
坐标杭州,这边阿里和网易的机会确实多,去年秋招的时候好几个前同事跳去了蚂蚁。但我这人比较懒,字节的基础架构虽然卷,但技术深度确实够,能让我这种"老油条"继续折腾点新东西。最近半年,公司内部在大力推AI提效,我也被leader"安排"去研究了一些AI相关的技术落地方案。
今天这篇文章,就想聊聊这半年我在技术探索上的一些实践和思考。不是那种高大上的架构设计,就是一个普通后端开发在日常工作中怎么用AI工具给自己"续命"的真实记录。
被大模型逼出来的效率革命
事情要从去年Q4说起。
当时组里接了个新需求,要给内部的RPC框架加一套智能诊断能力。说白了就是当线上服务出问题的时候,能自动分析调用链路,给出可能的根因建议。需求评审的时候,产品经理说了一句让我血压飙升的话:"这个功能竞品都有了,我们两周内能上线吗?"
两周?我看了看需求文档,光是要对接的日志系统就有四五个,加上链路追踪、指标采集、告警平台……我当场就想掀桌子。但转念一想,硬刚肯定不行,得想点别的办法。
也就是这个时候,我开始认真琢磨怎么用AI来提效。
通义千问:我的第一个AI编程搭子
说实话,在这之前我也用过ChatGPT,但受限于网络环境,用起来总是断断续续的。直到我开始用通义千问,体验才真正上了一个台阶。
先说个真实场景。当时我要写一个Go的日志解析模块,需要处理各种诡异的日志格式。以前这种活儿,我至少得花半天时间写正则、调格式、处理边界情况。但这次我直接把几段典型的日志丢给通义千问,让它帮我生成解析代码。
// 通义千问生成的日志解析核心逻辑
func ParseRPCLog(rawLog string) (*RPCLogEntry, error) {
// 支持多种日志格式:JSON、KV、纯文本
patterns := []string{
`(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})`,
`(?P<level>DEBUG|INFO|WARN|ERROR|FATAL)`,
`(?P<service>[a-zA-Z][\w.-]*)`,
`(?P<method>[a-zA-Z][\w.]*)`,
`(?P<traceId>[a-f0-9]{32})`,
`(?P<duration>\d+\.?\d*)`,
`(?P<statusCode>\d{3})`,
}
// 先尝试JSON格式
var jsonEntry map[string]interface{}
if err := json.Unmarshal([]byte(rawLog), &jsonEntry); err == nil {
return parseFromJSON(jsonEntry)
}
// 降级到正则匹配
combinedPattern := strings.Join(patterns, `\s+`)
re := regexp.MustCompile(combinedPattern)
match := re.FindStringSubmatch(rawLog)
if match == nil {
return nil, fmt.Errorf("unrecognized log format: %s", rawLog)
}
entry := &RPCLogEntry{}
for i, name := range re.SubexpNames() {
if i != 0 && name != "" {
switch name {
case "duration":
entry.Duration, _ = strconv.ParseFloat(match[i], 64)
case "statusCode":
entry.StatusCode, _ = strconv.Atoi(match[i])
default:
reflect.ValueOf(entry).Elem().FieldByName(
strings.Title(name)).SetString(match[i])
}
}
}
return entry, nil
}
这段代码我拿到手之后,大概花了20分钟做调整和优化。但关键是,它帮我省掉了至少3个小时的"从零开始"的时间。而且通义千问对中文的理解确实到位,我用中文描述需求,它返回的注释和变量命名都很符合国内开发者的习惯。
后来我总结了一个技巧:给AI喂上下文比让它凭空生成重要得多。我会把相关的接口定义、数据结构、甚至之前写的类似模块的代码一起丢给它,这样生成的代码质量会高很多。
扣子:低代码时代的AI工作流
如果说通义千问是我的"编程搭子",那扣子(Coze)就是我搭建自动化工作流的利器。
回到那个RPC诊断的需求。光解析日志还不够,我需要把解析后的数据跟链路追踪、监控指标关联起来,然后给出诊断建议。这个流程涉及多个系统的API调用,逻辑还挺复杂的。
以前我可能会写一堆胶水代码来串联这些系统。但这次我试了试用扣子来搭建工作流。
我的思路是这样的:
- 告警触发后,自动调用链路追踪API获取trace信息
- 把trace信息丢给大模型做初步分析
- 根据分析结果,调用监控API拉取相关指标
- 最终汇总成一份诊断报告
在扣子上搭建这个流程,大概花了两个小时。最让我惊喜的是它的插件生态,很多内部系统的API都有现成的插件可以用,省去了我写HTTP客户端的麻烦。
这里分享一个踩坑经验:扣子的工作流节点之间的数据传递,一定要做好字段映射。我第一次搭的时候,因为字段名没对齐,导致大模型拿到的数据全是空的,排查了半小时才发现这个问题。
# 扣子工作流配置片段(简化版)
workflow:
name: rpc-diagnosis
trigger:
type: webhook
config:
alert_id: "${alert.id}"
nodes:
- id: fetch_trace
type: plugin
plugin: internal-trace-api
input:
trace_id: "${trigger.trace_id}"
time_range: "30m"
- id: ai_analysis
type: llm
model: qwen-max
prompt: |
你是一个RPC链路分析专家。请根据以下链路信息,
分析可能的性能瓶颈和异常原因:
链路数据:${fetch_trace.output}
请从以下几个维度分析:
1. 耗时最长的节点
2. 错误率异常的节点
3. 可能的根因推断
output_format: json
- id: fetch_metrics
type: plugin
plugin: internal-monitor-api
input:
service: "${ai_analysis.output.suspicious_service}"
metrics: ["cpu", "memory", "qps", "latency_p99"]
- id: generate_report
type: llm
model: qwen-max
prompt: |
请综合以下信息,生成一份简洁的诊断报告:
链路分析:${ai_analysis.output}
监控指标:${fetch_metrics.output}
报告格式要求:
- 问题概述(一句话)
- 根因分析(分点列出)
- 建议操作(可执行的步骤)
上线之后,这个诊断工作流把原来需要值班同学花15-20分钟排查的问题,缩短到了2分钟出报告。虽然准确率大概只有80%左右,但对于初步定位问题来说已经够用了。
Aider:终端里的AI编程助手
说到AI编程工具,就不得不提Aider。
作为一个常年在终端里干活的后端开发,我对IDE的依赖其实没那么重。vim用习惯了,切到VS Code总觉得哪里不对劲。但Aider的出现,让我在终端里也能享受到AI辅助编程的便利。
Aider最打动我的一点是:它能直接理解你整个代码仓库的上下文。不像网页版的AI工具,每次都要手动复制粘贴代码,Aider会自动索引你的git仓库,在对话中直接引用相关的文件和函数。
举个实际的例子。上周我要给一个中间件加prometheus的metrics埋点。如果用以前的方式,我得先找到所有需要埋点的位置,然后一个个加代码。但用Aider,我只需要说:
帮我在pkg/handler/目录下所有的HTTP handler函数里加上请求耗时和状态码的prometheus metrics埋点,参考pkg/middleware/metrics.go里已有的实现方式。
然后Aider就会自动分析相关的代码,给出修改建议,甚至直接帮你改好。
# Aider的实际使用示例
$ aider --model qwen-max --git
> 在pkg/gateway/handler/下所有handler加上metrics埋点
Analyzing repository context...
Found 12 handler files in pkg/gateway/handler/
Reference implementation: pkg/middleware/metrics.go
Proposed changes:
--- pkg/gateway/handler/user.go
+++ pkg/gateway/handler/user.go
@@ -15,6 +15,8 @@
import (
"net/http"
+ "github.com/prometheus/client_golang/prometheus"
+ "myproject/pkg/middleware"
)
func HandleGetUser(w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+ defer func() {
+ middleware.RequestDuration.WithLabelValues(
+ "GetUser",
+ strconv.Itoa(r.Response.StatusCode),
+ ).Observe(time.Since(start).Seconds())
+ }()
+
// ... existing code
}
Apply changes? [Y/n/eskip]:
当然,Aider也不是万能的。它有时候会"过度发挥",改一些你不想让它改的地方。我的经验是:每次让Aider改代码之前,先git commit一下,这样改炸了可以随时回滚。另外,对于复杂的业务逻辑,还是得自己把關,AI生成的代码只能作为参考。
AI Agent:从工具到"同事"
上面说的这些工具,本质上还是"人驱动AI"的模式——我提出问题,AI给出答案。但最近半年,我一直在思考一个更进阶的方向:能不能让AI主动发现问题、主动解决问题?
这就是AI Agent的概念。
我们组最近在一个内部项目里做了个尝试:搭建了一个基于Agent的自动化运维助手。跟传统的告警机器人不同,这个Agent不只是发个消息通知你,而是能自己去排查问题、执行修复操作。
架构大概是这样的:
┌─────────────────────────────────────────────────┐
│ AI Agent Core │
│ ┌───────────┐ ┌───────────┐ ┌─────────────┐ │
│ │ Planning │ │ Reasoning │ │ Memory │ │
│ │ Module │ │ Module │ │ Module │ │
│ └───────────┘ └───────────┘ └─────────────┘ │
├─────────────────────────────────────────────────┤
│ Tool Layer │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌─────────┐ │
│ │ K8s │ │ Monitor│ │ Log │ │ Config │ │
│ │ API │ │ API │ │ API │ │ Center │ │
│ └────────┘ └────────┘ └────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
核心思路是用ReAct(Reasoning + Acting)的模式,让Agent在收到告警后:
- 先思考可能的原因(Reasoning)
- 调用相应的工具去验证(Acting)
- 根据验证结果更新判断(Observation)
- 循环直到得出结论或执行修复
这里有个关键的技术点:工具调用的权限控制。你肯定不能让AI Agent随便执行rm -rf之类的危险操作。我们的做法是定义了一套严格的工具权限体系,Agent只能调用预定义的、安全的操作。
// Agent工具权限定义
type ToolPermission struct {
ToolName string `json:"tool_name"`
AllowedOps []string `json:"allowed_ops"`
RequireAuth bool `json:"require_auth"`
DryRunOnly bool `json:"dry_run_only"`
}
var defaultPermissions = []ToolPermission{
{
ToolName: "k8s-pod-restart",
AllowedOps: []string{"get", "describe", "logs"},
RequireAuth: true,
DryRunOnly: true, // 重启操作需要人工确认
},
{
ToolName: "config-center",
AllowedOps: []string{"get", "diff"},
RequireAuth: false,
DryRunOnly: false, // 查看配置不需要dry-run
},
{
ToolName: "scale-service",
AllowedOps: []string{"get"},
RequireAuth: true,
DryRunOnly: true, // 扩容操作必须人工确认
},
}
// Agent执行工具前的权限检查
func (a *Agent) checkPermission(toolName, operation string) error {
for _, perm := range a.permissions {
if perm.ToolName == toolName {
if !contains(perm.AllowedOps, operation) {
return fmt.Errorf("operation %s not allowed for tool %s",
operation, toolName)
}
if perm.DryRunOnly {
return &DryRunRequiredError{Tool: toolName}
}
return nil
}
}
return fmt.Errorf("tool %s not found in permission list", toolName)
}
实际效果怎么样呢?说实话,还在迭代中。目前这个Agent能处理大概60%的常见告警场景,比如Pod OOM、服务超时、配置变更导致的异常等。但对于一些复杂的、需要跨系统关联分析的问题,它还是会"犯傻"。
最搞笑的一次是,有个服务因为下游依赖的数据库慢查询导致超时,Agent分析了一圈,最后给出的建议是"建议重启数据库"。当时运维同学看到这个回答,直接在群里发了个"???"。
一些掏心窝子的体会
写了这么多,最后分享几点我这半年折腾AI工具的感受。
第一,AI不会取代程序员,但会用AI的程序员会取代不会用的。
这不是句空话。就拿我们组来说,同样一个需求,我用AI辅助大概能比半年前快30%-40%。不是说AI帮我写了多少代码,而是它帮我跳过了很多"从零到一"的痛苦过程。以前写个新模块,光是搭架子、想命名、写注释就得花不少时间,现在这些机械性的工作AI都能帮你搞定,你可以把精力集中在核心逻辑上。
第二,Prompt Engineering是真的有用。
刚开始用AI工具的时候,我总觉得"这玩意儿生成的代码不行啊"。后来发现,很大程度上是我自己的问题——prompt写得太模糊了。现在我会花几分钟把需求描述清楚,包括输入输出格式、边界条件、性能要求等等,AI返回的结果质量会好很多。
第三,不要迷信AI,保持独立思考。
AI生成的代码,一定要自己review。它有时候会引入一些看起来很"聪明"但实际上有隐患的写法。比如上次通义千问帮我生成的一段并发代码,用了一个很tricky的channel用法,虽然能跑,但可读性很差,而且有个隐藏的deadlock风险。这种地方就得靠自己的经验去把关。
第四,技术探索要结合实际业务场景。
我见过有些人为了用AI而用AI,搞一些花里胡哨但实际没用的东西。我觉得技术探索的核心还是要解决实际问题。就像我们做的那个诊断Agent,虽然还不完美,但确实帮值班同学省了不少时间,这就是有价值的。
说了这么多,其实就想表达一个意思:技术在快速变化,我们做开发的也得跟着变。AI不是洪水猛兽,也不是万能药,它就是一个工具。关键是你怎么用这个工具,让它帮你把事情做得更好。
好了,不说了,leader在群里@我了,说是那个Agent又出了个bug,把测试环境的Redis给flush了……我得赶紧去擦屁股了。
下次再聊。


评论 0