那天我先问的不是“这个目录里有多少部电影”,而是一个更容易被忽略的问题:一个明确不能作为片源的目录,是否已经被严格排除?
这两个问题听起来相近,实际差得很远。前一个问的是系统有没有边界,后一个问的只是当前结果长什么样。当前列表里没有某个目录,并不能说明它以后不会被扫到;也不能说明换一个入口、换一种搜索方式,系统仍然会拒绝它。
这次被标记为禁区的,是一个不应进入片库候选的目录。为了公开表达,我把它称为“禁区目录”。当时的重点不在于公开它的真实位置,而在于确认这条规则有没有真正进入系统。后续核对显示,相关目录此前确实没有被收进片库,但原系统也没有一条明确的硬禁止。没有发生问题,不等于已经具备防止问题发生的能力。
先区分“没出现”和“不能出现”
我一开始最警惕的,就是把结果当成规则。检查索引时,如果发现禁区目录的条目数量是0,很容易顺手得出“已经排除”的结论。可这个0只能说明在那一次生成的结果里没有它,不能说明扫描器、搜索器和切集流程都认识这个禁区。
“当前没有被收录”是一次观察,“以后不允许被接触”才是一条规则。
这个区别为什么重要?因为片库通常不只有一个入口。可能有默认根目录,也可能有人临时指定一个目录重新扫描;可能有索引搜索,也可能有根据路径直接进入切集的动作。只在某个列表生成时删掉一批路径,相当于把门口看见的人请走,却没有告诉其他入口这里不能进。
如果不处理,后果不是某一条结果多出一行这么简单。禁区里的文件可能被当成候选片源,进入切集方案,接着被生成任务或发布流程继续引用。等到最后才发现来源不对,返工的范围已经从一条路径扩大到索引、方案表和产物。越靠后才拦截,越难判断哪些结果已经受到了影响。
追问数量,是为了看清边界
在确认排除关系之后,我继续核对目录里到底有多少部电影,以及其中有没有重复。这一步不是偏离主题,而是为了知道风险的规模,也为了避免把“严格排除”误解成“把所有看起来相似的内容都删除”。
核对结果里,原片库大约有648个视频文件,真正可作为电影处理的规模约为647部;其中还发现4组需要单独判断的重复情况。另有电视剧、访谈、纪录片系列和分卷文件,不适合直接按电影进入切集。这些数字来自当时的索引状态,后续还需要结合发布台账判断哪些已经做过。
这些细节让我更清楚地看到,目录禁区和内容清洗是两类问题。重复片要判断是否保留,电视剧要判断是否剔除,已经发布的片要和台账比对;但禁区目录不需要先参加这些判断。它在更前面就应该被判定为不可接触。
边界规则要先于内容判断,不能把“禁止进入”降级成“进入以后再筛”。
如果先扫描、再去重、再判断类型,禁区里的数据已经穿过了好几层系统。哪怕最后从总表里删掉,也无法自然证明搜索缓存、临时结果或其他入口没有留下它。真正稳妥的顺序,是先判断来源是否有资格进入,再对有资格的内容做数量、重复和切集分析。
三个入口,三种不同的漏法
这次处理时,我没有只盯着生成列表的那段逻辑,而是把所有可能接触片源的动作拆开看。第一个入口是根目录指定。如果用户直接把禁区目录指定为片库根目录,系统应该立刻报错,而不是开始扫描后再期待后面的过滤器补救。
第二个入口是扫描遍历。即使一个更大的根目录包含了禁区目录,遍历过程也应该识别它并跳过,不能因为它是根目录下面的子文件夹,就默认它有资格被继续读取。根目录校验解决的是“从这里开始不行”,遍历跳过解决的是“走到这里也不行”。
第三个入口是已有数据的使用。搜索结果和切集入口不能因为某条路径已经出现在索引里,就把它当成可信输入。于是,碰到包含禁区标记的路径时,这两个入口也要拒绝。这样做看起来像重复判断,但每一层面对的输入不同,能阻断的旁路也不同。
我选择把禁止条件放进这3个位置,是因为它们分别覆盖了输入、过程和动作。只改一个共享列表,代码可能更短,却把安全性寄托在所有调用者永远遵守同一个前提上。真实系统里,入口会增加,旧数据会残留,临时调用也会出现;规则如果只停留在一个结果层,新增入口就可能自动绕过它。
错误捷径是只修结果,不修路径
最容易误判的做法,是在生成总表时把一批路径删掉,然后认为任务完成了。这个方案短,也可能让当前验收立刻变绿,所以很有诱惑力。但它解决的是“这次输出不显示什么”,没有解决“以后哪些动作永远不能做”。
另一个错误捷径,是只在扫描器里加排除,默认搜索和切集会沿用扫描器的判断。可搜索可能面对旧索引,切集可能直接接受一条路径;它们未必经过同一个函数。把希望寄托在调用链“应该会经过那里”,本身就是没有验证的假设。
我没有选择只保留一条全局规则,是因为这次要守住的是来源边界,不是优化一份列表。**防线重复不是代码不优雅,而是不同入口拥有不同的失误方式。**如果未来确实能证明所有入口都经过同一个不可绕过的校验,再考虑收拢实现;在证据不足之前,分层拒绝更符合风险大小。
这件事让我重新意识到,系统中的“禁止”比“允许”更需要被写成动作。允许往往只要找到一条能跑通的路径,禁止却要问:谁能指定输入?谁能遍历目录?谁能调用旧结果?谁能把路径交给下一步?不把这些问题逐一列出来,禁止就只是文档里的态度。
用正向、负向和错误路径验收
规则加进去以后,验收不能只证明正常片源仍然能工作。正常路径是正向结果:合法目录可以继续被识别,片库原本的处理能力没有被一刀切掉。禁区路径是负向结果:扫描时不出现,搜索和切集入口也不接受它。指定禁区作为根目录则是错误路径:系统要明确报错,而不是悄悄返回空结果。
当时的检查显示,相关目录此前在索引中是0条;新增拦截后,指定为根目录会直接报错,扫描会跳过,搜索和切集入口会拒绝。两台环境的38条测试全部通过。这个结果比“我看了一眼列表,没发现它”有更强的证明力,因为它同时覆盖了结果、过程和失败行为。
后续的切集方案也提供了一个规模参照:美国片库共有648个视频文件,其中557部可切,共2921集。这个数字并不是禁区目录的内容,而是说明在完成边界隔离后,剩余片库仍然可以继续进入正常的规划流程。先隔离不该处理的来源,再统计可处理的内容,数量才有意义。
我会特别保留“指定根目录直接报错”这一类测试。静默返回空结果看起来更温和,却可能让使用者误以为目录只是暂时没有文件;明确报错则把边界告诉调用者,也让自动化任务有机会停止,而不是带着错误前提继续向后走。
下次我会怎么做
下次遇到“这个目录是否被严格排除”之类的问题,我会先把它改写成一张入口清单:它能不能被指定为根目录,能不能在遍历时被读到,能不能出现在搜索结果里,能不能被交给切集或其他后续动作。每一个“能不能”都要对应一个可观察的通过或拒绝结果。
然后我会先查当前结果,再查规则是否存在。结果为0只能作为现状证据,不能替代规则检查。若发现只有列表过滤,没有输入校验和动作拒绝,就把问题定义为“当前没出事,但边界没有落地”,而不是宣布已经完成。
最后,我会用3类测试收口:合法输入继续工作,禁区输入不进入结果,明显错误的调用得到明确报错。对于涉及片库、索引或内容生产的系统,还要把旧数据入口单独列出来,因为历史结果往往是新规则最容易漏掉的地方。
一个流程成熟的标志,不是它能顺利跑通一次,而是它能在下次少依赖临场记忆。**真正可靠的目录禁区,不是列表里暂时少了一条路径,而是每个接触数据的入口都知道这里不能通过。**这条规则以后也可以复用到备份、发布、训练素材和其他外部数据:先定义不可接受的边界,再枚举所有入口,最后用正向、负向和错误路径证明没有旁路。