周三下午,我坐在电脑前,给自己的代码审查系统LogicVisor做一个简单测试。我提交了一个回文检测的算法解法,请求顺利通过,评审结果正确。一切正常,那种熟悉的小确幸。
接着,我又提交了一遍完全相同的代码。结果还是正常——但我一点儿也高兴不起来。
那第二遍提交对整个系统来说,意味着额外的令牌消耗、更多的数据库内存占用、多的远程过程调用、多的AI调用以及更长的等待时间。而它解决的是同一个已经解决过的问题。
一个更让我后背发凉的场景浮现出来:如果有人恶意用同一段代码反复刷我的接口,我的AI账单和网站资源岂不是要被拖垮?速率限制只能控制每个客户端的请求次数,它完全不关心请求的内容。攻击者完全可以老老实实待在限速阈值之内,不紧不慢地一遍又一遍提交相同的解法,照样能把系统资源吃干抹净。
这条路走不通。我需要一套方案,能在新提交进来时第一时间认出它是“旧面孔”,接着直接返回缓存的响应,而不是每次从头到尾重新处理。可问题来了:你该怎么让计算机发现两段代码一模一样?
编程语言天然允许我们用无数种方式解决同一个问题,甚至同一套实现也可以披上完全不同的外衣。比如用户A写了个函数,接收并返回一个字符串,起名叫strawberries;用户B用一模一样的逻辑写出同一个函数,只是换了个名字叫bananas。名字不同,实现相同,结果相同。在LogicVisor的场景里,它们就应该被视作重复。但怎样让机器穿透这些表层差异,看清它们骨子里是同一段结构?
我从最底层开始琢磨。不管哪种编程语言,最终都会编译成二进制,再变成电脉冲。如果两道题用同一种思路解决,它们在机器层面触发的脉冲序列都是一样的,变量名叫什么都不重要。所以问题变成了:我能不能把一段解剥到它最根本的表示形式,然后拿那个形式去比较?
顺着这个思路,我翻出了操作系统课教授提过的一个概念——抽象语法树。当时我觉得这东西在Web开发里一辈子用不上,被当成无用的知识丢进了脑海深处。可哪有无用的知识呢?
抽象语法树是一种树状的层级数据结构,用来表示源代码的语法结构。它把分号、花括号、空格、注释这些表层语法全部剥离,只保留代码的结构骨架和语义骨架。你看的将是你写下的东西的“形状”,而不是它穿的那层“衣服”。
这正是我想要的。但几乎立刻,我就撞上了墙:我没办法在PostgreSQL里可靠地存储和索引一整棵抽象语法树。它是一个极其复杂的嵌套结构……
热门跟贴