原创 张汉东 2024-12-18 20:38 美国
在 AI 时代,编程语言的角色正在发生深刻的变化。那些提供强大类型系统和严格安全保障的 "Shift-Left" 语言,比如 Rust 语言,正在成为 AI 编程的最佳搭档。
“今年下半年懒癌发作了,输出的频率不高,各位读者请多多见谅,感谢各位读者的不离不弃,2025 年我一定重振雄风,为大家奉献更多更好的内容。
在软件开发的历史长河中,人们一直在探索如何构建更可靠、更安全的系统。从最早的机器码编程,到现在的人工智能辅助开发,我们的编程范式一直在不断演进,未来,也许会停留在一个终结性的变革发生后才会嘎然而止:由人类编码转向 AI 编码。
对于 AI 编码的终极形态,我之前以为,它应该是对于人类黑盒化的,AI 会有自己的语言,甚至可能直接进行比特编码,而无需人类干预。
但是我这种想法明显忽略了两个重要的因素:算力与安全。虽然 AI 比人脑要强很多,但你让它直接进行比特位编码,那消耗的算力应该会很庞大。
编程语言从汇编发展到现在的高级语言,就是为了让大脑以最少的“算力”而创造并控制最强大的系统。没有任何理由,轮到 AI 编码就让人类失去对系统的控制力。
所以,AI 编码采用的编程语言,势必也是结构化、且人类可以易于理解的语言。并且,AI 编码的系统,相比于人类开发,也应该是更加安全的系统。
那么,现阶段,什么样的语言是 AI 编码的最佳语言呢 ?
到今天为止,我对这个问题的答案是:Shift-Left 的语言,而目前最理想的 Shift-Left 语言就是 Rust 语言。
什么是 Shift-Left
从测试驱动开发说起
Shift-Left 语言这个术语是我今天在Dev.to[1] 看到的一篇文章中提出的。 这个术语其实并不是他原创。
要理解 "Shift-Left" 的概念,我们可以从软件测试领域的一个重要实践 —— 测试驱动开发(TDD)说起。
在传统的开发模式中,开发流程往往是这样的:
这种模式下,问题往往在开发后期才被发现,导致修复成本高昂。而 TDD 完全改变了这个顺序:
让我们看一个具体的例子:
# 传统开发方式
def calculate_price(quantity, price):
return quantity * price
# 后期才发现:
# - 没有处理负数输入
# - 没有处理非数字输入
# - 没有处理精度问题
而在 TDD 模式下:
# 先写测试
def test_calculate_price():
assert calculate_price(2, 10.0) == 20.0
assert calculate_price(0, 10.0) == 0.0
with pytest.raises(ValueError):
calculate_price(-1, 10.0)
with pytest.raises(ValueError):
calculate_price('2', 10.0)
# 然后实现函数
def calculate_price(quantity, price):
ifnot isinstance(quantity, (int, float)) ornot isinstance(price, (int, float)):
raise ValueError("Inputs must be numbers")
if quantity < 0or price < 0:
raise ValueError("Inputs cannot be negative")
return round(quantity * price, 2)
这就是最早的 "Shift-Left" 实践 ——将问题的发现从开发周期的右端(后期)移到左端(早期)。
为什么说 Rust 是理想的 "Shift-left" 编程语言
如果说 TDD 是开发方法论层面的 "Shift-Left",那么在编程语言层面,这个概念也在不断发展。
传统上,很多问题要等到测试阶段甚至部署后才被发现,这就像是在长途旅行中等到目的地才发现行李少带了重要物品。这种"后期发现问题"的方式既耗时又昂贵。这就是为什么行业逐渐向左移动(Shift-Left),试图在开发周期的早期就发现和解决问题。
(Claude ai 画图)
Rust 语言在这方面表现出色,它将大量的安全检查都移到了编译时。想象一下,这就像是在打包行李时就有一个智能助手,它会提醒你:"别忘了充电器","这件衣服不适合目的地的天气"。Rust 的编译器就是这样一个细心的助手,它在代码编写阶段就帮助我们发现潜在的问题。
让我们通过一个具体的例子来理解这一点。假设我们在开发一个处理用户数据的系统。在很多语言中,你可能会这样写:
def process_user_data(data):
user_name = data["name"]
process_name(user_name)
这段代码看起来简单明了,但它隐藏着多个潜在问题:数据可能不包含name
字段,process_name
函数可能失败。这些问题要等到程序运行时才会暴露。
而在 Rust 中,编译器会强制你考虑这些情况:
fn process_user_data(data: UserData) -> Result<(), ProcessError> {
let user_name = data.name.ok_or(ProcessError::MissingName)?;
process_name(user_name)?;
Ok(())
}
Rust 通过其类型系统和所有权机制,将大量的错误检查"左移"到了编译时。这就像是在建造一座大楼时,不是等到建成后才做安全检查,而是在设计图纸阶段就确保每个细节都符合安全标准。
Rust 的这种"左移"特性体现在多个层面。
1. 类型安全
// JavaScript - 运行时才发现类型错误
function processUserAge(age) {
return age + 1; // age可能是字符串,导致意外的字符串拼接
}
console.log(processUserAge("21")); // 输出:"211"
# Python - 动态类型,但支持类型提示
def process_user_age(age: int) -> int:
return age + 1 # 类型提示不强制,运行时可能出错
// Java - 编译时类型检查
public int processUserAge(int age) {
return age + 1; // 编译器确保age是整数
}
// Rust - 更严格的类型系统
fn process_user_age(age: u32) -> u32 {
age.checked_add(1).expect("Age overflow") // 处理数值溢出
}
2. 内存安全
// C语言 - 手动内存管理,容易出错
char* get_user_name() {
char* name = malloc(100);
strcpy(name, "John");
return name; // 调用者必须记得释放内存
}
// C++ - RAII模式,但仍可能有问题
class UserData {
std::string* name;
public:
UserData() : name(new std::string) {}
~UserData() { delete name; }
// 如果忘记实现拷贝构造函数,可能导致双重释放
};
// Rust - 所有权系统自动管理内存
struct UserData {
name: String, // 自动管理生命周期
}
impl UserData {
fn new(name: String) -> Self {
UserData { name }
}
} // 离开作用域时自动清理
3. 并发安全
# Python - 全局解释器锁(GIL)限制并发
from threading import Thread
counter = 0
def increment():
global counter
for _ in range(1000000):
counter += 1 # 可能出现竞态条件
// Java - 需要手动同步
public class Counter {
private int value = 0;
public synchronized void increment() {
value++; // 需要记得添加synchronized
}
}
// Rust - 编译时确保线程安全
use std::sync::Arc;
use std::sync::Mutex;
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
std::thread::spawn(move || {
*counter_clone.lock().unwrap() += 1;
}); // 编译器强制正确使用锁
我们可以通过一个新的可视化图表来展示不同语言在安全特性方面的对比:
(Claude ai 画图)
4. 错误处理
以“从文件中读取用户配置,解析配置内容,并应用这些配置”为例子。
// JavaScript 的错误处理
asyncfunction loadUserConfig(filePath) {
try {
const data = await fs.readFile(filePath, 'utf8');
const config = JSON.parse(data);
return applyConfig(config);
} catch (error) {
// 这里的错误可能来自多个地方:
// - 文件读取失败
// - JSON 解析失败
// - 配置应用失败
// 但我们很难区分是哪种错误
console.error('Failed to load config:', error);
throw error;
}
}
在 JavaScript 中,所有错误都通过一个通用的 try-catch 块处理。这种方式简单,但我们失去了对具体错误类型的控制,也容易遗漏一些错误情况。
# Python 的错误处理
def load_user_config(file_path: str) -> Config:
try:
with open(file_path, 'r') as f:
data = f.read()
try:
config_dict = json.loads(data)
try:
return apply_config(config_dict)
except ValueError as e:
raise ConfigError(f"Invalid config values: {e}")
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON format: {e}")
except FileNotFoundError:
raise ConfigError(f"Config file not found: {file_path}")
except IOError as e:
raise ConfigError(f"Failed to read config file: {e}")
Python 提供了更细粒度的错误处理,我们可以捕获具体的异常类型。但是这种嵌套的 try-catch 结构可能会变得复杂,而且仍然可能遗漏某些错误情况。
// Rust 的错误处理
#[derive(Debug, Error)]
pubenum ConfigError {
#[error("Failed to read config file: {0}")]
FileError(#[from] std::io::Error),
#[error("Invalid JSON format: {0}")]
ParseError(#[from] serde_json::Error),
#[error("Invalid config values: {0}")]
ValidationError(String),
}
pubfn load_user_config(file_path: &Path) -> Result<Config, ConfigError> {
// 文件读取错误会自动转换为 ConfigError::FileError
let data = std::fs::read_to_string(file_path)?;
// JSON 解析错误会自动转换为 ConfigError::ParseError
let config_dict = serde_json::from_str(&data)?;
// 配置验证错误会使用 ValidationError
apply_config(config_dict).map_err(|e|
ConfigError::ValidationError(e.to_string())
)
}
Rust 的错误处理有几个显著的优势:
相比之下,如果在 Python 或 JavaScript 中实现相同的功能,我们可能需要更多的样板代码,而且容易遗漏某些错误情况。更重要的是,这些语言无法在编译时确保我们处理了所有可能的错误。
AI 时代的编程范式变革
随着GitHub Copilot、Amazon CodeWhisperer 、cursor、windsurf 等 AI 编程助手和编辑器的普及,我们的编程方式正在发生深刻的变化。
这种变化主要体现在以下几个方面:
一、 开发者角色的转变
传统的开发流程是线性的:
需求分析 → 系统设计 → 编码实现 → 测试验证 → 部署上线
而在 AI 辅助开发中,这个流程没有什么太大的变化,但是更加敏捷和迭代频繁了:
需求分析 → 测试用例设计 → AI生成代码 → 实时验证 → 优化迭代 → 部署上线
人类开发者的角色转变:
虽然 AI 自己也可以对代码进行审查,自动检测一些错误,自动生成测试代码等等,可以从一定程度上提升安全性,但是对于安全问题,还是力不从心。
一方面是因为 AI 自身还有幻觉,准确度还不是百分百。另一方面,当前大语言模型类的 AI 的推理能力并没有想象中的好。
总的来说,AI 自动编码对于提升开发效率非常有帮助,但是对于代码品质这方面,似乎还达不到当前人类开发者的生产品质。
但是这种转变却使得 "Shift-Left"语言的优势更加明显。
让我们看一个实际的例子:
假设我们需要实现一个处理用户注册的功能:
# 使用Python时,AI可能生成这样的代码
def register_user(username, email, password):
if validate_input(username, email, password):
user = create_user(username, email, password)
send_welcome_email(email)
return user
这段代码看起来没问题,但存在多个潜在问题:
而使用 Rust,AI 必须生成更完整的代码:
#[derive(Debug)]
pubenum RegistrationError {
ValidationError(String),
DuplicateUser(String),
DatabaseError(DbError),
EmailError(EmailError),
}
pubasyncfn register_user(
username: String,
email: Email,
password: Password,
db: &DatabaseConnection,
email_client: &EmailClient,
) -> Result<User, RegistrationError> {
// 验证输入
validate_input(&username, &email, &password)
.map_err(RegistrationError::ValidationError)?;
// 在事务中处理用户创建
let user = db.transaction(|conn| {
// 检查用户名是否已存在
if user_exists(conn, &username)? {
returnErr(RegistrationError::DuplicateUser(username));
}
// 创建用户
let user = create_user(conn, &username, &email, &password)
.map_err(RegistrationError::DatabaseError)?;
Ok(user)
}).await?;
// 发送欢迎邮件(即使失败也不影响注册)
ifletErr(e) = email_client.send_welcome_email(&email).await {
log::warn!("Failed to send welcome email: {}", e);
}
Ok(user)
}
当然,你如果有严格的 Prompt 或者人类开发者的经验指导,AI 也可以生成像 Rust 一样严谨的 Python 代码。
然而,这里面的关键问题是,在 AI 自动开发中,代码质量的保障来自两个维度:人的经验和工具的反馈。
想象一个年轻画家在学习作画。最理想的情况是,他既有一位经验丰富的老师在旁指导,又有可靠的工具帮助他确保线条的准确和色彩的协调。在编程世界中,开发者的经验就像那位老师,而编程语言的工具链则像那些辅助创作的工具。
当我们使用 Rust 进行开发时,我们获得了一种独特的双重保障机制。
相比之下,当我们使用 Python 这样的动态语言时,代码质量主要依赖于开发者的经验和判断。虽然我们可以使用类型检查工具和代码分析器,但这些工具通常是可选的,而且往往在代码编写完成后才会运行。这就像一个学生独自练习绘画,虽然他可能有绘画指南可以参考,但缺少了实时纠正的机制。
这种差异在 AI 辅助开发中表现得尤为明显。
当 AI 生成 Rust 代码时,每一行代码都必须通过编译器的严格审查。如果代码中存在潜在的问题,编译器会立即指出,并提供清晰的修改建议。这创造了一个良性循环:AI 生成代码,编译器提供反馈,AI 根据反馈改进代码,开发者则可以专注于更高层次的架构和设计决策。
而在使用 Python 时,确保代码质量的责任主要落在了开发者身上。开发者需要仔细审查 AI 生成的每一行代码,考虑各种边界情况,预测可能的运行时错误。某些问题可能直到代码部署到生产环境后才会显现,这增加了维护的成本和风险。
这就像是两种不同的教育方式。Rust 的方式就像有一位严格但耐心的导师,会立即指出每个小错误,并解释如何改正。而 Python 的方式更像是自主学习,主要依靠学习者自己的判断和经验。两种方式各有其价值,但在 AI 辅助开发这个特定场景中,有一个能够提供即时、强制性反馈的工具显然更有优势。
延伸:Rust 语言 AI 自动编码的最佳实践
用了两年的 AI ,我发现我现在基本离不开 AI 的辅助了(现在我基本用 Claude 3.5 sonnet)。
在 AI 辅助开发的过程中,我逐渐发现一些效果特别好的实践:
1. 类型优先设计
先定义类型和接口,让 AI 在强类型系统的约束下生成实现:
// 1. 定义领域类型
pubstruct User {
id: UserId,
name: UserName,
email: Email,
status: UserStatus,
}
// 2. 定义业务规则
pubtrait UserService {
asyncfn register(&self, cmd: RegisterCommand) -> Result<User, RegistrationError>;
asyncfn update_profile(&self, cmd: UpdateProfileCommand) -> Result<User, UpdateError>;
asyncfn deactivate(&self, id: UserId) -> Result<(), DeactivationError>;
}
// 3. AI根据接口生成实现
Rust 的类型系统,本质上就是一种「逻辑推理」。 类型系统和编译器,完全可以补充大模型的推理短板。
2. 分层验证
结合多层次的验证机制:
3. 渐进式开发
采用迭代方式,让 AI 逐步改进代码:
// 第一轮:要完成基本功能和自动测试
pubasyncfn handle_request(req: Request) -> Response {
// 简单的请求处理
Response::ok()
}
// 第二轮:添加错误处理,并改进自动测试
pubasyncfn handle_request(req: Request) -> Result<Response, RequestError> {
validate_request(&req)?;
process_request(&req).await
}
// 第三轮:添加超时和重试,并改进自动测试
pubasyncfn handle_request(req: Request) -> Result<Response, RequestError> {
let result = timeout(Duration::from_secs(5), async {
retry_with_backoff(|| process_request(&req)).await
}).await??;
Ok(result)
}
// 第四轮:增加性能测试
// 第五轮:根据性能测试反馈,优化代码
// ... ...
二、 AI 编程助手的演进
现代 AI 编程助手(如 GitHub Copilot)正在变得越来越智能,甚至像 Cursor /Windsurf 这类 AI 编辑器,都能处理整理个项目的上下文。
当与"Shift-Left"语言,比如 Rust 配合时,它们能够:
fn process_data<T>(data: Option<T>) -> Result<(), Error> {
// AI会建议使用match表达式处理Option
match data {
Some(value) => // ...
None => // ...
}
}
impl TryFrom<RawData> for ProcessedData {
type Error = ProcessError;
fn try_from(raw: RawData) -> Result<Self, Self::Error> {
// AI建议处理所有可能的转换错误
let field1 = raw.field1.parse()?;
let field2 = raw.field2.parse()?;
// ...
}
}
// AI能识别并建议更安全的模式
// 而不是
let mut data = vec![1, 2, 3];
let first = &data[0]; // 可能导致panic
// 建议使用
if let Some(first) = data.first() {
// 安全地处理数据
}
展望未来
随着 AI 技术的进一步发展,我们可以预见:
结语
在 AI 时代,编程语言的角色正在发生深刻的变化。那些提供强大类型系统和严格安全保障的 "Shift-Left" 语言,比如 Rust 语言,正在成为 AI 编程的最佳搭档。它们不仅能够帮助我们写出更可靠的代码,还能指导 AI 生成更好的实现。
最近业内好像又产生一股「用 Rust 重写」的风潮,有 AI 的加持,这股风潮可能会更加持续。
选择编程语言就像选择一位合作伙伴。在 AI 时代,我们需要的不是一个过分宽容的伙伴,而是一个能够及时指出问题、帮助我们把控质量的严格合作者。从这个角度来看,Rust 这样的"Shift-Left"语言无疑是 AI 时代最好的选择之一。
让我们拥抱这个变革,用更智能的工具,构建更可靠的系统。
感谢阅读。
Dev.to:https://dev.to/szabgab/what-is-shift-left-programming-5601