前 篇排查完“读取调用完成,Canvas 宽高却仍未提供”后,我一度想用一个很常见的办法把界面补齐:没有值就显示0。表格会整齐,后面的尺寸判断也能继续写,似乎比一大片“未提供”更像一个完整功能。
幸好我没有直接提交那行默认值。因为0在程序里不是空白,它是一个明确数值。把undefined改成0,不是改善展示,而是在替底层回答“这个字段的值就是零”。接下来所有依赖尺寸的规则都会把这句话当真,得出通过、失败或某种修复动作。那时页面虽然不再留白,结论却已经不是读取结果了。
这篇复盘不再重复资源读取和图像源创建的过程,焦点是读取之后的第二道门:未知值如何显示、字段数量如何计算、规则何时可以开始判断、异常和缺失怎样分别处理。页面的固定说明内容不能代替 API 返回,其他属性也不能被我拿来补 Canvas 尺寸;这里要守住的是数据语义,而不只是表格样式。
那个看上去很无害的默认值
我最开始脑中出现的是下面这种写法:
// 错误示例:把没有返回的字段伪装成数值。 const width = metadata.webPMetadata?.canvasWidth ?? 0; const height = metadata.webPMetadata?.canvasHeight ?? 0; const isSizeValid = width > 0 && height > 0; this.canvasWidth = `${width}`; this.canvasHeight = `${height}`;表面上看,isSizeValid似乎很清楚:大于零通过,否则不通过。问题在于它混进了至少三种完全不同的情形:接口没有给出字段、接口给出了零、接口给出了负数或其他不符合业务条件的值。第一种是未知,后两种才是数值可以参与规则的状态。把它们压成一个false,日志里会出现“尺寸不通过”,阅读者却不知道到底该查文件、查接口、还是查规则本身。
这类错误比抛异常更危险。异常会逼着人停下来处理;默认值会让流程平静地向下走,还会产生一个很像结论的标签。以后有人看到“失败”,自然会认为系统已经看见了真实宽高并作出判断,很难意识到这只是我们替一个缺席字段编出来的数字。
这张图改变了我的处理顺序。以前是“先给默认值,再跑规则”;现在是“先判断信息是否完整,再决定能不能跑规则”。其中“未判定”不是失败的委婉说法,它表示规则没有足够输入。这个区别必须保留到 UI、日志和后续动作里,不能只藏在一个布尔变量中。
把三个状态分开,代码才不会替事实做主
这里最容易混淆的是undefined、0和异常。
undefined表示当前这次对象路径上没有可用值。页面用“未提供”呈现它,是为了让使用者知道不是一个数字。0是 API 实际交出的数值;它是否合乎某项规则,需要由规则本身决定,不能在显示函数里提前抹掉。异常则表示读取调用没有顺利完成,它应回到读取阶段和错误信息里处理,而不是伪装成某个字段未提供。
现有页面的valueText恰好守住了第一道边界:
private valueText(value?: number): string { return value === undefined ? '未提供' : `${value}`; }我以前会写成return value ?${value}: '未提供';,现在看来那是另一个坑。因为 JavaScript 和 ArkTS 中0会被当作假值,真实的零会跟缺失值一起被显示成“未提供”。虽然当前页面不该假定某字段会出现零,但代码的职责是保留返回语义,不能因“我觉得它大概不会是零”就把它吞掉。
同理,不能写成value || 0,也不能把Number(value)当成补救。前者会把零替换,后者很容易把未知转换成一个看起来可参与比较的数。只要业务后续存在阈值判断、面积计算或布局决策,这种转换都会让未知悄悄进入有效数据集合。
字段计数也必须服从同一套定义
当前页面同时展示了五项可能的 WebP 元数据字段。若只看表格,人很容易忽略“哪些是真的有值,哪些只是字段名存在”。所以代码计算了已提供字段数量。这个计数不是装饰,它是对本次返回完整程度的压缩描述,前提是它和表格使用完全相同的缺失判定。
const providedFieldCount: number = (canvasWidth === '未提供' ? 0 : 1) + (canvasHeight === '未提供' ? 0 : 1) + (delayTime === '未提供' ? 0 : 1) + (unclampedDelayTime === '未提供' ? 0 : 1) + (loopCount === '未提供' ? 0 : 1); this.frameCount = webp === undefined ? 'WEBP 元数据对象为空' : `元数据对象已返回 · 已提供字段 ${providedFieldCount}/5`;这里有两个我专门复查过的点。第一,计数是在valueText之后计算的,显示值和计数共同基于同一次 metadata 返回,不会出现表格更新了、汇总还停在上一轮的情况。第二,webp === undefined有独立提示。对象未返回与对象返回但五项都未给出,表面上都可能让每行显示“未提供”,但排查含义不同,不能为了简洁把它们揉成一个笼统的“0/5”。
不过这段代码也提醒我,字符串比较适合当前这个轻量页面,却不该被误解为通用数据模型。真正要把结果交给更多规则时,我会先保留数值和提供状态,再在最后一步生成文案。否则其他开发者一旦把中文显示值拿去比较,就可能因改文案、国际化或空格差异让计数悄悄出错。
更稳妥的思路如下:
interface FieldValue { provided: boolean; value?: number; } private toFieldValue(value?: number): FieldValue { return value === undefined ? { provided: false } : { provided: true, value }; } private canEvaluateCanvas(width: FieldValue, height: FieldValue): boolean { return width.provided && height.provided; }这不是要求当前演示页额外重写一层模型,而是我在接入真实业务规则前会采用的界线。显示需要文本,计数需要布尔值,规则需要真实数值;把三者都塞进一个字符串变量,早晚会有人把“未提供”当成“失败”,或把0当成缺失。
我如何处理“规则不能判定”
假设以后页面要增加一个尺寸门槛,例如宽高必须满足某个范围。最差的做法是对任何缺值直接给“失败”,第二差的做法是给“通过”以免阻断用户。两者都在替未知下结论,只是方向不同。
我会让规则返回至少三种结果:通过、失败、未判定。通过与失败都必须带着已知宽高;未判定表示缺少规则输入,需要保留原始“未提供”并提示下一步是重新读取、核对运行环境或转人工确认。这样,业务流程可以决定未判定该停住、重试还是允许暂存,但不会误以为读取到了一个零尺寸图像。
这个顺序里,字段计数不是规则的替代。五项里给了四项,不代表 Canvas 宽高就都给了;五项全给,也不自动代表任何业务规则通过。计数回答“当前列出的字段中有多少项存在”,规则回答“已知的目标输入是否符合某项条件”。我曾经差一点用providedFieldCount > 0当作“元数据可用”的总开关,后来删掉了这个想法,因为它会让无关字段的存在掩盖关键输入的缺席。
异常、对象为空、字段缺失,不能共享一个红色结论
页面的try/catch处理的是调用失败:资源无法读取、图像源无法创建、元数据 API 拒绝,都会让流程进入 catch。此时loadState与readState应说明读取失败,并将errorMessage保留下来。这里不应该强行把五个字段写成0,因为连“这次拿到了哪个 metadata”都不能确认。
try { const metadata = await source.readImageMetadataByType([ image.MetadataType.WEBP_METADATA ]); // 成功分支内再分别处理对象与字段的可选状态。 } catch (error) { this.loadState = '样本读取失败'; this.readState = '读取调用失败'; this.errorMessage = `readImageMetadataByType: ${JSON.stringify(error)}`; }对象为空不是 catch,它仍然属于成功返回后的信息状态。字段缺失又比对象为空更细一层。三者可以在视觉上有不同颜色或提示,但更重要的是动作不同:调用异常优先查看错误并重试;对象未返回需要确认当前 API 的实际返回;单个字段缺失则保留“未提供”,让依赖该字段的规则停在未判定。若把它们全叫“读取失败”,使用者很可能重复点按钮,却不知道应当检查哪里。
我也不会因为读取阶段显示完成,就给页面加“元数据完整”的标识。完整性是字段集合的观察结果,不是 API 调用的同义词。对当前五项来说,页面可以显示已提供数量,但这不表示它掌握文件的一切信息,更不能推出预览中其他内容的事实。克制地说清“当前返回了什么”,比写一个漂亮的大结论更有用。
还有一个我在代码评审里会追问的问题:字段计数能否成为重试条件?答案通常是否定的。0/5可能意味着对象没有返回,也可能意味着对象存在但这些项目都未提供;4/5也可能刚好缺失尺寸规则最需要的那一项。计数适合帮助人快速扫描完整程度,却不足以替代“Canvas 宽高是否都已知”的精确判断。若把它直接接到自动重试、拦截或放行逻辑上,系统又会从一个模糊数字推导出过度确定的行为。
我会把计数视为观察信息,把关键字段门槛视为执行条件。前者可以显示已提供字段 n/5,后者要逐项确认宽度与高度;两者各司其职。这样即使后续新增字段,汇总数量变化也不会在不经意间改变尺寸规则的含义。
显示层也不该替规则层制造颜色结论
当前表格根据文本是否为“未提供”使用不同颜色,这只是在帮助人扫读,不是风险等级。橙色表示当前字段没有显示数值,不自动表示文件有问题;绿色表示这一行有读取值,也不自动表示它满足任何尺寸限制。若把颜色直接复用成“成功/失败”,下一位维护者很容易把展示含义带进业务判断。
我在复测中会刻意检查这点:当宽高未提供时,字段表格可以显示缺席状态,读取阶段依旧可能是完成;当宽高都存在时,表格可以显示数值,规则是否通过仍取决于规则本身。把这三层信号放在一起看,才不会因一个颜色或一条短文案把未知、调用异常和业务不符合混在一起。
复测时,我把显示、计数和判断拆成三次检查
以前我的复测步骤只有一条:“打开页面,表格没空就行。”这条用例几乎保证会漏掉默认值问题。现在我按以下顺序走:先确认读取调用有没有成功结束,再逐行确认缺失字段显示“未提供”而非数字,最后对照汇总数量是否只统计了实际提供的行。如果将来加入尺寸规则,还要单独确认缺一项时结果为“未判定”,而不是任意一种二元结论。
重复读取时,还要关注状态复位。一次读到数值后,下一次如果字段未提供,旧数值必须消失;一次调用抛异常后,也不能拿上次成功结果继续显示成当前结果。最稳妥的做法是在发起调用时明确设置“正在读取”,在成功分支整体写入本次快照,在 catch 中明确标记失败。这样页面总能说明眼前的内容属于哪一种阶段,而不是把历史残留包装成新结果。
这次没有采用的几个“省事”方案
我没有用?? 0,因为它会制造数值。没有用|| '未提供',因为它会误伤真实零值。没有用“字段总数减去缺失数”随手计算规则可用性,因为关键字段与非关键字段的权重不同。也没有把“读取调用完成”当成通过标志,因为完成只说明调用返回,不说明业务输入充分。
还有一种看似友好的做法,是字段缺失时自动从别处推测尺寸,并在 UI 上不标注来源。它可能在某些场景给出看似正确的结果,却把读取 API 的事实、推断逻辑和页面展示混在一起。真要引入派生值,也应另列来源与置信状态,不能覆盖原字段,更不能伪装成webPMetadata.canvasWidth的直接返回。
我给自己留下的检查项
undefined、真实0和读取异常,是否拥有不同的状态与后续动作?- 字段“未提供”时,是否避免填入
0、通过、失败等确定性结论? - 字段计数是否逐项依据同一轮读取的缺失判定,而不是依据 UI 是否好看?
- 关键字段不完整时,尺寸规则是否停在未判定,而不是利用无关字段数量放行?
- 重复读取、重新进入页面或调用失败后,旧数值会不会残留为当前结果?
这次坑让我把一句习惯话改掉了:没有值不是零,没有结论也不是失败。当页面愿意把未知如实留在那里,后面的规则、日志和人工处理才有机会作出正确判断。
本文能确认的是当前页面的可选字段显示、五项字段计数和异常状态处理思路。某次实际运行会返回哪些具体元数据,仍需要由设备上的 API 返回决定;在拿到明确字段之前,不应把预览说明、其他属性或默认数字写成 Canvas 尺寸事实。