夜雨聆风学习资料网

ARTICLE · 1239375

用 AI 写代码算取巧?先看看编程工具从哪里来

用 AI 写代码算取巧?先看看编程工具从哪里来

借助工具,为什么就能少写那么多代码?从电路、机器指令到 C# 与 Agent,沿关键历史节点追踪答案。

假如一个人借助Agent,做出了一套能运行、也经过验证的程序,却得到这样的评价:

“代码又不是你自己写的,这不算本事。”

我想反问一句:我们平时亲手写下的代码,又有多少能力是自己从头造出来的?

先看一行普通的C#:

button1.Text = "检测中";

在按钮已经创建、程序确实执行到这里的前提下,它可以修改按钮的显示文字。

这行代码当然可以是你亲手敲的。可你没有实现窗口系统,没有画出每一个字的像素,更没有安排芯片里每一处电路该怎样变化。

你写了一行代码,却用上了远不止一行代码的工作。

这个事实并不削弱开发者的价值。相反,它带出了一个值得追问的问题:这些藏在后面的工作,是怎样一层层积累起来,最后让我们只需要写这一行的?

把它概括成“电脑将英文翻译为0和1”,还解释不了Text为什么有用、谁实现了它,以及最早没有编译器时,负责翻译的工具又是怎么造出来的。

这篇文章要追的,就是这条来路:一边看电路怎样为今天的程序提供执行基础,一边看人怎样改进表达和复用工具。电路是理解现代计算机的起点,程序控制的历史却更早;这两条线会在后面的发展中接起来。

沿着它走回这个按钮,还会遇到另一件事:代码可能已经执行,屏幕上的字却没及时更新;屏幕终于显示“合格”,也不代表实际任务就做对了。

到了这里,再讨论Agent,就有了更具体的判断依据:它替人完成了哪部分工作,又把哪些判断和风险留给了人。手写是不是唯一标准,也需要放在这段历史中重新看看。

一、电路不会读代码,但它可以执行规则

先把英文和窗口放到一边。现代数字计算机的底下,是能产生确定响应的物理电路。

设计者把可识别的电压范围对应为逻辑0和逻辑1。这里的0不是“完全没电”,1也不是泛泛的“有电”。软件讨论的是这些被约定好的逻辑状态,不必在每次运算时重新描述导线上的电压。

晶体管可以控制导通与截止,按特定结构连接起来,就能实现与、或、非等逻辑关系。输入改变,输出经过传播延迟后呈现相应状态。电路不理解“两个条件都满足”,但连接结构可以保证它产生相应结果。

这些简单规则可以组合。二进制加法需要算出当前位的和,还要把进位交给高一位;继续组合,就得到多位加法器,再得到能选择不同运算的算术逻辑单元。

到这里,电路已经能算。但它还缺少一件日后程序离不开的东西:记住上一步。

假如每次输出只由眼前输入决定,就无法保留中间结果,也无法记录一项任务做到哪里。引入存储之后,情况变了:当前输入可以和先前状态一起决定下一步。寄存器、计数器以及处理器中的许多控制过程,都依赖这种能力。

这就是组合逻辑与时序逻辑的区别。在常见同步设计中,组合逻辑计算下一状态,存储单元在约定的时钟边沿更新,再参与下一轮计算。时钟提供更新的时序约束,并不意味着所有数字系统都必须采用同一种同步设计。

有了运算、选择和状态,电路可以执行一个连续过程:读入数据,保留结果,根据条件决定接下来做什么。后来软件中的循环、分支和任务状态,都要落到这样的物理基础上。

这段连接也有清楚的数学背景:19世纪布尔研究逻辑的代数表达,1930年代香农将布尔代数用于开关电路的分析与设计,让逻辑关系与电路结构之间的对应有了系统方法。

不过,能把一套规则做进电路,距离今天的电脑还有一个关键差别。我们换一个程序,通常不用拆开主机、重新连接线路。电路的用途,是怎样变得可以更换的?

二、“换任务”,曾经意味着重新接线

