30秒速览
- 别用“帮我写个代码”这种话糊弄AI,它真会糊弄你回来。写Prompt得像写技术需求文档,输入输出、边界情况、性能要求一个不能少。
- 时间戳、单位、编码这些细节必须在Prompt里定死,不然AI按默认惯例来,上线就等着踩坑。
- 想要高性能代码?在Prompt里直接提具体优化点,比如“内联循环查找”、“常量移出函数体”,AI能给你惊喜。
- 把常用Prompt做成可填写的模板,效率高还稳定,这才是工程师该干的活。
- 知道Prompt的极限,复杂算法和深度集成的活儿,还得自己先搭架子,让AI填肉。
别把大模型当实习生用,它是个工具,不是人
上周,我团队里一个刚接触AI编程不久的小伙子,愁眉苦脸地跑过来找我。他负责我们给“捷运物流”做的一个智能分拣系统里,一个数据预处理模块的开发。这模块得解析上游系统吐出来的、格式五花八门的物流单JSON,然后统一成我们内部的标准格式。按理说,这活儿用Claude Code或者GPT-4写个Python脚本,应该分分钟搞定。但他折腾了两天,生成的代码要么漏字段,要么遇到嵌套结构就崩,要么性能慢得离谱。他给我看他和AI的聊天记录,开头第一句是:“你好,请帮我写一个Python程序来处理物流单数据。”我一看就乐了,这哪是跟AI说话,这分明是在给一个啥也不懂的实习生布置任务,还是口头布置。
我干了十年机器人和AI,从最早的规则引擎到现在的LLM,一个血泪教训就是:你把AI当成什么,它就会还给你什么。 你把它当实习生,它给你的就是实习生水平的、需要你反复盯着改的代码。你把它当成一个需要精确输入参数、严格遵循规范的工具,它才有可能给你生产级可用的代码。Prompt,就是给这个工具的“设计图纸”和“操作手册”。图纸画得模糊,手册写得潦草,出来的东西能好才怪。那个小伙子最初的Prompt,就犯了“需求不清”和“缺乏约束”两个大忌。AI只能基于那十几个单词,去猜他到底要什么,猜出来的结果自然天差地别。
我跟他说:“重来。别聊天,写需求文档。告诉它输入是什么格式,有几种可能,输出必须是什么样子,边界情况怎么处理,性能上有没有要求,用哪个库,代码风格怎么样。”他照着我的思路,把Prompt从一句话扩充成了一页“技术规格书”。重新生成后,代码质量肉眼可见地提升了,至少核心逻辑对了。但离“能用”还差得远,因为里面充满了假设,比如默认所有时间戳都是ISO格式。现实数据可没这么听话。
这件事让我觉得,是时候把我在项目里关于“如何用Prompt榨干AI编程潜力”的折腾和踩坑,系统地写一写了。这不是什么高深理论,就是实打实的工程经验,一行行代码试出来的。
从“一句话需求”到“技术规格书”:Prompt的蜕变
还是拿那个物流单解析器当例子。我们先看看“一句话需求”和“技术规格书”式的Prompt,生成的代码到底有多大差别。假设我们有一类物流单,JSON结构大概长这样,但变体很多:
// 变体A:标准格式
{
“waybill_no”: “SF1234567890”,
“timestamp”: “2024-05-27T14:30:00Z”,
“sender”: { “name”: “张三”, “address”: “北京市海淀区...” },
“receiver”: { “name”: “李四”, “phone”: “13800138000” },
“items”: [
{ “name”: “书籍”, “weight_kg”: 2.5, “quantity”: 1 }
],
“status”: “已揽收”
}
// 变体B:有些系统时间戳是毫秒级整数
{
“waybillNumber”: “YD987654321”,
“createTime”: 1716805800000,
“from”: “张三”,
“to”: “李四”,
“goods”: [ { “product”: “手机”, “weight”: 0.8 } ],
“currentStatus”: “运输中”
}
// 变体C:数据可能不全,甚至字段名是拼音
{
“danhao”: “ZT555555”,
“shijian”: “2024-05-27”,
“fajianren”: “王五”,
“items”: []
}
糟糕的Prompt(一句话需求):
写一个Python函数解析物流单JSON,提取运单号、时间、发件人、收件人、物品和状态。
用这个Prompt,AI生成的代码可能长这样:
import json
def parse_waybill(json_str):
"""解析物流单JSON"""
data = json.loads(json_str)
waybill_no = data.get('waybill_no')
timestamp = data.get('timestamp')
sender = data.get('sender')
receiver = data.get('receiver')
items = data.get('items')
status = data.get('status')
return {
'waybill_no': waybill_no,
'timestamp': timestamp,
'sender': sender,
'receiver': receiver,
'items': items,
'status': status
}
# 问题太多了:
# 1. 只能处理变体A,遇到变体B、C直接返回None。
# 2. 没有对时间戳进行任何格式化处理,可能是字符串也可能是整数。
# 3. 直接返回了原始的`sender`字典,而我们内部格式可能只需要`name`。
# 4. 没有错误处理,如果JSON非法直接崩溃。
# 5. 没有处理字段缺失的情况,虽然用了`.get`,但返回的None可能不符合下游要求。
# 这代码基本不可用。
改进后的Prompt(技术规格书 V1):
你是一个经验丰富的Python后端工程师。请编写一个健壮的函数,用于将不同来源的物流单JSON数据,解析并清洗成统一的内部格式。
**输入**:
- 输入是一个JSON字符串,可能包含以下常见字段名变体(不限于此):
- 运单号: `waybill_no`, `waybillNumber`, `danhao`
- 时间戳: `timestamp`, `createTime`, `shijian` (值可能是ISO格式字符串、毫秒整数、或“YYYY-MM-DD”字符串)
- 发件人信息: `sender`(对象), `from`(字符串)
- 收件人信息: `receiver`(对象), `to`(字符串), 对象中可能包含`name`, `phone`, `address`
- 物品列表: `items`, `goods` (列表,每个元素包含`name`/`product`、`weight_kg`/`weight`、`quantity`)
- 状态: `status`, `currentStatus`
- 任何字段都可能缺失或为null。
**输出**:
- 返回一个Python字典,严格遵循以下结构:
{
“waybill_no”: str, # 必须,如果缺失则用空字符串
“timestamp”: str, # 必须,统一为ISO 8601格式的字符串,如“2024-05-27T14:30:00Z”。如果无法解析,则使用空字符串。
“sender_name”: str, # 发件人姓名,如果缺失则用空字符串
“receiver_name”: str, # 收件人姓名,如果缺失则用空字符串
“receiver_phone”: str, # 收件人电话,如果缺失则用空字符串
“total_weight_kg”: float, # 物品总重量(千克),如果无法计算则用0.0
“item_count”: int, # 物品总件数(基于quantity求和),如果无法计算则用0
“status”: str # 状态,映射为中文:已揽收、运输中、派送中、已签收。未知状态统一为“在途”
}
**要求**:
1. 函数签名:`def parse_waybill(json_str: str) -> dict`
2. 使用Python标准库`json`和`datetime`。**不要**使用`pandas`等重型库。
3. 必须包含完整的异常处理。如果输入不是合法JSON,应记录错误(可以用print)并返回一个所有字段为默认值的字典。
4. 时间戳解析逻辑:优先尝试ISO格式,然后尝试毫秒整数,最后尝试“YYYY-MM-DD”。使用`datetime.fromtimestamp(timestamp_ms/1000)`处理毫秒整数。
5. 重量单位统一为千克。如果输入重量单位是克,需要转换(假设字段名可能是`weight_g`)。
6. 代码需要有清晰的注释,特别是字段名映射和转换逻辑。
7. 性能考虑:这个函数会被频繁调用(QPS约100),请避免不必要的循环和对象创建。
用这个Prompt生成的代码,框架就靠谱多了:
import json
from datetime import datetime, timezone
def parse_waybill(json_str: str) -> dict:
"""
解析异构物流单JSON至统一格式。
设计目标:健壮性优先,兼顾性能。
"""
# 首先定义默认返回值,确保任何异常情况下都有有效输出
default_result = {
“waybill_no”: “”,
“timestamp”: “”,
“sender_name”: “”,
“receiver_name”: “”,
“receiver_phone”: “”,
“total_weight_kg”: 0.0,
“item_count”: 0,
“status”: “在途”
}
# 1. 解析JSON,捕获最基础的错误
try:
data = json.loads(json_str)
except (json.JSONDecodeError, TypeError) as e:
print(f“JSON解析失败: {e}, 输入: {json_str[:100]}...”)
return default_result
# 辅助函数:安全地从字典中获取值,支持多个可能的键
def get_value(data_dict, possible_keys, default=“”):
for key in possible_keys:
value = data_dict.get(key)
if value is not None:
return value
return default
# 2. 提取并处理运单号
waybill_no = str(get_value(data, [“waybill_no”, “waybillNumber”, “danhao”]))
# 简单清理,移除可能的空格
waybill_no = waybill_no.strip()
# 3. 提取并统一时间戳 (这是复杂点,单独写逻辑)
raw_time = get_value(data, [“timestamp”, “createTime”, “shijian”])
iso_time = “”
if raw_time:
try:
if isinstance(raw_time, (int, float)):
# 假设是毫秒时间戳
dt = datetime.fromtimestamp(float(raw_time) / 1000, tz=timezone.utc)
iso_time = dt.isoformat().replace(“+00:00”, “Z”)
elif isinstance(raw_time, str):
if “T” in raw_time:
# 尝试解析ISO格式
dt = datetime.fromisoformat(raw_time.replace(‘Z’, ‘+00:00’))
iso_time = dt.isoformat().replace(“+00:00”, “Z”)
else:
# 尝试解析YYYY-MM-DD
dt = datetime.strptime(raw_time, “%Y-%m-%d”).replace(tzinfo=timezone.utc)
iso_time = dt.isoformat().replace(“+00:00”, “Z”)
except (ValueError, OverflowError, TypeError) as e:
print(f“时间戳解析失败 ‘{raw_time}’: {e}”)
# iso_time 保持为空字符串
# 4. 提取发件人/收件人信息
sender_info = data.get(“sender”)
if isinstance(sender_info, dict):
sender_name = str(sender_info.get(“name”, “”)).strip()
else:
# 可能是字符串类型的`from`
sender_name = str(get_value(data, [“from”, “fajianren”])).strip()
receiver_info = data.get(“receiver”)
receiver_name = “”
receiver_phone = “”
if isinstance(receiver_info, dict):
receiver_name = str(receiver_info.get(“name”, “”)).strip()
receiver_phone = str(receiver_info.get(“phone”, “”)).strip()
else:
receiver_name = str(get_value(data, [“to”, “shoujianren”])).strip()
# 电话可能在其他字段,这里简化处理
# 5. 处理物品列表,计算总重量和总件数
items_list = get_value(data, [“items”, “goods”], default=[])
if not isinstance(items_list, list):
items_list = []
total_weight_kg = 0.0
total_quantity = 0
for item in items_list:
if not isinstance(item, dict):
continue
# 重量处理:尝试多种字段名和单位
weight = 0.0
weight_key = get_value(item, [“weight_kg”, “weight”])
if isinstance(weight_key, (int, float)):
weight = float(weight_key)
# 如果字段名是`weight_g`,则转换为千克
weight_g = item.get(“weight_g”)
if isinstance(weight_g, (int, float)):
weight += float(weight_g) / 1000.0
total_weight_kg += weight
# 件数处理
qty = item.get(“quantity”, 1) # 默认1件
if isinstance(qty, (int, float)) and qty > 0:
total_quantity += int(qty)
else:
total_quantity += 1
# 6. 状态映射
raw_status = str(get_value(data, [“status”, “currentStatus”])).strip()
status_map = {
“已揽收”: “已揽收”,
“运输中”: “运输中”,
“派送中”: “派送中”,
“已签收”: “已签收”,
“picked up”: “已揽收”,
“in transit”: “运输中”,
“out for delivery”: “派送中”,
“delivered”: “已签收”
}
final_status = status_map.get(raw_status, “在途”)
# 7. 组装结果
result = default_result.copy() # 基于默认值创建副本,避免后续修改影响默认值
result.update({
“waybill_no”: waybill_no,
“timestamp”: iso_time,
“sender_name”: sender_name,
“receiver_name”: receiver_name,
“receiver_phone”: receiver_phone,
“total_weight_kg”: round(total_weight_kg, 3), # 保留3位小数
“item_count”: total_quantity,
“status”: final_status
})
return result
看,天壤之别。第二个版本虽然还不完美(比如电话提取逻辑太简单),但它具备了生产代码的骨架:明确的输入输出、异常处理、字段映射、格式转换、基本的性能提示(避免在循环内多次调用`get_value`)。拿到这个代码,我只需要针对真实数据样本进行微调,比如补充电话字段的提取逻辑,或者增加更多的状态映射。而第一个版本的代码,几乎需要推倒重写。
这个对比说明了一个核心道理:模糊的Prompt让AI猜,清晰的Prompt让AI执行。 猜,就有不确定性;执行,就有确定性。我们的目标就是把不确定性降到最低。
我的踩坑实录:一个“简单”的时间戳解析,让我多花了四个小时
上面那个“技术规格书V1”的Prompt,其实是我在踩了一个大坑之后才总结出来的格式。当时我亲自上手写这个解析器,最初的Prompt比V1还要详细,但偏偏在一个细节上翻了车:时区。
我的Prompt里写了“时间戳统一为ISO 8601格式”,也写了处理毫秒整数的逻辑。AI生成的代码最初用的是`datetime.utcfromtimestamp`。在开发环境用测试数据跑,一切正常。`2024-05-27T14:30:00Z` 完美,`1716805800000` 也正确转成了 `2024-05-27T14:30:00Z`。我信心满满地部署到测试服务器。
结果上线第一天,监控就报警,有大概5%的单据时间戳解析后比实际晚了8小时。我们是在中国,这典型的东八区问题。我一开始以为是数据源的问题,查了半天日志才发现,那些出错的单据,时间戳字段给的确实是毫秒整数,但它代表的是北京时间戳,而不是UTC时间戳! 有些老旧系统,存储的时间戳就是基于本地时区的。
`datetime.utcfromtimestamp` 这个函数,它期望输入的是自UTC 1970年以来的秒数。如果你传了一个代表北京时间的毫秒数进去,它当然会给你一个“错误”的UTC时间。`datetime.fromtimestamp` 在默认情况下,会把你传入的秒数当作本地时间(取决于运行环境的时区设置)来解释,然后生成一个本地时间的datetime对象。我们的服务器时区是UTC,所以`fromtimestamp`和`utcfromtimestamp`对于同一个代表北京时间的数字,结果会差8小时。
我当时的解决方案是打补丁:在解析毫秒整数时,先尝试当作UTC时间戳,如果解析出来的时间看起来“不合理”(比如在凌晨),再尝试当作本地时间戳去调整。这个逻辑写起来非常恶心,而且不靠谱。
折腾了一下午,我意识到问题的根源在于我的Prompt没有定义清楚时间戳的“语义”。我只说了“值可能是毫秒整数”,但没说这个整数是基于哪个时区的纪元。这是业务知识,AI不知道,它只能按最常见的惯例(UTC纪元)来处理。
所以,我重构了Prompt,在“技术规格书”里加入了关键的一条假设,并体现在代码中:
**关键假设与决策**:
- 时间戳整数:**我们统一假设所有整数/浮点型时间戳均代表UTC时间(自1970-01-01 UTC以来的毫秒数)**。如果数据源提供的是本地时间戳,需要在调用此函数前进行转换。这简化了函数逻辑,并将时区责任上推到数据接入层。
同时,我修改了代码,强制使用UTC时区:
if isinstance(raw_time, (int, float)):
# **明确假设:raw_time是UTC毫秒时间戳**
dt = datetime.fromtimestamp(float(raw_time) / 1000, tz=timezone.utc) # 固定使用UTC时区
iso_time = dt.isoformat().replace(“+00:00”, “Z”)
然后,我给数据接入组的同事提了需求,让他们在推送数据前,如果发现是本地时间戳,统一转换为UTC毫秒数。这样,解析函数的职责就单一且明确了。
这个坑让我付出的代价是四个小时的调试和沟通时间。但它换来了一个更深刻的认知:Prompt不仅要定义“做什么”和“怎么做”,还必须定义“在什么假设下做”。 尤其是涉及时间、货币单位、字符编码、坐标系这类容易产生歧义的概念时,必须在Prompt里白纸黑字地写清楚你的约定。AI生成的代码,只会忠实反映你Prompt里的显式信息和它训练数据中的常见模式,它不会主动为你考虑业务上下文里的潜规则。
进阶:用Prompt塑造代码结构和性能
解决了正确性问题,接下来就是追求代码的优雅和效率了。对于那个解析函数,当QPS上升到几百时,一些细微的性能瓶颈就开始显现。最初的版本,为了清晰,我让AI在循环里调用`get_value`辅助函数来获取`weight_kg`等字段。但`get_value`内部有一个小循环,这在最内层循环里被调用,就产生了 O(n*m) 的复杂度(虽然n和m都很小)。
另外,状态映射`status_map`在每次函数调用时都会重建,虽然是个小字典,但也是不必要的开销。还有,每次返回结果都用`default_result.copy()`,然后`update`。有没有更快的办法?
这时,Prompt就要从“功能规格书”升级为“架构与性能设计书”。我给AI提出了新的要求:
基于之前已实现的 `parse_waybill` 函数,请进行重构优化,目标是将平均执行时间降低30%以上(当前约500微秒)。请遵循以下优化指南:
1. **性能优化点**:
- 将`status_map`等常量字典移出函数体,定义为模块级常量,避免每次调用都重建。
- 重新审视`get_value`辅助函数。考虑在性能关键的内部循环(物品列表处理)中,**内联字段名查找逻辑**,避免多次函数调用和内部循环开销。可以保留`get_value`用于外部字段获取。
- 评估`default_result.copy()` 和 `update()` 的模式。尝试改为直接构建最终字典,看看是否更快。
- 考虑使用`try...except`来替代频繁的`isinstance`检查,如果数据结构高度一致,`try...except`在“成功是常态”的情况下更快。
2. **代码结构要求**:
- 保持代码可读性。为性能优化处添加简短注释,说明优化意图。
- 将核心解析逻辑拆分为几个私有的辅助函数(如`_parse_timestamp`, `_extract_person_info`, `_process_items`),使主函数逻辑更清晰。
- 使用类型提示。
3. **请提供优化前后的简单性能对比思路**(例如,使用`timeit`模块测量处理10000条样本的时间)。
AI基于这个Prompt给出的重构版本,核心的优化体现在物品处理循环和常量提取上:
import json
from datetime import datetime, timezone
from typing import Any, Dict, List
# --- 模块级常量,避免重复构建 ---
_STATUS_MAP = {
“已揽收”: “已揽收”,
“运输中”: “运输中”,
“派送中”: “派送中”,
“已签收”: “已签收”,
“picked up”: “已揽收”,
“in transit”: “运输中”,
“out for delivery”: “派送中”,
“delivered”: “已签收”
}
def _parse_timestamp(raw_time: Any) -> str:
"""优化后的时间戳解析,内联逻辑,减少函数调用"""
if not raw_time:
return “”
try:
if isinstance(raw_time, (int, float)):
dt = datetime.fromtimestamp(float(raw_time) / 1000, tz=timezone.utc)
return dt.isoformat().replace(“+00:00”, “Z”)
elif isinstance(raw_time, str):
if “T” in raw_time:
dt = datetime.fromisoformat(raw_time.replace(‘Z’, ‘+00:00’))
return dt.isoformat().replace(“+00:00”, “Z”)
else:
dt = datetime.strptime(raw_time, “%Y-%m-%d”).replace(tzinfo=timezone.utc)
return dt.isoformat().replace(“+00:00”, “Z”)
except (ValueError, OverflowError, TypeError):
return “”
return “”
def _process_items(items_list: Any) -> tuple[float, int]:
"""处理物品列表,内联字段查找以提升性能"""
total_weight_kg = 0.0
total_quantity = 0
if not isinstance(items_list, list):
return total_weight_kg, total_quantity
for item in items_list:
if not isinstance(item, dict):
continue
# 性能优化:内联字段查找,避免在循环内调用通用get_value
weight = 0.0
# 尝试常见的重量字段名
w_kg = item.get(“weight_kg”)
if isinstance(w_kg, (int, float)):
weight += float(w_kg)
w = item.get(“weight”)
if isinstance(w, (int, float)):
weight += float(w)
w_g = item.get(“weight_g”)
if isinstance(w_g, (int, float)):
weight += float(w_g) / 1000.0
total_weight_kg += weight
# 处理数量
qty = item.get(“quantity”, 1)
if isinstance(qty, (int, float)) and qty > 0:
total_quantity += int(qty)
else:
total_quantity += 1
return total_weight_kg, total_quantity
def parse_waybill_optimized(json_str: str) -> Dict[str, Any]:
"""优化版本的物流单解析器"""
# 快速路径:无效JSON直接返回默认值
try:
data = json.loads(json_str)
except (json.JSONDecodeError, TypeError):
# 生产环境应使用日志器而非print
return _get_default_result()
# 主逻辑:直接构建结果字典,避免copy+update
result = {
“waybill_no”: “”,
“timestamp”: “”,
“sender_name”: “”,
“receiver_name”: “”,
“receiver_phone”: “”,
“total_weight_kg”: 0.0,
“item_count”: 0,
“status”: “在途”
}
# 1. 运单号 (保持使用简化的get_value逻辑,因为调用次数少)
for key in [“waybill_no”, “waybillNumber”, “danhao”]:
if key in data:
val = data[key]
if val is not None:
result[“waybill_no”] = str(val).strip()
break
# 2. 时间戳
time_key = next((k for k in [“timestamp”, “createTime”, “shijian”] if k in data), None)
if time_key is not None:
result[“timestamp”] = _parse_timestamp(data[time_key])
# 3. 发件人/收件人信息 (逻辑略,同样可以优化)
# ... 这里省略具体实现,但思路类似,优先使用 `in` 检查和直接键访问
# 4. 处理物品列表 (调用优化后的函数)
items_key = next((k for k in [“items”, “goods”] if k in data), None)
if items_key is not None:
weight, count = _process_items(data[items_key])
result[“total_weight_kg”] = round(weight, 3)
result[“item_count”] = count
# 5. 状态 (使用模块级常量)
status_key = next((k for k in [“status”, “currentStatus”] if k in data), None)
if status_key is not None:
raw_status = data[status_key]
if isinstance(raw_status, str):
result[“status”] = _STATUS_MAP.get(raw_status.strip(), “在途”)
return result
def _get_default_result() -> Dict[str, Any]:
"""返回默认结果的函数,隔离默认值定义"""
return { ... } # 同上的默认字典
# 性能对比测试框架 (AI建议的)
if __name__ == “__main__”:
import timeit
sample_json = json.dumps({ ... }) # 填充一个样本
number = 10000
time_old = timeit.timeit(“parse_waybill(sample_json)”, setup=“from __main__ import parse_waybill, sample_json”, number=number)
time_new = timeit.timeit(“parse_waybill_optimized(sample_json)”, setup=“from __main__ import parse_waybill_optimized, sample_json”, number=number)
print(f“旧版函数 {number} 次耗时: {time_old:.3f}秒”)
print(f“优化版函数 {number} 次耗时: {time_new:.3f}秒”)
print(f“性能提升: {(time_old - time_new)/time_old*100:.1f}%”)
实际跑下来,优化版比最初版快了大约40%-50%,达到了预期目标。这个过程中,我没有手动重写循环,我只是通过Prompt告诉了AI优化的方向和具体关注点。AI就能给出符合要求的、结构更优的代码。这就像你是一个架构师,给一个执行力极强的资深程序员布置了明确的重构任务。
这里的关键是,Prompt要具体到代码层面:“内联字段名查找”、“常量移出函数体”、“尝试直接构建字典”。你不能只说“优化性能”,那太模糊了。
Prompt工程化:从临时对话到可复用的模板
在几个项目里反复编写类似的高质量Prompt后,我意识到这玩意儿必须“工程化”。不能每次都从头开始写小作文。我的做法是建立一套Prompt模板系统,主要包含以下几个部分:
| 模板部分 | 内容示例 | 目的 |
|---|---|---|
| 角色与上下文 | “你是一个为物流行业开发数据中间件的资深Python工程师,擅长编写健壮、高性能的数据清洗代码。” | 设定AI的“人设”,引导其思维方式。 |
| 任务目标 | “编写一个函数,用于将上游多种格式的订单数据,清洗并转换为下游分析系统要求的单一格式。” | 一句话概括核心任务。 |
| 输入规范 | 使用列表或表格详细列出所有可能的输入字段、类型、格式、示例和出现频率。 | 消除输入歧义。 |
| 输出规范 | 严格定义输出数据结构(字典、类)、每个字段的类型、默认值、约束条件(如字符串长度、枚举值)。最好提供示例。 | 锁定输出格式。 |
| 技术要求 |
|
约束技术选型和代码质量。 |
| 关键假设 |
|
明确业务潜规则,避免踩坑。 |
| 测试要求 | “请为函数生成3个典型的单元测试用例,覆盖正常情况、边界情况和异常情况,使用`pytest`格式。” | 引导AI生成可测试的代码。 |
我把这个模板存成了Markdown文件。每次需要生成新的数据解析或转换代码时,我就复制一份,然后像填表格一样,把具体业务细节填进去。效率提升了不止一倍,更重要的是,生成的代码质量非常稳定。
更进一步,对于一些非常通用的任务(比如“写一个FastAPI的CRUD端点”、“写一个读取配置文件的类”),我甚至准备了更细化的模板,里面包含了几乎所有的最佳实践。这相当于把我多年的编码习惯和项目规范,“灌输”给了AI。
Prompt的边界:知道什么时候该自己动手
吹了这么多Prompt的好处,但它不是银弹。有些时候,过于复杂的Prompt反而会适得其反,或者AI根本生成不出你想要的、高度定制化的复杂逻辑。
我遇到过两个典型的场景:
1. 算法逻辑复杂且新颖。 有一次需要实现一个自定义的、基于动态规划的路径成本估算算法,里面有很多业务特有的权重公式和状态转移条件。我尝试用Prompt描述,结果AI生成的代码要么逻辑错误,要么性能极差。因为它没有在训练数据里见过类似的模式。这时候,最好的办法是自己先写出核心算法的伪代码或者草稿,然后用Prompt让AI帮你“填充”细节、完善边界条件、或者转换成更优雅的实现。也就是“人主攻,AI辅助”,而不是反过来。
2. 与现有复杂代码库深度集成。 如果你需要在一个庞大的、结构特殊的现有项目中添加一个功能,单纯靠Prompt让AI理解整个项目上下文是不现实的。它生成的代码很可能接口对不上,或者破坏了现有的设计模式。我的策略是:先手动创建好函数签名、接口定义和关键位置的注释,然后让AI根据这些“骨架”和上下文,去生成函数体内的具体实现。或者,使用Claude Code的“@”引用功能,直接指向相关的现有文件,让AI去学习。
记住,Prompt是杠杆,可以放大你的效率,但它不能替代你对问题的深刻理解和对系统的全局掌控。当你发现和AI“沟通”的成本(反复修改Prompt、纠正错误)已经接近或超过自己动手写的成本时,就该停下来了。这时候,往往意味着问题本身需要你先想清楚,或者当前的AI工具还不适合解决这类问题。
写在最后:与其抱怨AI蠢,不如反思自己懒
回顾在“捷运物流”这个项目里的经历,从最初那个小伙子的一句“帮我写个程序”,到最后我们有一套稳定的Prompt模板生成生产级代码,核心的转变就一点:我们开始像对待一个严肃的开发工具一样对待AI编程。
以前我们写API调用,会仔细看文档,设计请求参数和响应处理。现在用大模型写代码,Prompt就是那份“文档”和“参数”。你敷衍它,它就敷衍你。你认真设计它,它就能回报你高质量的、近乎可以直接提交的代码。
我见过太多人抱怨“AI写的代码bug多”、“没法用”。说实话,一开始我也这么想过。但后来我明白了,问题往往出在输入上。我们给了模糊、矛盾、缺乏上下文的需求,却期望得到一个完美的输出,这本身就不符合工程规律。
所以,别再简单地把AI当做一个“自动代码生成器”或者“高级搜索引擎”。把它当成一个能力超强但需要极其精确指令的代码生成工具。你的Prompt,就是给这个工具的编程语言。掌握这门语言,是你用好AI编程的第一步,也是最关键的一步。这门语言的语法,就是清晰、具体、无歧义;这门语言的库,就是你积累下来的Prompt模板和业务知识。
下次当你对AI生成的代码不满意时,先别急着骂它。回头看看你的Prompt,问问自己:如果我是程序员,拿到这样一份需求文档,我能写出好代码吗?如果答案是否定的,那么问题出在谁身上,就很清楚了。