1. 从一个真实的小项目说起:为什么接口设计和异常处理值得单独拎出来讲
做过几个小项目之后你会发现一个规律:功能代码写得再漂亮,只要接口设计得随意、异常处理得潦草,这个项目基本就活不过第二次迭代。我自己就踩过这个坑。早些年做一个内部用的数据同步小工具,功能很简单,就是定时从几个数据源拉数据、清洗、入库。当时想着“小项目嘛,能跑就行”,接口参数随手定,错误处理就是一层try...except包住整个流程,出错就打印一行日志。结果上线第三天,上游一个字段类型变了,整个任务静默失败,日志里只有一句KeyError,排查花了整整一个下午。
那次之后我就明白了一件事:接口设计和异常处理不是“大项目才需要讲究”的东西,恰恰相反,小项目因为人手少、没有专门的测试和运维,更需要把这两块做扎实。接口设计决定了你的模块之间怎么对话、别人怎么调用你、你自己三个月后还看不看得懂;异常处理决定了出问题的时候你是五分钟定位还是五小时抓瞎。
这篇内容就是围绕“让 AI 帮你补齐接口设计和异常处理”这个实战主题展开的。我会把一个小项目从“能跑”到“跑得稳”的完整过程拆开讲,包括接口怎么定、状态码和错误码怎么设计、异常怎么分层捕获、AI 在这个过程中具体能帮上什么忙、哪些地方 AI 给的方案不能直接抄。适合有一定编程基础、正在做或者刚做完一个小项目、想让代码质量上一个台阶的朋友。哪怕你之前没系统学过接口设计,跟着走一遍也能落地。
核心关键词会贯穿全文:接口设计、异常处理、API、HTTP状态码、错误码。这几个词不是孤立的,它们是一条链上的东西——接口设计是骨架,异常处理是保险,HTTP状态码和错误码是这套保险对外沟通的语言。
2. 接口设计到底在设计什么:先想清楚再动手
2.1 接口设计的本质是“契约”,不是“函数签名”
很多人一提到接口设计,脑子里第一反应就是“这个函数要传几个参数、返回什么类型”。这只是最表层的东西。接口的本质是一份契约:调用方和被调用方之间约定好的规则。这份契约至少包含四层信息。
第一层是输入约定:需要哪些参数、每个参数的类型和取值范围、哪些必填哪些可选、默认值是什么。第二层是输出约定:正常返回什么结构、字段含义是什么、有没有分页、时间格式统一不统一。第三层是错误约定:什么情况下会失败、失败时返回什么、调用方怎么区分“参数错了”和“服务挂了”。第四层是行为约定:这个接口是幂等的吗?调用两次会不会产生两条数据?有没有副作用?
我见过太多小项目的接口只做了第一层,第二层勉强凑合,第三层第四层完全空白。结果就是调用方只能靠“猜”和“试”,一出错就抓瞎。所以让 AI 帮忙之前,你自己得先知道要让它帮你补什么。我的做法是先手写一版“最小契约”,把输入输出列清楚,然后让 AI 基于这个骨架去补错误约定和行为约定——这部分恰恰是人最容易漏、AI 最擅长的。
2.2 小项目接口设计的三个务实原则
大厂那套 RESTful 规范、版本管理、HATEOAS 之类的,小项目真没必要全盘照搬。我总结下来,小项目接口设计守住三条线就够了。
第一条:命名要能自解释。别用doStuff、handleData这种名字。用fetchUserProfile、syncOrderStatus这种“动词+名词”的结构,一看就知道干什么。参数名同理,id不如userId,data不如orderPayload。这条看着简单,但它是后面所有工作的基础——名字清晰,AI 补出来的错误处理才有针对性。
第二条:返回结构要统一。不管成功失败,返回的 JSON 外壳保持一致。我习惯用这样的结构:
{ "code": 0, "message": "success", "data": { ... } }成功时code为 0,data放业务数据;失败时code为非 0,message放人类可读的说明,data为 null 或者放调试信息。这样调用方永远只需要判断一个code字段,不用去猜这次返回的是数组还是对象还是错误字符串。
第三条:错误要分类,不要一锅炖。参数错误、权限不足、资源不存在、服务内部错误,这是四类完全不同的东西,调用方的处理方式也完全不同。参数错误应该提示用户改输入,权限不足应该引导登录,资源不存在可能是正常的业务分支,服务内部错误才需要报警。如果全都返回一个500加一句“出错了”,调用方根本没法做差异化处理。
2.3 让 AI 介入接口设计的最佳时机
AI 不是用来替你从零设计接口的,那样出来的东西往往“正确但不对味”——规范是规范,但不符合你项目的实际情况。我的经验是,AI 最适合在三个节点介入。
第一个节点是你写完接口草稿之后。把你手写的接口定义(参数、返回值、简单说明)丢给 AI,让它帮你检查有没有遗漏的边界情况、命名是否一致、返回结构是否统一。这一步 AI 经常能揪出你自己没注意到的坑,比如“这个接口在资源不存在时返回什么没定义”。
第二个节点是设计错误码体系的时候。错误码这东西,人写容易写乱,要么重复要么遗漏。让 AI 基于你的业务场景生成一套分类清晰的错误码表,效率极高。后面会详细讲怎么让 AI 生成可用的错误码。
第三个节点是写接口文档的时候。AI 把接口定义转成 Markdown 表格或者 OpenAPI 描述,比手写快得多,而且格式统一。
提示:让 AI 参与接口设计时,一定要把项目的业务背景、技术栈、调用方是谁说清楚。你给的信息越具体,AI 补出来的东西越贴合。只说“帮我设计一个用户接口”,出来的必然是网上随处可见的模板。
3. 异常处理的分层思路:别让一个 try 包住全世界
3.1 异常处理的核心是“分层”,不是“捕获”
新手写异常处理最常见的模式就是:整个函数体外面套一个大try,except里打印日志返回错误。这种写法的问题在于,你根本不知道错误是从哪一层冒出来的。是参数解析错了?是数据库连不上?还是业务逻辑里某个字段为空?全被同一个except吞掉了。
正确的思路是分层捕获、逐层处理。一个典型的请求处理流程可以分成四层:参数校验层、业务逻辑层、数据访问层、外部调用层。每一层负责捕获和处理自己范围内的异常,处理不了的往上抛,由上层决定是转成业务错误还是系统错误。
参数校验层的异常,比如类型不对、必填缺失,应该在进入业务逻辑之前就拦下来,直接返回“参数错误”类的错误码。业务逻辑层的异常,比如“订单状态不允许取消”,这是业务规则,应该返回明确的业务错误码。数据访问层的异常,比如连接超时、唯一键冲突,一部分可以重试,一部分要转成系统错误。外部调用层的异常,比如第三方 API 返回了非预期结果,需要做超时控制和降级处理。
这样分层之后,每一层的try...except范围都很小,职责很清晰。出问题的时候,看日志里的层级标记就能快速定位。
3.2 HTTP 状态码和业务错误码的分工
这里有个很多人搞混的点:HTTP 状态码和业务错误码是两套东西,各管各的。
HTTP 状态码是传输层和协议层的语言,它告诉调用方“这次 HTTP 请求本身成没成功”。200 表示请求被正常处理了,400 表示请求本身有问题(参数格式错误等),401 表示没认证,403 表示没权限,404 表示路径或资源不存在,500 表示服务端内部错误。它是给 HTTP 客户端、网关、负载均衡这些基础设施看的。
业务错误码是应用层的语言,它告诉调用方“请求虽然被处理了,但业务上没成功”。比如“余额不足”“库存不够”“订单已取消”,这些情况下 HTTP 状态码可能还是 200,但业务错误码是非 0 的。
我见过两种极端。一种是所有错误都返回 200,业务错误码里区分一切,结果监控系统完全看不出服务健康度。另一种是滥用 HTTP 状态码,把业务错误也映射成 4xx、5xx,导致调用方分不清到底是网络问题还是业务问题。正确的做法是:HTTP 状态码反映请求处理的技术结果,业务错误码反映业务执行结果。两者配合使用。
举个具体的例子。用户调用“取消订单”接口,传了一个不存在的订单号。HTTP 层面请求是合法的,返回 200;业务层面订单不存在,返回业务错误码比如40001。如果用户传的订单号格式都不对(比如应该是数字却传了字母),那 HTTP 层面就该返回 400,因为请求本身就不合法。
3.3 错误码设计的实用规范
错误码设计没有绝对标准,但有几条经验值得参考。
用数字分段,不要用字符串。数字错误码便于比较、便于日志检索、便于国际化映射。我习惯用五位数字:第一位表示错误大类,后面四位表示具体错误。比如1xxxx是参数类错误,2xxxx是权限类,3xxxx是资源类,4xxxx是业务规则类,5xxxx是系统类。这样一看错误码就知道大概是什么方向的问题。
错误码要稳定,message 可以变。错误码一旦发布就不要改,因为调用方可能已经基于它写了判断逻辑。但 message 可以优化措辞、可以做多语言。所以调用方判断逻辑应该基于 code,展示给用户的文案基于 message。
每个错误码都要有明确的触发条件。不要设计“通用错误”这种万能码。每个码对应一种明确的场景,这样排查问题的时候才能精准定位。下面这张表是我在一个项目里实际用的错误码分类,可以参考:
| 错误码段 | 类别 | 示例 | 典型 HTTP 状态码 |
|---|---|---|---|
| 10000-19999 | 参数错误 | 10001 必填参数缺失 | 400 |
| 20000-29999 | 认证授权 | 20001 未登录 | 401 |
| 30000-39999 | 资源相关 | 30001 资源不存在 | 404 |
| 40000-49999 | 业务规则 | 40001 状态不允许操作 | 200 |
| 50000-59999 | 系统错误 | 50001 数据库连接失败 | 500 |
这张表的价值在于,它把“HTTP 状态码”和“业务错误码”的映射关系也定下来了。调用方拿到响应,先看 HTTP 状态码判断技术层面,再看业务 code 判断业务层面,两层信息都有。
4. 让 AI 补齐接口设计和异常处理的完整实操
4.1 第一步:把手写草稿整理成结构化输入
AI 的输出质量取决于你的输入质量。我一般会准备一份这样的草稿,包含接口清单和每个接口的基本信息:
项目背景:一个订单管理小工具,Python + FastAPI,调用方是内部前端和定时任务。 接口1:创建订单 输入:userId(必填,字符串), items(必填,数组), remark(可选,字符串) 输出:orderId, status, createdAt 接口2:查询订单 输入:orderId(必填,字符串) 输出:orderId, userId, items, status, createdAt 接口3:取消订单 输入:orderId(必填,字符串), reason(可选,字符串) 输出:orderId, status这份草稿很粗糙,但信息是准的。接下来我会让 AI 做三件事:补全每个接口的错误场景、生成统一的错误码表、给出异常处理的分层建议。
4.2 第二步:让 AI 补全错误场景
我给 AI 的提示词大概是这样:
基于上面的接口清单,帮我列出每个接口可能出现的错误场景,按参数错误、权限错误、资源错误、业务规则错误、系统错误分类。每个场景说明触发条件和建议的 HTTP 状态码。
AI 返回的结果通常会覆盖得比较全。比如“创建订单”接口,它会列出:userId 为空(参数错误)、userId 格式不对(参数错误)、items 为空数组(参数错误)、items 里商品不存在(资源错误)、用户被禁用(权限错误)、库存不足(业务规则错误)、数据库写入失败(系统错误)。这些场景我自己写的时候大概率会漏掉“用户被禁用”和“库存不足”这两个业务场景。
但 AI 的结果不能直接抄。它有时候会过度设计,比如给一个内部小工具加上“请求频率限制”这种根本用不上的错误场景。我的做法是过一遍,把不符合项目实际的删掉,把遗漏的补上。这一步花十分钟,能省后面几个小时的调试。
4.3 第三步:生成错误码表并人工校准
拿到错误场景清单后,让 AI 生成错误码表:
基于上面的错误场景,生成一套五位数字错误码,按类别分段。输出 Markdown 表格,包含错误码、类别、含义、触发条件、建议 HTTP 状态码。
AI 生成的表格通常结构不错,但有几个地方需要人工校准。一是错误码的粒度,AI 有时候会把很细的场景都单独给一个码,导致码表膨胀;有时候又合并得太粗,一个码对应好几种情况。我的标准是:调用方需要做不同处理的场景,才给独立错误码。如果两种场景调用方都是“提示用户重试”,那可以共用一个码。
二是 HTTP 状态码的映射,AI 有时候会把业务规则错误也映射成 4xx,这是不对的。业务规则错误(比如库存不足)HTTP 层面应该还是 200,因为请求本身是合法的,只是业务没成功。
4.4 第四步:生成异常处理代码骨架
错误码表定下来之后,让 AI 生成异常处理的代码骨架:
基于上面的错误码表,用 Python 写一套异常处理骨架。要求:自定义业务异常类,包含 code 和 message;分层捕获,参数校验层、业务层、数据层分别处理;统一异常处理器把异常转成标准响应结构。
AI 生成的骨架大概长这样:
class BusinessError(Exception): def __init__(self, code: int, message: str): self.code = code self.message = message super().__init__(message) class ParamError(BusinessError): pass class ResourceNotFoundError(BusinessError): pass class BizRuleError(BusinessError): pass然后是一个统一异常处理器,把BusinessError转成{"code": ..., "message": ..., "data": null}的响应,把未预期的异常转成50000系统错误并记录完整堆栈。
这个骨架直接可用,但我会做两处调整。一是加上日志分级:业务异常记warning,系统异常记error并带堆栈。二是加上请求上下文信息,比如requestId,方便串联日志。
4.5 第五步:用 AI 做“对抗性检查”
代码写完之后,我会让 AI 扮演一个“挑剔的调用方”,专门找接口设计和异常处理的漏洞:
假设你是一个要调用这些接口的前端开发者,你会对哪些地方感到困惑?哪些错误场景的处理方式不明确?哪些返回结构不一致?
这一步经常能发现意想不到的问题。比如 AI 会指出“取消订单接口在订单已经取消的情况下返回什么没定义”,或者“创建订单成功但库存扣减失败时,订单状态是什么”。这些问题在写代码的时候很容易忽略,但上线后一定会遇到。
5. 常见问题与排查技巧实录
5.1 异常处理里最容易踩的五个坑
坑一:捕获了异常但没记录足够信息。except Exception as e: return error(str(e))这种写法,日志里只有一句错误消息,没有堆栈、没有请求参数、没有用户标识。出问题的时候根本没法复现。正确做法是记录完整堆栈加关键上下文。
坑二:用异常做流程控制。比如用try...except来判断一个 key 存不存在,而不是用if key in dict。异常的开销比正常分支大得多,而且可读性差。异常应该只用于“真正异常”的情况。
坑三:吞掉异常。except: pass是万恶之源。哪怕你暂时不知道怎么处理,至少也要记一条日志。吞掉异常等于把问题埋起来,早晚会以更严重的形式爆发。
坑四:异常信息泄露敏感数据。直接把数据库错误、文件路径、内部 IP 返回给调用方,既不安全也不专业。对外返回的错误信息应该是脱敏的、面向用户的,详细技术信息只记在服务端日志里。
坑五:错误码和 HTTP 状态码映射混乱。前面讲过,这两套东西各管各的。我见过一个项目,所有错误都返回 200,然后业务码里既有“参数错误”又有“服务不可用”,监控系统完全没法用。也见过所有错误都返回 500 的,调用方根本分不清是自己传错了还是服务挂了。
5.2 接口设计常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决建议 |
|---|---|---|---|
| 调用方说“返回看不懂” | 返回结构不统一 | 检查成功和失败的返回外壳是否一致 | 统一为 code/message/data 结构 |
| 错误信息对用户不友好 | 直接透传了技术异常 | 检查异常处理器是否做了脱敏 | 对外 message 面向用户,技术细节记日志 |
| 分不清错误类型 | 错误码粒度太粗 | 检查是否所有错误共用一个码 | 按调用方处理方式拆分错误码 |
| 接口行为不明确 | 缺少行为约定 | 检查文档是否说明幂等性、副作用 | 补充行为说明,必要时加幂等键 |
| 参数校验散落各处 | 没有统一校验层 | 检查校验逻辑是否混在业务代码里 | 抽出独立校验层,进入业务前完成校验 |
5.3 我个人的几条实操心得
第一,错误码表要当成代码一样维护。放在版本控制里,改动要走 review。我见过错误码表散落在各个文件里、互相冲突的项目,维护起来是灾难。
第二,每个接口至少写三个测试用例:一个正常路径、一个参数错误、一个业务错误。这三个用例能覆盖大部分异常处理逻辑,写起来也快。AI 可以帮你生成这些用例的骨架。
第三,日志里一定要有 requestId。从请求进来生成一个唯一 ID,贯穿整个处理链路,所有日志都带上。排查问题时用 requestId 一搜,整条链路清清楚楚。这个习惯在小项目里尤其重要,因为小项目往往没有完善的链路追踪系统。
第四,AI 生成的错误码表要人工过一遍。AI 有时候会生成一些你项目里根本不会出现的错误场景,也会漏掉一些你业务特有的场景。把它当草稿,不要当成品。
第五,异常处理的代码量应该和业务代码量成正比。如果一个项目业务代码一千行,异常处理只有十行,那基本可以断定异常处理是不到位的。当然也不是越多越好,关键是覆盖到所有关键路径。
6. 把 AI 用成“结对伙伴”而不是“代码生成器”
回到最开始那个数据同步工具的例子。如果当时有现在这套方法,我会这么做:先把接口草稿整理出来,让 AI 补全错误场景和错误码表,然后基于错误码表生成异常处理骨架,最后让 AI 做对抗性检查。整个过程大概多花两个小时,但能省下后面无数个排查问题的下午。
AI 在这个流程里的角色不是“替你写代码”,而是“帮你查漏补缺”。它最擅长的是穷举——把所有可能的错误场景列出来,把错误码分类分好,把异常处理的模板生成出来。这些工作人做起来费时费力还容易漏,AI 做起来又快又全。但最终的判断和校准必须由你来做,因为只有你知道项目的实际边界在哪里。
接口设计和异常处理这两件事,说到底是一种“防御性思维”的体现。你在写代码的时候多问一句“这里如果出错了会怎样”,多定义一条“这种情况返回什么错误码”,后面就少一次半夜被叫起来排查的经历。小项目资源有限,更要把这种思维刻进习惯里。AI 能帮你把这种思维落地得更快更全,但思维的起点,还是得你自己迈出那一步。