From dda36cfae549b20bd8646ba9ce050ca1fdf2480a Mon Sep 17 00:00:00 2001 From: Shuery <2463253700@qq.com> Date: Sat, 26 Sep 2026 21:47:29 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=AE=B0=E5=BD=95=E6=94=B9=E9=80=A0=20?= =?UTF-8?q?spec=EF=BC=88=E9=AA=8C=E6=94=B6=E9=94=9A=E7=82=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 四件事的目标、9 条验收门槛、30 条已确认决策、十步执行顺序与回滚控制。每项改动都要能被其中某一条判成通过或失败。 --- .scratch/ms-conventions-refactor/spec.md | 112 +++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 .scratch/ms-conventions-refactor/spec.md diff --git a/.scratch/ms-conventions-refactor/spec.md b/.scratch/ms-conventions-refactor/spec.md new file mode 100644 index 0000000..3546356 --- /dev/null +++ b/.scratch/ms-conventions-refactor/spec.md @@ -0,0 +1,112 @@ +# 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` |