作者: 一了 <1liao3@funlang.org>
日期: 2026-10-20
一句话总结: 腾讯元宝的第三方代码质量分析:综合 7.1/10,约 1.29 万行手写内核、62 个自托管模块,附五类结构性债务与改进清单。基于 2026-10-20 快照,数字会随代码演进。
Fun 代码质量分析报告 - 元宝
分析对象:github.com/funlang/fun · main 分支 · v9.x(CHANGELOG 已至 9.4.5,2026-10-20) 分析方式:拉取完整源码(499 文件)后静态阅读 + 脚本化度量,非 README 转述。所有行号/数据以仓库实际内容为准。 分析快照:2026-10-20。仓库每日合入,文中行号、文件数与统计只代表这一天的 main。
编者按:本文是腾讯元宝以第三方视角完成的独立、中立分析,不代表 Fun 项目官方立场。本站只做了标题、标点与少量事实口径的编辑,评分、判断与改进建议保留原文,仅订正计数口径并弱化两处过强表述。站内另有一篇《Fun语言源码深度分析报告 - 元宝》,偏架构与能力面;本篇偏代码质量评分、模块体检与风险清单,两篇可以对照着看。
原文的度量基于 2026-10-20 的 main 快照,而代码库每天都在演进,所以具体数字会随提交漂移。上架前复核了一次:标准库顶层
.fun文件仍是 89 个(不含fun/lib/tools下的 6 个),逻辑模块仍是 62 个;顶层测试脚本 64 → 65,黄金快照仍是 95 个,内核raise/try仍是 38 / 53,有顶层var的文件仍是 54 个。结论的方向没有变,引用具体行号或计数时请以引用当日的 HEAD 为准。
一句话结论
这是一个「极小内核 + 极宽能力面」的成熟工程:手写 Pascal 内核仅约 1.29 万行代码(不含 lex/yacc 生成表),却完整覆盖词法/语法分析、对象与闭包、正则、FFI、运行时 C 编译、JIT 与内联汇编;标准库 62 个逻辑模块(9,897 行)全部用 Fun 自身编写。
代码风格高度统一、注释记录的是「为什么」而不是「做了什么」、几乎零重复代码,这是二十余年单一作者演进的典型优点。
代价同样清晰:错误处理偏薄(全内核仅 38 处 raise)、引用计数无环检测、标准库全局状态泛滥、平台变体近乎双份实现、安全沙箱(-safe)尚未落地。
它不是「不规范」,而是「单人维护规模下的极致压缩」;一旦要交给多人协作或长期多平台演进,几处结构性债务必须优先偿还。
| 综合质量分 | 手写内核代码行(Pascal) | 标准库代码行(Fun) | 自托管标准库模块 |
|---|---|---|---|
| 7.1 / 10 | 12.9k | 9.9k | 62 |
一、整体评分卡
| 维度 | 评分 | 判定依据 |
|---|---|---|
| 架构与设计一致性 | 8.5 | 分层干净(core / parse / lib / regex / utils / prj),树遍历 + Variant 值模型一条主线贯穿到底;CAssignFast 等热点有显式快路径设计并写明取舍。 |
| 风格一致性与可读性 | 8.0 | 注释 99.9% 为英文、换行 100% CRLF、无硬编码绝对路径、命名(C* 类 / _* 内建)自洽;函数切分普遍很细:parse.pas 最长仅 78 行,core.pas 最长 93 行,连 385 行的 _ParseJson 主函数体也只有 19 行。扣分项是 {$I} 嵌在函数体内导致的工具链失明。 |
| 健壮性与错误处理 | 6.0 | 1.6 万行内核只有 38 处 raise、53 处 try,大量失败路径是 exit / 返回 nil 的静默降级;异常类型直接复用 SysUtils.Exception,无层级。 |
| 测试与工程化 | 6.5 | 5 平台 CI + 95 个黄金快照是加分项;但测试多为快照比对而非断言式单测,仅 33/89 个文件有覆盖,无覆盖率度量。 |
| 可维护性与演进 | 7.5 | CHANGELOG 细到日粒度、CONTRIBUTING 中英双份、授权分层清晰;负担来自平台变体双实现与密集条件编译。 |
| 安全性 | 4.5 | 作者自撰 SAFETY 清单逐分支标注危险能力,态度诚实;但 NAT/MEM 类拦截(getapi/@toCallback/TCC/汇编)全部开放,-safe 未实现。 |
| 依赖治理 | 7.0 | vendored 第三方(KOL ≈ 6.96 万行、PCRE)明确隔离在 src/3rd、src/regex/pcre;缺失时构建降级而非失败,规则写进 CI 与文档。 |
评分口径:以「同等规模单人/小团队系统语言实现」为基准,非以 Linux 内核级工程要求衡量。
二、规模与构成:先把数字摆正
| 层次 | 文件行数 | 代码行 | 注释率 | 职责与质量观察 |
|---|---|---|---|---|
src/core/ | 6,028 | 4,637 | 13% | 值模型(CValue=Variant)、CList/CHash(Trie)、节点树、对象与闭包、流程控制、IO。注释率最低但代码密度最高。 |
src/parse/ | 8,099 | 5,219 | 31% | 其中 6,057 行是 lex/yacc 生成表(ytab/ltab/yact/yacc/lact/ycon),手写仅约 2,042 行:parse.pas + 语义动作 + BNF 定义。 |
src/lib/ | 5,259 | 3,899 | 16% | 内建运行时:libase(字符串/JSON/时间/系统)、libset(集合)、libfd(FD 读写)、lffi(libffi 后端)、winapi/winole、UI。 |
src/regex/ | 2,715 | 1,940 | 20% | PCRE 移植(vendored)+ 一层 regex.pas 封装成语言值类型。PCRE 缺失时构建降级。 |
src/utils/ | 687 | 464 | 26% | 打包/解包(FunPack)与工具函数。 |
src/prj/ | 539 | 330 | 30% | 唯一入口 funcmd.dpr:编译 → 执行两步,注释解释线程模型取舍。 |
fun/lib/ | 12,936 | 9,897 | 14.6% | 89 个 .fun 文件 = 62 个逻辑模块(含平台变体)。 |
fun/test/ | 3,957 | — | — | 64 个测试脚本 + 95 个 expected 黄金快照。 |
汇总:src/ 自写(排除 src/3rd)总行 23,327,代码行 16,489,注释率 21.9%;vendored 第三方约 69.6k 行(KOL 为主)。
口径提醒:README 与白皮书提到的「1.5 万行 / 61 模块」是逻辑代码口径(剔除生成表与 vendored)。实际仓库里
src/3rd/KOL.pas一个文件就有 62,633 行,占了全仓库代码量的 80% 以上,但属于 GUI 相关的 vendored 依赖,评估内核质量时应整体剔除,否则会严重误判。
三、内核 Pascal 分析
3.1 做得好的地方(值得学习)
-
注释写「为什么」,不写「做了什么」。
funcmd.dpr对cthreads的说明:「每次分配加锁会让运行慢约 20%,所以做成 opt-in」;core.pas对CAssignFast的说明:「首次运行解析槽位类型并缓存,后续直接读写」;fun.pas解释为什么指针宽度别名集中在一处定义。这类注释在单人项目里极其罕见,说明作者有意识地对抗「代码自解释」的错觉。 -
几乎零重复代码。 对全部 Pascal 源文件做 5 行以上连续重复块检测,只命中 2 组、冗余约 10 行(还是
destructor Destroy; override;这类声明)。对比之下标准库有 83 组重复,内核的重构纪律明显更严。 -
平台差异被刻意收敛到类型层。
fun.pas用int/uint/intptr/uintptr/real一套别名屏蔽 Delphi 与 FPC、32 位与 64 位差异,并专门注释「不用 PtrInt/NativeInt,避免各单元各写一套」;base.pas为 64 位指针读取单独提供PtrOfVal,避免fun.int(val^)截断地址。这是有 64 位移植实战痕迹的代码。 -
数据结构选择有依据。 变量表用
CHash(Trie + 顺序 List),兼顾按名查找与插入顺序;CList带autoDel语义;引用计数用lockInc/lockDec原子操作实现IInterface,为跨线程回调留出余地。 -
风格零漂移。 全部 38 个 Pascal 文件 100% CRLF;注释中仅 5 行含中文(0.1%);无一处硬编码盘符或绝对路径;构建产物被
.gitignore完整覆盖。 -
热路径有显式优化且被注释固化。
CAssignFast、calc-fastpath(测试里有对应用例)说明作者做过性能剖析并留下了回归保护。
3.2 明确的问题(按严重度)
口径说明:两处函数长度度量的更正
第一处:度量脚本起初按「相邻两个
function/procedure头的行距」估算长度,曾据此称io.pas存在 395 行的读取函数,该结论错误。脚本对class function形式的函数头匹配失败,又把嵌套过程DoRead(L321) 当成边界,加上条件编译({$IfDef})造成的begin/end不配对,最终把DoRead(L321) 到LoadLib(L716) 的 395 行行距误报成了函数体长度。改用「顶层函数头 → 下一个顶格end;」并人工核验后,io.pas实际最长函数是CIO.Find135 行、CIO.Load129 行。第二处:据此把
libase.pas的_ParseJson(385 行,内嵌libfd.inc后展开约 694 行)定性为「巨型函数」,这个定性同样不准确。它的主函数体只有 19 行(L1643–1661,一个if fd then ParseFD else ParseJSON派发器),385 行里绝大部分是 8 个嵌套子过程与两个平级扫描器。该模块是「宽容 JSON → JSON 兼容 → FD → strict」逐层长出来的(CHANGELOG L446 加深度限制、L491 加 RFC 8259 分隔文法可佐证),且生长伴随了全仓唯一一组断言式测试(89 个 strict 用例)。本节及 §3.3、§五、§六 的数据与定性均已按修正后的口径更新。
| 级别 | 问题 | 证据与影响 |
|---|---|---|
| 高 | 错误处理不成体系 | EBase = SysUtils.Exception,没有任何自定义异常层级(无 EFunParse / EFunRuntime / EFunFFI)。全内核 raise 38 次、try 53 次,摊到 1.6 万行约每 420 行一次异常抛出;core.pas 里有 9 处裸 exit、11 处 result := nil。后果:脚本层错误往往是「静默拿到 nil」而非报错,定位成本高;FFI/JIT 这类高风险路径的失败同样缺少结构化信息。 |
| 高 | 引用计数无环检测 | CBase._Release 归零即 Destroy,无 mark-sweep 兜底、无弱引用。class 是闭包的语法糖,对象与父环境天然容易成环。后果:长生命周期脚本(服务、常驻 Agent)一旦成环就会持续泄漏,作者自己在考察报告里也承认「必须显式打破循环」。 |
| 中 | _ParseJson:{$I} 嵌在函数体内 | 函数体本身只有 19 行(L1643–1661,纯粹的 if fd then ParseFD else ParseJSON 派发器),真正的两个扫描器 ParseJSON(263 行)与 ParseFD(309 行)是它的嵌套子过程,整个 385 行其实是「派发器 + 8 个辅助子过程 + 2 个平级扫描器」。 |
真正的问题是 ParseFD 通过 L1641 的 {$I 'libfd.inc'} 嵌进函数体内部:libfd.inc 不是一个独立编译单元,IDE、静态分析、函数列表都看不到它,也不能被单独引用或测试。后果:导航与工具链失明,且两个扫描器共享父函数的局部变量(Root/Level/Stack),无法独立演化。 | ||
| 中 | 扫描器内部的 goto 状态机 | ParseJSON(263 行)用 label _STR, _NUM, _KEY + goto 做扫描跳转(_STR: L1442、_NUM: L1529、_KEY: L1561),是字符串/数字/裸键三类 token 的公共出口。评价:这是解析器里常见且高效的写法,但 263 行 + goto 让它难以分段验证,目前只能靠 §五 提到的 89 个 strict 用例兜底。 |
| 中 | 条件编译密度过高 | libase.pas 114 处、utils.pas 91 处、calc.inc 84 处、io.pas 78 处 {$IfDef}。后果:同一文件的「有效代码」在不同平台完全不同,静态审查与重构工具基本失效,也意味着未被 CI 覆盖的平台(WinCE、32 位 Windows)实际处于腐烂风险中。 |
| 中 | goto 与手工控制流 | 全内核 52 处 goto:生成代码 yacc.inc 20 处可忽略,但 calc.inc(10)、base.pas(4)、libase.pas(3)、ie.pas(3)是手写代码里的跳转,多半是性能考量下的早退路径。 |
| 低 | Variant 值模型的性能天花板 | CValue = System.Variant 让值与 Pascal 生态零成本互操作(这是设计目标),但每次运算都要判类型标签,无法类型特化。仓库自带基准显示简单算术在 78–143 ns/次量级,与内联汇编路径相差数百倍。这是已声明的取舍,不是缺陷,但集成方必须知情。 |
| 低 | 技术债标记偏少 | 全仓库仅 16 处 TODO、6 处 XXX。以这个体量和能力面而言,说明大量「已知但不处理」的点没有落档。 |
3.3 分模块体检表
| 文件 | 行数 | 函数数 | 最长函数 | 评价 |
|---|---|---|---|---|
core/core.pas | 2,125 | 286 | 93 | 良 节点树与对象系统主心骨,类划分细、函数短(最长 CIdx.find 93 行),边界检查(字符串/索引越界 raise)近期补齐。 |
core/base.pas | 1,345 | 143 | 171 | 中 值模型 + 容器 + 类型转换全家桶,varComp 171 行且带 4 处 goto,是全局热点。 |
parse/parse.pas | 1,172 | 149 | 78 | 良 手写解析器 + 表达式解析,最长函数仅 78 行(CParser.ParseFileName),函数切分是全仓最细的;语法定义在 bnf/fun.ebnf 与 yacc.y 双份维护,改语法需同步。 |
lib/libase.pas | 1,833 | 67 | 385 | |
| (主函数体 19) | 中 _ParseJson 385 行但主函数体仅 19 行(派发器),内含 8 个子过程 + ParseJSON(263) / ParseFD(309,经 {$I} 嵌入) 两个扫描器;ParseFD 自身又有 11 个子过程、主函数体 86 行。结构合理,问题在 {$I} 嵌在函数体内;114 处条件编译亦为全仓最高。 | |||
core/io.pas | 763 | 38 | 135 | 中 最长 CIO.Find 135 行(嵌套拆分合理)、CIO.Load 129 行;规模其实健康,问题集中在 CIO.Load 混合了编码探测/转换/分块读三件事。 |
regex/pcre/*.pas | 2,223 | 230 | 170 | vendored PCRE 移植层,不计入自研质量(最长 ComputeReplacement 170 行);历史版本移植、无独立安全更新通道。 |
lib/lffi.pas | 509 | 26 | 120 | 良 libffi 后端,7 处 raise 且错误文案具体(含库名/符号名),是本仓错误处理做得最好的模块之一。 |
utils/utils.pas | 687 | 41 | 104 | 中 打包/解包,91 处条件编译。 |
四、标准库 .fun 分析(62 个自托管模块)
4.1 结构:62 个逻辑模块,89 个文件
89 个 .fun 文件合并平台变体后是 62 个逻辑模块,其中:
- 7 个是纯文档占位模块(0 行代码):
lib-string、lib-type、lib-winapi、lib-winole、lib-file、lib-match、lib-dyns,它们用#注释块列出内核内建函数清单。这是「文档即模块」的写法,可读性尚可,但会让「模块数」这个指标虚高(README 的 61 与此接近)。 - 多个
-lnx.fun是薄转发壳(8–9 行,正文只有一条use),符合 CONTRIBUTING 声明的分层约定:<name>.fun(Windows) /-unx.fun(Unix) /-base.fun(共享)。 - 14 个模块存在平台变体,但只有 winsock 真正做了
lib-winsock-base.fun(427 行)共享抽象;ado / crypt / xml / cmdline / jsonrpc 等仍是两套独立实现。
| 分组 | 代表模块(代码行) | 观察 |
|---|---|---|
| 底层能力 | lib-asm-a64(501)、lib-asm(143)、lib-tcc(49)、lib-jit(9) | AArch64 汇编后端是最新的重头(642 行、分支密度 44.4%,全仓最高);lib-jit 仅 14 行做路由,委托干净。 |
| 数据格式 | lib-json(16)、lib-fd(106)、lib-bnf(104)、lib-yaml(218)、lib-xml(132) | JSON 只有 16 行,因为解析/序列化已下沉为内核内建(@toJson/getJson),库层只剩薄封装;FD 的 BNF 自举(fd.bnf.fd 描述 FD 自身)设计上很漂亮,实现只有 114 行。 |
| 网络 | lib-winsock-unx(480)、lib-winsock-base(427)、http-server(397)、lib-ajax(369) | http-server 是 532 行的完整框架(路由/中间件/静态文件/日志),是库里最像「应用」的模块。 |
| ORM / 数据库 | lib-orm(478)、lib-ado-unx(380)、lib-ado(195) | 注释率仅 5–6%,是全文注释最稀薄的角落,也是逻辑最复杂的部分,风险集中。 |
| 系统 / UI | lib-os(394)、lib-ui-base(142)、lib-registry(93) | lib-os.fun 含 62 个模块级 var(全仓最高)。 |
| 基础 | lib-utils(150)、lib-set(139)、lib-zlib(137)、lib-math(27) | lib-utils 被 9 个模块依赖,是事实上的基石,但注释率仅 1%。 |
4.2 优点
-
平台变体的头部文档质量极高。
lib-ado-unx.fun开头 10 行就讲清「Windows 版是 COM 形状的 ADODB,无法在 Unix 复用,因此 Unix 后端改用 SQLite 但对外保持同一 ADO 类与契约」。这类「为什么要有两份」的说明,是跨平台代码里最值钱的信息。 -
依赖图浅而清晰。 37 个模块参与依赖,最高入度:lib-host(10)、lib-utils(9)、lib-os(7)、lib-regex(6);未发现跨模块循环依赖(检测到的自引用是变体的幂等
use守卫)。 -
能力下沉边界合理。 字符串、正则、类型、文件这些高频操作下沉到内核,库层只留 API 形状,避免了脚本层反复低效实现。
4.3 问题
| 级别 | 问题 | 证据与影响 |
|---|---|---|
| 高 | 模块级全局状态泛滥 | 89 个 .fun 文件中 54 个存在顶层 var(按 62 个逻辑模块算,覆盖 42 个):lib-os.fun 62 个、lib-ado-unx.fun 46 个、lib-ado-schema.fun 44 个、lib-crypt.fun 33 个、lib-winsock-unx.fun 40 个。连 lib-fd.fun 的解析器都把 root/stak 挂在模块全局上。后果:模块不是可重入的,同一进程内无法安全地并发解析两个 FD 文档,也无法持有两个独立实例;多模块同名 var 存在互相踩踏风险。 |
| 高 | 平台变体近乎双份实现 | 行级相似度:lib-ado 0.07、lib-crypt 0.10、lib-winsock 0.11、lib-xml 0.07、lib-ajax 0.07、lib-os 0.05(仅 lib-ado-schema 0.78、lib-jsonrpc 0.70 较高)。83 组、约 415 行重复块主要集中在 ADO 常量表等处被原样复制两遍。后果:修一个 bug 要改两处,且两处无人交叉验证,这是多平台维护中最典型的腐烂源。 |
| 中 | 注释密度两极分化 | 整体 14.6%,但分布极不均匀:lib-regex 74%、lib-json 52%(多为 API 说明块)vs lib-orm 6%、lib-ado-schema 1%、lib-utils 1%、lib-cstruct 4%。最复杂的代码恰是最少注释的代码。 |
| 中 | 热点模块复杂度集中 | 分支密度(每 100 行的分支/循环数):lib-asm-a64 44.4、lib-tree 44.0、lib-cwinapi 39.1、lib-cstruct 38.1、lib-utils 37.4、lib-orm 31.3;全库平均 18.5。这些模块既缺注释又缺测试。 |
| 中 | 测试覆盖不均衡 | 只有 33/89 个文件(约 37%)有对应测试脚本;lib-orm、lib-yaml、lib-cstruct、lib-async、lib-ui*、lib-md5、lib-bind 等复杂度不低的模块无测试。测试多为 -lnx 侧(受 CI 平台限制),Windows 侧几乎无自动化。 |
| 低 | 占位模块造成指标失真 | 7 个 0 代码模块让「61 个模块」的说法在技术评估时偏乐观;建议 README 区分「模块 / 文档清单」。 |
五、工程化、测试与安全
做得好的
-
CI 矩阵实在:5 个平台(Linux x64/arm64、macOS x64/arm64、Windows x64)全为 gate,并明确写了 windows-arm64 因 FPC 3.2.2 无 win64/aarch64 目标而禁用及其原因,把「做不到」写进 CI 注释,是成熟做法。
-
宿主感知的回归体系:
regression-skip-<platform>.txt+expected/<platform>/快照覆盖机制,让「平台不支持」与「平台输出不同」分开处理;缺失能力(PCRE、ffi.dll)时探测后降级为 SKIP 而非失败。 -
文档双语、授权分层:README/CHANGELOG/CONTRIBUTING/SAFETY 均有中英两份;LICENSE(MIT)+ LICENSE.custom + LICENSE.dll 三层。
-
CHANGELOG 细到日:9.0 → 9.4.5,最近条目日期 2026-10-20,说明仓库处于高频迭代而非归档状态。
-
安全态度诚实:
SAFETY.zh.md按分支(而非按函数)逐个标注move/movs/toNum/fromByte/eval/compile的能力类别(NAT/MEM/FSW/SRC/ENV),并直接写明哪些已落地、哪些未实现。 -
有一处真正的断言式测试(全仓唯一): JSON/FD 这一块没有沿用黄金快照,而是写了
strict-valid.fun(63 个用例)与strict-probe.fun(26 个用例),用try/except包住getJson(json: 2),逐条打印PASS/FAIL并区分「该接受却拒绝」与「该拒绝却接受」两种失败方向。覆盖率也细致到分隔文法层面:尾随逗号、前导/重复逗号、缺逗号、缺/重复冒号、;/=分隔、单引号与反引号字符串、@日期字面量、十六进制数字、非字符串键、括号错配、顶层尾随值,以及「数字保持宽容(.5/03/1.仍接受)」这类刻意保留的行为也被固化为用例。这正是「功能长大 = 测试长大」的实证:CHANGELOG 里
MaxNestDepth=512(L446)与 RFC 8259 分隔文法(L491)两条新增,都对应这批用例的扩充。建议把这个模式推广到其余模块,它比快照比对更能定位语义回归。
短板
- 测试多为黄金快照,不是断言。95 个
expected/*.txt是整段输出比对,失败时只能看到 diff,无法定位语义;没有单测框架、没有覆盖率统计、没有属性测试/模糊测试。 - 测试/产品代码比约 0.17(3,957 行测试 vs 约 22,750 行产品代码),对解释器这类「分支即语义」的软件偏低。
- 安全边界未落成机制。SAFETY 文档自述:越界检查(MEM 的分支/参数部分)已于 2026-09-14 落地,但
-safe开关、s.move(numDest)、toNum(ptr:-1)、getapi/@toCallback/COM 拦截尚未实现。当前默认构建下,任何 Fun 脚本都可以执行任意机器码。 - vendored 依赖无更新通道。KOL(≈6.96 万行)与 PCRE 移植是静态快照,无 submodule / 版本清单 / CVE 跟踪;KOL 已属历史 Pascal 生态组件。
- 贡献门槛实际很高。CONTRIBUTING 要求改动解析器时同步
fun.ebnf+yacc.y+lex.l,并需本地准备 Delphi/FPC 工具链与第三方库(PCRE、TCC),新贡献者上手成本高。
六、风险清单与改进建议(按优先级)
| 优先级 | 动作 | 预估工作量 | 收益 |
|---|---|---|---|
| P0 | 建立异常层级 EFunError → EParseError / ERuntimeError / EFFIError,并给错误附 file:line 上下文(ErrPos 已有雏形) | 2–3 周 | 把「静默 nil」变成可定位错误,直接改善可调试性与嵌入场景的可信度。 |
| P0 | 实现 -safe 能力开关(NAT/MEM/FSW 三类),把 SAFETY 文档变成可执行策略 | 3–4 周 | 文档已经把分支列全了,实现成本低而收益大:这是嵌入第三方应用与 AI Agent 场景的准入条件。 |
| P0 | 为标准库引入实例作用域:把模块级 var 收敛进工厂函数返回的对象 | 4–6 周 | 消除 54 个文件(42 个逻辑模块)的全局状态,使库可重入、可并发、可嵌套使用;是库层最大的结构性债务。 |
| P1 | 把 {$I 'libfd.inc'} 从 _ParseJson 函数体内部提出来:让 libfd.inc 成为独立单元,两个扫描器通过显式参数而非共享父函数局部变量协作 | 1–2 周 | 注意:这不是「拆分巨型函数」。主函数体只有 19 行,结构本身是合理的。 收益是让 IDE / 静态分析能看到 ParseFD(现在它对工具链完全隐形),并让两个扫描器可独立引用与演化。风险低,因为已有 89 个 strict 用例兜底。 |
| P1 | 把 strict-valid.fun / strict-probe.fun 的断言式测试模式推广到 lib-orm / lib-yaml / lib-cstruct 等无测试的高复杂度模块 | 2–3 周 | 这是全仓唯一一处 PASS/FAIL 断言(其余 95 个是整段输出快照);照抄这个模板的边际成本很低,收益是失败时能直接定位语义而非看 diff。 |
| P1 | 把 io.pas 的 CIO.Load(129 行)中 BOM 探测 / 代码页转换 / 分块读拆成独立函数,并补编码往返测试 | 1–2 周 | 编码处理是跨平台 bug 重灾区,拆开后可单独测试,且能覆盖当前 CI 无法验证的 Windows 路径。 |
| P1 | 平台变体抽共享层:按 lib-winsock-base 的先例,为 ado / crypt / xml / cmdline / orm 抽出 -base,常量表只留一份 | 3–5 周 | 消除约 415 行重复,把「改两处」变成「改一处」。 |
| P1 | 为 lib-orm / lib-yaml / lib-cstruct / lib-async / lib-ui 等无测试的高复杂度模块补回归用例;引入覆盖率统计 | 3–4 周 | 把测试覆盖从 37% 提到 70%+,并让 CI 差异可被量化。 |
| P1 | 引用计数补环检测(周期标记扫描或引入弱引用类型) | 2–4 周 | 让 Fun 能胜任常驻服务;否则长跑场景必须靠人工破环。 |
| P2 | 为 vendored 第三方建立版本清单与更新流程(KOL / PCRE / TCC) | 1 周 | 安全合规与长期可维护性。 |
| P2 | README 区分「模块」与「文档清单模块」,并把 vendored 代码量从总代码量中单列 | <1 周 | 避免外界(含 AI 摘要)误读规模与自研比例。 |
| P2 | 降低条件编译密度:把 libase.pas(114 处)等按平台拆成 -win/-unix 实现单元 + 薄适配层 | 4–6 周 | 让静态分析工具生效,减少未覆盖平台的腐烂。 |
七、给不同角色的建议
考虑嵌入 fun.dll
先做三件事:
- 确认目标平台在 CI 5 矩阵内;
- 明确脚本来源可信:当前无
-safe,脚本等价于本机任意代码执行; - 用长跑脚本压测内存,验证引用计数泄漏在你的场景下是否可控。
想学习这门实现
推荐阅读顺序:
src/core/fun.pas(类型别名与平台抽象)→ src/core/base.pas(值模型与容器)→ src/core/core.pas(节点树与对象)→ src/parse/bnf/fun.ebnf(语法)→ fun/lib/lib-fd.fun(自举解析器)。
注释里大量「为什么」,是本仓最值钱的部分。
想贡献代码
最容易被接受且价值最高的三类改动:
- 补标准库回归测试(尤其 Windows 侧与 ORM/YAML);
- 把
{$I 'libfd.inc'}提为独立单元; - 为平台变体抽
-base共享层。
注意 CRLF + 英文注释 + SPDX 头,改语法需同步三份语法文件。
分析说明 分析方式:拉取 main 分支完整源码(499 文件)后,结合脚本化度量(行数/注释率/函数长度/分支密度/重复块/依赖图/条件编译密度/换行与编码一致性/goto 与异常统计)与关键文件精读。 所有数字均由仓库实际内容计算得出,未采用 README 或第三方文章的口径;vendored 第三方(
src/3rd、src/regex/pcre)与 lex/yacc 生成表已在内核指标中单独剔除或标注。 函数长度采用「顶层函数头 → 下一个顶格end;」口径并人工核验边界;条件编译导致的begin/end不配对会使自动配对失效,故未采用纯字符配对法。 本报告为静态分析结论,不含运行时验证(未编译执行),性能与安全影响的最终判断需结合目标平台的实测。