如果每增加一个任务,都要重新设计硬件,软件就很难成为今天这样的东西。

把机器的能力与这次的工作安排分开,早在电子计算机之前就有人尝试。19世纪初,雅卡尔织机用穿孔卡控制织造花纹;机器提供动作能力,孔位和卡片排列安排动作。它不是通用计算机,但已经展示出改变工作安排的办法。

巴贝奇的分析机设计进一步面向通用计算。1843年,洛夫莱斯发表的相关注释中,已经包含为分析机安排计算步骤的内容。分析机当时没有按完整设计建成,但人们已经在设想:能否让一台机器接受不同的操作安排?

1936年,图灵从理论上讨论计算机器,以及一台通用机器怎样根据其他机器的描述模拟计算。这为理解通用性提供了数学模型;它与设计、制造一台实际机器,是相互关联却不同的工作。

到了1946年公开亮相的 ENIAC,电子电路已经能高速计算,最初安排程序却仍主要依靠接线和开关设置。计算快了,换题目依然费工夫。

存储程序带来的变化,就在这个地方:把操作指令也编码、存进机器,让机器读取不同的指令序列。

1945年的《EDVAC报告初稿》是这段发展的重要文献之一;1948年6月21日,曼彻斯特 Baby 成功执行存储在电子存储器中的程序,成为工程实现的重要节点。这些报告、设计与实际运行逐步推进,不能合并成某个人在某一天独自发明了现代软件。

从此,改变任务有了一条新的路径:保留运算部件,更换组织它们工作的指令。

图1|左侧表现最初依靠接线与开关安排计算的方式,右侧示意从存储器取指。

以简化处理器为例,寄存器保存当前操作需要的数据,算术逻辑单元完成运算,控制部分根据指令编码安排数据通路,程序计数器保存取指相关的地址状态。顺序执行时位置向后推进;分支、跳转和调用则可以改变接下来执行的位置。

某个编码为什么表示加法,另一个编码为什么表示跳转?因为软硬件共同遵守指令集架构,也就是 ISA。它规定软件可见的指令语义、寄存器等约定,处理器按照这些约定实现行为。

想看到具体约定,可以查看公开的 RISC-V RV32I 规范:寄存器、程序计数器和指令格式都有明确规定。这里用它说明什么是指令集,不代表后面的 Windows/C# 实验运行在这种架构上。

现代处理器还会使用流水线、乱序执行等复杂实现。“取指—译码—执行”可以概括功能,却不能理解成一条指令恰好花一个时钟周期。软件依赖的是指令语义,通常不需要安排内部每根线路的动作。

硬件也没有停在原地:1947年的晶体管突破、1950年代末的集成电路突破,为更小、更密集的电路创造了条件。它们与软件工具的发展并行,并非先把硬件全部发明完,人们才开始考虑编程。

现在,机器可以接受不同指令了。剩下的麻烦转到了人这一边:谁愿意长期维护成千上万个数字编码和地址?

三、 第一个编译器,是谁编译的?

用符号代替难记的编码,是很自然的下一步。

汇编语言提供助记符和标签,汇编器再把这些表示转换成机器可以执行的形式。不同汇编器还可能提供宏和伪指令,所以一句汇编也不能绝对等同于一条机器指令。

这的确方便了,但随即冒出一个绕不过去的问题:汇编器也是程序,编译器也是程序。在这些工具还不存在时,第一个版本怎么来的?

答案没有无限倒退下去。最早的工具,可以先用人直接安排的机器指令建立。

剑桥 EDSAC 的初始指令是一个具体例子:预先设置的指令被装入机器,随后读取纸带,转换并装入后续程序,再把执行交给它。人并不需要先拥有一整套现代编程环境,才能让机器运行第一小段工具。

不过,装入程序、汇编器和编译器各有职责。装入解决把程序放到可执行的位置;汇编转换接近机器操作的符号表示;编译器还要处理高级语言的语法与语义。EDSAC的例子说明起步办法,不等于它已经完成现代编译器自举的全部工作。

