Shell脚本维护性取决于可读性、可修改性和健壮性:注释需说明“为什么”及后果,文档须含快速上手、配置说明和故障排查,重构应拆分为单一职责函数,错误处理要带上下文提示与恢复机制。

Shell脚本不是写完就能扔的临时工具,尤其当它要被多人协作、长期运行或随系统升级反复执行时,维护性直接决定它能不能活过三个月。真正有效的维护性设计,不靠玄学,靠三件事:让别人(包括未来的你)一眼看懂在做什么、知道怎么改、以及改了之后不会崩。
注释要“说人话”,别只写“做了什么”
很多脚本注释停留在“# 安装 Homebrew”这种层面,其实没用。真正有用的注释解释“为什么这么做”和“不这么做的后果”。比如:
- 在判断是否跳过安装前加一句:# 跳过已存在二进制:避免重复安装触发 brew doctor 报错,影响后续依赖链
- 在修改 PATH 的行旁注明:# 追加而非覆盖:保留用户原有路径顺序,防止 zshrc 中其他工具失效
- 对关键条件判断加注释说明边界:# 只在 macOS 13+ 执行:旧系统缺少 systemsetup 命令,会静默失败
函数开头用多行注释说明输入、输出、副作用,比零散的行内注释更可靠。不用追求每行都注,但凡涉及状态变更、外部依赖、或容易误解的逻辑,必须写清楚。
文档不是 README.md 里的一段话
一份能用的文档至少包含三块内容,且要和脚本本身保持同步:
-
快速上手:给出最简可运行命令(如
./mac --dry-run),并说明它实际会做什么(比如“只打印将执行的操作,不改动任何文件”) -
配置说明:列出所有环境变量(如
SKIP_XCODE=true)、参数开关(--no-oh-my-zsh)及其默认行为,不要让用户翻源码猜 -
故障排查锚点:针对常见失败场景,直接给出检查路径。例如“若报错 ‘command not found: brew’,请确认 /opt/homebrew/bin 是否在 PATH 中,并运行
which brew验证”
文档里避免出现“详见源码”这类话——如果源码真那么好懂,就不需要文档了。
重构从“拆函数”开始,而不是重写
维护性差的脚本,往往是一个 800 行的单体文件。重构第一步不是重写,而是识别出稳定、独立、有明确输入输出的逻辑块,封装成函数:
- 把“检测 CPU 架构 + 设置 Homebrew 路径 + 初始化 brew”打包为
setup_homebrew,调用时只关心“是否成功”,不关心内部怎么选路径 - 把“读取 config.yaml → 解析 YAML → 合并默认值 → 校验必需字段”抽成
load_config,后续换 JSON 或 TOML 只需改这一个函数 - 把“生成随机字符串 + 写入 .env + 设置 chmod”合成
generate_secret,避免密钥生成逻辑散落在不同地方
每个函数控制在 20 行以内,只做一件事。函数名用动宾结构(install_nodejs、backup_database),不叫 step3 或 helper。函数之间通过返回值或变量传递数据,避免全局变量污染。
错误处理不是加个 exit 1 就完事
脚本崩溃不可怕,可怕的是静默失败或报错信息让人看不懂。关键操作后必须检查退出码,并给出上下文明确的提示:
- 用
if ! command; then echo "ERROR: failed to fetch config from $API_URL" >&2; exit 1; fi,而不是command || exit 1 - 对非致命操作(如可选组件安装失败),用
warn函数统一输出黄色警告,继续执行后续步骤,而不是中断整个流程 - 捕获信号(
trap 'cleanup; exit' INT TERM),确保 Ctrl+C 或超时也能清理临时文件、释放锁、还原环境
错误信息里带具体位置(“第 142 行”)、关键变量值(“尝试写入路径:$DEST_DIR”)、以及下一步建议(“请检查磁盘空间或手动创建该目录”),才算真正完成错误处理。

















