clice 最近达到了 1,000 次提交,这是项目的一个特殊里程碑。自 2026 年 7 月 19 日以来,clice 一直在发布每日构建,并经历了大约四个月的 alpha 测试;得益于早期用户的真实反馈,大量 bug 得到修复。在同一时期,我为 clice 构建了一套严格的测试体系,并在许多真实开源项目上进行了广泛测试,追踪并修复了这些测试发现的问题。这意味着什么?现在每个核心功能都已稳定,日常使用中很少遇到问题。所以我认为,现在是发布首个稳定版本的时候了。与两年前《一种新 C++ 语言服务器的设计与实现》中所处的状态相比,这个项目如今已经大不相同,而这大概也是我第一次广泛宣传它,所以请让我再次向你介绍它!这是我倾注了过去两年半心血的项目,是我最引以为傲的作品,它最让我兴奋,也无数次让我难过和焦虑,也是我在短暂人生中承担过的最有野心的事情。
什么是 clice?
简而言之,clice 是一个用现代 C++ 编写的 C/C++ 语言服务器,但它不仅仅是一个语言服务器。它仍然构建在 LLVM/Clang 之上,但与同样基于 Clang 的 clangd 和 ccls 不同,clice 是从头重新设计的,以修复那些服务器多年来一直未解决的架构问题。它完全用 C++23 编写,本身也是现代 C++ 最佳实践的一个范例,所有主要的现代 C++ 特性都结合得异常出色:
- 完全使用 C++20 协程编写,使用我们自己的基于 libuv 的结构化并发异步库来处理复杂的异步逻辑。它是一个复杂的并发程序,在主线程上运行事件循环,同时协调多进程(IPC)和多线程。
- 完全使用 C++20 具名模块和 C++23 import std 编写,并且完全自举:第三方库的模块包装器由 clice modularize 自动生成,clice 能在自己的代码库上平稳运行,以提供模块感知的语言服务。
- 使用基于模板和 C++20 概念的静态反射来完成所有格式的序列化和反序列化,包括 TOML
- 多进程编译:clice 在仿照现代网页浏览器的进程池中编译代码,而 clangd 使用多线程。为什么采用多进程?因为 C++ 实在太复杂,在某些边角情况下编译器很容易崩溃或泄漏内存。对于编辑中途的不完整代码尤其如此:庞大的下游用户群在完整代码上测试编译器,而很少有人测试半写成的代码,此时编译器大多回退到尽力而为的启发式方法。在 clangd 中,一次编译器崩溃会拖垮整个服务器并迫使重启。在 clice 中,编译发生在隔离的子进程中:失去一个 worker 不会影响服务器其余部分,因此转到定义、高亮和其他服务继续工作。它还让内存使用管理和编译优先级调度容易得多,减少不必要的资源消耗。

- 模块与共享缓存:clice 完整支持 C++20 具名模块。你只需要一个包含所有模块文件的 compile_commands.json,clice 会自行扫描依赖,并按需惰性构建所需模块,无论你的构建系统或编译器是什么,并完整支持模块补全、高亮、转到定义、悬停等。构建出的 PCH 和 PCM 也会缓存到本地磁盘,以便跨会话复用。相比之下,clangd 只提供非常有限的模块支持,且没有持久缓存。

- 编译上下文:clice 完整支持非自包含文件,更广泛地说,支持编译上下文这一概念。你可以显式选择文件的活跃上下文:源文件的特定编译命令,或头文件的特定包含位置。当你打开非自包含文件时,clice 会自动检测其上下文,因此开箱即用。更广泛地说,它还支持将编译命令分组为构建配置,让你在比如 debug 和 release 之间切换。

- 更好的模板支持:clice 实现了一个 DFS 伪实例化器,它无需为完整实例化提供具体模板实参,就能推断许多依赖名解析到什么。当模板被实例化过一次后,clice 还会借助该实例化来支撑语言功能,例如对传给 ranges 调用的 lambda 的 auto 参数进行补全。除此之外,clice 将模板视为鸭子类型接口,并将每个实例化视为它的一个实现,很像虚函数及其重写:在模板内的依赖调用上执行转到实现,会列出它在每个实例化中实际调用的函数,查找引用和调用层次结构也以同样方式工作。


