Files
Shuery dda36cfae5 docs: 记录改造 spec(验收锚点)
四件事的目标、9 条验收门槛、30 条已确认决策、十步执行顺序与回滚控制。每项改动都要能被其中某一条判成通过或失败。
2026-09-26 21:47:29 +08:00

8.5 KiB
Raw Permalink Blame History

spec:增强健壮性 + 重构代码 + 重组结构 + 微软规范落地

Status: accepted Date: 2026-09-26

本文件是这次改造的唯一验收锚点。每一项改动都要能被这里的某一条门槛判成通过或失败;判不了的就不要做。

1. 四件事

  1. 增强健壮性 —— 修掉实测确认的缺陷,并把「支持 5.1」从纸面承诺变成可验证的事实。
  2. 重构代码 —— 消除重复实现、死代码与闭包依赖,让两个入口只做编排。
  3. 重组结构 —— 让文件系统与职责对齐,并落一份可读的模块导出面。
  4. 按微软规范格式化代码与文档 —— 落到可重复执行的工具上,而不是一次性手工活。

2. 验收标准

# 门槛 改造前 目标
1 Pester 套件 175 通过 / 0 失败 全绿(覆盖不得减少)
2 零依赖套件(瘦身后为冒烟) 101 通过 / 0 失败 全绿
3 全仓 Parser::ParseFile(5.1 与 7) 5.1 上 6/6 文件失败 两版都零错误
4 tests/Run-E2E.ps1(36 项) 只在 7.x 上跑过 7 与 5.1 都全绿
5 PSScriptAnalyzer 默认规则集 未安装 -Severity Warning,Error 下 0 条
6 PSScriptAnalyzer 格式化规则集 无 0 条
7 只读冒烟 未系统跑过 Backup.ps1 -DryRun 与 Restore.ps1 -VerifyOnly 在真实清单上跑通,一个字节都不写
8 markdownlint(可选档,不进必绿门槛) 无 配置就位,0 error
9 术语表与决策记录 无 CONTEXT.md + docs/adr/ 6 条

第 3、4 条是新增的:改造前这两条根本不存在,而 README 却承诺了 5.1。

3. 已确认的决策

3.1 过程

  • D1 基线与分支:先提交当前工作区为基线,再从基线开 refactor/ms-conventions。
  • D2 过程产物:本文件即 spec;不为四条工作流各开 ticket。
  • D3 行为边界:重构类改动保持行为等价;健壮性类改动可改变边界行为,但对外契约只增不改(参数名、退出码语义、配置字段、清单语法、归档布局一律向后兼容)。

