作者 | CQT&星云团队
一、前言
代码理解作为软件知识图谱重要的技术之一,可以为构建、测试、定位、代码解释等提供基础的技术和数据保障,也是持续集成的起点,只有理解了代码才能有目的性的进行有效构建。代码理解对于软件开发的成功和维护的高效性具有重要意义,是提高软件质量、降低开发成本和提高开发效率的关键。
二、什么是代码理解
代码理解是一个以软件系统为被分析对象,对其内部的信息和运作流程进行分析,获取相关知识信息的技术领域,这些信息可以在CI&CD各个阶段被访问和应用。
代码理解常用的分析手段有静态分析、动态分析、非源码分析3种,但是随着LLM大模型时代的到来,我们也在研究大模型在代码理解领域的突破与应用。
静态分析:是指在不运行代码的方式下,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标的一种代码分析技术。
动态分析:软件系统在模拟的或真实的环境中执行之前、之中和之后,对软件系统行为的分析技术。
非代码分析:主要是对数据文件、配置文件等非源码文件和源码间进行关联分析,当代码仓变更时,能感知变更内容对源码、功能的影响。
基于LLM的分析:依托大模型的推理演绎能力,挖掘程序动静态数据中的知识。
三、代码理解主要作用
本文主要对静态代码分析进行详细介绍。静态代码分析是代码理解中的一项重要技术,它在CI&CD的各个阶段都扮演着重要的角色:
1、静态代码分析可以帮助开发者理解代码的结构、逻辑和功能。通过分析代码的语法、语义和行为,静态代码分析工具可以发现潜在的问题和错误,并进行修复和改进,这可以大大提高代码的可维护性和可靠性。
2、静态代码分析可以通过分析代码的逻辑和结构,为开发者提供重构和优化的建议,帮助开发者改进代码的质量和效率。这可以使代码更易于维护、扩展和更新。
3、静态代码分析也可以帮助开发者检测安全漏洞。通过分析代码的逻辑和数据流,静态代码分析工具可以检测出潜在的安全漏洞和攻击路径,并给出相应的建议和修复方案,提高系统的安全性。
4、静态代码分析还可以帮助开发者进行自动化测试。通过分析代码的行为和功能,静态代码分析工具可以生成测试用例,自动执行测试任务,提高测试效率和准确性。
5、通过理解代码,团队成员可以更好地协作,因为他们知道如何理解和使用彼此的代码。
6、通过理解代码,开发者可以更容易地将代码重用于其他项目或系统,因为他们知道如何修改和适应代码。
静态代码理解在CI&CD中的主要应用包括:
1、漏洞检测和修复:静态代码理解工具可以通过语法分析、语义分析等方式,检测代码中的错误、潜在问题以及其他不符合规范的地方,帮助开发者及时发现和修复问题,提高代码的质量和可靠性。
2、代码重构和优化:静态代码理解工具可以在不执行程序的情况下,对代码进行语法和结构分析,提供代码重构和优化的建议,帮助开发者改进代码结构、提高代码效率。
3、代码质量评估:静态代码理解工具可以对代码进行全面的质量评估,包括代码的可读性、可维护性、可扩展性等,帮助开发者更好地了解代码的质量状况,从而采取相应的措施来改进代码质量。
4、自动化测试:静态代码理解工具可以将传统开发过程中的手动干预转变为自动化流程,例如自动化构建、自动化测试、自动化部署等。这可以大大缩短开发周期,提高开发效率和质量。
四、典型的技术方案介绍
传统的代码理解技术方案主要通过代码解析层、代码分析层、应用层三部分组成。如下图所示,整体方案过程可以简述为通过语法解析源代码、构建抽象语法树(AST)或中间表示(IR)并对其进行遍历、提取代码的各类特征、最终生成代码特征文件。
1、源代码解析:这是构建AST或IR的第一步,涉及将源代码分解成更小的部分(例如,单词、符号、表达式等)。这一步通常由词法分析器(也称为扫描器或词法器)执行。
2、构建AST/IR:一旦源代码被解析为较小的部分,这些部分就可以被组合成抽象语法树(AST)或中间表示(IR)。AST和IR都是源代码的结构化表示,但它们的粒度和复杂性有所不同。AST更接近于源代码的语法结构,而IR则更接近于机器代码。构建AST或IR的过程通常由语法分析器执行。
3、提取特征:遍历访问AST或IR的每个节点。在遍历过程中,可以提取有关代码的各种特征,如变量、函数、控制流结构、数据依赖关系等,也可以识别特定的代码模式、计算某些代码度量(例如,代码复杂度、重复代码等),或者提取有关代码的其他信息。
4、生成特征文件并应用:将提取出的代码特征存储在代码特征文件中(例如,JSON、XML或数据库),可供应用方进行各类分析场景的落地使用。
然而,传统的代码理解技术方案在实践过程中各个环节挑战比较多,落地阻力较大。从代码解析基建的角度,对于代码解析技术的专业知识要求较高,需要进行各类语言开源解析工具的选型和改造,而且对存储扩展和执行效率要求较高。对于代码分析层,遍历AST所需的分析能力较高,并且分析能力不能复用,对多样化的场景来说存在较多重复的开发工作。从数据使用方的角度,落地应用的门槛高,针对不同语言的代码特征文件的标准不同,需要进行反复适配。对应用方的开放性较差,支持扩展的场景开发成本较高。
五、我们的技术方案
为了解决上述传统技术方案中存在的应用门槛高、代码分析成本高、专业知识要求高等问题,我们提出了全新的解决思路:重新定义代码解析服务, 将解析和分析解耦,建立基于三层架构的代码理解服务。
5.1 基础层
基础层旨在通过构建高效易扩展的多语言代码编译前端数据,解决在代码解析层面需要依赖高专业知识要求的问题。基础层主要由以下几个部分组成:
1、多语言解析器。 针对不同代码语言选取合适的代码解析方案,如c++语言的cppcheck 2.5、go语言的go native ast等。各解析器必须保证优异的性能,解析效率可达到200w+行代码/小时。解析器将代码解析为多种粒度的数据如token、ast、symbol,供上层分析层使用。
2、数据存储。 建立面向多语言的通用存储Schema标准,同时又可依据各语言特性进行个性化扩展。综合代码解析数据的量级大小、查询需求、查询速度等方面考虑,灵活采用file/neo4j等多种存储方案。
3、缓存机制。 在实际落地过程中面临的难题是一些大型模块的解析效率慢(超过1小时)。分析发现主要有2个原因:①代码文件多。②解析过程重复。一方面,通过多进程并发解析来提升效率;另一方面,通过设计缓存机制来避免重复解析过程,实现仅分析部分文件即可得到完整代码前端数据。
5.2 分析层
分析层旨在通过抽象通用专业分析能力刻画模块内外代码关系,解决代码分析层面成本高的问题。主要思路是结合业务场景,拆分能力图,由内到外,逐步建立起整个关系网。
5.3 服务层
分析层旨在通过抽象通用专业分析能力刻画模块内外代码关系,解决代码分析层面成本高的问题。主要思路是结合业务场景,拆分能力图,由内到外,逐步建立起整个关系网。
5.4 技术效果
-
探索一套通用的代码理解方案,构建白盒级软件知识图谱,在C++/GO 上落地实践
-
基础能力:覆盖多语言,高效易扩展
-
支持3种 语言、10+ 种代码实体数据源
-
C/C++效率突破,效率缩短近9倍 ,增量效率**<200s**,具备铺面落地水平
-
标准一致的shcema和通用提取框架,易扩展
-
分析能力:多样化
-
12 通用分析能力
-
20+ 种常用关系的建立
-
对外服务能力上:易用且开放
-
3种 数据访问方式
-
200+ APIs
-
业务可低成本完成基础白盒策略开发(<1小时)
覆盖2000+业务代码路径数据,积累1T+代码知识数据,支撑10+ 个质效应用孵化落地,日均调用2.4k+ 次。
六、代码理解在百度的典型应用
上文中提到代码理解在缺陷检测、智能构建、CR、问题定位、代码修复等方面均有广泛应用,下面我们将从几个实际的场景,介绍代码理解技术在百度的落地使用。
6.1 代码理解应用场景-智能UT
当前百度大批量模块从别的语言迁移到GO语言上, 但在GO语言召回工具链处于空白,需要建设主动召回代码风险能力。智能UT是一种常见主动召回风险代码的应用。单元测试(unit testing)是针对代码中最小单元进行测试,传统 UT 依赖开发人员手工编写单测代码来进行测试,存在开发成本高、依赖人可能遗漏部分边界场景等缺点。智能 UT 工具可以通过理解被测函数内容和测试条件,自动构造测试数据生成单元测试代码,运行智能UT用例可实现代码问题的主动召回。
【解决方案】
为了让智能UT理解被测函数生成高质量的单测用例,需要通过代码理解能力获于控制流、调用链、数据流等信息,为智能UT提供分析能力,理解被测函数内容和测试条件
△代码理解-智能UT
【效果&收益】
支持GO智能UT能力孵化并且落地,通过生成UT召回单个Q有效风险问题400+,智能UT用例的准确率达到65%。
6.2 代码理解应用场景-无用函数清理
在百度信息流和搜索在技术债治理上,无用函数清理是一个典型的场景。无用代码的存在增加了软件开发、测试、以及问题排查的开销,如 QA 和 RD 需要更多额外的精力来评估需求的影响范围,因此需要被治理。理想状态下, 如果一个函数已不再被调用即在调用链上没有入度的函数可以被认为是无用函数(孤岛函数)。但在实际场景中,会有一些函数作为基础库函数供外部调用以及隐式调用函数和断链的场景,在这些场景下会发现没有入度的函数其实并不是无用函数。
【解决方案】
为了准确识别孤岛函数,需要通过代码理解能力, 在调用链的基础上,筛选出入度为0度函数, 进一步结合类、函数、宏定义等信息对孤岛分析能力抽象建模,实现孤独函数识别准确率的提升。
△代码理解-孤独函数识别应用
【效果&收益】
通过代码理解+抽象建模,当前孤独函数识别准确率达到97%,落地57个业务代码模块,帮助业务上清理7.6w+行孤岛函数。
6.3 代码理解应用场景-SA
SA(static analysis)是静态代码扫描, 通过词法分析,语法分析,语义分析等技术对代码进行扫描。依据编程语言的自身特性,可将各类风险场景提取转化为通用的规则进行异常拦截。SA的规则是依赖人工经验开发的,依赖后验知识,导致问题召回存在瓶颈。为解决该问题,百度探索AI-SA 通过深度学习让模型学习代码语义和问题缺陷触发状态,主动识别代码风险问题。AI-SA受限模型原因,单词判别输入代码长度有限制,因此需要在有限字数内,提取能够表征缺陷的代码片段给模型。
【解决方案】
为了保证供给给AI-SA模型的代码在长度限制内, ,需要通过代码理解对控制流和数据流分析,筛选在调用链路上和目标变量强相关的代码上下文片段保证模型召回的准确率。
△代码理解-AISA
【效果&收益】
从0到1建设AI-SA工程落地,累计召回有效风险问题2000+。
七、大模型时代下的代码理解技术考虑
传统的代码理解技术底层是通过AST或IR等方式进行,上层更多的是靠人工规则去挖掘场景的方式,这样会带来多给弊端:
1、对不同语言的适配有较大的工程成本。
2、语言的语法更新迭代飞快,这样会导致底层解析也需要随之发生变化。
3、应用上场景过分依赖于人及人的认识范围内进行分析和挖掘,极大的限制了代码理解的应用。
4、基于规则的场景只能是0/1的判断在召回方面也有很大的局限性,应该想办法应用模型预测的方式进行场景挖掘。
基于上面的几个弊端和随着大模型技术的发展,我们希望开辟了另一项技术探索,即把最基础的代码片段解析再输入给大模型,大模型即可从缺陷、定位、优化等角度给出答案甚至风险大小和类型,这样可以极大的降低代码理解的工程技术投入和解除场景挖掘的限制。
在代码理解能力方面,大模型可以对代码理解的存储层、分析层、模型层等进行优化,提供更好的代码理解能力。
1、存储层:引入向量数据库,将分析层中解析出来的向量结果和上下文依赖关系存入向量数据库。除原代码理解之外的函数关系网和模块关系网之外,还可以引入通过语义转换和Embedding计算的大模型向量关系网,未后续的代码分析提供更多维度的基础数据
2、分析层:利用大模型的语言理解能力,可以更加合理的对文件块,上下文等进行切割分析,通过大模型的知识迁移能力,能够解决传统代码分析多语言迁移能力的难题。同时调优同学也可以利用大模型的多轮对话能力,对特定领域的代码分析大模型进行微调,低成本的建立特定领域的代码分析模型,使其更加准确。
3、模型层:引入GPT-4,文心一言等相关大模型更加精准的对代码中的风险进行识别、功能理解等。提高风险识别、功能理解的准确率。
在应用场景方面,可以帮助开发人员和测试人员更加高效的学习和理解一段代码,方便开发人员进行重构和测试,主要变化会有:
-
多轮问答方式理解代码,降低程序员和测试人员对于代码的学习成本。
-
利用大模型提供代码重构、改进意见和测试用例设计,降低开发人员和测试人员的人力成本。
-
利用大模型对代码进行文档化,降低开发人员的人力成本。以及通过代码理解生成的文档进行人工调整,反复训练大模型提高模型代码理解准确度。
-
利用大模型预期代码风险类型和大小,可以将此结果输入给构建系统,来决策构建系统的行为,从而提升构建效率。
当前业界已经涌现出利用大模型对代码进行解析的一系列工具,经过调研发现目前行业界内比较火的相关工具如下表所示:
通过对当前业内较火的大模型分析工具进行调研可以发现,这些工具均在做通过利用大模型提高代码理解的能力以及上下文分析能力,为开发人员和测试人员等提供简单的代码填充和测试用例设计指导。也许随着未来大模型的发展以及迭代,开发人员完全可以通过多轮自然语言的交互即可完成代码开发、代码调优、代码测试极大的降低开发成本和测试成本。
---------- END ----------
推荐阅读【技术加油站】系列:
DeeTune:基于 eBPF 的百度网络框架的设计与应用
代码级质量技术之基本框架介绍
基于openfaas托管脚本的实践
百度工程师移动开发避坑指南——Swift语言篇
百度工程师移动开发避坑指南——内存泄漏篇
百度工程师教你玩转设计模式(装饰器模式)