自动化脚本的契约设计:输入、退出码与幂等边界
把效率脚本从一次性命令变成可复用工具,关键是先固定输入、输出、失败方式与副作用:用参数和默认值描述输入,用 stdout 输出机器可读结果、stderr 输出人读信息,用非零退出码区分失败类型,并按副作用而非方法名判断能否重试。编排工具和具体服务端点的选择应放在契约之后,API.PLUS 相关接口与配额需以官方文档核实。
自动化脚本从“我自己能跑”变成“别人也敢调用”,分界线不在于用了多少工具,而在于有没有把输入、输出和失败方式固定下来。把脚本当接口写契约,意味着换一个调度器、换一位同事、隔三个月再跑,行为仍然可预测。下面给出一组可直接套用的约定和一个结构示例。至于用本地 cron、CI 还是某个 API 平台来触发,属于实现选择;API.PLUS 的具体端点、配额与鉴权方式,本次材料未提供,需以其官方文档为准(待核实)。
契约先定五个字段#
- 输入:必填项、可选项、默认值、类型与取值范围。缺省值要写进
--help。 - 输出:stdout 只放机器可读结果(建议 JSON),进度和提示走 stderr,便于下游管道解析。
- 失败:非零退出码加稳定错误码,错误信息写 stderr,不把失败当成功返回。
- 副作用:会写哪些文件、发哪些请求、改哪些记录,事先列清楚。
- 幂等:重复执行是否安全;不安全时,需要什么键或状态检查才能安全重试。
退出码分档,错误体统一#
建议约定:0 成功,2 参数错误,3 依赖不可用(网络、凭据、上游服务),1 未分类失败。退出码只有 0–255,别塞业务细节;业务错误码放进 JSON 错误体。这样调度器只判断成败,人再按错误码分诊。
一个最小骨架(未执行示例)#
以下代码仅示范契约结构,未实际执行,也未连接任何真实端点;环境为 Python 3,依赖需按实际情况补全。
#!/usr/bin/env python3
# 未执行示例:只演示输入、输出与退出码的分层
import argparse, json, sys
EXIT_OK, EXIT_UNKNOWN, EXIT_USAGE, EXIT_DEP = 0, 1, 2, 3
def parse_args(argv):
p = argparse.ArgumentParser(description="批量任务,示例")
p.add_argument("--input", required=True, help="输入文件路径")
p.add_argument("--dry-run", action="store_true", help="只校验不落盘")
return p.parse_args(argv)
def main(argv):
try:
args = parse_args(argv)
except SystemExit as e: # argparse 参数错误时退出码为 2
return EXIT_USAGE if e.code else EXIT_OK
result = {"ok": True, "processed": 0, "dry_run": args.dry_run}
print(json.dumps(result, ensure_ascii=False)) # stdout 只放结果
return EXIT_OK
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
密钥不要写进代码或日志,用环境变量占位,例如 API_KEY=YOUR_KEY(占位符,非真实凭据)。
能否重试,看副作用而不是方法名#
“GET 可以重试、POST 不行”只是常见近似,不是保证。判断依据应是这一步有没有产生不可重复的副作用:读操作可退避重试;写操作要么带幂等键,要么先查询当前状态再决定。批处理还应记录处理位点,重跑时跳过已完成部分。重试要有次数上限、退避间隔和 attempt 日志,否则故障时会把上游压垮。具体服务的重试语义与配额,需要对照其文档确认。
落地顺序#
- 挑一个每周都要跑的脚本,先写清五个字段。
- 补
--help、参数校验与退出码分档。 - 把结果改成 JSON 输出,让下游可以接着消费。
- 加上幂等键、重试上限与结构化日志。
- 最后再接入编排或平台调用。
边界#
并非所有脚本都值得契约化:探索性脚本保持短小更快。契约化的成本在于前期设计,收益随调用方数量增加。本文未做任何性能或并发测试,调度频率、并发上限与配额应以实际运行环境和服务方文档核对。