第二十章 开源贡献
一
王志远的第一个开源贡献,源于一个 bug。
他在做排班系统的时候,用了一个开源的日期计算库——date-utils-java。这个库在 GitHub 上有两千多个 Star,文档齐全,API 设计得也不错。
但他在使用的时候发现了一个问题:当计算"某个月的最后一个工作日"时,如果那个月的最后一天是周六,库返回的是周五(正确);但如果最后一天是周日,库返回的是周日本身(错误,应该返回周五)。
他查了一下源码,发现是一个条件判断的逻辑错误——代码只处理了"最后一天是周六"的情况,漏掉了"最后一天是周日"的情况。
他在项目的 Issues 里搜了一下,发现已经有人报告了这个 bug,但一直没有人修复。Issue 的创建时间是三个月前。
二
他把这个 bug 告诉了赵北辰。
赵北辰说:"你自己修了它。"
"什么意思?"
"给这个开源项目提一个 PR,修复这个 bug。"
王志远愣了一下:"我?给开源项目提 PR?"
"为什么不?你已经找到了 bug 的原因,修复也不复杂。这是参与开源的最好方式——从修复一个 bug 开始。"
"但我从来没给开源项目贡献过代码……"
"所以才要开始。"赵北辰说。"你在公司写的代码,只有公司的人能看到。你给开源项目贡献的代码,全世界的人都能看到。这是两种完全不同的影响力。"
三
王志远决定试试。
他先仔细阅读了这个项目的 CONTRIBUTING.md——贡献指南。里面写了:
- Fork 项目到自己的 GitHub 账号
- 创建一个新的分支(分支名格式:
fix/issue-编号-简短描述) - 修复 bug,写测试
- 提交 PR,描述清楚改了什么、为什么改
- 等待 Maintainer Review
他按照指南一步一步来。
先 Fork,然后克隆到本地。他花了半小时熟悉项目的代码结构——比他想象的简单,核心代码只有几千行。
然后他创建了一个分支:fix/issue-234-last-business-day-sunday。
修复代码只有两行:
// 修复前:只处理了周六
if (lastDay.getDayOfWeek() == DayOfWeek.SATURDAY) {
return lastDay.minusDays(1); // 返回周五
}
// 修复后:同时处理周六和周日
if (lastDay.getDayOfWeek() == DayOfWeek.SATURDAY) {
return lastDay.minusDays(1); // 返回周五
} else if (lastDay.getDayOfWeek() == DayOfWeek.SUNDAY) {
return lastDay.minusDays(2); // 返回周五
}
但他没有只改这两行。他还做了三件事:
- 写了测试用例,覆盖了"最后一天是周五/周六/周日"三种情况
- 检查了代码里有没有类似的问题——果然,"某个月的第一个工作日"也有同样的 bug,他一并修复了
- 更新了文档,在 API 说明里补充了"工作日计算会自动跳过周末"的说明
四
他写 PR 描述的时候,花了比写代码更多的时间。
## 修复内容
修复 #234:`getLastBusinessDay()` 在月末为周日时返回错误结果。
## 问题描述
当某月的最后一天是周日时,`getLastBusinessDay()` 返回周日本身,
而不是上一个工作日(周五)。原因是条件判断只处理了周六的情况。
## 修复方案
- 在 `getLastBusinessDay()` 中增加周日的判断
- 同时修复了 `getFirstBusinessDay()` 中的类似问题
## 测试
- 新增 6 个测试用例,覆盖周五/周六/周日三种边界情况
- 所有现有测试通过
## 相关 Issue
Fixes #234
他检查了三遍,确认没有拼写错误和格式问题,然后点了"Create Pull Request"。
提交之后,他盯着屏幕看了好一会儿。他的代码,第一次出现在一个公开的、全世界都能看到的地方。
五
等待的过程比他想象的漫长。
第一天,没有人回复。
第二天,还是没有。
第三天,他开始怀疑——是不是 PR 写得不好?是不是代码有问题?是不是 Maintainer 不活跃了?
第四天,终于有人回复了。
是项目的 Maintainer,一个叫 @alexchen 的开发者。他的评论很简短:
Thanks for the fix! A few comments:
- The fix looks correct. But can you also handle the case where the last day falls on a holiday? We have a
HolidayCalendarclass that can be used.- Please add a test case for the holiday scenario.
- Minor: the variable name
lastDaycould be more specific, likelastDayOfMonth.
三条评论。王志远看到第一条的时候,心里一紧——他没有考虑节假日的情况。如果月末的最后一个工作日恰好是节假日(比如国庆节),应该继续往前推。
他花了一个晚上修改了代码,加入了节假日的处理逻辑,补了测试用例,改了变量名。然后更新了 PR。
第二天,@alexchen 回复了:
LGTM (Looks Good To Me). Merging. Thanks for the contribution! 🎉
PR 被合并了。
王志远看到那个绿色的 "Merged" 标签时,差点从椅子上跳起来。
他的代码,被合并到了一个有两千多个 Star 的开源项目里。全世界使用这个库的开发者,都会用到他写的代码。
这种感觉,跟在公司完成一个任务完全不同。在公司,他的代码服务的是一个客户。在开源社区,他的代码服务的是所有人。
六
他把这件事告诉了赵北辰。
赵北辰笑了:"恭喜。第一个 PR 被合并,是每个开源贡献者最难忘的时刻。"
"赵哥,你也给开源项目贡献过代码吗?"
"当然。我给 Spring Framework 提过两个 PR,给 Apache Commons 提过一个。都是修 bug。"
"Spring Framework?!"王志远惊了。
"Spring 的代码也有 bug。"赵北辰笑了。"没有完美的软件。开源的好处就是——任何人都可以发现问题、修复问题。这就是'众人拾柴火焰高'。"
他看着王志远:"你现在已经迈出了第一步。接下来可以做更多——不只是修 bug,还可以加新功能、改进文档、回答 Issue 里的问题。开源社区需要各种各样的贡献,不只是代码。"
七
这次开源贡献的经历,让王志远的视野发生了一个微妙的变化。
以前他觉得"写代码"就是在公司里完成任务、拿工资。现在他意识到,代码可以有更大的意义——它可以帮助素不相识的人解决问题。
他开始关注开源社区。他在 GitHub 上 Follow 了几个感兴趣的项目,定期看 Issues 和 Discussions。他发现,很多 Issue 并不需要高深的技术——有些是文档的拼写错误,有些是测试用例的缺失,有些是新手友好的 bug(标记为 good first issue)。
他又给另外两个项目提了 PR——一个是修复文档里的代码示例错误,一个是给一个工具库加了一个新的 API。两个 PR 都被合并了。
他的 GitHub 主页上,绿色的贡献方格开始多了起来。
有一天,张小龙看到了他的 GitHub 主页,惊讶地说:"小胖哥,你给开源项目贡献过代码?好厉害!"
王志远笑了:"不厉害。第一个 PR 只改了两行代码。但那两行代码,让我觉得自己不只是一个'打工人',还是一个'贡献者'。"
八
那天晚上,王志远又刷了一遍 GitHub 的通知页面。那个绿色的 "Merged" 标签还在那里,安安静静的,两行代码,一个修复。
他想起赵北辰说过,他给 Spring Framework 提过 PR。当时觉得那是遥不可及的事情。现在他的第一个 PR 也被合并了——虽然只是一个小项目的小 bug,但在公司写代码服务一个客户,在开源社区写代码服务所有人。这感觉不一样。
他关掉浏览器,心里默默给自己定了个目标:有一天,也给一个顶级项目贡献代码。
不急。千里之行,始于足下。
:::{admonition} 小胖的笔记 :class: tip
本章知识卡片:参与开源
1. 参与开源的方式
| 方式 | 难度 | 说明 |
|---|---|---|
| 修复文档错误 | ⭐ | 最简单的入门方式 |
| 回答 Issue 问题 | ⭐⭐ | 帮助其他用户 |
修复 good first issue |
⭐⭐ | 项目标记的新手友好 bug |
| 修复普通 bug | ⭐⭐⭐ | 需要理解项目代码 |
| 添加新功能 | ⭐⭐⭐⭐ | 需要与 Maintainer 讨论方案 |
| 成为 Maintainer | ⭐⭐⭐⭐⭐ | 长期贡献后被邀请 |
2. 提 PR 的流程
1. 阅读 CONTRIBUTING.md(贡献指南)
2. Fork 项目到自己的账号
3. 创建分支(命名规范:fix/xxx 或 feat/xxx)
4. 修改代码 + 写测试
5. 本地跑通所有测试
6. 写 PR 描述(改了什么、为什么、怎么测试)
7. 提交 PR
8. 根据 Review 意见修改
9. 等待合并
3. PR 描述模板
## 修复内容 / 新功能
简要描述改了什么。
## 问题描述 / 动机
为什么要做这个改动?
## 修复方案 / 实现方式
怎么做的?关键设计决策是什么?
## 测试
新增了哪些测试?如何验证?
## 相关 Issue
Fixes #xxx
4. 开源许可证速查
| 许可证 | 特点 | 适用场景 |
|---|---|---|
| MIT | 最宽松,几乎无限制 | 大部分项目 |
| Apache 2.0 | 宽松,有专利授权 | 企业级项目 |
| GPL | 传染性,衍生作品必须开源 | 希望保持开源的项目 |
| BSD | 宽松,类似 MIT | 学术项目 |
核心原则:开源不只是代码,是一种协作方式。每个人都可以让世界变好一点点。
:::