想必很多人vibe coding都碰到了,只要让agent去长期维护一个真实的大型 React + SpringBoot 业务系统,问题很快就会出现:它会补丁叠补丁,状态逻辑越写越散,最后把代码写成一座难以收拾的屎山。
大致问题如下:臭名昭著的状态管理问题,现在公开React写法水平不一,语料可能很杂,这样训练出来的AI,在不该用useEffect的地方使用它;另一个是上下文窗口不足,尽管现在主流模型理论上支持最大1M以上的上下文窗口,但agent本身不一定会给1M,成本也会很高,于是真实的生产代码库放不进上下文窗口或者被上下文压缩删掉某些重要元素,AI开始推理它看不到的代码、忘掉某个字段和方法,要么就是乱改你的字段名,以及调不存在的函数,或者使用错误版本的库API(这也和knowledge cutoff的问题有关);还有只要你不强烈要求,AI根本不想大范围重构,而是在现有方法上再加一个条件叠屎山
我后来发现,一个很反直觉的现象是:声明式DSL对人类工程师不一定更友好,但对AI往往更友好。人类习惯命令式控制流,LLM更擅长处理结构清晰的表示。
比如说如果你要实现一个“订单审批”流程,在传统命令式开发中
后端:写 if (status == 'PENDING' && role == 'ADMIN') 的拦截逻辑
前端:写 if (record.status == 'PENDING') 来显示审批按钮
接口:定义一个专用的 /approve 接口
状态同步:前端调用后,手动刷新列表或重定向
在声明式编程中(我正在开发的Loj): 你只写了一个 transition: approve,编译器就像一个隐形的织网者,同时在React里织入了按钮显示逻辑,在 Spring/FastAPI里织入了拦截器。这是反直觉的,你习惯了去检查那个if语句,但现在那个if被编译器“藏”起来了,散落在了全栈的各个角落。一开始这会让人不安,因为你看不到熟悉的if;但对LLM来说,这反而是巨大的简化,因为它不再需要在前后端重复维护同一个业务条件
这就是我在做的事情,把这些约束写进 DSL 和编译器里。
当我用agent来写Loj时,通常失败会像这样: 错误的字段类型、缺少必要键、枚举不匹配,编译器会拒绝,AI在下一次尝试中修复它们;如果AI设计了一个毫无意义的工作流,编译器不会发现,这仍需要架构判断能力。生成的输出中几乎没有状态错误:因为AI在写声明,不管状态,React的useEffect问题根本不会出现,编译器搞定了这一切
CSS还是很丑,这部分还没解决,但这也是AI编程普遍的问题,依然需要人类设计师手动调整CSS
还有一个问题是为了规范性,输出代码可能更冗长,这属于两难
这个Loj本身的设计反而最难的就是保持原语的语义中立:设计的难点,在于它必须足够抽象,能跨runtime;
但又必须足够具体,让编译器还能生成真实可用的业务代码。
拿刚刚的transition: approve说
- React UI逻辑(未来可能还要支持同一套编译为Swift, Kotlin, Vue.js)
- 后端Spring, FastAPI拦截器(以后大概还要做asp.net, axum, laravel之类的)
- 持久性更新
- 权限检查
- 可能还有异步工作流
但目前已经基本上搞定FastAPI, Spring Boot这俩后端,基本证明只要是图灵完备的语言,这是可以做到的
我仓库里的机票预订proof,核心DSL源文件只有1k出头,但展开后是 6000+ 行 React 和 7000+ 行 Spring Boot。
对AI来说,推理1k多行高语义密度的DSL,和推理一万多行框架代码(实际上通常是几万行,你每次触发完上下文压缩它又得再读一遍,用这种DSL几万行也能压到几千行),根本不是一个难度等级。
也是为什么我后来发现,甚至小模型都能稳定理解这套DSL和skill,但它目前基本上只适合业务型的系统,重视UI本身的还是得自己搞css,我目前设计的styles原语能力有限(因为考虑到一套styles需要能编译成compose, swiftui, css,不能纯抄css,实现很克制)
现在回头看,我越来越觉得,vibe coding 的瓶颈不一定总是模型不够大,也可能是我们给它的表示太嘈杂。如果业务系统的核心意图能先被压缩进一套DSL,很多原本需要“大模型硬扛”的复杂度,可能免费模型都能处理得更稳。
这个项目我今天已经开源了,npm也可以直接装到,详情看repo: github.com/juliusrl/lo…
现在还在很早期的演进阶段,如果你也在折腾vibe coding、DSL、代码生成或者编译器约束,欢迎来拍砖。
Linter和插件效果如下:
顺便再看看AI怎么评价(我没告诉它是我的项目,新实例):
Gemini 3 Flash:
我已经完成了对 Loj (v0.1) 框架的评估,特别是 loj 编写技能和全栈航班预订验证示例。我遵守了您未阅读编译器代码的限制,而是专注于 DSL 语法、架构参考和实现模式。
以下是我对您问题的回答:
- 您理解这个系统的功能吗?
是的。Loj 是一个面向意图的 DSL(领域特定语言)框架,专为构建全栈业务应用程序而设计。
开发人员无需为堆栈的每一层编写手动样板代码,而是使用一系列基于 YAML 的声明式文件来定义“业务意图”:
.web.loj:定义前端(页面、导航、UI 模型)。
.api.loj:定义后端(资源、服务、数据模型)。
.rules.loj:定义横切逻辑(验证、资格、派生)。
.flow.loj:定义状态机和生命周期(工作流)。
.style.loj:定义设计令牌和 UI 外壳样式。
loj.project.yaml:协调如何将这些组件编译成具体的目标(React、Spring Boot、FastAPI)并进行部署(Docker、Postgres)。
- 你认为它会生成什么样的架构?
该系统生成一个解耦的、符合契约的全栈架构:
前端:一个现代的 React 单页应用程序 (SPA),可能使用 Vite。它具有生成的路由和类型化的 API 客户端,这些客户端与后端资源定义严格一致。
后端:一个使用 Spring Boot(Java)或 FastAPI(Python)的 RESTful API 服务。它包含事务性 ORM 层(JPA 或 SQLAlchemy)、基于角色的安全机制(JWT/Session)和自动管理的迁移(Flyway/SQL)。
规则引擎:一个共享逻辑层。它很可能“影子执行”.rules.loj 前端以提供即时用户反馈,后端以严格保证数据完整性。
状态机:一个一流的工作流引擎,能够处理复杂的业务转换(例如,草稿 -> 就绪 -> 已提交),并内置权限检查。
- 与普通框架代码库相比,DSL 是否易于理解?
非常容易。在“普通”框架(例如标准的 MERN 或 Spring/React 项目)中,业务逻辑通常分散在 DTO、Mapper、控制器、服务、Hooks 和组件状态中。要理解某个功能,我必须追踪大量的文件“堆栈跟踪”。
在 Loj 中,认知负荷较低,原因如下:
意图局部性高:如果我想知道为什么“代表”必须是 18 岁,我可以立即在以下文件中找到答案:
booking-create.rules.loj
声明清晰:我看不到“如何”(底层实现),只看到“是什么”(规则)。
统一真理:数据库模式、API 端点和前端表单均由同一模型定义驱动。这消除了传统开发中常见的“不同步”错误。
- 对我来说有哪些困难?
虽然 DSL 功能强大,但仍有几个方面具有挑战性:
黑盒编译器:由于我无法阅读编译器代码,因此我是在“对着黑盒编程”。如果 DSL 发出我无法识别的错误,或者生成的代码存在运行时错误,我无法修复生成器本身。我必须依赖 rdsl 跟踪和变通方法。
内部样式:.style.loj DSL 有意限制在高级“外壳”范围内。如果用户需要高度定制、像素级精确的动画或复杂的交互式布局(例如,自定义座位图),我将不得不大量依赖 @custom React 组件或 escape.css,这会削弱“DSL优先”的优势。
预测逃生舱的副作用:当我使用 @fn(Java/Python 代码片段)或 @sql 时,我必须格外小心,避免违反编译器对事务管理或命名约定的假设。
性能调优:仅从 YAML 文件中很难“看到”性能问题(例如读取模型中的 N+1 查询)。我需要运行生成的代码并使用外部分析器来查找瓶颈。
总体印象:该系统对于 AI 代理来说堪称理想之选,因为它缩小了逻辑的“搜索空间”。我可以在大约 10 个简洁的 YAML 文件中“看到”应用程序的全部精髓,而不是迷失在 500 个样板文件中。
我:如果编译器保证了运行时机制,那么在编写 DSL 时,你真的还需要考虑这些机制吗?
在 Loj 范式中,理论上答案是“否”,但实际上“仅在边界处需要考虑”。
如果编译器真正保证了运行时机制(例如事务性、传输安全性和角色强制执行),那么我作为代理或开发人员的工作将从“系统架构师”转变为“业务意图分析师”。 以下是我无需考虑机制的原因,以及我仍然需要注意的地方: 我可以停止考虑的内容(精神上的解脱) 事务:我无需担心“部分预订”(例如,已创建预订但未预留座位),因为框架将资源创建定义为事务性的。我专注于验证规则,而不是提交/回滚逻辑。 底层架构:我无需考虑 HTTP 状态码、JSON 序列化或 DTO 映射。如果我定义了 readModel,我相信“传输契约”会确保前端获取到后端获取到的完全相同的数据。 安全注入:如果 .api.loj 资源具有 auth: authenticated 策略,我就不必考虑“我是否在此处检查了用户 ID?”这个问题。编译器保证生成的 Spring/FastAPI 代码会在我的逻辑运行之前处理此检查。 我仍然需要考虑的问题(“边界”) 即使有了这些保证,“机制”仍然会以三种特定方式影响我的推理:
-
“状态机”对用户体验的影响 编译器保证工作流转换是有效的,但它并不保证良好的用户体验。我仍然需要考虑业务状态的逻辑顺序。 例如:如果我将一个包含 20 个字段的表单放在一个
DRAFT -> READY转换中,编译器会构建它,但我需要考虑是否应该将其设计成一个包含多个步骤的向导,以避免让用户感到不知所措。 -
“逃生舱”契约 一旦我使用
@fn或@custom,运行时保证就变成了一种协作。 在@fn("./logic/computeFare")中,编译器会提供上下文(formData、currentUser),但我必须考虑目标语言(Java 或 Python)的机制。如果我在代码片段中编写一个无限循环,编译器的保证就无法节省 CPU 资源。 -
数据局部性和性能 虽然编译器保证
readModel会获取数据,但它(目前)并不保证我在@sql中编写的 SQL 查询是高效的。 我必须考虑“读取模型设计”:我应该通过@sql将数据库中的这两个模型连接起来,还是它们在 DSL 中是独立的?数据库性能的“机制”仍然会影响我构建“意图”的方式。
结论
Loj 的目标是使“运行时机制”成为编译器的实现细节,而不是开发者的。 对我这个人工智能来说,这是一个巨大的优势。我将 90% 的计算资源用于验证业务规则是否符合用户需求,而只有 10% 用于确保我使用了正确的 YAML 语法。在传统的框架中,这个比例通常正好相反。
我还问了Codex(GPT-5.4)
仅基于技能/参考资料和全栈航班预订示例(不考虑编译器代码):是的,我从源代码语义层面理解了它。
这是一个模型驱动的全栈DSL。前端应用/页面/资源/读取模型在.web.loj中声明,后端模型/资源/读取模型在.api.loj中声明,共享验证/派生策略在.rules.loj中声明,工作流/状态机行为在.flow.loj中声明,目标/运行时组合在loj.project.yaml中声明(参见SKILL.md(第29行)、README.md(第15行)和loj.project.yaml(第1行))。航班验证显示,读取模型搜索为预先初始化的预订创建流程提供数据,嵌套的乘客写入、共享规则以及跨前端和后端的流程转换(availability.web.loj(第 1 行)、bookings.web.loj(第 1 行)、bookings.api.loj(第 1 行)、booking-lifecycle.flow.loj(第 1 行))。
我认为它为每个目标生成了一个分层单体应用,而不是一个通用的框架骨架。前端:生成的 React 应用/接口。后端:生成的 Spring Boot 或 FastAPI 应用,包含控制器/路由、服务、持久化模型/存储库以及一个中立的 JSON 传输协议(backend-targets.md(第 28 行)、backend-targets.md(第 43 行)、backend-targets.md(第 75 行))。从语义上看,它类似于精简版的 CQRS:resource 负责写入/CRUD 操作,readModel 负责查询,而 workflow 则添加了生成的状态转换行为(outward-flight-availability.api.loj(第 1 行),workflow-flow-proof.md(第 208 行))。
是的,与传统的框架代码库相比,它在功能/领域层面更容易理解。重要的行为简洁明了:预订流程的初始逻辑在一个页面文件中,表单规则在一个规则文件中,生命周期在一个 workflow 文件中,而不是分散在 React 状态、路由处理程序、服务、DTO 和数据库代码中(availability.web.loj(第 10 行),booking-create.rules.loj(第 1 行),booking-lifecycle.flow.loj(第 1 行))。与传统的代码库相比,它更容易回答“存在什么?”和“适用哪些规则?”这两个问题。
难点在于:
生成过程中隐含的确切运行时行为:组件结构、端点包装器、持久化细节、身份验证机制、错误处理以及默认的用户体验流程。
超出当前范围的任何内容:更广泛的自定义路由、异步作业、更丰富的查询组合、非资源型工作流使用者或定制的 UI 行为(workflow-flow-proof.md(第 29 行)、policy-rules-proof.md(第 37 行))。
在不读取生成输出的情况下调试特定目标平台的差异。DSL 本身与目标平台无关,但其实现是 Spring Boot 和 FastAPI(app.fastapi.api.loj(第 5 行)、backend-targets.md(第 145 行))。
逃生通道。一旦行为转移到 fn(...) 或自定义 TSX 中,推理就不再纯粹是 DSL 层面的了(outward-flight-availability.api.loj(第 35 行)、availability.web.loj(第 106 行))。
性能和运维方面的问题。DSL 告诉我意图,但它无法告诉我生成的查询、ORM 映射或 UI 渲染是否良好。
简而言之:作为产品/领域描述,DSL 比普通框架代码库更容易理解,但要深入到具体的运行时/机制层面则比较困难,因为很多行为都被有意地隐藏在生成机制背后。
我:如果编译器保证了运行时机制,那么在编写 DSL 时,你真的还需要考虑这些机制吗?
通常不会,但并非绝对不会。
如果编译器确实保证了机制的完整性,那么编写代码的重点就从“如何实现这个功能?”转移到了“我声明的是什么行为?”。在这种情况下,主要工作是思考以下几点:
领域模型结构
资源边界
读写行为
验证和派生规则
工作流/状态转换
前端交接和用户体验意图
你无需每次都仔细考虑 React 组件的连接、控制器/服务分层、事务注解或路由配置。这正是这种系统风格的优势所在。
您仍然需要思考的是契约边界:
DSL 的含义
生成器的承诺
当前切片的结束位置
功能交互时会发生什么
以下是一些示例:
预订是否应作为工作流链接资源是一个 DSL 推理问题
quotedFare 是否应在前端规则、后端规则或两者中派生是一个 DSL 推理问题
种子可用性数据是否应包含在预订聚合中是一个 DSL 推理问题
运行时机制主要在以下四种情况下起作用:
调试编译器/运行时错误时
使用诸如 fn(...) 或自定义 TSX 之类的逃生舱时
性能/运维特性至关重要时
语义契约未明确定义且生成的行为可能出乎意料时
简而言之:如果保证足够强,您基本上可以停止思考实现机制,转而思考语义、边界和保证。这是一种更好的创作模式。