12 之后,顺序任务服务的每条任务记录仍保存在 []TaskRecord 中。默认批量输出要把这些记录变成统计摘要;早先结果还可以直接持有 []TaskResult。是否值得为这种变换加一个泛型函数?先检查调用方希望保留哪种输入切片,再检查函数体真正要用什么操作,而不是把整套任务模型都改成泛型。

同一累计工程本章新增 Summaries[T HasSummary],主 CLI 的默认输出直接调用 Summaries(records)。查询、文件输入、错误分类、panic 注入测试和临时任务 ID 都仍在原工程;本篇完整累计归档包含正反命令与历史 Go 文本证据,SHA256SUMS提供下载校验。

独立复跑步骤列出本章正反例、源码校验及运行命令。

约束只承诺一个方法

1
2
3
4
5
6
7
8
9
10
11
type HasSummary interface {
Summary() string
}

func Summaries[T HasSummary](records []T) []string {
lines := make([]string, len(records))
for position, record := range records {
lines[position] = record.Summary()
}
return lines
}

函数体只要 Summary() string,因此 HasSummary 没有要求存储状态、ID 或可变操作。08 的 TaskRecord.Summary() 来自嵌入的 TaskResult;两者都满足约束。TestSummaries 依次传入 []TaskRecord、[]TaskResult,对同一计数断言摘要均为 words=1 bytes=2 runes=2;还用 []HasSummary 验证接口切片也能传入,并测试空记录结果长度为 0。测试所证明的是功能与类型匹配,不证明某种切片的内存布局或运行耗时更优。

主 CLI 在输入均通过既有校验后将 []TaskRecord 交给 Summaries;输出依旧按记录索引拼上 task[0] 等 ID,非批次模式只写摘要。--text 'Go 写 Go' 实测 words=3 bytes=9 runes=7,批次 --batch 'Go|one two' 仍给两条顺序结果;--lookup 'task[0]' 的查询路径继续由 09 的最小接口处理,不必为了默认输出的泛型变换修改查询契约。含空项的批次 stdout 仍为空、应用退出 2,因为对所有输入的校验发生在摘要形成和写 stdout 之前。

类型参数不能替调用方猜字段

独立的 testdata/invalid-summarizer.go 用 []int 实例化同样的函数。固定的 Go 1.27.1 工具链下编译退出 1:int does not satisfy hasSummary (missing method Summary)。另一个 testdata/invalid-type-operation.go 在函数体里尝试写 value.Words,但约束只保证 Summary;即使实际传入的某个具体类型恰好有 Words 字段,泛型函数也不能假设所有满足约束的类型都有它。本机编译退出 1,stderr 明确指出 type T has no field or method Words。两个负例均是 testdata/ 里的独立短文件,不会把整个累计工程变成编译失败状态。

这组对照说明了泛型的取舍。如果函数声明为接收 []HasSummary,同样可以逐项调用 Summary,但原来的 []TaskRecord 不能自动当成 []HasSummary 使用,必须逐项构造接口元素切片。泛型版本直接接收具体切片,且不向调用方额外要求其他方法;它不是另一个存储层,更没有取代 09 的查询接口。把类型参数盲目加给 TaskRecord、TaskLookup 或每一个错误类型,只会扩张尚未使用的类型空间。

本章 go test -count=1 -v ./... 的根包及 stats 包退出 0;两个隔离编译反例分别退出 1。构建后单次、批次、查询正例退出 0,空项批次退出 2;真实命令、版本、环境与原始双流保存在 writing-plans/go-language/evidence/13/。素材包经 SHA-256 校验并解包后,再次执行这些正反编译、两包测试和 CLI 输出,记录在本篇素材 unpack-*。这里只有功能正反例,没有 benchmark、逃逸或 profile 记录,因此不对泛型相比接口的性能作判断。

练习:若以后只想获得 TaskRecord.ID 的列表,能直接在 Summaries[T HasSummary] 内增加 record.ID 吗?答案:不能,HasSummary 没有承诺字段 ID;第二份失败短程序正是相同规则的 Words 版本。可以让调用者在原始 []TaskRecord 上处理 ID,或者根据确切的新复用需求设计新约束。为一个当前只服务于具体记录的 ID 操作,未必值得再建一个泛型抽象。

系列在这里仍是顺序 CLI:没有 goroutine、取消、HTTP、SQL 或发布验收,其他平台与 race NOT_RUN。Go 工具链 tag go1.27.1、Go 镜像查询的 Git SHA 862c888e612ac346c7c4d99c9392bdfd265f33b0 已登记,官方 Gitiles 尚未交叉核验。下一篇为文件/标准输入限制读取长度,并为 JSON 输出建立 Writer 边界,先决定输入与输出资源由谁持有。

参考资料