从删库到佛系:一个双非学生的代码洁癖康复实录
大家好,我是小林,某双非院校大二在读,目前在本地一家做区块链基础设施的小团队“混”了快两年——没错,就是那种老板画饼、产品经理天天改需求、运维半夜拉你进群问“服务是不是挂了”的草台班子。不过话说回来,能在大二就摸到生产环境的K8s集群,还能参与设计一个轻量级联盟链节点调度模块,我已经比同龄人幸运太多。
但今天不聊技术栈,也不吹K8s多香(虽然它真的很香),我想聊聊一件让我差点“原地退役”的事儿:代码洁癖。
曾经的我,看到别人写的 if 嵌套超过三层就想重写,函数名带个 getXXX 就觉得 low 到爆,变量命名不用驼峰?直接血压拉满。更离谱的是,有一次同事为了赶双11前的上线 deadline,在一段业务逻辑里硬塞了五个 TODO: fix later,我当场瞳孔地震,当晚失眠三小时,脑子里全是“这代码怎么能跑起来?”
直到上个月,我终于被现实狠狠教育了一顿——顺便治好了我的代码洁癖。
起因:一个来自区块链项目的“脏活”
事情发生在上周三下午。我们团队最近在搞一个基于 Hyperledger Fabric 的供应链溯源 PoC 项目,客户是某省农产品协会。本来架构挺清爽:前端 Vue + 后端 Go 微服务 + 链码(chaincode)跑在 K8s 上。结果临近演示,客户突然说:“能不能加个功能?让农户用小程序扫码后,能看到整个物流链路上每个环节的温湿度数据?”
行吧,需求不难。但问题是——留给我们的开发时间只有三天。
更要命的是,这段数据其实散落在三个不同微服务里:冷链监控服务、运输调度服务、仓储管理服务。而这些服务……咳咳,有些是我刚入组时写的,有些是前同事留下的“遗产”,还有些是实习生临时补的 bug fix。代码风格?那叫一个百花齐放:有的用 snake_case,有的用 PascalCase,还有一段居然混用了中文注释和英文变量名!
我原本打算花半天时间先把这三个服务的接口层统一重构一遍,结果刚 pull 代码,就被组长在群里 @:
“小林,链码那边先别动!先把数据聚合接口怼出来,明天下午客户要看 demo。能跑就行,别整那些花里胡哨的。”
我盯着屏幕,内心挣扎得像在打 Dota 选不出英雄。一边是“整洁代码是程序员的尊严”,一边是“demo 挂了你就滚去写三个月 Excel”。
最后,我咬咬牙,点开了那个最脏的 Go 文件——里面甚至有未关闭的 defer 和裸露的 error 忽略。
过程:在屎山代码里游泳是一种什么体验?
我决定采用“最小改动原则”:不动原有逻辑,只在外层包一层胶水代码。于是,我写出了职业生涯中最“丑”的一段聚合函数:
func GetFullTraceData(orderID string) (*TraceResult, error) {
// 先拿冷链数据(注意:这个服务返回的字段叫 temp_humi,不是 temperature_humidity)
coldResp, _ := http.Get(fmt.Sprintf("http://coldchain-svc/api/v1/data?oid=%s", orderID))
// 别问,问就是上游没给错误处理文档
// 再拿运输数据(这个服务要用 POST,而且 body 是 JSON,但文档写的是 GET)
transportBody := map[string]string{"order_id": orderID}
buf, _ := json.Marshal(transportBody)
req, _ := http.NewRequest("POST", "http://logistics-svc/trace", bytes.NewBuffer(buf))
transportResp, _ := http.DefaultClient.Do(req)
// 最后是仓储……算了,反正没人看这块注释
warehouseResp, _ := callWarehouseLegacyAPI(orderID) // 这个函数藏在另一个 package 里,命名毫无规律
// 手动拼接,能跑就行
return &TraceResult{
ColdChain: parseColdResponse(coldResp),
Logistics: parseTransportResponse(transportResp),
Warehouse: warehouseResp,
}, nil
}
看到这里,曾经的我可能会尖叫:“这也能叫代码?!”
但现在的我,只默默加了一句注释:
// TODO: 如果活着走出 demo,请重构此函数 —— 2024年6月20日,于工位第3次喝冰美式后
神奇的是,这段“脏代码”不仅跑通了,还在 demo 当天扛住了 50+ 并发请求(虽然 P99 延迟飙到了 1.2s)。客户满意,老板高兴,产品经理甚至夸我“响应迅速”。
那一刻我突然悟了:代码的第一使命是解决问题,不是取悦审美。
转折点:当 K8s 救了我的命
你以为这就结束了?No no no。
Demo 结束第二天,线上链码突然报错:
Error: Chaincode invoke failed: timeout exceeded while waiting for transaction commit
排查半天,发现是因为我在聚合接口里没加超时控制,导致某个微服务卡死,进而拖垮整个链码执行上下文。更糟的是,这个错误只在高负载下偶现——典型的“薛定谔 Bug”。
我本想立刻重构那段脏代码,但运维老哥直接甩给我一个 Grafana 面板链接:“先恢复服务,重构的事儿等双休再说。”
情急之下,我灵机一动:既然不能改应用层,能不能在 K8s 层面兜底?
于是,我给那个聚合服务的 Deployment 加了个资源限制和探针配置:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: trace-aggregator
resources:
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
env:
- name: HTTP_CLIENT_TIMEOUT
value: "3s" # 强制所有下游调用 3 秒超时
同时,在 Istio(对,我们小团队居然上了 Service Mesh)里加了个超时策略:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
http:
- route:
- destination:
host: trace-aggregator
timeout: 5s
奇迹发生了:即使那段脏代码偶尔抽风,K8s 也能自动重启容器,Istio 会快速熔断慢请求,整个系统反而更稳了。
那一刻我彻底放下执念:整洁的代码很重要,但健壮的系统更重要。有时候,用基础设施兜底“不完美”的应用代码,反而是更务实的选择。
综合反思:洁癖 vs 工程师思维
回过头看,我的代码洁癖其实源于一种“学生思维”——总觉得世界非黑即白,代码要么完美,要么垃圾。但在真实工程中,一切都是权衡(trade-off)。
| 维度 | 学生思维 | 工程师思维 |
|---|---|---|
| 时间 | “我要写出教科书级代码” | “三天内能跑就行,后续迭代再优化” |
| 责任 | 只对自己写的代码负责 | 对整个系统的 SLA 负责 |
| 价值 | 代码美观、可读性强 | 功能可用、故障可恢复 |
| 工具观 | 依赖编码规范 | 善用 K8s、监控、自动化兜底 |
特别是在我们这种小团队,综合交付能力远比单点代码质量重要。你写得再优雅,demo 挂了,客户跑了,公司倒闭了,你的“艺术品”也就成了数字坟墓里的陪葬品。
当然,我不是说从此就可以乱写代码。我的新原则是:
- 关键路径必须整洁:比如链码核心逻辑、资金相关模块,绝不妥协。
- 胶水代码允许“脏”:临时集成、POC、紧急修复,能跑就行,但要打上醒目 TODO。
- 用自动化弥补人工缺陷:CI 加 lint 检查、K8s 加资源限制、日志加 trace ID——让系统自己守住底线。
写在最后:从洁癖到“可控混乱”
现在,我已经能心平气和地看着同事写 var a = getUserInfo(); 而不冲过去抢键盘了。甚至上周五晚上加班时,我还主动帮测试同学 mock 了一个返回 { "code": 0, "data": null } 的假接口——虽然我知道这违反了 RESTful 规范。
但那又怎样?软件工程不是艺术创作,而是解决问题的协作游戏。在 deadline、人力、技术债的夹缝中,找到那个“足够好”的解,才是成年人的体面。
如果你也正被代码洁癖折磨,不妨试试我的“三问疗法”:
- 这段代码会影响线上稳定性吗?
- 重构它能带来显著收益吗?
- 现在真的是最佳时机吗?
如果三个答案有一个是“否”,那就先让它活着吧。
毕竟,在这个用区块链讲信任、用 K8s 讲弹性的时代,真正的专业,不是追求完美,而是驾驭混乱。
(P.S. 那段脏代码,我终于在上周末重构了。但不是因为洁癖——是因为它真的又崩了一次。😅)

评论 0