type
Post
status
Published
date
Apr 8, 2026
slug
thought1
summary
tags
成长
category
icon
password
起因:开源社区committers一致要求新同学做前几个需求时必须古法编程。
我做的背景是Dubbo-go Issue #3259:Java 泛化调用 dubbo-go 时,Go 方法如果是
args ...string 这种可变参数,应该怎么传。我发现是框架不支持,那就补上这个特殊情况,让框架支持就好了.我也是这么做的,首版 commit 只改了 4 个文件,新增只有138 行,测试跑通了,本地看问题解决了,我就提交了 PR #3284。但是PR 交上去以后,committer review 出了 2个P0、5个P1、5个P2 问题。跟他交流之后,我才发现这个修复不能只是在 generic filter 里补一段参数处理,还会牵扯到 proxy/invoker、Triple reflection、server reflection 和多层测试,没考虑到我的这个小修改会影响这么多地方。这个看起来很小的 special case,进框架以后变成了调用语义问题,可是我连往这方面想的意识都没有。很明显我当时看问题的思维太差了,对链路的理解不够,也没有从不同角度去看,比如说从全局视角、owner视角看问题。
所以,对刚入行新手时,写前几个需求的时候,我非常推荐先古法编程:自己读调用链,自己复现问题,自己改代码,自己跑测试,再把 PR 交给社区 review。虽然AI可以很快生成一版答案,但真正自己去做,去发现问题,再去改正,能让你更好理解编程。最重要的是你看问题的意识变了,你开始成为一个能解决问题的人。
对新人来说,AI 时代最危险的不是“不会用 AI”,而是“只会把 AI 当代写器”。你是否有能力给 AI 提对问题、设对边界、看出它哪里错”。这一层不会,你是能用AI完成任务,但AI跑偏了怎么办?
古法编程输出倒逼输入,有了古法编程的经验,之后能更好review AI写的代码。
那我从这次2个P0、5个P1、5个P2问题经历中学到了什么?
第一课:外形相似不等于语义相同
我一开始就是在这里想简单了:看到参数像 slice,就想把它当成 variadic 尾参处理。
我首版的想法很直接:既然目标方法是 variadic,最后一个参数又是 slice 或者能整理成 slice,那我就在泛化调用路径里把它整理好,后面反射调用时配合
CallSlice。这个思路在本地能跑,所以我觉得合理。结果……
review 提醒我,普通
[]interface{} 参数和 ...interface{} 在泛化调用里可能长得很像。如果我只靠“最后一个参数像 slice”判断,就可能把普通调用误判成 variadic 调用,进而包错参数、拆错参数,甚至走错反射路径。改的时候,我把思路放到了 marker 上:generic filter 明确识别并整理过 variadic 参数后,写入
GenericVariadicCallSliceKey;后续 proxy、Triple、server 反射路径看到这个 marker,才继续判断是否允许 CallSlice。特殊逻辑必须有来源标记,不能靠“看起来像”触发。以后我再写类似逻辑会想:这个分支为什么知道自己是特殊路径?如果是“参数形态看起来符合”,我会去检查特殊逻辑有没有明确 marker。
第二课:reflect.Set 前必须先做类型门禁
一开始把反射写得太像普通赋值了,但反射没有编译期兜底。
我首版在 generic filter 里重塑 variadic slice 时,思路是把传进来的值转成目标 slice 里的元素,再用
reflect.Set 塞进去。当时我关注的是“能不能构造出目标参数”,没有把每一次 Set 可能 panic 的情况拆开看。我当时只关注构造成功,没有先检查构造过程是否安全。结果……
review指出来,不能直接
Set(reflect.ValueOf(x))。nil 能不能被目标类型接住,值能不能 AssignableTo,不能 assign 时能不能 ConvertibleTo,这些都要在 Set 前确认。我错把类型不匹配留给运行时 panic了。改完以后,这块逻辑变成了先做类型门禁:nil 按目标类型处理;能 assign 就直接用;不能 assign 但能 convert,就转换;都不满足,就返回清晰错误。array、slice、zero value 这些边角形态,也不能凭感觉混在一起处理。反射代码要先确认值能安全进入目标类型,再谈调用。
以后我看到
reflect.Set、reflect.Call 会自然多想一步:这里有没有 nil、assign、convert 的保护?错误是能返回出来,还是会直接 panic?以后要检查反射前的类型门禁。第三课:CallSlice 会改变语义,不能宽松触发
我一开始把
CallSlice 当成解决 variadic 的工具,却没有先确认它会不会改掉普通调用,对反射分发里的语义切换不太熟悉。我首版的方案是:方法是 variadic,最后一个参数是 slice,那就走
CallSlice。因为 Go 反射里调用 variadic 方法确实经常会想到 CallSlice。我当时的问题是只看到了目标 case,没有考虑普通调用也可能满足同样条件。结果……
review提醒我,这个条件太宽。普通本地调用也可能传入一个 slice,如果这时因为方法是 variadic 就切到
CallSlice,原有调用语义就被改掉了。我做错的点,是让特殊修复有机会影响普通路径。改的时候,我把
CallSlice 的触发条件收紧了:方法必须是 variadic,调用必须带有 generic variadic marker,参数数量要匹配,最后一个参数还要能匹配声明的 variadic slice 类型。只有这些条件同时成立,才说明这是 generic filter 明确准备好的 variadic 调用。语义切换不能靠单个宽松条件触发。以后我再写框架分发逻辑会想:这个特殊分支会不会被普通调用误进来?如果会,就说明门禁还不够。以后我要检查特殊路径会不会误伤普通路径。
第四课:测试要封住语义影响面
我一开始更像是在测“issue 复现路径好了没”,review 要求我测“相关语义有没有被改坏
首版里,我主要关注 Java generic call 调 Go
args ...string 这个主 case。它能跑通,我就觉得核心问题已经解决了。结果……review 里的 P1 回归测试不足评论 要求补离散参数、packed array、
...interface{} 不要二次包装、固定参数加 variadic、nil、零个 variadic 参数、单个参数、多类型参数等场景。看到这些要求以后,我才意识到:reviewer 关心的不是“我遇到的 case 能不能过”,而是“这个改动可能影响的语义有没有被圈住”。我做错的点,居然是按复现步骤测,而不是按影响面测。改完以后,测试不只补在 generic filter,还覆盖到了 proxy/invoker、Triple reflection、server reflection。这个跨度让我明白,一个参数整理问题在框架里会穿过多层调用路径,测试也要跟着调用链往下铺。框架修复的测试要覆盖它实际经过的路径。
以后我写测试会想:普通路径测了吗?特殊路径测了吗?最容易混淆的参数形态测了吗?跨层传递的 marker 或上下文测了吗?以后我要检查测试有没有封住语义影响面。
剩下的 review,让我补上社区习惯
能合并的 PR 不只要核心逻辑对,还要让维护者读起来、查起来、合起来都顺。
一开始我会天然觉得,命名、错误信息、重复
reflect.ValueOf、target branch、Codecov、SonarQube 这些事情没有 P0 那么关键,可以等核心逻辑差不多了再处理。review 之后我才发现,这些“小问题”会一起影响 PR 的维护成本。我当时低估了社区项目里一致性的价值。改的过程中,我开始对齐项目里的命名习惯,比如
ivkURL 这类缩写风格;错误信息也要贴近项目已有格式;反射路径里明显重复的操作要顺手收掉;target branch、CI、覆盖率、质量门禁也要自己提前核对。社区规范决定你的代码能不能自然长进项目。以后我再提 PR 会注意这些规范,而不是等 maintainer 或 bot 来提醒。提前处理能省掉很多来回沟通,减少返工。要主动检查代码的规范性问题和可维护性问题
总结
如果这次我从头到尾都交给 AI,能更快完成任务。但我亲手读链路、亲手提交、亲手被 P0/P1/P2 review 打回来,再一条条修掉问题,才有了 marker、类型门禁、
CallSlice 语义、测试影响面和社区规范这些意识。PR #3284 之后,我再写框架类改动,会先想:这个特殊修复有没有来源标记?会不会误伤普通调用?反射前会不会 panic?测试有没有封住语义?风格和流程是否符合社区要求?尽量不出问题,减少返工。
新人阶段的古法编程,让你从一个只会写玩具项目、只会死背八股的人,变成一个有解决实际问题思维的人。高级程序员大多拥有好的编程思维,如果一直Vibe coding,一直做prompt工程师,不自己去思考,如何提升?等着被取代吗?
分享