3.2 结构

  • D4 库代码形态:BakNRet/ 目录承载模块 —— BakNRet.psd1 + BakNRet.psm1(薄加载器,按显式顺序点源)+ Public/ + Private/,一函数一文件,文件名 = 函数名。
  • D5 入口与配置原地不动:Backup.ps1、Restore.ps1、BackupList.txt、SoftwareCatalog.psd1、BackupConfig.psd1 全部留在仓库根。
  • D6 src/ 不用。实测证据:纯 PowerShell 且无构建步骤的仓库里,用 src/ 的为 0。
  • D7 公共面:.psd1 写显式 FunctionsToExport 白名单(官方要求,且 PSUseToExportFieldsInManifest 恒开不可关);私有件靠 Private/ + 不导出双保险。
  • D8 测试拿私有函数:用 Pester 的 InModuleScope(已实测在 5.1 与 7 上都可用)。不让测试点源 Private/*.ps1 —— 那会把模块级状态建在测试脚本自己的作用域里。
  • D9 入口沉到只做编排:Backup.ps1 / Restore.ps1 里的逻辑(压缩→校验→原子替换、manifest 条目读写、解压三分支、孤档审计、7z 校验、工具定位)全部下沉进模块,分两个提交:先纯搬家,再参数化。
  • D10 闭包依赖参数化:Save-ItemRecord 不再直接改脚本级 $manifest;Invoke-BackupItem 不再读脚本级 $tool / $password;Test-ItemSelected 不再读 $Only / $Skip。
  • D11 模块级状态收敛:5 个 $script: 变量集中到 Private/State.ps1,访问器只覆盖"写",并补一个测试用的状态重置函数。

3.3 命名

  • D12 公共面函数补 BakNRet 前缀;私有面保留短名。动词已 63/63 全部是批准动词,动词不动。
  • D13 Set-Alias 过渡垫片不留。
  • D14 产品名统一写作 BakNRet(见 CONTEXT.md)。

3.4 声明与编码

  • D15 可拼接性不变量(三条,写成测试):每个私有文件不写自己的 param() / using / Set-StrictMode / Export-ModuleMember;私有文件不引用 $PSScriptRoot 或相对路径;加载顺序只在加载器里出现一次。
  • D16 tools/Build-BakNRetModule.ps1:按加载器声明的同序拼接各文件,产出 dist/BakNRet.psm1(gitignore);不引入 ModuleBuilder/Sampler。
  • D17 源文件一律 UTF-8 with BOM;.editorconfig 用 charset = utf-8-bom 把这条锁住。
  • D18 .gitattributes 用 * text=auto eol=lf 加二进制规则(照 PSScriptAnalyzer 的写法)。
  • D19 版本声明:#Requires -Version 5.1;绝不写 #Requires -PSEdition。.psd1 写 CompatiblePSEditions = @('Desktop','Core')。
  • D20 Set-StrictMode -Version 3.0(不用 Latest,双版本要的是确定性),先在库模块内开,跑绿后再考虑入口。

3.5 静态分析与文档

  • D21 PSScriptAnalyzer 装进 .tools/(不动机器上的全局模块);PSScriptAnalyzerSettings.psd1 放仓库根;tools/Invoke-Analyzer.ps1 是唯一入口;独立门禁脚本,不塞进 Pester 用例。
  • D22 全局 ExcludeRules 排除 PSAvoidUsingWriteHost(彩色控制台输出是本工具的刻意设计)。
  • D23 PSUseSingularNouns 用 NounAllowList 放行 SecurityRecords / MapKeys / ArchiveTopLevelNames。
  • D24 Backup.ps1 补 SupportsShouldProcess(新增 -WhatIf / -Confirm,属只增不改)。
  • D25 启用 PSAvoidLongLines,上限 120,全仓折断。
  • D26 注释帮助:公共面补齐 .SYNOPSIS / .DESCRIPTION / .PARAMETER / .OUTPUTS / .EXAMPLE;私有面只要求前两个。
  • D27 文档拆分:README 瘦身成「是什么 + 快速开始 + 命令速查 + 指向 docs/」;11 篇主题进 docs/;「相对旧版修了什么」抽成 CHANGELOG.md;known-limits 留在 README。
  • D28 6 条 ADR,不拆更细。
  • D29 markdownlint 用 default: true,行长放宽到 240(照 PowerShell 仓库的实践),只作可选检查。
  • D30 测试入口:根级 test.ps1 作为唯一入口(-Suite 与 -PSVersion 参数);tests/ 保留 Pester 的 *.Tests.ps1 命名,端到端降级为 tests/E2E.ps1 与 tests/Drill.ps1。

3.6 明确不做

  • 不搬三个配置文件(D5)。
  • 不把库发布到 PSGallery,不引入 ModuleBuilder / Sampler(D16)。
  • 不留旧函数名的 Set-Alias 垫片(D13)。
  • 不为 manifest 写一次性迁移脚本 —— 它是运行产物,下次运行自然老化。
  • 不补一批特征化测试再动手 —— 现有 276 个断言足够,新增断言按「修一个补一个」加。
  • 不自动搬 baknret.key。搬密钥是使用者的动作,本改造只负责停止往仓库里放默认路径。

4. 执行顺序

每一步独立提交,每一步都跑一遍全套验证。

  1. chore 基线提交(含 .gitignore 加 *.key)
  2. fix 源文件改存 UTF-8 with BOM + 立起 5.1 验收门槛
  3. fix 暂存目录泄漏 —— 全仓唯一会把 junction 留在真实数据上的缺陷
  4. docs CONTEXT.md 术语表
  5. refactor Common.psm1 → BakNRet/{Public,Private},纯搬家 + 拼接校验
  6. refactor 入口逻辑下沉 + 闭包依赖参数化
  7. refactor 改名 + 模块清单显式导出面
  8. fix 其余 P0(口令进日志 / manifest 原子写 / 空间守卫 / 并发保护)
  9. style 格式化规则集 + 机械重排,再逐条修静态分析告警
  10. docs README 拆分 + CHANGELOG.md + 6 条 ADR + test.ps1 / Invoke-Analyzer.ps1 + 门禁

顺序的三条依据:先让 5.1 能解析(否则后续所有验证都少一半);先修唯一会伤数据的那一项;把可证明无损的结构搬家放在行为修复之前(搬家用拼接校验证明零变化,之后每个修复都是小文件里的几行 diff,各自独立可回滚)。

5. 风险与回滚

风险 控制
BOM 改造后 5.1 暴露新问题 单独一个提交,改完立刻跑 5.1 验收;有问题就在这一个提交上回退
结构搬家引入行为变化 用「同序拼接 == 模块内容」逐文件校验;先纯搬家、函数体一行不改
改名牵动 276 个断言 纯改名单独提交,跑绿后再做其它;不留别名垫片,避免两个名字并存
机械重排污染后续 review 排在行为修复之后,单独提交,用 git diff -w 复核
真实归档数据受影响 全程不碰 Backups/;只读冒烟只用 -DryRun / -VerifyOnly