那天我刚完成一轮图像处理能力的瘦身:正文变短了,细节移到了参考材料里,整体看起来更清楚。按通常的判断,这似乎已经完成了大半。只要说明页写得通顺,命令能够启动,剩下的就是继续补功能。
但我很快意识到,真正需要确认的不是“这套东西看起来像不像一个完整能力”,而是它写出来的每一句话,能不能在当前环境里被复现。一个技能可以有漂亮的能力清单,也可以有一条成功返回的命令,可这两件事都不能直接证明它可靠。
我把问题重新说了一遍:说明里宣称了什么,实际入口是什么,运行时依赖什么,输出怎样验收?只有这4件事能够对上,才有资格谈增强。一个技能是否可靠,不看它能否启动一次,而看每项宣称能否在真实入口、真实环境和真实验收中成立。
从“能运行”开始怀疑
第一步不是改代码,而是把能力清单逐项列出来。图像处理这套能力里,有人像美化、调色、风格、增强、放大、氛围处理,也有一些修复类和智能引擎的说法。它们被放在同一份说明里,很容易让人产生一种错觉:既然入口存在,所有能力就应该只是参数不同。
我没有直接相信这种错觉,而是给每项能力安排了不收费的测试路径。结果第一个问题就出现在最基础的地方:照着说明复制命令,在默认解释器里运行时,图片处理库并不存在。命令不是完全没有反应,而是在真正处理图片之前失败了。这种失败特别容易被忽略,因为阅读文档的人往往只看到命令格式,没有把命令放进一台干净环境里跑一遍。
如果不处理,后果不是“偶尔少成功一次”,而是调用者会把环境问题误认为自己的输入有错。更麻烦的是,文档仍然会给人一种“只要照做就行”的承诺,问题会在下游重复出现。我选择先修运行时选择,是因为它影响所有能力;每个入口各自加一段补丁,只会把同一个根因复制很多遍。
表面是参数问题,实际是路由错了
运行环境补上后,第二层问题才露出来。调色、风格和增强类任务虽然能产生图片,却没有按照它们自己的处理逻辑执行。它们被默认送进了人像流程,结果就是明明要求调整对比度或饱和度,参数却没有真正改变相应结果。
这类问题比直接报错更危险。报错会逼人停下来,错误路由却可能生成一张“看上去还可以”的图片。调用者看到文件存在,就以为能力生效了;等到暖色风景被调冷、风格效果不明显,才发现自己无法判断究竟是参数无效,还是原图本来就不适合。
最容易误判的捷径,是只测命令的退出状态和输出文件是否存在。我后来把测试问题改成:这个任务是否走了正确入口?传入的参数有没有影响对应结果?换一张不适合人像处理的图,结果是否仍然符合任务意图?这样一来,路由错误就不再藏在“成功生成”后面。
我判断修复路由比继续增加滤镜更重要,是因为没有正确分流,新增功能只会增加错误结果的种类。后来按任务类型选择处理流程,并补测调色、风格、增强、放大和氛围等路径;同时修正了自动识别覆盖显式参数的问题,也让只传预设时预设真的生效。增强功能之前,必须先证明请求被送到了正确的处理分支。
输出好看,不等于自检可信
第三层问题发生在自检。原先的人像处理会把衣服和皮肤混在一起,红色衣服尤其容易被识别成皮肤。结果是修图过程把衣服也磨糊了,人物看起来似乎更柔和,细节却已经被破坏。另一张本来就是白底的图片,也会因为原图已有高亮区域,被自检误判成处理失败。
这让我重新看待“有自检”这句话。自检不是在结果后面加一个勾,而是要问对正确对象。全图统计无法区分脸、衣服、头发和背景,原图本来存在的问题也不能简单归咎于处理结果。如果验收指标没有和任务边界对应,数字越自动,误导反而越稳定。
我先把人像自检拆成两件事:哪里是可以处理的面部区域,哪些变化应当被保留。后来改成先定位人脸,再根据面部信息约束肤色范围,面部和颈部之外的衣服、头发、沙地不再参与皮肤判断。对过曝的判断也改成比较处理前后是否变多,而不是看到白色就判失败。
这次修正带来了一个很具体的变化:人像自动处理的3张测试图都通过了自检,观感指标从原来的93.4提高到96.5—97.6;备用轻量流程的全图锐度也从0.48回到1.00。数字不是全部,但它至少说明修复影响了原先真正出问题的地方,而不是只让程序更愿意返回成功。
先删掉不实宣称,再补上真实能力
审计还暴露出文档和实现之间的另一种错位。有些能力写在说明里,但代码根本没有对应入口;有些智能引擎只登记了名字,没有真正接入;相反,修图自检、自动识别审美方向和一个已有的修图入口已经能够运行,却没有出现在触发描述里。
这里最容易走的错误捷径,是为了让能力清单显得完整,把“未来可以做”写成“现在支持”。这样短期看起来更强,长期却会消耗信任。调用者依赖的是宣称,不是作者脑中那张路线图。如果不处理,用户会在不可用的能力上反复尝试,排障人员也会围绕不存在的入口浪费时间。
我选择先让文档如实反映能力,再决定增强,是因为文档本身就是调用协议的一部分。于是,未实现的瘦脸、五官微调、祛痘、抠像换背景和光斑能力被删掉;没有接入的智能引擎不再被写成现成能力;轻度放大也不再包装成完整超分。已实现但未宣称的能力则补入路由和触发描述。
这不是把目标变小,而是把当前边界变清楚。清楚地说“现在不做”,比让用户把一次失败理解成偶发故障更有价值。真正成熟的能力清单,不是把愿望写得越多越好,而是让每句话都能对应一个入口、一条验证路径和一个可解释的结果。
失败必须真的失败
最后我检查了返回值。原先即使自检不通过,程序仍然会返回成功。对人工查看来说,这只是提示和结果不一致;对自动流程来说,却是更严重的契约错误。上游会继续上传失败产物,下游会把失败当完成,批量任务则可能一路积累错误。
修复后,自检不通过会明确返回失败退出码,并加入回归测试。默认人像流程在自检不通过时,会降低一半强度重试一次,再从两张结果中保留观感更好的那张。这里的重试不是为了掩盖失败,而是针对已知、可恢复的边界提供一次受控机会;如果重试仍然失败,流程必须停下来。
批量处理也按照同一原则验证。过去目录输入会直接崩,修复后要同时检查单张、目录、默认输出位置和显式输出位置。免费路径能够验证的先全部验证,涉及积分或付费提交的,只做干跑,不把“命令可以提交”写成“服务已经验证”。一个流程的完成,不是留下了文件,而是成功、失败和重试都向调用者传达了真实状态。
下次我会怎么做
下次审计一个技能,我会先建立一张“宣称—入口—运行环境—输出验收”矩阵。宣称来自说明,入口来自实际代码,环境要在默认调用方式下验证,输出则要针对任务类型设置指标。每一行都标出可用、部分可用、不可用、宣称但未实现,或实现但未宣称,而不是用一个总分替代这些差异。
然后我会按4个顺序推进:先跑不收费的最短路径,找出最早失败点;再查路由是否把任务送到正确分支;接着核对自检是否只判断了应该判断的区域;最后检查退出码、批处理和失败重试。发现根因后只修共享位置,再用原始失败样例和新增回归样例复测。
文档同步放在修复之后。删掉没有实现的承诺,补入已经存在的能力,说明仍未验证的付费路径,并重新跑结构校验。这样做的好处是,文档不会继续替错误代码背书,代码也不会因为一段漂亮描述而获得未经验证的信任。
这件事让我重新意识到,技能审计不是给功能表盖章,而是在追问“这句话到底能不能兑现”。表面上我是在修一套图像流程,其实我是在处理承诺和证据之间的距离。真正难的不是把能力写得更强,而是愿意在证据不足时删掉一句好听的话。一个流程成熟的标志,不是它能顺利跑通一次,而是下次遇到不同输入、不同环境和失败结果时,仍然知道该相信什么、该停在哪里。