有了一套能工作的工具,再用它制造下一套,能力就可以逐步增长。现代 LLVM 的构建文档仍能看到这种分阶段过程:先由宿主机已有编译器构建 stage1,再用 stage1 构建 stage2。

一个编译器也可以用它所实现的语言来编写,这叫自托管。它与自举有关,但不意味着第一个可执行版本能凭空出现;仍需要已有工具或更早的实现作为起点。

图2|上方是EDSAC初始装入的简化过程,下方是LLVM分阶段构建的例子。两者解决的问题不同;这里只比较“已有起点怎样支持下一步”,不把装入器与编译器混为一谈。

回头看,编程史里很有意思的一条线已经出现了:人造出工具,再借助工具,减少下一次造工具的困难。

只是,汇编解决了“难记”,还没有充分解决“难表达”。写一个业务过程,人依然需要安排许多贴近机器的动作。于是,新的语言开始尝试把这一部分工作交给编译器。

四、英文出现以后,机器仍然没有学会英语

高级语言让人可以用数学表达式、条件、循环和过程组织程序,不必事事从机器指令开始描述。

1957年发布的 FORTRAN,是这一变化的重要里程碑。程序员可以写较高层的数学表达,再由编译器生成目标机器可以执行的代码。“高级”说的是抽象层次,不是所有情况下都比低层表达更好。

程序越来越大之后,少写几条指令已经不够。数据怎样组织?不同功能怎样隔离?改动一处,会不会影响另一处?这些问题把过程、模块、类型和对象推到了更重要的位置。

1960年代的 Simula 已经探索对象化的程序组织。后来的 C、C++、Java 等语言,又在系统开发、抽象能力与运行环境上作出不同取舍。它们互有影响,却不是逐代淘汰的升级链;C#也只是这片发展中的一条路线。

这些变化和开头的按钮有直接关系。button1能被当作一个对象使用,是因为语言和库已经允许我们把状态与行为组织在一起。对象也不一定对应现实中的物品:一份配置、一次检测结果、一个通信会话,都可以这样表达。

类型则限制数据可以怎样使用。同样是数值100,可以表示产品数量,也可以表示像素强度。存储中的比特并不知道自己表示什么;类型、编码与程序使用规则共同确定它的意义。

因此,看到英文拼写,很容易产生一种错觉:电脑终于开始理解人说的话了。

实际上,编译器分析的是符合语言规则的文本。关键字、开发者声明的名称、库中提供的成员,来源和作用各不相同。Text在某个类型中有效,换一个不提供这个成员的类型,就可能无法通过编译。它不靠英文单词的字面含义工作。

语言把“怎样表达”变方便了,但还有另一半问题:你写下Text时,那些让文字显示出来的实现,究竟由谁来写?

五、你能只写一行,是因为有人已经写了很多行

如果每个应用都要自己完成文件读写、设备访问和窗口管理,高级语言再好用,开发起点也不会太高。

软件复用的发展,正是在减少这种重复。早期 EDSAC 的工作已经包含子程序与子程序库:把常用过程整理起来,留给后续程序使用。

操作系统则处理更公共的资源问题,包括运行中的程序、内存、文件和设备。1961年演示的 MIT CTSS 是分时系统发展的一个节点,展示了多个用户怎样通过调度共享计算机;它不是操作系统从无到有的唯一时刻。

在现代系统里,应用通常通过公共接口使用服务,驱动与特定硬件及系统机制配合。应用、操作系统核心和驱动各有职责。也不是每调用一个函数,都必须一路访问到某台设备。

再往应用这一侧看,几种经常混用的名字就能分开了。

程序库提供可复用的实现;API是调用入口及约定,例如参数、返回值和使用条件;SDK通常把开发所需的库、工具、文档和示例组织成套;框架则往往连应用结构与控制流程也一起提供,在适当时机调用你写的处理代码。它们可能互相包含,但不是同一个东西。

这解释了一个看起来不太起眼的区别:你调用某个库时,通常是你决定何时调用;在框架里,你写好了事件处理方法,还可能要等待框架来调用你。

