lo 项目贡献指南从函数命名到文档体系的 Go 泛型库开发规范【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo本文是 losamber/lo基于 Go 1.18 泛型的 Lodash 风格函数库仓库中 贡献指南 的深度展开。文章以该指南为骨架结合仓库内slice.go、errors.go、find.go、it/、mutable/、parallel/等源码以及docs/data/文档数据与docs/scripts/校验脚本系统讲解为 lo 贡献新 helper 时应遵循的命名约定、变体后缀体系、泛型约束设计、测试规范、性能要求与文档/示例交付标准。读者读完后可以独立完成一个从命名评审到文档上线的完整贡献流程。一、贡献前的核心原则自解释命名与不破坏兼容贡献指南开篇即强调两个基调这也是 lo 所有 helper 设计的第一性约束Helper 必须自解释self-explanatory且尊重业界标准命名应参考其他语言、其他库如 Lodash、标准库slices/maps已有的习惯让使用者一眼明白函数语义。贡献者可以在 issue 或 PR 中自由提议多个候选名让社区评审。极度厌恶破坏性变更breaking changesthink twice——新增 API 前必须反复斟酌签名因为 lo 遵循 SemVer v1在 v2.0.0 之前不会对导出的 API 做破坏性修改README 中明确This library is v1 and follows SemVer strictly. No breaking changes will be made to exported APIs before v2.0.0, except for experimental packages underexp/。一旦 helper 命名或签名确定并发布就很难再更改。二、函数命名与变体后缀体系Variants2.1 变体后缀的完整规范当一个 helper 需要提供多种能力维度时lo 使用统一的后缀体系来区分变体。这是贡献指南中最重要的工程约定之一仓库源码中随处可见其实例后缀含义源码实例F函数式惰性求值版本Switch/CaseF/DefaultF、If/IfF、Ternary/TernaryF见 condition.goI谓词回调中带index int参数MapI、UniqMapI见 it/seq.goErr回调可返回 error函数返回(结果, error)MapErr、FilterErr、FindDuplicatesByErr见 slice.go、find.goWithContext显式接收context.Context以支持取消/超时BufferWithContext、WaitForWithContext、MapWithContextX可变参数数量族varying arity如MustXMust0~Must6见 errors.go以Err变体为例MapErr的源码实现slice.go展示了其语义遇到第一个错误立即返回nil, err不再继续处理后续元素——这与 Go 传统的错误处理哲学一致func MapErrT, R any (R, error)) ([]R, error) { result : make([]R, len(collection)) for i : range collection { r, err : transform(collection[i], i) if err ! nil { return nil, err } result[i] r } return result, nil }2.2 变体可跨子包扩展指南明确When applicable, some functions can be added to sub-package as well:mutable,itandparallel. 即同一功能的变体可以同时存在于多个子包中满足不同使用场景lo/mutable原地修改切片避免分配。例如mutable.Map直接改写传入切片mutable/slice.go而lo.Map返回新切片slice.golo/parallel并发执行回调例如parallel.Mapparallel/slice.goREADME 说明其Results are returned in the same order保持结果顺序与输入一致lo/it基于 Go 1.23 迭代器iter.Seq的惰性求值版本例如it.Mapit/seq.go可与slices.Collect配合使用。新增 helper 时如果某个功能在逻辑上适合以上三种形态应该考虑同时贡献对应子包变体并为每个变体添加独立文档见第五节。三、切片类型参数~[]T约束的设计考量指南指出Functions use~[]Tconstraints to accept any slice type, including named slice types, not just[]T. 这是 lo 泛型设计中一个容易被忽略但非常重要的细节。Go 泛型中[]T约束只匹配字面切片类型而~[]T波浪号约束匹配所有底层类型为切片的类型包括用户自定义的具名切片类型。使用~[]T后helper 的返回类型也能保持与输入相同的具名类型而不是退化为[]T。仓库中的实际签名可以印证这一点例如// slice.go func Uniq[T comparable, Slice ~[]T](collection Slice) Slice // slice.go#L242 func UniqBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice // slice.go#L293 func FindDuplicatesByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error) // find.go#L450注意FindDuplicatesByErr的参数名为Slice而非[]T且返回类型同样是Slice——这意味着如果传入type IDs []int返回的仍是IDs类型完全保持。这与贡献指南Other conventions / Types一节的要求完全一致Generic functions must preserve the underlying type of collections so that the returned values maintain the same type as the input.参见 #365 相关讨论。四、可变参数Variadic设计指南提到Many functions accept variadic parameters (likelo.Keys(...map[K]V)accepting multiple maps), providing flexibility while maintaining type safety.以Keys为例它接受一个或多个 map将所有键合并返回// map.go#L23 附近 func UniqKeysK comparable, V any []K这种设计在保证编译期类型安全K、V均受泛型约束的同时允许调用方一次性传入多个集合减少样板代码。贡献者在设计新 helper 时如果接收多个同类集合是合理的语义如Concat、Interleave、Assign、Union等应考虑使用 variadic 参数。五、测试规范覆盖率与命名约定5.1 覆盖率要求指南设定目标We try to maintain code coverage above 90%. 仓库为几乎所有 helper 都配套了单元测试slice_test.go、map_test.go、find_test.go等均为千行级别的大文件并有专门 benchmark 目录 支撑性能回归。5.2 同名 helper 多测试函数的命名规则当一个 helper 需要多个Test函数例如覆盖不同内部代码路径、调度阈值或成功/失败场景时命名格式为TestHelperName_scenarioHelperName与声明完全一致下划线后跟小写字母开头的场景标签。该规则与 Go 自身ExampleFoo_suffix的约定对齐当后缀并非真实符号时必须以小写字母开头。仓库中的真实案例TestUniq_small、TestUniq_largeslice_test.go——分别覆盖小切片与大切片的内部路径TestCut_success、TestCut_failslice_test.go——成功与失败场景TestWithout_small、TestWithout_largeintersect_test.goTestMode_small、TestMode_largemath_test.go。关键例外当下划线后的后缀本身是文档化的变体后缀F、I、Err、WithContext、X、By等时不要加下划线——因为此时后缀命名的是真实的变体 helper/家族而非测试场景。例如TestFindDuplicatesByErr与TestMustX就是正确的写法ByErr、X是真实变体家族。这一区分是为了保持测试名与 API 命名的可读性和一致性。六、基准与性能要求贡献指南对性能提出了明确要求编写高性能 helper限制额外内存消耗Write performant helpers and limit extra memory consumption.构建通用 helper不为特定用例过度优化Build an helper for general purpose and dont optimize for a particular use-case.鼓励编写基准测试Feel free to write benchmarks.仓库为此专门建立了 benchmark 目录内含针对 core 与 it迭代器子包的大量基准文件如core_slice_bench_test.go、it_map_bench_test.go、parallel_slice_bench_test.go、mutable_slice_bench_test.go、core_type_manipulation_bench_test.go等覆盖切片、映射、查找、数学运算、字符串、元组、类型操作等多类 helper。新增 helper 时应同步考虑补充对应 benchmark。此外指南特别警示迭代器场景Iterators can be unbounded and run for a very long time. If you expect a big memory footprint, please warn developers in the function comment.——it/子包基于 Go 1.23 的iter.Seq支持无限序列与惰性求值如果某 helper 可能产生巨大内存占用必须在函数注释中明确警示使用者。七、文档规范docs/data 体系与 llms.txt7.1 文档文件结构每个新 helper 必须在 docs/data/ 目录下创建一份 Markdown 文档命名模式为category-helper-name.md如core-map.md、it-filter.md、mutable-fill.md、parallel-foreach.md。文件头部需包含 YAML frontmatter核心字段如下字段说明namehelper 显示名PascalCaseslugURL 友好的短横线名与文件名去掉 category 前缀后一致sourceRef源码引用格式file.go#L123指向精确行号categorycore、mutable、parallel、it等必须与文件名前缀一致subCategory功能分类condition、map、find、slice、math、string、type、error-handling、retry、time、function、channel、tuple、intersect等signatures函数签名字符串数组只列本 category 的签名playUrlGo Playground 可运行示例链接variantHelpers同一 helper 的不同签名/参数变体同 category 同 subCategorysimilarHelpers功能相关的其他 helper可跨包position页面内排序0、10、20、30...按源码中声明顺序排列每页重置以core-map.md为例docs/data/core-map.md其sourceRef为slice.go#L45与slice.go中Map函数实际行号一致similarHelpers列出了core#slice#maperr、core#slice#filtermap、parallel#slice#map、mutable#slice#map等关联 helper。7.2variantHelpers与similarHelpers的区别这是文档体系中最容易混淆的两个字段指南给出了清晰界定variantHelpers同一个 helper 的不同版本同包同分类仅签名/参数不同。例如Map家族core#slice#map基础版、core#slice#maperr可返回错误、core#slice#mapi带 index、core#slice#mapwithcontext带 contextsimilarHelpers功能相关但不同的 helper允许跨包。例如FilterMap同时与core#slice#map转换和core#slice#filter过滤相关parallel#slice#map、mutable#slice#map是不同子包的等价实现Find/Filter/FindBy/FindOrElse是同类查找家族的替代选择。7.3 分组与关联更新相关 helper 合并成单文件当多个 helper 作用于同一结构体或用途相似时合并到一个文档文件。例如Map家族Map/MapI/MapWithContext/MapIWithContextSwitch家族都操作switchCase[T, R]Switch构造器、Case/CaseF添加分支、Default/DefaultF提供默认值统一记录在core-switch.md中。双向链接维护当你给新 helper 添加similarHelpers时同时也要更新被链接的 helper 文档把新 helper 加入对方的similarHelpers——保持引用关系对称。避免数字变体链接不要链接数字变体用core#slice#zipx而非core#slice#zip2。7.4 加入 llms.txt新增 helper 后必须将其登记到 docs/static/llms.txt。该文件是 lo 为搜索引擎和 AI 助手准备的清单式文档A Go library for functional programming... 300 carefully crafted utilities按 Condition、Concurrency、Error Handling、Slice、Map、Math、String、Tuple 等功能域列出全部 helper 名称。同步更新它是为了让外部检索系统与 AI 工具能够发现新 API。八、示例规范Example 测试与 Go Playground8.1 Example 测试文件每个函数都需要在xxxx_example_test.go文件中提供示例如 lo_example_test.go、it/map_example_test.go。这些示例会被 Godoc 收录成为 pkg.go.dev 页面上可直接运行的文档示例。8.2 Go Playground 链接的双重落点每个 helper 必须有一个可运行的 Go Playground 示例链接同时存在于两处源码注释函数 doc comment 块的最后一行紧邻func关键字之前格式为// Play: url。例如 slice.go 中UniqMap的// Play: https://go.dev/play/p/fygzLBhvUdB文档 frontmatterdocs/data/category-slug.md的playUrl字段。8.3 编写 Playground 示例的实用建议指南给出了编写示例代码的具体技巧使用现实但简单的数据用fmt.Println打印结果让输出可见适当包含边界情况空输入、错误场景Err变体同时展示成功与错误两种情形时间类 helper 用time.Date()保证输出确定随机类 helperSampleBy、SamplesBy使用rand.New(rand.NewSource(42))保证输出可复现。导入路径约定// 核心 helpers import github.com/samber/lo // lo.Map(...) // 迭代器 helpersit/ 子包需要 Go 1.23 import ( slices github.com/samber/lo/it ) // slices.Collect(it.Map(...))切片转迭代器用 slices.Values([]int{1, 2, 3}) // 并行 helpers import lop github.com/samber/lo/parallel // lop.Map(...)8.4 Playground 的已知限制首次运行超时若github.com/samber/lo模块尚未在 go.dev/play 缓存首次执行可能超时重试即可未发布的新 helper如果 helper 源码与文档同时创建而模块尚未发布Playground 无法编译——此时可跳过 playground 示例playUrl留空待下个版本发布后再补充SIMD helpersexp/simd/下的 helper 需要go1.26goexperiment.simdamd64构建标签Go Playground 不支持因此无法提供 playground 示例参见 exp/simd/。九、sourceRef 行号同步与自动化校验9.1 sourceRef 的维护sourceReffile.go#L123格式指向源码中的精确行号。任何.go文件的改动新增、删除、重排函数都会导致该文件中后续所有函数行号偏移从而让同一文件内所有已文档化 helper的sourceRef失效——不只是被编辑的那个。维护流程对每个改动的.go文件运行gopls symbols file列出全部符号函数/方法/类型及其当前行号将每个符号与docs/data/*.md中对应 helper 的sourceRef逐一比对更新行号不匹配的sourceRef。9.2 自动化校验脚本仓库在 docs/scripts/ 提供了一组 Node.js 校验脚本用于保证文档与源码的一致性check-function-signatures.js遍历 Go 源码验证 frontmatter 中的signatures是否与真实函数声明一致检测[missing-helper]、[duplicate-signature]、[unknown-signature]、[missing-signature]、[sourceRef-outdated]五类问题支持--check参数以非零退出码标记失败check-filename-matches-frontmatter.js校验文件名与 frontmatter 的 slug/name 匹配check-cross-references.js校验variantHelpers/similarHelpers交叉引用是否有效且双向check-duplicates-in-category.js检查同一 category 内是否存在重复 helpercheck-similar-exists.js与check-similar-keys-exist-in-directory.js验证 similar helper 引用真实存在check-helpers-visible-in-pages.js确保新 helper 在文档站点页面中可见。十、其他约定回调命名与类型保持10.1 回调参数命名贡献指南给出了三类回调的命名约定返回单个bool的回调 → 命名为predicate谓词如Filter、Find、Every的回调将集合元素转换为其他形态的回调 → 命名为transform转换如Map、FlatMap、UniqMap的回调无返回值void的回调 → 命名为callback回调如ForEach、Times的回调。这一约定在 slice.go 的签名中得到了严格贯彻Map的参数是transformFilterMap的参数是callback同时承担转换与过滤而lo.ForEach的回调不返回值。10.2 类型保持Type PreservationGeneric functions must preserve the underlying type of collections so that the returned values maintain the same type as the input. 即对~[]T具名切片类型的输入helper 必须返回相同的具名类型见第三节Uniq/UniqBy/FindDuplicatesByErr的实现。这样用户的自定义类型可以安全地在整个函数式管道中流动而不会在某个环节被悄悄降级为[]T。结语一份贡献的完整检查清单综合以上所有规范为 lo 贡献一个新 helper 的完整流程可以概括为命名评审选择自解释、符合业界习惯的名字在 issue/PR 中讨论变体方案是否需要I/Err/WithContext/F/X后缀、是否需要mutable/it/parallel子包变体实现用~[]T保持具名切片类型使用 variadic 参数提升灵活性注意性能与内存分配避免过度优化特定场景测试在xxx_test.go中编写测试多场景用TestHelper_scenario命名变体后缀场景不加下划线目标覆盖率 90%在xxx_example_test.go中提供示例视情况补充 benchmark文档在docs/data/创建category-helper.md填写完整 frontmatter含sourceRef、signatures、playUrl、variantHelpers、similarHelpers维护双向引用同步更新docs/static/llms.txt校验运行docs/scripts/下的全部校验脚本确保签名、文件名、交叉引用、sourceRef 行号全部一致若改动了.go源码用gopls symbols同步所有受影响 helper 的sourceRef。这套规范的价值在于它把一个泛型工具库如何在社区协作下长期保持 API 稳定、文档可校验、代码可维护沉淀成了可执行的工程流程。对于任何希望为 lo 添砖加瓦的贡献者或任何想要借鉴这种源码—文档—示例—校验四位一体协作模式的开源项目维护者这份指南都值得仔细研读。【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
