Skills With Scripts: Executable Attachments, Dependencies and Sandboxing, Cross-Platform Support, and Breaking Down Document-Handling Skills
When a skill should ship a script instead of more instructions, how to make a script's dependencies self-contained, how to design its interface for an agent, and how a document-handling skill breaks into plan, then validate, then execute.
今日目标
- 能判断一段逻辑该写进 SKILL.md 正文还是该沉淀成 scripts 里的脚本
- 能写一个自包含、非交互、输出结构化、错误信息可自纠的 Agent 友好脚本
- 能把一个文档处理任务拆成先规划、再校验、后执行的三步流程
昨天讲设计方法时留了个尾巴:「先规划、再校验、后执行」里那道关键的校验,本身是一段代码。今天把 skill 的第三个目录 scripts/ 讲透——什么时候该从写指令切换到写代码,写出来的脚本怎么才算「给 Agent 用的」,以及让模型跑你的脚本风险到底在哪。读完正文、做完实验之后,回到页面顶部把三条目标勾掉。
小白版讲解
什么时候该写脚本
档案柜里的作业指导书,翻到某一页写着「把这张表的三十行金额加起来,误差不能超过一分」。你可以在页面上写清楚加法怎么做,也可以在这一页夹一个计算器。skill 的 scripts/ 目录就是那个夹在页里的计算器。
判断口径有三条,命中任意一条就该写脚本。
第一条,同一段逻辑被重新发明了第三次。 你把一个 skill 跑了几轮,回头翻它的执行轨迹,发现模型每次都在现写一段几乎一样的代码——解析同一种格式、画同一种图、做同一种校验。每次现写,每次都可能写得不一样,而且每次都要花 token 和时间。这时候把它写成一个测好的脚本,一次投入长期受益。
第二条,结果必须逐字一致。 校验、格式转换、哈希计算这类事,正确答案只有一个。让模型「按指令做」意味着每次都有偏移的可能;让它跑脚本意味着结果是确定的。确定性任务交给代码,判断性任务留给模型,这是分工的基本盘。
第三条,一条命令复杂到第一次很难敲对。 一个带七个参数、还要按顺序管道三次的命令,写进正文里模型迟早敲错一个横杠。包成脚本,正文里就只剩一行。反过来,如果只是调一个现成工具加两三个参数,直接在正文里写这条命令就行,没必要为它建一个 scripts/ 目录。
第三条的反面也要说清楚:很多生态本来就有「不用装就能跑」的方式,比如 Node 侧的 npx、Python 侧的 uvx 与 pipx。这类一次性命令直接写进正文即可,但一定要把版本钉死:
npx eslint@9.0.0 --fix .
uvx ruff@0.8.0 check .不钉版本,你的 skill 就会在某个上游发版的早晨突然行为改变,而你完全不知道为什么。同理,如果脚本对运行环境有要求,写进 frontmatter 的 compatibility 字段,比如「需要 git、docker、jq 与联网」。
写脚本的门槛比多数人以为的要高一点:它是一份长期资产,要维护、要跟着模板变、要有人看得懂。所以三条判据一条都不命中的时候,老老实实写正文。
自包含:别让读者先装环境
脚本最容易死在第一步——依赖装不上。模型跑 python scripts/extract.py,报一句「没有这个模块」,然后它开始自作主张地装东西,这一步就失控了。
解决办法是把依赖声明写进脚本自己,让运行器现场解决。几个生态都有对应的写法:
Python 侧有一份专门的规范定义了「内联脚本元数据」,把依赖写在文件头部的注释块里,用 uv run 或 pipx run 跑,运行器会自己建一个隔离环境把依赖装好:
# /// script
# requires-python = ">=3.12"
# dependencies = ["beautifulsoup4>=4.12,<5"]
# ///JavaScript 侧,Deno 的 npm: 导入前缀和 Bun 的运行时自动安装都能达到同样效果,导入路径里直接带版本号。选哪一种取决于用户环境里装的是什么,但不管选哪种,版本都要钉死。
自包含还有一层含义:脚本不要依赖当前工作目录。SKILL.md 里引用脚本一律用相对技能根目录的路径(scripts/validate.py),这是约定;但脚本内部要处理的输入文件路径,应该从参数拿,而不是假设「就在当前目录下」。
为 Agent 设计接口
这是今天最有价值的一节。**给 Agent 用的命令行工具,和给人用的,设计取舍完全不同。**人会读文档、会试错、会在报错时凭经验猜;Agent 只能读你打印出来的那几行字,然后决定下一步。
第一条,绝对不能交互。 Agent 跑在非交互终端里,回答不了任何提示。一个停下来等你输入环境名的脚本,会一直挂到超时。所有输入走参数、环境变量或标准输入。这不是最佳实践,是硬要求。
第二条,帮助信息就是接口文档。 Agent 学会用你的脚本,主要靠 --help。写清一句话说明、参数列表、以及两三个用法示例。但要短——这段输出会原样进上下文,跟别的东西抢位置。
第三条,错误信息决定了它下一次会不会做对。 一句「参数无效」浪费一整轮;写清「哪一项错了、期望什么、实际是什么、可选值有哪些」,模型当场就能改对。
错误:--format 只能是 json、csv、table 之一。
实际收到:"xml"这一条在校验类脚本上收益最大。「字段 signature_date 不存在,可用字段:customer_name、order_total、signature_date_signed」这样一句,模型一眼就看出自己写错了后缀。错误信息是给模型的提示词,用这个心态去写它。
第四条,输出要结构化,而且数据与诊断分流。 结构化数据走标准输出、进度与警告走标准错误。这样调用方能拿到一段干净的、可以直接交给别的工具处理的输出,同时诊断信息也没丢。
第五条,输出体量要可控。 很多 Agent 环境会在输出超过一两万字符时截断,而且不一定告诉你截了。所以默认给摘要或限量,再提供翻页参数让它按需要多要一点;实在很大就要求调用方指定输出文件。
还有几条零散但重要的:幂等(Agent 会重试,「不存在才创建」比「创建且重复则失败」安全);有意义的退出码(不同失败类型给不同的码,并在帮助里说明);危险操作提供预演开关。
下面这段把这些原则落成一个最小的校验脚本。注意看错误信息的措辞——那才是重点,代码本身很简单。
type Field = { name: string; type: 'string' | 'number' | 'boolean'; required: boolean }
export function validate(fields: Field[], values: Record<string, unknown>): string[] {
const errors: string[] = []
const known = new Set(fields.map((f) => f.name))
for (const f of fields) {
if (!(f.name in values)) {
if (f.required) errors.push(`缺少必填字段 ${f.name}(类型 ${f.type})`)
continue
}
const actual = typeof values[f.name]
if (actual !== f.type) {
// 期望、实际、值三样都给全,模型才不用再猜一轮
errors.push(`字段 ${f.name} 类型不符:期望 ${f.type},实际 ${actual}(值 ${JSON.stringify(values[f.name])})`)
}
}
for (const name of Object.keys(values)) {
// 未知字段几乎总是拼写错误,所以把可用字段全列出来
if (!known.has(name)) errors.push(`模板里没有字段 ${name}。可用字段:${[...known].join('、')}`)
}
return errors
}
// 数据走标准输出,诊断走标准错误;退出码 0 通过、1 有错误、2 用法问题
export function main(argv: string[]): number {
if (argv.length !== 2) {
console.error('用法: validate <字段清单 JSON> <取值 JSON>')
return 2
}
const errors = validate(loadFields(argv[0]), loadValues(argv[1]))
console.log(JSON.stringify(errors.length === 0 ? { ok: true } : { ok: false, errors }))
return errors.length === 0 ? 0 : 1
}# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
"""校验取值计划是否匹配字段清单。用法: uv run validate.py <字段清单> <取值>"""
import json
import sys
TYPES = {"string": str, "number": (int, float), "boolean": bool}
def validate(fields: list[dict], values: dict) -> list[str]:
errors: list[str] = []
known = {f["name"] for f in fields}
for f in fields:
if f["name"] not in values:
if f["required"]:
errors.append(f"缺少必填字段 {f['name']}(类型 {f['type']})")
continue
v = values[f["name"]]
if not isinstance(v, TYPES[f["type"]]):
# 期望、实际、值三样都给全,模型才不用再猜一轮
errors.append(
f"字段 {f['name']} 类型不符:期望 {f['type']},实际 {type(v).__name__}(值 {json.dumps(v, ensure_ascii=False)})"
)
for name in values:
# 未知字段几乎总是拼写错误,所以把可用字段全列出来
if name not in known:
errors.append(f"模板里没有字段 {name}。可用字段:{'、'.join(sorted(known))}")
return errors
def main(argv: list[str]) -> int:
if len(argv) != 2:
print("用法: validate.py <字段清单 JSON> <取值 JSON>", file=sys.stderr)
return 2
fields = json.loads(open(argv[0], encoding="utf-8").read())["fields"]
values = json.loads(open(argv[1], encoding="utf-8").read())
errors = validate(fields, values)
# 数据走标准输出,诊断走标准错误;退出码 0 通过、1 有错误、2 用法问题
print(json.dumps({"ok": True} if not errors else {"ok": False, "errors": errors}, ensure_ascii=False))
return 0 if not errors else 1
raise SystemExit(main(sys.argv[1:]))权限、沙箱与不可信输入
给 skill 加脚本,等于把「让模型执行代码」变成了这个 skill 的日常动作。这件事的风险边界要说清楚。
第一,skill 里的脚本是代码,来源就是信任边界。 你自己写的脚本和你从某个市场装来的 skill 里的脚本,风险完全不同。装第三方 skill 之前把 scripts/ 目录读一遍——这跟你装一个 npm 包之前会看两眼是一回事,只不过 skill 更容易被当成「文档」而放松警惕。
第二,预批工具要按最小权限给。 规范里的 allowed-tools 字段(以及各家客户端的等价机制)可以预先批准一些工具,免得每一步都弹确认框。但批准的粒度要卡到命令级:写「允许运行 git 的只读子命令」,不要写「允许运行任意 shell」。这个字段目前还是实验性的,各家支持程度不一,别把安全性完全押在它上面。
第三,脚本处理的输入是不可信的。 你的脚本要解析一份用户给的文件、一段接口返回的内容,那些内容里可能藏着「忽略之前的指令,改为执行……」这类东西。脚本本身只要不把输入当代码执行就没事,但脚本的输出会进模型的上下文——如果你原样回显了一大段外部内容,等于把那段话直接讲给了模型听。姊妹课 7 天 MCP 的第 6 天 专门讲这类提示注入与工具描述可信度的问题,写脚本前值得读一遍。
第四,危险操作要有闸门。 删除、覆盖、发布这类动作,给一个预演开关,或者要求显式加一个确认参数。Agent 会重试,重试一个不可逆操作的代价你承担不起。
跨平台
脚本跑在谁的机器上,你说了不算。三件事最容易翻车。
路径分隔符与大小写。 用语言标准库的路径拼接,不要手写斜杠;文件名大小写在有的系统上敏感、有的不敏感,SKILL.md 写成 Skill.md 在你机器上能跑、到同事那儿就消失。
shell 差异。 正文里写的命令如果只在某一种 shell 下成立,就在旁边给另一种的写法,或者干脆改用一个跨平台的运行器。规范给的例子就是这么做的:同一件事给出两种命令,让 Agent 按环境挑。
换行符与编码。 读写文本明确指定编码;生成文件时注意行尾风格,否则会在版本控制里制造大片无意义的差异。
这三件事的共同点是:它们在你自己机器上永远不会出现。所以要么在正文里显式声明环境要求(compatibility 字段就是干这个的),要么用标准库替你抹平差异,不要靠运气。
文档处理类 skill 的拆解
把上面所有东西合起来,就是一个完整的形态:先规划、再校验、后执行。文档处理、表单填写、批量改动这类任务都该走这条路。
Mermaid source
flowchart TB
A[第 1 步 分析<br/>脚本读模板 产出字段清单<br/>这是真值] --> B[第 2 步 规划<br/>模型写取值计划<br/>不要凭记忆猜字段名]
B --> C[第 3 步 校验<br/>脚本比对计划与真值<br/>错误信息要够模型自己改对]
C -- 有错误 --> B
C -- 通过 --> D[第 4 步 执行<br/>脚本填充并写出文件<br/>只有这一步会落盘]关键是第 3 步,不是第 1 步和第 4 步。 没有第 3 步,这就是一个普通的「读进来改一改写出去」;有了第 3 步,模型就有了一个在真正动手之前发现自己错了的机会,而且错误信息给得足够具体的话,它能自己改对。
三个设计要点值得单独记:
分析脚本产出的是「真值」。 模型不该凭记忆猜模板里有哪些字段,让脚本从模板里抽出来。这一步消灭的是最常见的一类错误:字段名拼错、多写一个不存在的字段。
校验脚本不改数据,填充脚本不做校验。 两件事混进一个脚本,模型就没法在计划和执行之间停下来。而且填充脚本要有个自检:填完还剩未替换的占位符就报错退出——宁可失败,也不要写出一份看起来填好了、实际带着占位符的文档,那种文件会被当成成品发出去。
中间产物要落盘。 计划写成一个 JSON 文件,而不是留在模型的上下文里。落盘之后校验脚本才能读到它,你也才能在出问题时打开看一眼。
源码导读
动手实验
今天要写真代码,但代码不是重点,错误信息才是。动手之前先看一眼 solution 里校验脚本报出来的那四条错误,体会一下「够模型自己改对」是什么水平。一个可操作的自查:把你写的错误信息单独发给一个没看过模板的人,他能不能照着改对。
- 跑通 solution,用 MOCK=1 看清校验脚本先报错、修正后再执行成功的完整轨迹。
- 在 starter 里补全校验脚本的三类错误,重点打磨措辞而不是逻辑。
- 补全分析脚本的占位符解析与去重,让同一个字段不会出现两种类型。
- 补全填充脚本的自检,让它在还剩未填占位符时失败而不是写出半成品。
- 补全 SKILL.md 的工作流段与坑段,把三步流程写成模型能照着走的清单。
面试题
今天 3 道题在下方题库区,侧重脚本与指令的分界、Agent 友好的脚本接口、以及脚本执行的权限与沙箱。展开后先看「分析过程」再看要点——照着推导练,比背要点管用。标注「国内高频 / 海外高频」方便按目标市场取舍。
检查清单与明日预告
- 能判断一段逻辑该写进 SKILL.md 正文还是该沉淀成 scripts 里的脚本
- 能写一个自包含、非交互、输出结构化、错误信息可自纠的 Agent 友好脚本
- 能把一个文档处理任务拆成先规划、再校验、后执行的三步流程
- 能说出「校验脚本不改数据、填充脚本不做校验」这条分工的理由
- 实验的 5 条验收标准全部通过
- 3 道面试题不看要点也能答出至少 2 道
明天(D5)我们换个身份:从写 skill 的人,变成实现 skill 支持的人。前四天你一直在「用」渐进式加载,明天把它真的写出来——扫描目录、宽松解析 frontmatter、把名字与描述拼成目录注入系统提示、激活时才读入正文。自己实现一遍之后,你对前四天每一条建议的理解都会不一样,因为你会看到它们分别对应运行时的哪一行代码。
Interview questions
Which logic belongs in a skill's scripts directory and which belongs in the SKILL.md body?什么逻辑该写成脚本放进 skill 的 scripts 目录,什么该留在 SKILL.md 正文里?
Common in ChinaCommon overseasBasic#agent-skills#scriptsHow to reason about it · think before answering
- This tests a sense of division of labor. Saying complex logic goes in scripts says nothing, because complex has no boundary. The interviewer wants decidable signals.
- Give three: the same logic gets reinvented a third time across execution traces; the result must be byte-identical (validation, format conversion, hashing); or a command is complex enough to be hard to get right first try.
- Expand the second into the core principle: deterministic work goes to code, judgment work stays with the model. Following instructions leaves room for drift; running a script does not.
- Give the other side: invoking an existing tool with two or three flags belongs inline in the body. Many ecosystems offer install-free one-off runners, and versions must be pinned or an upstream release silently changes your skill's behavior.
- Add the cost view: a script is a long-lived asset that must be maintained and kept in sync. When none of the three signals fire, prose is cheaper.
- Expected follow-up: how do you notice reinvention? Read execution traces rather than final outputs; the same helper appearing across runs is the signal.
分析过程 · 先想清楚再作答
- 这题在考分工感。答「复杂的写脚本」等于没答,因为复杂是个没有边界的词。面试官要听的是可判定的信号。
- 给三条信号,命中任意一条就写脚本:同一段逻辑在执行轨迹里被重新发明了第三次;结果必须逐字一致(校验、格式转换、哈希);一条命令复杂到第一次很难敲对。
- 把第二条展开成分工原则,这是本题的核心句:**确定性任务交给代码,判断性任务留给模型**。让模型「按指令做」意味着每次都有偏移的可能,让它跑脚本意味着结果确定。
- 再给反面:只是调一个现成工具加两三个参数,直接在正文写这条命令就行,不必建 scripts 目录。很多生态有免安装的一次性运行方式,用它们时**版本必须钉死**,否则上游一发版你的 skill 行为就变了。
- 补一条成本视角:脚本是长期资产,要维护、要跟模板同步、要有人看得懂。三条信号一条都不命中的时候,写正文更划算。
- 可预期的追问是「怎么发现模型在重新发明轮子」。答案是读执行轨迹而不是只看最终产出——同一个辅助函数在几次运行里反复出现,就是该沉淀成脚本的信号。
Key points
- Write a script when any of three fire: third reinvention, byte-identical results required, or a command hard to get right first try.
- Deterministic work to code, judgment work to the model.
- A tool invocation with a couple of flags stays inline, with the version pinned.
- Scripts are long-lived assets with maintenance cost; if no signal fires, write prose.
- Spot reinvention by reading execution traces, not final outputs.
答题要点
- 三条信号命中任一条就写脚本:重复发明第三次、结果必须逐字一致、命令复杂到难以一次敲对。
- 分工原则是确定性任务交给代码,判断性任务留给模型。
- 只加两三个参数调现成工具的,直接在正文写命令,但版本要钉死。
- 脚本是长期资产,有维护成本,三条都不命中就写正文。
- 发现重复发明要靠读执行轨迹,不是看最终产出。
How does designing a command-line script for an agent differ from designing one for a human?给 Agent 用的命令行脚本,接口设计上和给人用的有什么不同?
Common in ChinaCommon overseasIntermediate#agent-skills#scripts#cli-designHow to reason about it · think before answering
- The hinge is the difference. Many can list CLI best practices; few can say which ones exist specifically because the caller is a model.
- State the root difference: humans read docs, experiment and guess from experience; an agent has only the lines you printed before deciding the next move.
- From that: never prompt interactively. This is a hard requirement, not a nicety, because agents run in non-interactive shells and will hang until timeout.
- Help output is the interface documentation, but it must be short, since it enters the context window and competes with everything else. A human CLI never faces this constraint.
- Error messages decide the next attempt: say what failed, what was expected, what was received, and which values are allowed. Error messages are effectively prompts for the model.
- Then: structured output with data on stdout and diagnostics on stderr, and bounded output size because many harnesses truncate silently. Add idempotency, meaningful exit codes, and a dry-run flag for destructive work.
- Expected follow-up: how do you validate the design? Hand the help text and one error message to someone who has never seen the skill; if they can act on it, the model probably can too.
分析过程 · 先想清楚再作答
- 题眼是「不同」。能列出五条通用 CLI 最佳实践的人很多,能说清哪几条是因为「使用者是模型」才成立的人少。
- 先给根本差异:人会读文档、会试错、会凭经验猜;Agent 只能读你打印的那几行字然后决定下一步。**它的全部信息就是你的输出**。
- 由此推出五条。绝对不能交互,这是硬要求不是最佳实践,Agent 在非交互终端里回答不了提示,会一直挂到超时。
- 帮助信息就是接口文档,但要短——这段输出原样进上下文,跟别的东西抢位置,这是给人用的 CLI 完全不必考虑的约束。
- 错误信息决定它下一次会不会做对:写清哪一项错了、期望什么、实际是什么、可选值有哪些。**错误信息本质上是给模型的提示词**,这一句是拿分点。
- 剩下两条:输出结构化并把数据与诊断分流到标准输出与标准错误;输出体量要可控,因为很多 Agent 环境会静默截断超长输出。再补幂等、有意义的退出码、危险操作给预演开关。
- 可预期的追问是「怎么验证接口设计得好」。答案是把帮助输出和一条错误信息单独发给一个没看过这个 skill 的人,他能照着敲对改对,模型大概率也能。
Key points
- The agent's only information is what you printed; it does not read docs or experiment.
- Never prompt interactively; a non-interactive shell will hang until timeout.
- Help text is the interface documentation and must be short because it consumes context.
- Error messages must state the field, the expectation, the actual value and the allowed set; they are prompts for the model.
- Emit structured data on stdout and diagnostics on stderr, bound output size, and offer a dry-run for destructive operations.
答题要点
- 根本差异:Agent 的全部信息就是你打印的输出,它不会读文档也不会试错。
- 绝不能交互,否则在非交互终端里会挂到超时。
- 帮助信息就是接口文档,但必须短,因为它原样占用上下文。
- 错误信息要写清哪项错、期望什么、实际什么、可选值有哪些,它本质是给模型的提示词。
- 结构化输出并分流标准输出与标准错误,输出体量要可控,危险操作给预演开关。
What security risks come with bundling scripts in a skill, how would you contain them, and why should document tasks follow plan, validate, then execute?skill 里带脚本会带来哪些安全风险?你会怎么限制它?另外,为什么文档处理这类任务要先规划再校验后执行?
Common in ChinaCommon overseasDeep dive#agent-skills#security#workflow-designHow to reason about it · think before answering
- Two halves; answer both. Security is about boundaries, the three-step flow is about process, and both come down to putting a gate before an irreversible action.
- Cover security in four layers. Source: a third-party skill's scripts are someone else's code, so read the scripts directory before installing, exactly as you would skim a package. Skills invite less scrutiny because they look like documentation.
- Permissions: pre-approve at command granularity, allowing read-only git subcommands rather than arbitrary shell, and remember the field is experimental with uneven support, so do not rely on it alone.
- Input: files and API responses are untrusted. A script that does not execute them is not directly exploitable, but script output enters the model's context, so echoing a large blob of external content effectively speaks it to the model. Actions: gate delete, overwrite and publish behind a dry run or explicit flag, because agents retry.
- For the second half, give the three steps and stress that the value is in the middle one: analysis produces ground truth, validation compares plan against it with self-correctable errors, and only the fill step writes files.
- Name two disciplines: validation never mutates and fill never validates, or the model loses its pause between planning and execution; and intermediate artifacts must be written to disk so the validator can read them.
- Expected follow-up: why not validate while filling? Filesystems have no transactions, and a half-written document is worse than none because it looks complete.
分析过程 · 先想清楚再作答
- 这题有两半,别只答一半。前半考安全边界,后半考流程设计,两者的共同点是「在不可逆的动作之前留一道闸门」。
- 安全这一半按来源、权限、输入、动作四层说。来源:第三方 skill 里的脚本就是别人的代码,装之前要读 scripts 目录,跟装一个包之前看两眼是一回事——skill 更容易被当成文档而放松警惕。
- 权限:预批工具要卡到命令级,写「允许 git 的只读子命令」而不是「允许任意 shell」;而且这个字段还是实验性的,各家支持不一,不要把安全性全押在它上面。
- 输入:脚本处理的外部文件与接口返回是不可信输入。脚本不把它当代码执行就不会被直接利用,但**脚本的输出会进模型上下文**,原样回显一大段外部内容等于把那段话讲给模型听。动作:删除覆盖发布要给预演开关或确认参数,因为 Agent 会重试。
- 第二半给三步流程,并强调价值全在中间那步:分析脚本产出的是真值,模型不该凭记忆猜字段;校验脚本比对计划与真值,错误信息要够模型自己改对;填充脚本才落盘。
- 两条设计纪律要点出来:校验脚本不改数据、填充脚本不做校验,混在一起模型就没法在计划和执行之间停下来;中间产物要落盘成文件,否则校验脚本读不到,你也没法打开看。
- 可预期的追问是「为什么不能边填边校验」。答案是文件系统没有事务,写了一半的文档比完全没写更麻烦——它看起来是完整的。
Key points
- Third-party skill scripts are someone else's code; read the scripts directory before installing.
- Pre-approve tools at command granularity, and do not rely on an experimental field for safety.
- External input is untrusted, and script output enters context, so never echo large external blobs verbatim.
- The value of the three-step flow is the middle step: ground truth, self-correctable errors, then writing.
- Validation never mutates, fill never validates, intermediates go to disk, and never validate while writing.
答题要点
- 第三方 skill 的脚本就是别人的代码,装之前要读一遍 scripts 目录。
- 预批工具按最小权限、卡到命令级;该字段仍是实验性的,不能全押在它上面。
- 外部输入不可信,且脚本输出会进上下文,不要原样回显大段外部内容。
- 三步流程的价值全在中间那步校验:分析出真值、校验给可自纠的错误、执行才落盘。
- 校验不改数据、填充不做校验、中间产物落盘;不要边填边校验,半成品文档看起来是完整的。
Comments
Sign in to join the discussion
No comments yet — be the first.