Prompt写得好不好,AI代码质量差了一倍:我在物流分拣系统里交的学费

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的“人设”,引导其思维方式。
任务目标 “编写一个函数,用于将上游多种格式的订单数据,清洗并转换为下游分析系统要求的单一格式。” 一句话概括核心任务。
输入规范 使用列表或表格详细列出所有可能的输入字段、类型、格式、示例和出现频率。 消除输入歧义。
输出规范 严格定义输出数据结构(字典、类)、每个字段的类型、默认值、约束条件(如字符串长度、枚举值)。最好提供示例。 锁定输出格式。
技术要求
  • 使用Python 3.9+,仅限标准库和项目已引入的`pydantic`(可选)。
  • 必须包含完整的错误处理与日志记录(使用`logging`模块)。
  • 函数必须有详细的类型注解和docstring。
  • 性能要求:目标P99延迟 < 10ms。
约束技术选型和代码质量。
关键假设
  • 所有金额单位为人民币“分”,输出时转换为“元”(浮点数)。
  • 所有时间戳均为UTC毫秒纪元。
  • 字符编码为UTF-8。
明确业务潜规则,避免踩坑。
测试要求 “请为函数生成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,问问自己:如果我是程序员,拿到这样一份需求文档,我能写出好代码吗?如果答案是否定的,那么问题出在谁身上,就很清楚了。

发表评论