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

113 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` |