图形交互进一步把这种协作摆到了屏幕上。1970年代的 Xerox Alto 等研究系统,把图形显示、鼠标与交互软件结合起来。用户通过画面操作,软件响应输入,再把状态呈现出来。交互方式发生了变化,底下依然是执行中的程序。

于是,今天的按钮已经不只是一个图案。它处在窗口、输入、事件与控件实现组成的环境里。开发者可以复用这一整套工作,只补上“点击以后做什么”。

当然,复用不会替你决定业务。相机接口可以提供采图能力,却不会自动确定何时采图、超时如何恢复、这张图究竟属于哪个产品。接口解决了可调用的能力,应用还需要把能力组织成正确的过程。

到这里,开头那一行为什么能那么短,已经有了着落。接下来需要把它真正送进执行环境。C#、.NET和Visual Studio,也是在这一步最容易被混成一件事。

六、从 C# 到 CPU,中间还站着谁?

2002年,C# 1.0 随 Visual Studio .NET 2002 发布。类、接口、属性、事件等在早期版本中就已出现,泛型、LINQ、异步等能力则在后来逐步加入。它所面对的,已经是有着庞大软件积累的开发环境。

写代码的地方,不等于运行代码的条件

C#规定怎样表达程序,编译器负责检查并转换表达。.NET提供运行时、基础库、开发工具及不同应用所需的技术。Visual Studio帮助编辑、构建和调试,应用运行时并不需要一直打开它。

还有一个需要区分的名字:公共语言基础结构 CLI。ECMA-335标准定义类型系统、元数据和CIL指令集等内容;具体运行时再实现相关执行能力。语言规则、公共执行约定和某个运行时版本,是相互配合的几个层次。

.NET本身也在演进。.NET Framework主要面向Windows;2016年的.NET Core 1.0采用开源、跨平台与模块化路线;2020年的.NET 5沿着.NET Core继续发展,并非给.NET Framework 4.x换一个版本号。

所以,“用C#写的”还不能回答它能在哪台机器运行。现代.NET支持跨平台,不代表WinForms、WPF或依赖某个Windows原生SDK的应用也自动跨平台。还要检查目标框架、界面技术和依赖组件。

编译器先要查清:这个名字究竟是谁

回到button1.Text = "检测中";,编译器先处理源文本的语法,再结合声明与引用信息建立符号、分析类型,把名称绑定到具体成员。

button1在当前上下文中代表什么?它的类型有没有可写的Text属性?这些必须有确定答案,后面才谈得上生成代码。缺少声明、引用不全或成员不允许赋值,都可能在编译阶段暴露。

这也是为什么同一句代码,复制到另一个项目里可能根本不能用。拼写相同,不代表它拥有同样的上下文与依赖。

在常见托管编译路径中,C#编译器生成公共中间语言CIL,通常简称IL,同时生成描述类型、成员及引用的元数据,组织进程序集。

程序集是.NET中的代码组织、引用与部署单元,带有元数据和清单信息,不一定等于整个应用。看到DLL后缀也不能直接认定它是托管程序集,原生库同样可能使用这个扩展名。

程序集里的成员引用,也不是已经写死的物理内存地址。运行时还要解析引用、找到并加载所需组件。开发机上能编译,换一台电脑却提示缺库,问题可能就出在这些运行条件上。现代.NET分别为托管程序集与原生库提供相应加载机制。

生成了 IL,还没有等于生成最终机器码

在常见CoreCLR/JIT路径中,运行时会在需要时把IL方法编译为目标处理器的机器代码。JIT本身也是处理器执行的软件。它继续完成转换,不是有一个隐藏的解释者在阅读英文。

现代.NET还可能先快速生成代码或使用预编译代码,再对部分方法生成更优化的版本,这称为分层编译。因此,“运行到一行就逐字翻译一行”不是准确模型。

也可以提前做这项工作。Native AOT在发布时把IL编译为原生代码,但它与其他预编译方案有不同取舍,不能据此假定所有UI框架、反射用法和第三方SDK都支持相同策略。

