尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

检查存在,不等于检查在岗

发布时间:2026/9/28 18:02:26

资讯中心
01
ARTICLE

检查存在,不等于检查在岗

检查存在,不等于检查在岗
检查存在不等于检查在岗2026-09-26 · 工业上位机开发笔记今天一整天的活表面上是三件事底下是同一句话一个检查有没有用不看它「有没有」看它「长在哪条路上、挂在什么上」。一件是有人一句「超了限位也没提示」牵出一整类从来没系统查过的防呆缺口。一件是一个用了很久的故障判据被拿到另一天的数据上又跑了一遍 —— 零反例。一件是一次专项考讲评把上面这两件事抠成了一套能说出口的方法。序 · 今天聊了什么#事一句话1防呆的两副面孔「失败被吞」和「该拦没拦」是两个问题2拦截长在哪条路上同一个危险动作有 N 个入口拦截只长在其中一两个上3⭐ 怎么把这种缺口扫出来信号是对称性不一致抄送了五个人总有一个漏了4⭐ 判据要挂在什么上两条禁令 一条正解挂在「状态迁移」上5假阳性与假阴性一对跷跷板改一边最容易顺手带出另一边6证据的三种强度指纹 结果 时间7两个数据源对不上先比时间覆盖再怀疑事件8计数要带样本窗同一句「N 次」换了窗口差一个量级9结论能说多远代码层判不了的事不许对外说10语法条件编译 / 前置守卫 / TryParse / 断言今天真正用到的 C#第一部分 · 概念防呆的两副面孔1.1 先把两件事分开防呆这个词过去我们用得太笼统把两种完全不同的问题混在一个词里┌──────────────────────────────────────────────┐ │ ① 失败被吞 出了事事后看不见 │ │ ② 该拦没拦 事还没发生事前没人挡 │ └──────────────────────────────────────────────┘①是可见性问题②是覆盖问题。一个系统可以把①修得很好 —— 异常全都记了日志、弹了提示、上报了 —— 而②一处都没有一个本来就不该被允许的动作照样一路下发给设备。而且这两类问题的修法完全不通用①的修法是「别吞异常、别只记不报」②的修法是「在动作的入口上装守卫」。用①的眼光去看②你会得出「我们做得挺好的」这种结论因为异常确实都被记录下来了 —— 记录的是「动作执行失败了」而不是「这个动作本来就不该执行」。比喻①相当于楼里装了监控出了事能回放②相当于门口该有的门禁。监控再全也拦不住一个本来就不该进的人 —— 它只负责事后告诉你「他进来过」。反过来门禁再严也替代不了监控进了门之后发生的事门禁一无所知。这两样东西不是一回事也不能互相替代。今天这次扫描专门去查②。它此前从来没被系统查过 —— 之前几轮审计查的全是①。1.2 拦截长在哪条路上第一章的例子一个「危险动作」有几个入口┌─► 入口 1手动点一下──► [ 有前置检查 ✅ ] ──► 执行 危险动作 ◄──────────┤ └─► 入口 2自动流程跑──► [ 无 ❌ ] ──► 执行 ↑ 拦截长在「谁点的」那条路上 不长在「这个动作」本身上。手动那条路有人盯着出事了自己负责所以当年顺手加了检查自动那条路是机器自己跑的没人看反而没有。比喻一栋楼三个门只在正门装了读卡器。装门禁的人汇报时完全可以诚实地说「我们装了门禁」—— 这句话一个字都不假但它挡不住从侧门进来的人。「装了」和「挡住了」之间隔着「装在哪」。这个例子最扎人的地方在于它不需要谁偷懒也能自然长出来。手动路径是给人用的写的人会本能地想到「人要犯错」自动路径是机器用的写的人会本能地假设「机器不会乱来」。两个假设都合理合起来就是一个洞。1.3 ⭐ 找这种缺口的方法对称性不一致这类缺口怎么系统性地找今天摸出来一个核心信号同一个校验、同一个前置在被复制到 N 条同族路径上时总有一条漏了。「同族」是关键 —— 不是漫无目的地读代码而是专挑那些在仓库里出现了一次以上的检查然后反查跟它同族的其它路径有没有跟着做。执行手法四步① 先建「正向基线」 全仓找仓库里已经做对的拦截长什么样 找那个明确抛异常、明确拒绝的写法 │ ▼ ② 挑其中「重复出现」的校验 同一个校验在两个地方各有一份 → 那它是被当模板抄过的 → 反查还有第三条同族路径吗 │ ▼ ③ 对「危险动词」全仓穷举 把「能让设备动 / 能让射线源亮 / 能写参数」的那几个调用点全列出来 → 逐个看它前面有没有前置 │ ▼ ④ 追到「最后一层」才定性 上层没查可能是「调用方已保证」 追到真正把参数写下去的那一层还没查才是事实。第④步今天特别值钱。同一条调用链上前面几层都还能辩解 ——「上层已经保证过了」「这里只是转发」。只有追到真正动手的那一层才没有辩解空间参数直写、立刻启动、中间一个判断都没有。而同一个类里软限位的属性就明明白白摆在那几行只是那两个定位方法一次都没读过它。比喻不是「家里没有灭火器」是灭火器挂在墙上、标签朝里、从来没被谁伸过手。有和用之间差的是一次接线。未命中也要如实记。这次扫的四个方向里有一个方向某个角度参数的范围校验查下来是做对的—— 统一在一个 helper 里两条调用路径都老老实实调了它。不是所有对称性猜测都成立只报命中的那次扫描是没法判断它准不准的因为你不知道它一共猜了几次。1.4 三个条目与危害排序这一轮实扫出三个条目两条证据已经坐实、一条只有疑点条目缺的是什么证据强度危害A一个界面上输入的定位目标值超出行程范围全程无拦截、无提示照样下发✅ 已坐实撞机械B一个安全联锁只长在手动路径上能触发它的若干入口里多数没有前置检查✅ 代码层已坐实硬件侧有无独立回路未知人身安全C一处联锁的测试断言与源码对不上 —— 疑似测试根本没在跑⚠️ 待实测掩盖 A/B 类问题排序按危害B A C。排序本身就是一条方法论按后果分不按「哪个更容易改」分。B 的后果是人A 的后果是设备C 的后果是「你的检查手段本身可能失效」—— 它不直接伤人但它会让 A 和 B 在自动检查里静默通过。C 这一类最容易被忽略因为它不长得像 bug// 测试里写着示意Assert.Equal(0,counter.CallCount);// 断言一下都没调而源码里那句联锁调用是活的按源码推演这个计数应该等于 1。断言和实现对不上只有两种可能① 代码后来恢复了联锁、断言忘了改回来②这个测试项目根本没在跑。两种可能都不下结论标「待实测」。因为往下猜一步就是在编。比喻这是烟雾报警器的年检表上盖着「通过」但报警器其实没通电。表是真的、章是真的、检查也是真的 —— 只是检查的对象和它声称的不一样。1.5 一处自我留痕今天我在这一部分犯过一个错值得留一笔一开始我看到两个计数对不上一个数据源报的多、另一个报的少得出的结论是「证据可疑但不充分」。错的不是数字是判断方式—— 我没有先去按已有的分型判据把这几条记录分个类就把「数量少」当成了「判据不可靠」。这件事的直接教训放在 3.2 讲。这里只留一句「数字对不上」和「判据不成立」之间还隔着好几步推理不能一步跨过去。第二部分 · 概念判据要挂在什么上上午那一轮是在复验一个用了很久的故障判据。结论是「零反例」但过程里有一整套判断标准值得单独讲 —— 它比结论本身通用。2.1 两条禁令 一条正解一个用来判断「正常 / 异常」的判据不能挂在什么上❌ 禁令 a不许挂在「每轮都有」的标记上 → 这个标记正常轮也打印、异常轮也打印 → 它区分不了好坏只会把真信号淹掉 → 症状误报假阳性 ❌ 禁令 b不许挂在「新版本会删掉」的标记上 → 某个版本之后这个标记不再打印了 → 判据变成「条件恒真」→ 永远报「设备没问题」 → 症状看不见的失灵假阴性 ✅ 正解挂在「状态迁移」上 → 本该从 A 走到 B结果却变成了 C → 只挑出「没按剧本走」的那些轮禁令 a 今天的原话可以更狠一点判据挂在了一个「每轮都会出现」的标记上—— 那就等于每轮都命中等于没有判据。禁令 b 是我今天亲眼撞上的换过一个版本之后判据里依赖的那个标记不再打印了。「没有它」这件事于是永远为真判据欢快地报了一整段时间的「无故障」。这是最阴的一种坏法 ——它不报错它报平安。比喻家里装了个火灾报警器结果它对着烟囱禁令 a—— 天天响真着火的时候没人信或者它接在了一根明天就要拆掉的线上禁令 b—— 灯是绿的纯粹是因为它已经感受不到任何东西了。正解为什么是「状态迁移」因为状态迁移是「本该」和「实际」的差它天然带着一个对照组 —— 正常的轮长什么样、这轮长什么样一比就出来了。而单个标记只告诉你「它出现过」不告诉你「它出现得对不对」。2.2 ⭐ 一个判据的复核长什么样今天这个判据的复核过程本身就是它值得信的理由① 它原本是在一段时间的数据上得出的有它的样本窗 │ ▼ ② 今天换了一份「完全在它样本窗之外」的数据来跑 │ ▼ ③ 结果该命中的全命中该排除的全排除零反例 │ ▼ ④ 再用一个「完全不依赖这个判据」的独立算法数一遍 用两个总量相减看看差出来几个 │ ▼ ⑤ 两条路数出来的结果互相吻合 —— 这才叫「互相校验」第④步是关键。如果只有一个算法给出了「异常几次」这个数你没法排除这个算法本身有问题。但换一条完全不同的路再数一遍两条路对上了可信度就不是加一倍是换了个量级。比喻一个人报账说本月花了 100 块。你让他把每笔小票拿出来对同一份数据的两种读法—— 对得上是内部一致你自己再翻一遍流水另一条独立的路径—— 也对得上那才叫复核。只有内部一致没有独立复核两个人一起错的可能性是存在的。2.3 假阳性与假阴性是一对跷跷板今天三处地方都撞上了这个跷跷板判据太松 判据太紧 挂在每轮都有的标记上 挂在会被删掉的标记上 │ │ ▼ ▼ 误报淹没真信号 真的故障一条不报 假阳性 假阴性 │ │ └──────────► 你要的那条线 ◄────────┘ 状态迁移你今天往哪边修就最容易顺手带出另一边的毛病。所以改判据之前先问自己一句「我这次到底是在修哪个方向」判据紧的那版死了假阴性你今天把它放松一点 —— 那你要防的是假阳性不是又把它做回去了。今天讲评里有道题打的就是这个一个判据只覆盖了绝大多数形态还差最后一种。正确答案不是「它有噪声」也不是「把那一例剔掉」而是「它还没覆盖全部形态只能算部分可用」—— 差的那一例是信息不是噪声。顺带一条口径报成绩的时候先报排除、再报命中。先说「全部无害样本都被正确排除」再说「全部致命样本都被命中」—— 顺序反了听的人第一反应是「那你误报了多少」。2.4 判据要按版本分段一个之前在用的判据为什么今天差点失效因为它依赖的标记在某个版本之后不再打印了。把几轮排查拼起来得到一个通用结论时期可用判据旧版本依赖那个「进曝光」标记 一个状态标记两个都缺换 SDK 之后不能用那个标记了改用「等不到某个状态就直接回退就绪」这一条换固件之后某条警告整个消失出现另一条超时提示那条不是故障是一次超时后的自动重发补回通用怎么换都不依赖「回退了就绪却不出图」这个状态迁移本身任何判据换版本后都要先在旧数据上回验一遍再往新数据上套。今天正是这条救了一次场先用旧判据在旧数据上跑出零反例才敢确认「它在旧版本区间里是有效的」然后才谈「换版本之后怎么办」。比喻老式温度计靠水银柱电子温度计靠热敏电阻。你换了一支表不能拿新手表的刻度去读旧水银柱的数据 ——也不是新手表坏了是它俩量的不是一个东西。2.5 找对了观测手段 ≠ 堵住了故障今天还多找到一个观测指标某条错误信息是逐帧打印的所以它出现的条数 这一轮实际到达的帧数。这个指标好用在哪它是计数型的不是「某一行出现过 / 没出现过」的单点标记。正常跑满的一轮条数 该轮的帧数出故障的那一轮只剩上一轮尾巴留下的那一帧 →条数很少但不是零。「不是零」这一点很重要 —— 它给出的数字能拿去和另一侧的遥测互相对账两侧各说各话的时候能立刻发现。但必须把话说死这是一个观测手段不是一个修复手段。它让你看见得更清楚它没有让故障少发生一次。比喻你换了一支更准的体温计你量出来的发烧温度更可信了 ——但体温计不负责退烧。别因为「现在能看清楚了」就把这件事标记成「进展」。第三部分 · 概念结论能说多远3.1 证据的三种强度指纹 结果 时间同一个结论可以靠三种不同强度的证据支撑时间相邻 结果变好 指纹相同 弱 中 强 「换完它之后 「换完之后 「两份日志除了时间戳、 就没再犯过」 确实好了」 线程号之外逐字相同」时间相邻最弱。今天正好有个反例 —— 两个变化、两个时间点如果只看「换完之后好了」很容易把功劳记错给其中一个。要一一对上哪个变化出现在哪个时间点之后。结果变好中等。但「好了」有可能是因为别的变量一起变了。指纹相同最强。同一个缺陷每次发作时留下的痕迹逐字相同—— 这几乎就把「随机性」排除掉了可以定性为确定性缺陷。比喻破案的时候「他最后跟受害人吃过饭」是弱证据时间相邻「他换了一辆车之后就再没出过事」是中等证据结果变好「现场指纹是他」才是强证据指纹相同。前两个能让你锁定嫌疑人只有第三个能让你定案。今天用这条工具把一个「反复出现的同类事件」定性了下来它们留下的痕迹除了时间戳和线程号之外逐字相同—— 所以不是「碰巧又坏了」是同一个缺陷在反复发作。反面用法也成立要说「这不是巧合」你就得能说出「正常轮之间的日志是有个体差异的」—— 有对照组差异才叫差异。3.2 ⭐ 两个数据源对不上先比时间覆盖今天解开的那个「表面矛盾」值得单独讲设备侧日志 ████████████████████████ 一直是满的 上位机日志 ████████████░░░░░░░░░░░░ ██████████ ↑ 这一段没有日志某一侧说那天发生了异常另一侧的记录里却没有。第一反应几乎必然是「设备侧多报了一次」或者「数据源不可靠」。正确的第一动作却是先去比两边的时间覆盖。一比就明白了上位机那份日志那天的某段时间完全空白文件按大小滚动两头都截掉了。那一次异常正落在这个洞里。而且把全盘的日志都翻了一遍这段时间的记录别处没有副本—— 也就是说那一笔是永久缺失不是「没发生」。这条教训要写死两个数据源计数对不上时先查两边的时间覆盖再怀疑事件本身。「一边说发生了、另一边说没有」最常见的解释是另一边在那段时间没有日志不是说有的那边误报。比喻两个人对账一个说这个月有 30 笔支出一个说 29 笔。先别急着说谁记错了 ——先看看第二个人那本账本是不是中间缺了几页。同类坑今天还有一个镜像版只扫一个目录漏掉了别处的记录。这次是目录扫全了但时间轴上有洞。3.3 计数必须带样本窗今天定了一条新规矩我觉得它会长期有用任何一个计数必须同时写清「哪段时间、哪些日志」。原因很直白 —— 同一句「N 次」换了窗口可能差一个量级。举个跟本行无关的例子某个服务上线后统计「报错次数」说法样本窗和上一行是同一件事吗「累计报错 42 次」上线至今 8 个月—「本周报错 6 次」最近 7 天与上一行完全不同的区间不是「昨天出现了 2 次」单日全天而且落在这两个窗之外不是三句话可以同时成立。但如果你把它们抄进同一张表、只留「次数」那一列读的人会以为它们在互相对账 —— 于是「总数对不上」这个假矛盾就诞生了。不写窗口这些数字互相之间没法核对也没法证伪。一个不可复核的数字在报告里的价值接近于零 —— 甚至比零更糟因为它看起来是可复核的。比喻「我这个路口从来没出过事故。」这句话得先问一句「这路通车多久了」——通车三天没出事和通车三十年没出事是两句话。顺带一条同一件事的两个数对不上时先看哪个数更旧。今天就有一次是旧快照里落下的那一笔 —— 后来补齐了旧数没跟着改。这类「早于当前口径的旧快照」是数字类记录里最典型的过期形态。3.4 结论能说多远代码层判不了的事不许对外说B 条目那条发现我写下来的时候专门加了一段边界声明大意是本机代码无法判定设备侧有没有独立的安全回路。工业设备的常见做法是把这类联锁做在独立于上位机的硬件回路里 —— 那种情况下上位机软件不需要、也不应该参与。若硬件侧已有独立回路 → 上位机缺这一层属于冗余缺失 无提示风险降级为「体验差、拦不住无效作业」若硬件侧没有 → 那是最高等级的后果。必须先现场确认再定严重性。在确认之前不得对外声称结论。这段声明不是自我保护的套话它是结论正确性的一部分。代码能看到的是代码看不到的是硬件 ——把「我没看到」说成「它不存在」是这类审计里最容易犯、后果最重的一个错。比喻你在自己家查线路发现你家配电箱里没有保险丝。这不能推出「整栋楼没有保护」—— 总闸可能在楼道的配电间里你看不见。你要说的是「我这侧的配电箱里没有」然后去楼道看一眼再下结论。3.5 口径纪律讲评里抠出来的四条这一部分是讲评里最像「表达技巧」、其实是判断力的一部分的东西#纪律为什么1主动补限定词「到目前为止没有出现」后面主动跟一句「仍在观察中」。自己先说比被追问再补可信度差一个量级2报可核对的原始量不报自己推的派生量「跑了 N 轮」比「折合 X 个工作日」强 —— 后者是你算的别人复核不了3该保守的地方不给向好结论「还没查清」不许说成「可以忽略」影响面不做小4责任切分先认下自己那段分出「我这侧的」和「不在我这侧的」不在我侧的那段推不动从来不妨碍我改我自己的这段第 4 条今天有个很直接的形态一个问题被分成两条独立的线 —— 一条挂在我方发出的命令之后的日志序列上另一条挂在设备状态跳变出现在命令之前上。方向相反所以时间先后本身就是责任判据。两种处置也不同一条自己改而且已经改完了另一条推不动只能把问题整理好递出去。关键是不因为「那条我改不了」就停在这 —— 我这一侧的窗口是我能关掉的。比喻水管漏水一段在你家、一段在楼道。楼道那段你确实拧不动 —— 但这不妨碍你今天就把家里那段先拧紧。「另一半不归我管」是事实不是停工的理由。3.6 一个小的命名纪律讲评里被一句反问逼出来的一条动词的主语是谁要说清。我们内部说「取图 / 收图」但设备那一侧的动作其实是「曝光」。「取图」是我方在做的事「曝光」是设备在做的事 —— 混着说讲到根因的时候会自然地把责任归错边。比喻说「我们收不到货」和说「对方没发货」是同一个事实的两种说法 ——但追责的时候这两句话指向的人不一样。命名即归因。第四部分 · 语法今天用到的 C#这一部分才是今天真正落到键盘上的东西。上面所有的概念最后都要变成这几行语法。4.1#if条件编译 —— 编译期剔除 ≠ 运行期不执行今天判据失效的那个坑一半来自这个语法。// 示意类名、符号名、方法名均为示意publicvoidOnFrame(IntPtrbuffer){Dispatch(buffer);// 每帧无条件走到这里#ifSAVE_PREVIEWSavePreview(buffer);// 只有定义了 SAVE_PREVIEW 才会被编译进去#endif}为什么详讲这个点#if是预处理指令它在编译之前就被处理完了。没有定义SAVE_PREVIEW的时候SavePreview(buffer);这一行根本不会进入编译产物—— 它不在 IL 里不在调试符号里运行时没有任何痕迹。它和if的区别是根本性的#ifif生效时间编译期运行期代码还在吗不在了还在只是不执行调试器能看到吗看不到能看到断点仍可下典型用途不同构建配置走不同代码运行时的条件分支什么时候用需要在构建期就决定「这块代码要不要」的时候 —— 分不同发行版、去掉不想要的调试功能、按目标平台裁剪。这时它比if好因为不想要的代码真的不会被打包进去体积和性能都省下来。常见坑①诊断看不见它。线上排查时你会理所当然地以为「这段逻辑在跑」其实它压根没被编进去。②两边都要能看到同一个符号在不同构建配置下不一致就会「我这里好的、你那里坏的」。③别拿它当开关真正需要运行时切换的用if或配置项用#if会把「配置」变成「另一个二进制」。回到今天的场景判据里依赖的那个标记是在某个版本里被条件编译掉了或者等价地源码里那一段被注释停用了。判据没有报错它只是失去了感知能力然后一路报平安。—— 这正是 2.1 那条禁令 b 的语法层成因。4.2 前置守卫 vs 异常兜底今天那条「超限位无拦截」的根因用两个写法一摆就清楚了// ❌ 异常兜底先发出去出错了才提示try{awaitaxis.MoveTo(target);}catch(Exceptionex){ShowMessage(ex.Message);// 事后通知}// ✅ 前置守卫根本走不出发送这一步if(targetaxis.LimitMax||targetaxis.LimitMin){thrownewArgumentOutOfRangeException(nameof(target),目标位置超出行程范围);}awaitaxis.MoveTo(target);为什么详讲这个点这两种写法的差别不在「有没有提示」在「提示发生在动作之前还是之后」。catch里的弹框是事后通知—— 动作已经执行了副作用已经产生了。这是 1.1 里「①失败被吞」和「②该拦没拦」在语法层的分界。异常类型怎么选是有讲究的异常类型什么时候用ArgumentOutOfRangeException参数的值超出允许范围越界、负数、超上限ArgumentNullException参数是nullInvalidOperationException参数没问题但对象当前状态不允许这个操作没连接、没初始化、顺序不对自定义异常调用方需要单独 catch 它并做特殊处理时选错类型的代价调用方没法可靠地区分「我传错了」和「时机不对」。常见坑①catch (Exception ex)一把抓会把「调用方 bug」和「设备故障」混成一类上游分不清该重试还是该改代码。②只在 catch 里记日志、不重新抛等于把异常吃掉这就是①类问题的语法层写法。③ 守卫要写在最靠近危险动作的那一层—— 写在上面几层中间任何一条新加的路径都可能绕过去这正是 1.3 里「追到最后一层才定性」的原因。4.3int.Parsevsint.TryParse扫描时顺带捞出来的一处// ❌ 非数字输入直接抛异常varnint.Parse(text);// ✅ 解析失败走正常分支不抛if(!int.TryParse(text,outvarn)){ShowMessage(请输入数字);return;}为什么详讲这个点两者都是 .NET 标准库的解析 API区别在失败时怎么表达Parse用异常表达失败—— 失败是「异常路径」TryParse用返回值表达失败—— 失败是「正常路径」。out参数是这里的语法重点out var n是 C# 7 起的内联声明写法等价于先在调用前声明int n;再传out n。out的语义是由被调方负责赋值所以方法内部所有路径都必须给它赋过值编译器会检查 —— 这是它比「返回一个int再用魔法值表示失败比如返回-1」可靠的地方。怎么选用户输入、外部文件、网络报文这类「失败是常态」的场景用TryParse由代码保证一定合法的内部值用Parse更简洁失败就是真 bug让它抛。常见坑① 用Parse处理用户输入等于把「输错一个字母」变成一次异常 —— 有性能代价而且会把真正的程序异常淹掉。②TryParse返回false时不能直接用那个out值它是默认值0必须先判断返回值。顺带说回今天的场景这一处的写法问题非数字输入无校验、直接抛和「超限位无拦截」是同一类—— 都是把校验交给了「出事之后」。4.4 属性摆着没人用不是没实现是没接线今天最有代表性的一个代码形状// 示意classAxis{publicdoubleLimitMax{get;set;}// 一直就在这儿publicdoubleLimitMin{get;set;}publicTaskMoveTo(doubletarget)Send(target);// 从没读过上面那两个}为什么详讲这个点这个写法不报错、不警告、编译通过。LimitMax是个自动属性有 getter 有 setter谁都可以读写它 —— 只是MoveTo一次都没读过。也就是说能力在接线不在。排查的时候很容易误判成「这个功能没做」其实是「做了但没人用」—— 这两种情况的修法完全不同前者要写后者只要接上。自动属性{ get; set; }的语法要点编译器自动生成一个私有匿名字段做后备存储。它和手写字段的区别只在于「你要不要自己控制读写逻辑」—— 需要加校验、加通知INotifyPropertyChanged时才展开成手写。常见坑①软限位这类东西写成可写属性本身就是个口子—— 谁都能把它改掉。真正想让它可靠要么在 setter 里加校验要么干脆做成只读、由配置初始化。② 更根本的一条限位应该在「最底层那个真正下发参数的方法」里检查一次4.2 的第③条—— 放在任何上层都是在赌「没人绕过我」。4.5 断言是「期望」不是「事实」C 条目那一处的形状// 测试里写的示意Assert.Equal(0,counter.CallCount);// 期望一次都没调用为什么详讲这个点Assert.Equal(expected, actual)的参数顺序是「先期望、后实际」—— 反了的话测试失败时的输出信息会误导你它会把「实际值」当期望值念出来。有些框架提供Assert.Equal(actual, expected)的重载用的时候要认准自己这套。更重要的一条断言写的是你以为会发生什么不是实际发生了什么。这两者不一致的时候不代表「代码错了」只代表「有一边过时了」—— 可能是代码变了断言没改也可能是这个测试根本没被执行。为什么单列这一条这不是「某个功能没做防呆」这是防呆的检验手段本身可能失效。它会让 1.4 里那类问题在自动检查里静默通过。常见坑① 把测试项目当成「一定在跑」的东西 —— 要看它有没有进解决方案、有没有进 CI。②测试项目没进解决方案是今天另一个元级发现有些工程文件压根不在.sln里那么「编译整个解决方案」就从来不会碰它们。回到 1.4 的比喻年检表上盖着「通过」但表上的这一行对应的是另一台设备。4.6 附 · 两条工具语法这两条不是 C#但确实是今天的工作现场单列在这里。一日志编码要按文件探测不能统一假设# 示意拿一个只可能出现在该文件里的中文串去探编码probe开始rawopen(path,rb).read()ifprobe.encode(utf-8)inraw:textraw.decode(utf-8)elifprobe.encode(gbk)inraw:textraw.decode(gbk)为什么要这样同一批日志里设备侧那份是 GBK上位机侧那份是 UTF-8。按一种编码统一去读另一份的所有中文串都会数出 0。最坏的地方在于它不报错—— 拿错编码去解码得到的是一堆乱码或替换字符程序照样跑完你会静默地得出一个结论「这天没有发生过那件事」。这就是 1.1 里说的「失败被吞」出现在你自己的分析脚本里。语法点str.encode(enc)把字符串转成字节串bytes.decode(enc)反过来判断「子串在不在」时要在同一类型之间比bytes in bytes拿str去in一个bytes会直接抛TypeError。二二进制日志先去\0再解码rawopen(path,rb).read().replace(b\x00,b)# 不去掉后面会中途停textraw.decode(gbk,replace)grep-a关键字app.log# 不加 -a含 \0 会被当二进制计数中途停止为什么日志文件里夹着\0字节很多工具会据此判定「这是二进制文件」然后在第一个空字节那里停下来—— 结果是计数偏小、而且不报错。语法点b\x00是bytes字面量前缀breplace在bytes上同样可用grep的-a是「当作文本处理」Python 里同样效果的保险做法是先用二进制读、先把空字节替换掉、再解码。收尾 · 今天最该记的三条「装了」和「挡住了」之间隔着「装在哪」。防呆不是「有没有」的问题是「长在哪条路上」的问题。同一个危险动作有几个入口只给其中一个装了守卫汇报时照样可以诚实地说「我们有守卫」—— 而那扇门是开着的。判据不许挂在「每轮都有」和「新版会删」的标记上要挂在「状态迁移」上。前者会把真信号淹掉误报后者会让条件恒真、一路报平安看不见的失灵。后一种最危险 —— 它不报错它报平安。两个数据源对不上时先比时间覆盖再怀疑事件任何一个计数都要带上样本窗。「一边说发生了、另一边说没有」最常见的解释是另一边那段时间没有日志。而一个不带窗口的数字看起来可复核实际上不可复核 —— 那比没有数字更糟。本文为学习笔记代码均为示意类名、方法名、参数名与数值均已泛化。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。