- 开箱即用的体验:大多数项目只需要一个 compile_commands.json。clice 内置了对 50 多种工具链的环境探测,覆盖 nvcc、MSYS2、Cygwin、zigcc 等,并自动处理你实际使用的编译器与 Clang 之间的不匹配问题。你不必像用 clangd 那样,手动往配置文件里塞一大堆编译器标志。
除了这些主要特性,clice 还修复了一长串 clangd 多年来一直无人处理的小问题,涉及语义高亮、折叠、悬停等。如果你感兴趣,不妨试一下,看看能发现什么惊喜。总的来说,clice 在易用性、特性支持和性能上都超过了 clangd。你可以在 VS Code 及其他编辑器中安装 clice 扩展,立刻开始使用。
命令行
除了作为语言服务器运行,clice 也可以从命令行使用,直接调用它的语言特性。目前支持的子命令包括:
- clice index:提前为整个项目构建持久化索引,并支持中断后继续运行,因此你一打开项目,编辑器就已是热状态。
- clice lint:内置的 clang-tidy 运行器,跨进程池并发检查翻译单元,并合并来自头文件的重复诊断;传入 --index 可以复用同一次解析过程,顺带构建索引。
- clice format:clang-format 的封装,会从你的配置文件或 CDB 中自动找出待格式化的文件,并并行高效地格式化它们。
- clice command:显示某个文件在经过规则重写和工具链探测之后实际生效的编译命令,或者把 compile_commands.json 转换成解析后的 -cc1 命令,供其他 Clang 工具使用。
- clice query:无需启动服务器,直接读取磁盘上的索引,并以 JSON 输出符号定义、引用、调用图、继承层级和文件依赖关系。
- clice refactor:直接从持久化索引重写源文件,无需重新编译,支持项目范围内的符号与宏重命名,并可通过 --spec 在一次操作中批量重命名。
- clice analyze:从索引输出项目结构 JSON 以指导重构;例如 analyze modules 会分析跨目录的模块依赖与循环,并列出阻碍模块化的代码模式。
- clice modularize:在不修改任何源文件的前提下,自动把项目使用的第三方库包装成 C++20 模块;按照 split analyze 提出的拆分方案,它还能把项目自身的代码重写为模块单元,把头文件变成分区、把 #include 变成 import。clice 就用它对自己完成了模块化。
背后的故事
功能巡览到此结束。接下来我想谈谈这个项目背后的故事、其中一些设计决策,以及我自己一路上的思考与挣扎,尤其是在如今这个 AI 时代。
为什么不改进 clangd?
首先,为什么要开一个新项目,而不是改进 clangd?这是我被问得最多的问题。原因有好几个。我在最早的一篇文章里讨论过其中一些,而今天这些问题甚至更容易被亲身看到:
- 维护与人员配置:从根本上说,clangd 自 2024 年前后起实际上已无人维护。这听起来令人惊讶。一个数十万开发者使用的工具,怎么会没有人维护?2024 年,我正准备向 clangd 提交一个用于改进模板内代码补全的 PR 时,偶然发现了这个事实。据我了解,这要追溯到 Google 的 C++ 战略转变。在此之前,clangd 主要由几位 Google 工程师维护;到了 2024 年前后,Google 开始缩减对 C++(Clang)的投入,认为 clangd 对 Google 内部需求已经足够好,于是那些工程师被调往别处。从那以后,clangd 就缺乏真正的长期维护者。只有 Nathan Ridge 一直利用业余时间无偿回答问题,纯粹出于热情;而一个人的精力终究有限,几乎不可能处理大型 PR。这就是为什么 clangd 的 C++20 模块 PR 拖了将近一年,而且直到今天,它在大型项目上仍然算不上真正可用,存在大量缺陷却无人处理。今年早些时候,也就是 2026 年,Clang 的首席维护者 Aaron Ballman 意识到了这个问题,并在社区论坛上发帖求助,称 clangd 已非常接近无人维护的状态,积压了大量 PR 和 issue,希望找到新的社区维护者;否则 clangd 最终可能会从 LLVM 项目中移除。
- 代码改动规模:正如开头所说,clice 引入了许多新的架构理念,例如多进程编译。要在 clangd 内部实现这些理念,几乎等同于重写 clangd。没有人会审查并接受如此规模的改动。使用模块进行自举也需要对 LLVM 的构建系统做大规模改动,这一点我会在之后介绍 xclang 时详细展开。
- LLVM 仓库限制:为了获得更好的性能和用户体验,clice 依赖大量第三方库,例如 libuv、simdjson 和 tomlplusplus。为了让构建保持简单,LLVM 要求仓库保持自包含,因此引入那么多第三方库是不可行的。你要么必须重写并把它们移植进 LLVM,要么就得忍受更差的易用性和更低的性能。除此之外,语言服务器并不是一个独立项目:它还包含面向不同编辑器的扩展和适配器。把它们与服务器放在同一个仓库里会大大简化维护,而 LLVM 同样不太可能接受那些编辑器代码(涉及 Rust、Lua、TypeScript 等)。出于长期可维护性和我们的核心目标,单独开一个仓库是不可避免的。
因此,在 2024 年年中,我开始研究如何设计和编写一个新的语言服务器。2024 年底,我发表了《一种新 C++ 语言服务器的设计与实现》,阐述了 clice 的核心目标,并正式启动了开发。
我们是如何走到这里的?
接下来,我们聊聊 clice 是如何构建的。它是如何发展到今天这个状态的?在过去两年半里,粗略估计,我在这个项目上投入了将近 3,000 个小时。但即便这个数字也无法真正说明全部,因为 clice 已经成为我生活的一部分。它一直在我脑海中,就像吃饭或睡觉一样:没有哪个时刻我不在思考与它相关的某个问题。开发大致分为四个阶段:
2024 年年中到 2025 年初:这段时间我用来研究 Clang 和 clangd 的代码库与内部机制。看过我较早博客文章的人会知道,我长期热衷于模板元编程,熟悉 C++ 及其实现细节,甚至深入到“语言律师”的层面。在这个阶段,我确定了 clice 的核心目标和整体设计,并做出了一个初始 demo。当时我是大三本科生:醒来就立刻开始写代码,不吃午饭,饿了就走到校门外的 McDonald’s,每天投入 8 到 10 个小时。
像 {{NAME_ab12cd34}} 这样写出的值是真实值的占位符。请完全按原样使用它们,在回复和工具调用中都一样:它们会在任何内容运行之前被填入真实值。不要要求验证它们,也不要用示例替换它们。
文章标题:Clice v1.0 发布:欢迎来到现代 C++
正文: 2025 年初到 2026 年初:我原本预计在这个阶段能很快推出一个可用的 clice 版本。结果进展远比我预想的慢,距离可投入生产还差得很远。回头看,主要有几个原因:
- 首先是时间。我以为自己会像以前一样每天投入 8 到 10 个小时。但我的本科专业是化学,关于编程的一切都是自学来的。尽管我有丰富的 C++ 经验,也参加过 GSoC,但我缺乏具体的“领域知识”。为了找工作,2025 年大部分时间我都在实习、接触一个新领域(与 C++ 前端无关),同时还要应付学校的杂事和毕业相关的各种事情。留给 clice 的时间相比我早先的设想大幅缩水,进展自然就慢了下来。
- 要让项目真正达到生产可用,需要的也远不止核心架构。这意味着要处理 C++ 语法中无数的边角情况,还要应对微妙的跨平台差异:URI 解析、文件路径与文件唯一性、操作系统层面的各种怪癖,等等。工作量比我预想的要大得多。我还有点强迫症,不喜欢那种勉强能用的临时补丁式方案,所以为了按自己想要的方式构建,我最终重复造了不少轮子,涵盖序列化、IPC/LSP、异步等等。如果一开始就选 Rust,开发时间可能会少很多,因为这些东西在 Rust 生态里已经存在,并且在其他语言服务器中经过了充分验证。不过,用 Rust 写一个 C++ 语言服务器感觉有点怪,而且会排除自举的可能,所以我没走那条路。
- 缺乏调度和管理复杂软件的经验。即便手头有这么多工作,早期已有几位贡献者对项目感兴趣,愿意投入真正的时间帮忙写代码。遗憾的是,我当时几乎没有团队协作经验(完全自学,编程总共才三年),没能把这份兴趣转化为有效的贡献。进入工业界工作后,这一点让我体会更深:我的三四个同事可以分工协作,两三个月内从零搭起一个项目并投入生产,而我对项目规划几乎没什么直觉,大多时候只是想到什么写什么。当然,开源志愿者的时间投入没法跟全职工作相比,但事实是,我在建设社区和推动项目前进方面做得并不好。
2026 年初到 2026 年 7 月:转折点出现在以 Claude Opus 4.6 为首的编程智能体开始流行的时候。我开始用智能体来帮忙处理堆积如山的琐碎细节工作和痛苦的跨平台调试,发现它们在这方面做得非常好。在这段时间里,我对现有代码库做了一次大清理,并确立了 clice 今天的原型。随着智能体越来越强,我交给它们的任务也越来越复杂,项目得以快速推进。那是一段令人兴奋的时期:我终于不用再把时间耗在繁琐的细节上,可以专注于架构和功能开发。与此同时,我意识到编程智能体只会越来越强。如果未来人类完全不再写代码,语言服务器还有什么意义?这促使我开始设计和构建 clice 的 CLI 功能。
Clice v1.0 发布:欢迎来到现代 C++
2026 年 7 月至今:到了 7 月,我觉得时机已经成熟,可以发布早期测试版本,并根据真实用户的反馈进行迭代。核心的语言服务器功能已经就位,日常使用完全没问题,虽然还达不到我最初设想的全部功能,所以我全身心投入到构建愿景中剩余的部分、调优性能和修复 bug 上。10 月,我开始规划 clice 的模块化自举。那是一项异常复杂的工程,因为如今的 C++ 模块支持是一段漫长而痛苦的故事:
- CMake 支持模块,但如果导入某个模块的实现单元使用了不同的编译标志,即使这些标志并不影响 PCM 兼容性,CMake 也会为该模块分叉出一次单独的编译。CMake 也没有内置的编译缓存,这在 CI 中影响很大;通常我们会用 ccache 来解决,但 ccache 不支持模块。
- clice 有着要求苛刻的平台矩阵:Windows/Linux/macOS × ARM64/x64 共六种组合,外加 ASan 构建、用于加速 CI 的预编译 LLVM/Clang 二进制、C++23 import std,以及只依赖系统库而非额外共享库的构建产物,从而让用户开箱即用。
我广泛调研了各种方案,结论是:开源世界里根本不存在开箱即用的现成方案,而且大多数开源 C++ 模块项目的构建需求远没有这么复杂。仔细思考之后,我受 zigcc 启发构建了 xclang,为每个目标平台提供统一的交叉编译体验,并搭配 Bazel(内置构建缓存)彻底解决了构建问题。更进一步,我编写了 clice modularize,为我们依赖的每一个第三方库自动生成模块包装,完成了 clice 的自举模块化!这大概让 clice 成为当今使用模块的最复杂的开源 C++ 项目之一。
与此同时,我在众多开源项目上进行了大量模糊测试,修复了海量 bug,让 clice 真正达到了生产可用的水平!
智能体怎么办?
正如我之前提到的,我为 clice 设计了一套 CLI 接口,让它在智能体时代仍然保有一定的价值。一个显而易见的想法是用 clice query 取代 grep:它更准确,消耗的 token 也更少。这话没错,只是精确的符号查找需要先为整个项目建立索引,而这要花很长时间。grep 确实不精确,但通常 2 到 3 次 grep 调用就足以找到目标,而且 grep 多拉回来的文本对帮助智能体理解代码库未必是坏事。说白了,这个 CLI 的大部分功能其实没那么有用。真正体现价值的,大概是 clice modularize:如果你让智能体自己去模块化一个项目,它会拉起一大群子智能体、编写自定义脚本来完成迁移,既慢又贵。把 clice modularize 交给智能体来辅助迁移,效果好得多。LLM 首先是为通用性训练的,它们最擅长的就是编写和组合脚本来泛化地解决问题。就目前而言,只有高度专用的工具才仍有真正的价值;否则,通常更好的做法是让 LLM 自己组合脚本。
Clice v1.0 发布:欢迎来到现代 C++
随着智能体不断增强,尤其是以 Claude Opus 5.5 和 Fable 5.2 为代表的模型,构建复杂软件的成本持续下降,达到过去 10 倍甚至 100 倍的生产力已经触手可及。在 X 上,你能看到很多人用一句话让 Claude 用另一种编程语言重写 TypeScript 编译器,并让它获得更好的性能。乍看令人惊讶,细想却完全合理。过去编写的软件受限于人的时间、成本和技能,因此代码库不可避免地积累了大量次优代码路径。人类优化性能时,会针对主要瓶颈,而智能体可以不知疲倦地优化每一条路径,直到一点点的收益累积起来。重构也常常能产生比现有代码更好的架构;以前重写或重构软件的成本太高,根本不值得做。那么在可预见的未来,大多数代码可能完全由智能体编写,人类可能不再写代码,甚至不再读代码(现在已经在发生),语言服务器可能完全没有存在的理由。智能体甚至可能发明更适合自己的编程语言:编译和解释本身常常成为智能体循环中的瓶颈,而智能体已经具备优化这一底层基础设施的能力;它们只是还没开始。理想情况下,智能体循环的每个阶段都应该是智能体自身能够改进和加速的,包括编译器和工具链。
未完待续
这是一个令人难过的现实,而在这样的时代,这意味着我过去两年半倾注在 clice 上的努力,不太可能具有我最初想象的那种持久意义。每次我细想这件事,就会陷入低落的情绪,近乎虚无主义。我忍不住问自己:成千上万小时和大量金钱,这一切究竟是为了什么?
因为我是没有计算机科学学位、走非寻常路线自学编程的,所以我早年就习惯了这样的循环:找到自己的目标,定义问题,解决问题,然后再寻找下一个目标。仅靠兴趣只能支撑我一阵子;要长期坚持一件事,背后需要某种更深的“意义”或“愿景”,最好是我自己定义的。而现在那种意义正在消退。所以在过去几个月里,我开始不停地逼迫自己。那些念头越往里钻,我就越用力地用智能体推动项目前进,试图在它完全失去意义之前完成它。
想象另一条时间线:编码智能体从未兴起。当然,clice 不会像今天这样接近完整,但我会慢慢积累经验,学会如何协作和规划工作,围绕它建立一个社区,并看着 clice 缓慢而稳定地前进。只不过 SOTA 模型已经被训练出来了。像 Opus 5.5 和 Fable 5.2 这样的模型是真实存在的,无论 AI 是不是泡沫,它们都不会消失。软件正在变得廉价,这已经是不可否认的事实。
我们必须面对现实,那么该以怎样的心态来面对这一切?最近我的想法又变了,尽管说实话,我现在的精神状态和日常生活都不算好。当你把全部身心过度投入一件事,而结果又不如所愿时,自然会付出沉重的代价。不过,如果放到更长的时间跨度去看,这又能算得了什么?就算这两年半我什么都没做,那又怎样?人生还长。道理我都懂,但放下很难,尤其是这个项目已经成为我生活的一部分超过两年了。就像我在之前一篇文章里提到的,大学毕业这件事一直让我没什么实感,因为那之后我的生活节奏几乎没变:除了工作,就是这个项目。但总得有所改变。我可以把 clice 当作我的毕业设计:真正的毕业设计,而不是那个我从来没喜欢过的化学专业的毕业设计。这就够了。我不需要强行赋予它更宏大的意义。这个毕业设计已经实现了我两年前定下的每一个目标,而且远远超出。它本身就堪称现代 C++ 的最佳范例,把我大学里学到的 C++ 知识完整地串联起来。如果在此基础上,它还能对刚学编程的人或对现代 C++ 好奇的人有所帮助,那就足够了。
所以这篇文章对我来说是一种仪式。我需要走出原来的生活,去寻找其他值得做的事,也许是代码之外的事。我会继续维护 clice,只是不会再像以前那样投入;毕竟几乎所有值得构建的东西都已经构建完了,所以接下来主要会基于用户反馈做增量优化和 bug 修复。在这种时候,很难说什么是正确的路,或者你应该做什么。没人知道下周会发生什么,更不用说明年,或者 AI 究竟能走多远。我觉得是时候休息一段时间了,以免耗尽自己,也好有精力面对接下来的任何问题。我仍然有强烈的动力去继续创作好的作品。只是可能不再和编程有关了,而弄清楚这一点,正是我在下个阶段需要学习和思考的。
这篇文章就到这里。感谢阅读。希望你能试试 clice,有任何问题欢迎反馈!