市集表单内核(第3篇):HarmonyOS 跨字段校验与错误聚合【共创稿事节】
一次很有代表性的故障发生在市集经营时段和电源申请的联合校验:单个输入框看似合法,但选择“餐饮”却未填写食品安全承诺,或者请求用电但功率留空。乍看只是某个控件或接口的行为不理想,向下追踪却会发现根本原因是数据归属和异常路径没有写成明确约定。

从一次失败现象开始
表单里最棘手的错误往往不是“某个字段没有填”,而是两个字段单独看都合法,放在一起却违反业务要求。经营类别选餐饮是合法值,承诺书未勾选也可能是合法的编辑中状态,但点击提交时二者不能同时成立。
“单字段格式校验只是第一层,跨字段业务不变量才决定能否提交”是这篇最重要的设计判断。单看成功路径,市集经营时段和电源申请的联合校验可以靠若干事件回调工作;但只要用户中途修改、取消或重复操作,若没有清晰约束,就无法确定最终输出究竟属于哪一次输入。工程拆分必须以这些不确定性为起点。
我们希望得到的不是一张孤立的效果图,而是一条可追溯的处理链。整个过程拆为输入变更、字段规则、业务规则、错误映射、错误定位。每一段只承诺自己负责的结果,下一段不靠猜测前一步的内部状态。对规则聚合而言,这种清晰分工也是回归测试能够写得具体的前提。
把规则拆成两个层级可以减少混乱。字段规则负责类型、长度、非空;整表规则负责“如果是餐饮就要同意食品承诺”和“申请用电时功率必须为正”。提交时合并结果,展示时再把错误映射回有稳定 key 的字段。
分层识别根因
实施选择是:分别计算字段错误和整表错误,再合成为稳定的错误映射并按展示顺序定位。这里不能倒过来,先把组件和请求写完,再向现有代码里硬塞判断。规则入口只接收必要数据,输出也尽量可序列化。这样在没有设备的环境下仍能对核心逻辑做确定性检查。
“输入变更”阶段很容易被认为已经完成了整个功能,其实它只提供一个前提。只有进入“字段规则”之后,才能知道前一步是否真的满足业务要求。把这个检查写成显式返回值,比依赖某个内部字段在恰当时刻改变更稳健。
当路径执行到“字段规则”,先区分用户仍在编辑、数据已经校验和动作已经确认三种事实。随后执行“业务规则”时,使用同一份中间结果才能避免旧数据回流。日志或测试也应记录阶段,而不是只看最终页面有没有消失。
把“业务规则”到“错误映射”视作可中断的边界。任何阶段都可能遇到无效输入或设备状态变化,恢复时应回到上一个仍然可信的事实,而不是为了显示流畅强行跳到最终步骤。这样的路径更适合实际调试。
对布尔字段尤其要小心。明确选择“不需要用电”的 false 和尚未回答应当区分;若只写 if (!value) 可能把有效的否认作缺漏。本例对 agree 使用明确业务含义,对电源申请则以 power 作为选择的事实。

实现一条可控路径
下面给出可以独立检查的关键实现。代码只覆盖本篇要证明的核心行为;涉及工程权限、服务端协议或构建设置的条件已在上下文列明。它应被放入明确的模块中,而不是把所有业务逻辑堆进 ArkUI build()。
interface Application { category: string; power: boolean; watt: string; agree: boolean }
interface Issue { field: string; message: string }
function validate(s: Application): Issue[] {
const errors: Issue[] = []
if (s.category === '') errors.push({ field: 'category', message: '选择经营类别' })
if (s.category === '餐饮' && !s.agree) {
errors.push({ field: 'agree', message: '先阅读食品承诺' })
}
if (s.power && !(Number(s.watt) > 0)) {
errors.push({ field: 'watt', message: '填写正数功率' })
}
return errors
}
function errorCount(s: Application): number { return validate(s).length }
用户修改某字段后不要沿用上次完整错误列表。否则经营类别已从餐饮改为文创,旧的食品承诺错误仍可能留在 UI。每次重新计算返回新数组,再根据当前可见字段执行定位,而不是在原数组上随意删除一项。
代码中的关键判断来自业务约束,而不是某个屏幕比例。以“业务规则”为界,输入应先满足所属阶段的前置条件;当判断失败时,返回错误或保留状态比直接抛出无法解释的页面状态更可控。对需要 UI 反馈的部分,随后由组件转换为明确的文案。
如果把上述核心逻辑接到一个 ArkUI 页面,推荐组件仅处理状态投影和交互事件:例如 TextInput.onChange 接收新的字符串,Button.onClick 触发一次显式动作,得到结果后再设置 @State 驱动展示。对于不是输入框的规则聚合功能,则采用相同的单向数据边界而不强行引入 TextInput。

