Bend语言设计缺陷:AI编码时代的验证陷阱

文章分析Bend语言在形式化验证方面的设计问题,指出其未借鉴成熟方法导致代码冗余。通过对比SPARK实现,揭示AI辅助编程中忽视领域知识的风险。

2026 年 9 月 18 日

本文讨论的重点是 Bend 语言的设计思路,以及它所暴露的「vibe-coding」常见问题。作者本人对这门语言的设计历史并不了解,文中引用的一切均以「一位假设的作者」为讨论对象,而非针对具体个人。

SPARK实现仅需少量代码完成相同功能

Bend 被宣传为面向 AI 编码时代的语言:人类负责编写「规律」(laws),由 AI 编写实现和证明,编译器则负责验证证明是否正确。这一愿景可以理解,但其设计决策存在明显问题。

以下从 Bend 官方仓库主页演示所要求开发者编写的基本代码开始分析:

https://github.com/bendlang/bend/blob/main/demos/app_win_is_bug_2d/LAWS.bend

这段代码本身不值得逐行复述,真正值得注意的一点是它的体量:代码量相当大,而玩家实际上永远无法触旗或赢得游戏。另一个已知问题是 LLM 可以重新定义游戏子程序并任意改写行为,但这并非本文的讨论范围。

再看 LLM 为证明上述「规律」所需编写的代码:

https://github.com/bendlang/bend/blob/main/demos/app_win_is_bug_2d/PROOF.bend

问题在这里变得具体:仅仅是证明几个简单的属性,就需要 442 行代码。

这被称为「vibe-coding 陷阱」的原因在于:在对问题领域有充分了解之前,完全有可能构建出一个实质性的解决方案——一位开发者可以独立完成一门完整的语言及其编译器,却始终不知道相关领域的成熟方法已经存在。

这个领域就是形式化验证(formal verification)。值得注意的是,在 Bend 的网页和代码库中,「形式化验证」一词均未出现。开发者在似乎并未意识到该领域存在的情况下,围绕它从零构建了一门完整的语言。

为了说明问题所在,可以用 SPARK 重建 Bend 演示中使用的同一个程序。以下实现仅由指令「在 SPARK 中重做该演示」直接生成,没有提供任何进一步指导:

package Game with SPARK_Mode is
   subtype Column is Integer range 0 .. 11;
   subtype Row is Integer range 0 .. 7;
   type State is record
      X : Column;
      Y : Row;
      Won : Boolean;
   end record;
   Start : constant State := (8, 5, False);

   function Wall (X : Column; Y : Row) return Boolean is
     (((X = 3 or X = 11) and Y <= 3)
      or ((Y = 3 or Y = 7) and X <= 3));
   function Cell (X : Column; Y : Row) return Character is
     (if Wall (X, Y) then '#' elsif X = 1 and Y = 1 then 'F' else '.');

   --  Inductive invariant: outside the sealed room, off walls, not won.
   function Safe (G : State) return Boolean is
     ((G.X > 2 or G.Y > 2) and not Wall (G.X, G.Y) and not G.Won)
     with Ghost;
   procedure Step (G : in out State; Key : Character)
     with Post => (if Safe (G'Old) then Safe (G));

   --  Both Bend laws, including the actual cell drawn by the terminal.
   function Replay (Keys : String) return State
     with Post => not Replay'Result.Won
       and Cell (Replay'Result.X, Replay'Result.Y) /= 'F';
end Game;

------------------------------

package body Game with SPARK_Mode is
   procedure Step (G : in out State; Key : Character) is
      X : Column := G.X;
      Y : Row := G.Y;
   begin
      case Key is
         when 'w' => Y := (Y - 1) mod 8;
         when 's' => Y := (Y + 1) mod 8;
         when 'a' => X := (X - 1) mod 12;
         when 'd' => X := (X + 1) mod 12;
         when others => return;
      end case;
      if not Wall (X, Y) then
         G := (X, Y, G.Won or Cell (X, Y) = 'F');
      end if;
   end Step;

   function Replay (Keys : String) return State is
      G : State := Start;
   begin
      for Key of Keys loop
         pragma Loop_Invariant (Safe (G));
         Step (G, Key);
      end loop;
      return G;
   end Replay;
end Game;

------------------------------

with Ada.Text_IO; use Ada.Text_IO;
with Game; use Game;

procedure Main is
   G : State := Start;
begin
   Put_Line ("Winning is impossible. WASD + Enter to move; q + Enter to quit.");
   loop
      for Y in Row loop
         for X in Column loop
            Put (if X = G.X and Y = G.Y then 'P' else Cell (X, Y));
         end loop;
         New_Line;
      end loop;
      Put_Line (if G.Won then "WON (this should be unreachable)" else "still not won");
      exit when End_Of_File;
      declare
         Keys : constant String := Get_Line;
      begin
         exit when Keys = "q";
         for Key of Keys loop
            Step (G, Key);
         end loop;
      end;
   end loop;
end Main;

这份实现覆盖了与 Bend 演示相同的两条规律。它与 Bend 方案的关键区别在于:正确性证明所需的全部要素都已内置在代码规范中,无需 LLM 再消耗时间和算力去编写 442 行证明。直接运行 GNATprove 即可得到:

Success: all checks proved (12 checks).

用详细的规范取代冗长的手写证明,正是形式化验证领域现行的标准做法,而 Bend 的设计似乎完全没有参考这一既有成果。在动手编写完整的语言和编译器之前进行一些基础调研,本可以显著改善最终结果。

这一案例的意义也超出了 Bend 本身:vibe-coding 使得构建一个糟糕或落后几十年的设计变得轻而易举。当需求被表述为「设计一门能通过证明来验证函数正确性的语言」时,LLM 会照单全收地执行,乐于从零推导一切;它不会主动指出计算机完全可以在没有 LLM 参与的情况下自动生成复杂的证明、从而消减 99% 的工作量,也不会提醒用户所构建的东西早已作为成熟方案存在。

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读