113 lines
8.5 KiB
Markdown
113 lines
8.5 KiB
Markdown
# 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` |
|