重新构造故障
最应该提前考虑的风险是:不可用布尔值是否为 false 判断必填,因为零和明确的否也可能是合法答案。这句话不是发布时随手附加的注意事项,而是编码时必须坚持的限制。只要异常路径缺乏明确处理,下一次回归就可能把一个本来可恢复的状态变成难以判断的失败。
验证时先构造两个错误:未勾选承诺和功率留空。修复功率后应仅剩承诺错误,再将类别改成文创应清空承诺错误。这种状态跃迁比单独测试一个 required 属性更能证明规则正确。
逆序验收。 把正常操作反过来执行,例如先触发业务规则,再补齐字段规则所需要的条件。若界面没有把错误留在错误位置,或者隐式使用上一轮结果,就说明阶段约束并未完全落实。真正的测试应覆盖操作顺序变化,而非只按产品示例步骤点击。
重复验收。 连续两次执行业务规则,再执行错误映射,检查第二次动作是新意图还是前次动作的继续。对于规则聚合尤其要明确重复处理规则;“按钮点了两次”是用户行为事实,并不自动意味着应当产生两份业务副作用。
错误恢复。 在错误映射成功、错误定位尚未完成的位置构造异常。应能说明数据暂存在哪里,用户看到何种反馈,是否允许重新触发,以及什么条件下必须停止。不要为了追求一张好看的页面而隐藏正在等待确认的真实状态。
跨版本核对。 在数据和配置持续演进的前提下,固定一份输入,记录预期输出;随后修改不相关的界面颜色或布局,再运行同一用例。如果结果变化,说明规则聚合与展示层之间出现了不应存在的隐性耦合。
留下一个可维护的边界
只有在错误集合稳定后,“滚动定位到第一项错误”才有意义。将错误排序交给字段 schema 的展示顺序,不要按对象键随意遍历;一旦字段隐藏,错误定位也应重新计算。
本篇可用以下结果作为验收目标:连续修复两处错误后,错误计数会单调下降且不遗留旧提示。检验时先从固定输入入手,再加入中途切换、重复操作和失败恢复。可解释的错误结果也属于正确实现的一部分;没有必要为了让每个步骤都显示绿色而悄悄跳过不满足的前置条件。
调试不应从增加大量打印开始。先把输入变更 → 字段规则 → 业务规则 → 错误映射 → 错误定位按顺序列出来,每次只在边界处记录不含个人信息的阶段与结果类别。这样即使没有真机性能指标,仍能通过构造输入与检查返回值定位哪一步发生分歧;反过来,如果只看最终页面颜色,就难以证明中间没有错误。
关于不可用布尔值是否为 false 判断必填,因为零和明确的否也可能是合法答案这一风险,建议把它写进实际测试用例的名称和断言,而不是只放在代码注释。注释会随着重构失效,测试中的输入与预期结果更容易暴露行为变化。测试本身同样要避免依赖随时变化的网络与时间,否则会把产品故障和测试环境偶然因素混在一起。
当方案需要扩展,优先问“新需求将改变哪个阶段”。如果它只改变业务规则,便不应触及后面的错误映射;如果它改变最终业务语义,规则层和序列化层才需要一起调整。这个决策方式能够避免每加一个按钮就复制一份流程逻辑,也使连载中的代码可以逐篇成长。
对于业务异常,提示文案至少要回答当前状态、可以继续的动作以及不能继续的原因。市集经营时段和电源申请的联合校验里的用户通常不关心内部类名或错误码,但需要知道是等待、重试还是修改输入。把工程错误类别映射为用户可理解的行为建议,比把原始异常堆栈放在对话框里更合适。
交付的验证证据应当与主张相称。纯函数逻辑可以使用固定输入输出来证明;界面效果只能由真实运行或明确标注的示意展示;网络限流、签名、权限等涉及外部系统的结论,需要在对应环境中完成复核。不要把静态检查结果提升为已经完成真实设备测试的结论。

这类问题的适用边界同样清楚:单字段格式校验只是第一层,跨字段业务不变量才决定能否提交。当具体 SDK、设备能力或服务端协议发生变化,应重新查看对应的官方接口约束,再重新跑上述场景验证,而不是根据一张示例图推断系统行为。
官方资料
更多推荐

所有评论(0)