运行时还提供托管内存管理。垃圾回收器根据对象可达性等信息管理托管堆,却不意味着文件句柄、相机连接等资源都能不管。它们有各自的释放要求。

至于框架依赖部署与自包含部署,区分的是运行时从哪里来:由运行机提供,或随应用提供。它与JIT/AOT不是同一条分类轴。一个EXE也可能只是启动应用的原生apphost,并不能证明全部业务代码已经变成机器码,更不能证明没有其他依赖。

现在,我们知道代码怎样获得含义、怎样被转换,也知道它为什么需要运行环境。但开头那次赋值仍有一个细节没拆开:看起来只是存一个字符串,为什么会触发控件行为?

七、代码执行了,为什么屏幕上的字还没变?

先看赋值本身。

在C#里,属性可以通过访问器执行代码,写入属性并不等同于直接修改一个公开字段。

为了不把语言机制与按钮实现混在一起,我们先使用一个最小类型。以下摘录类型与待观察方法,程序入口和读取IL的辅助代码省略:

public sealed class Status{    public string Text { get; set; } = string.Empty;}internal static class Program{    public static void SetStatus(Status state)    {        state.Text = "检测中";    }    // Main及验证辅助代码省略。}

这里的Text是自动实现属性。本次在.NET SDK 9.0.319、C# 13、net9.0、Release条件下编译测试程序,读取到这个方法的IL:

IL_0000: ldarg.0IL_0001: ldstr "检测中"IL_0006: callvirt System.Void Status::set_Text(System.String)IL_000b: ret

先取得传入对象的引用,再取得字符串,随后调用set_Text访问器,最后返回。看起来像写入一项数据的语句,在这一层已经表现为一次访问器调用。

这四行是最小样例的IL,不是WinForms按钮的反汇编,也不是CPU机器码;它描述IL求值栈上的操作,不是处理器寄存器的逐步记录。调用名称由测试程序从元数据解析得到,核验源码另行保留。

图3|按本次最小实验结果排版,长成员名在图中换行显示;不是调试器截图。

还有一个容易被名字带偏的地方:本例没有把属性声明为virtual,仍然生成了callvirt。不能看见操作码名字就反推C#声明;测试也确认空对象引用会抛出NullReferenceException。

等IL进入运行时,内联等优化还可能改变最终机器代码中的调用边界。应当保持的是程序语义,不是一行C#、一条IL和一条CPU指令之间固定的一一对应。

从一个属性,进入一整套控件实现

最小类型只解释访问器。换成真实按钮,访问器后面连接着框架代码。

以.NET WinForms v10.0.0源码为例,Control.Text的set访问器会检查新旧值,按条件更新缓存,设置WindowText并调用OnTextChanged。当控件句柄已经创建,WindowText的相关路径会调用原生窗口文本设置接口;句柄尚未创建时,路径又不同。

同版本ButtonBase.Text转交给基类属性;它重写的OnTextChanged涉及自动尺寸布局,并调用Invalidate()标记需要更新。你没有在赋值处写出这些操作,是因为控件已经把它们组织在实现里。

这部分是固定版本源码核查,与上面的.NET 9最小编译实验分开,不冒充一次完整按钮实测。不同控件、样式以及WPF、网页等框架,也不能直接套用同一条绘制路径。

赋值完成,与人已经看见,是两件事

图4|同一UI线程被同步任务占用时,状态改变与屏幕绘制可能不能及时衔接。

在常见WinForms应用中,Application.Run启动当前线程的消息循环,处理窗口相关输入。控件把相应操作组织成事件,再执行开发者绑定好的处理代码。

代码改变控件状态,控件标记需要更新,Windows再通过更新区域、绘制消息等机制组织绘制。Invalidate不保证新画面在下一行代码执行前已经呈现。

所以可以出现这样的情况:点击按钮后,程序先把文字设为“检测中”,随即在同一UI线程执行长时间同步任务。赋值已经完成,画面却迟迟没更新,因为消息处理与绘制没有得到及时执行。

问题并不出在那几个字母写错了,而在执行过程的安排。进一步要处理异步任务、线程协作和UI线程访问规则,不能把反复刷新控件当作通用解决办法。

到了字形、颜色怎样出现在屏幕上,又进入控件绘制、系统图形服务与显示输出的职责。本文没有继续穷举所有渲染路径。追到这里,至少可以把两件事分开:程序状态发生变化,以及这种变化被显示出来。

按钮写着“合格”,也不能证明检测真的合格

如果按钮后面连接采图、图像分析或通信,链路还会延伸。有些库由托管代码实现,有些SDK提供C#封装,再调用原生组件;.NET也支持COM等互操作机制,要看具体组件的设计。

P/Invoke是.NET调用原生库函数的一种方式,涉及参数表示与必要的数据封送,并非所有SDK都必经的统一通道。

不管使用哪种接口,线程、内存、设备状态和驱动条件都需要满足。采图接口成功返回,也还要确认拿到的是本次图像、关联的是当前产品。

因此,界面显示“检测中”只证明执行了相应显示操作;显示“合格”,也还需要业务逻辑证明结果来自哪里、是否对应本次任务。一个看得见的正确画面,背后可能仍有错误的数据关系。

这也是追踪这行代码的实际价值:它不仅解释屏幕为什么会变,还帮助我们判断,画面变了以后,究竟能证明什么。

八、一直在借助前人的工具,用 Agent 为什么就算“取巧”?

重新看开头的按钮,那一行已经不太像孤立的英文字母了。

电路、指令集、编译器、操作系统、运行时和控件实现,把大量工作留在了你看不见的地方。你能从这一行开始,是因为前面已经有人把路铺到了这里。

沿着这段历史看,一条方向很清楚:人不断把重复、繁琐、贴近机器的工作交给工具,争取用更少的精力表达要做的事。

从机器编码到汇编,从汇编到高级语言,再到程序库、框架和SDK,变化远不止语法更好记。很多原来需要亲自安排的操作,后来可以直接调用;原来需要反复解决的问题,后来成了公共能力。

这降低的是许多任务的表达与实现成本,不是说整个软件工程从此越来越简单。人也在借助这些能力,去做规模更大、要求更高的系统。维护、安全、数据关系与需求判断,并不会跟着输入的代码行数一起减少。

手写代码值得骄傲,但它不是唯一的资格证明

到了Agent,这件事又向前走了一步。

一类基于语言模型的系统可以结合工具与环境反馈,参与生成、执行和调整步骤。ReAct等研究展示了这种组织方式;它不只是让一句代码变短,而是让工具参与一部分原本由开发者逐项推进的工作。

已经有编码Agent能根据任务修改项目、运行测试,再把改动交给人审查。这里以GitHub公开说明的Copilot cloud agent为例,不意味着所有工具、所有任务都有相同能力。

于是,“这些代码不是你亲手写的”开始被拿来质疑成果的价值。

说实话,我不太认同这个判断标准。

今天用C#开发的人,通常不需要亲手生成每一条机器指令;使用界面框架的人,也不需要重写窗口系统。我们接受这些帮助,是因为没必要在每个项目里重做相同的工作。

如果把“必须自己从头写”当成唯一标准,那编译器、程序库和SDK,同样绕不过这道审问。沿着这个标准一路退回去,最后剩下的不是更专业的开发,而是不断重复前人已经做过的事情。

既然借助前人的工具并不丢人,借助更新的工具,也不该先被贴上“不算真本事”的标签。

这不代表会编程不值得骄傲。写出清楚、可靠、能维护的程序,本来就是一项扎实能力。但值得认可的是能力和成果,不能只剩下“这些字是我亲手敲的”。

把成熟做法继续整理给工具复用,也是同一方向的积累。例如,一些Agent生态中的Skill会打包操作指引、脚本与参考资料。它与提供接口和开发工具的SDK不是同一种东西,却都能让后续工作利用已有积累,不必次次重新摸索。

接受更好的工具,不等于把判断也一起交出去

不过,这里有一个技术差别,不能因为支持AI就略过。

编译器依据明确的语言规则分析和转换程序;Agent面对的常常是还不完整的需求,需要推断、选择和尝试。它可能误解意图、使用不存在的接口,或者写出看起来合理却不满足要求的实现。它不是一个可以直接信任的“更高级编译器”。GitHub也明确要求审查和测试生成代码,特别提醒安全与敏感应用中的风险。

因此,我不赞成用“不接受AI”概括所有谨慎。资深开发者指出边界条件、维护成本和安全风险,是在讨论工具是否适合任务,这些意见应该认真听。合同、保密与学习考核等场景,也可能有明确的使用限制。

但如果理由只剩下“我当年是手写过来的,所以你也必须手写”,讨论的就不再是程序质量,而是某种熟悉的工作方式能不能继续作为门槛。

如果需求、验证和项目规定都满足了,质疑却只剩一句“不是你亲手敲的”,那你维护的是质量标准,还是资格门槛?

图5|两种实现方式都需要满足任务要求、使用权限和交付标准。

假设同一个任务,用Agent协作能够在满足要求的前提下,更快地完成并通过验证,那么少花一些时间敲代码,有什么需要羞愧的?

这里的快,也要把审查、测试和返工的时间算进去,不能只计第一份代码生成得有多快。

反过来,生成得快却没人知道怎么验收,改了一处就破坏另一处,出了问题只会再问一遍AI,这也不能算把事情做好。

手写代码不是免检证明,Agent生成也不是免检理由。

前面那个按钮就是现成的提醒:屏幕显示“合格”,不代表检测真的合格。换成Agent也是一样,它报告“已完成”、给出一份能编译的代码,并不自动证明你的问题已经解决。

能否把需求说清,能否发现结果与目标的偏差,能否判断哪些操作不能擅自执行,仍然是开发者的重要能力。经验丰富的人完全可以把这些经验带进新的工具协作,而不必站到工具的对面。

对于未来,我的判断是:随着能力、工具链和验证机制继续改善,会有更多适合委托的编码工作,不再需要人逐行亲手输入。开发者可能把更多时间放在定义问题、选择方案和核验结果上。但这是一种趋势判断,不是对所有任务都会加速、手写代码一定消失的保证。

我们也可以继续把做好的能力封装成模块,补齐接口、文档、示例与版本约定,交给别人或Agent复用。工具帮助完成的可靠成果,有机会成为下一次开发的基础;错误假设也可能沿同一条路扩散,所以验证依然重要。

说到底,编程的目的从来不只是把代码敲出来。写代码,是让计算机按要求完成任务的一种方式;能否可靠地解决问题,才是评价工程成果的核心。

回到最初那一行C#:它之所以能让电脑干活,是因为前面已经积累了大量实现。到了Agent,我们可以继续利用新的积累,同时对新的风险负责。

我们继承的,不该只是前人留下的工具,也该包括继续改进工具的想法。

代码不是自己手写的,会改变你对开发者能力的判断吗?你更在意哪一点?

主要参考资料

以下为主要参考资料,感兴趣的读者可按机构与资料名称检索。

逻辑电路与计算机结构:Nand to Tetris课程;MIT《6.004 Computation Structures》及布尔与香农资料;RISC-V RV32I指令集规范。

计算机与编程史:计算机历史博物馆、英国科学博物馆的历史资料;《EDVAC报告初稿》;IBM的Fortran历史、奥斯陆大学的Simula资料。

工具自举与程序复用:剑桥大学EDSAC初始指令与早期程序库资料;LLVM分阶段构建文档。

C#与.NET:Microsoft Learn的C#、编译器、程序集及运行时文档;ECMA-335公共语言基础结构标准。

界面与原生互操作:dotnet/winforms v10.0.0源码;Microsoft Learn的消息循环、窗口绘制与原生互操作文档。

Agent的工作方式与使用边界:ReAct论文(arXiv:2210.03629);GitHub Copilot agents负责任使用说明;Anthropic Agent Skills文档。

相关学习资料