命名不等于发现
你现在了解七种MOAD模式。了解名称很重要:它让你能在看到时认出一个模式。但在受控课程中的识别不同于在你从未打开过的代码库中进行检测。
代码库不标记其缺陷。沉积MOAD不带有注释说// O(N²) — 修复这个。雷鸣羊群不会自我宣布为缓存丢失蹂躏。你通过带着一个具体问题阅读代码来找到它们:什么数据结构持有这些值,什么操作在循环内针对它运行?
检测是一种独立于识别的技能。 识别说:是的,那个模式是MOAD-0001。检测说:让我找到这个代码库中所有可能存在该模式的地方,无论我是否能看到完整的代码或仅仅是一个符号名称。
首次扫描
首次传递使用grep。每个MOAD都有一个基质:一种数据结构或API,其存在于某些操作附近时是一个值得调查的信号。
MOAD-0001(沉积):循环内的List.contains
# Signal: membership test on a list variable inside a loop
grep -rn '.contains(' src/ | grep -v HashSet | grep -v TreeSet
grep -rn 'visited =' src/ | grep -v set | grep -v Set
MOAD-0002(缠绕):跨阶段的共享可变标志
# Signal: static mutable field written by one subsystem, read by another
grep -rn 'static ' src/ | grep -v final | grep -v class | grep -v void
MOAD-0003(泄露的上下文):池化执行器中的ThreadLocal
# Signal: ThreadLocal.set() without guaranteed ThreadLocal.remove()
grep -rn 'ThreadLocal' src/
grep -rn 'ThreadLocal.set' src/ -l
MOAD-0004(记录的秘密):日志输出中的HTTP头
# Signal: log call with headers variable near auth endpoints
grep -rn 'log.*header' src/
grep -rn 'Authorization' src/ --include='*.log'
MOAD-0005(雷鸣羊群):没有同步的缓存丢失
# Signal: cache.get() + null check + cache.put() without lock
grep -rn 'cache.get' src/ -A4 | grep 'cache.put'
这些模式产生候选项,而不是确认的缺陷。每个候选项都需要分诊:阅读周围的代码,验证数据结构类型,确认操作在规模上运行。
阅读代码以理解复杂性
Grep找到候选项。阅读确认它们。当你打开候选文件时,你用一个问题阅读:这个操作的成本是否随输入大小增长?
对于MOAD-0001,确认协议:
1. 找到外层循环。什么界定其迭代计数?
2. 找到内层操作(.contains, .indexOf, 'in')。什么数据结构针对它运行?
3. 那个数据结构是否随驱动外层循环的相同输入增长?
4. 如果是:成本是O(N²),其中N=输入大小。确认的缺陷。
5. 如果否:内层结构是有界的(配置、枚举、小常数)。假阳性。
访问N个节点的图遍历,在每一步检查一个visited列表:循环和内层数据结构都随N增长。确认。
请求处理器检查5个管理员IP的允许列表:允许列表永远不随请求量增长。假阳性。
相同的协议适用于每个MOAD:识别外层驱动,识别内层结构,问两者是否一起规模化。
激增分数:优先处理你的发现
并非所有确认的缺陷都值得立即补丁。在有10,000个下游依赖者的库中的MOAD比在私有内部工具中的相同MOAD有更高的激增分数。
激增分数=加速×入度。 加速:修复在典型生产规模下运行得快多少?入度:有多少个下游包或服务在上游合并时会自动继承修复?
在Apache Maven的依赖解析器中确认的MOAD-0001,在50,000个节点的图上运行,有1,000+个下游Maven插件自动继承更改:激增分数非常高。这个修复属于你的队列前面。
在没有依赖者的单用户CLI工具中确认的MOAD-0001:激增分数接近零。值得修复,但不是紧急的。
工作狂vs贪吃鬼节点。 一个节点具有高中介性&高加速是工作狂:它处理关键流并在解除阻塞时刷新下游队列。仅在确认下游容量后修复它。一个具有高出度&低加速的节点是贪吃鬼:它消费提供给它的一切,感到无痛。在不为下游容量做准备的情况下修复工作狂会在基础设施规模创造MOAD-0005(雷鸣羊群)。
从扫描到合并:MOAD管道
一个具有高激增分数的确认缺陷经过一个管道。每个阶段产生一个工件。没有阶段是可选的。
scan → candidate list (grep output, static analysis results)
ticket → defect description (MOAD number, location, complexity analysis)
patch → code change (data structure swap, primitive adoption)
test → unit test (O(1) proof: time the fix at N=100 and N=10,000)
UNDF → public disclosure post (undefect.com, public domain)
disclose → CVE or CWE reference if security-relevant
PR → upstream pull request with patch + test + UNDF link
merge → maintainer acceptance; fix propagates via version bump
每个工件馈送下一个阶段。没有测试的补丁无法验证。没有披露的测试无法传播到相同模式的其他实例。没有上游PR的披露将修复搁置在一个fork中。
MOAD文章(UNDF)是大多数工程师省略的阶段。 他们修复缺陷,提交PR,然后认为自己完成了。但没有命名文章的修复意味着每个将来遇到相同模式的工程师必须独立重新发现问题和修复。MOAD文章关闭知识环:它命名模式,显示检测方法,&链接到补丁。未来的研究人员通过搜索模式名称找到修复。
行星修补处于规模。 一个广泛使用库中的单个MOAD-0001修复传播到导入它的每个项目。MOAD文章确保项目中的工程师永远不会升级该库仍然学习修复。两条路径并行运行。
编写缺陷工单
一个好的缺陷工单回答五个问题:
1. 哪里:确切的文件、类、函数和行范围
2. 什么:数据结构类型和针对它的操作
3. 为什么:复杂性分析(O(N²)或更差,定义N)
4. 影响:什么输入触发最坏情况行为,在什么规模
5. 修复:要替换的数据结构或原始类型
一个回答所有五个的工单是自包含的:一个从未读过你分析的维护者可以重现你的发现并验证你的修复。跳过(3)或(4)的工单需要维护者在合并前重复你的复杂性分析。那个摩擦降低了合并的概率。
可信度复合。 第一个包括清晰工单、目标明确补丁&基准测试的PR被合并。来自相同作者的第二个PR以较少摩擦被审查。第三个PR被合并前两个的维护者审查。开源中的声誉是工件的分类帐:每个接受的补丁为下一个赚取信任。
阅读真实候选
这是Python中的真实MOAD-0001候选。阅读它并完成分诊协议。
class DependencyResolver:
def resolve(self, package, resolved=None, seen=None):
if resolved is None:
resolved = []
if seen is None:
seen = []
if package in seen:
return
seen.append(package)
for dep in self.registry.get_dependencies(package):
self.resolve(dep, resolved, seen)
resolved.append(package)
return resolved
分诊问题:
1. `seen`是什么数据结构?
2. 什么操作在第6行针对它运行?
3. `seen`是否随输入大小增长?
4. 驱动递归调用的循环是否也随输入大小增长?
5. 这是确认的MOAD-0001还是假阳性?
你的补丁
具有高激增分数的确认缺陷需要一个完整的补丁:代码修复、证明改进的测试,&MOAD文章概括。
测试必须是性能测试,而不是正确性测试。 正确性测试在修复前后通过——这是要点;输出不改变。性能测试在两个输入大小证明改进:
import time
def build_graph(n):
# n packages, each depending on the previous one
return {f'pkg{i}': [f'pkg{i-1}'] if i > 0 else [] for i in range(n)}
for n in [100, 1000, 5000]:
registry = build_graph(n)
resolver = DependencyResolver(registry)
start = time.perf_counter()
resolver.resolve(f'pkg{n-1}')
elapsed = time.perf_counter() - start
print(f'n={n}: {elapsed:.4f}s')
在修复前,经过时间以n二次方增长。在修复后,它线性增长。打印两者并在你的PR描述中包括数字。
MOAD文章概括涵盖:模式名称、基质(Python依赖解析器)、检测方法(grep搜索in seen,seen开始于[])、修复,&到你的PR的链接。文章去undefect.com作为公共域。未来搜索'Python列表成员资格在循环中缓慢'的工程师会找到它。