第二十章 开源贡献

一

王志远的第一个开源贡献,源于一个 bug。

他在做排班系统的时候,用了一个开源的日期计算库——date-utils-java。这个库在 GitHub 上有两千多个 Star,文档齐全,API 设计得也不错。

但他在使用的时候发现了一个问题:当计算"某个月的最后一个工作日"时,如果那个月的最后一天是周六,库返回的是周五(正确);但如果最后一天是周日,库返回的是周日本身(错误,应该返回周五)。

他查了一下源码,发现是一个条件判断的逻辑错误——代码只处理了"最后一天是周六"的情况,漏掉了"最后一天是周日"的情况。

他在项目的 Issues 里搜了一下,发现已经有人报告了这个 bug,但一直没有人修复。Issue 的创建时间是三个月前。

二

他把这个 bug 告诉了赵北辰。

赵北辰说:"你自己修了它。"

"什么意思?"

"给这个开源项目提一个 PR,修复这个 bug。"

王志远愣了一下:"我?给开源项目提 PR?"

"为什么不?你已经找到了 bug 的原因,修复也不复杂。这是参与开源的最好方式——从修复一个 bug 开始。"

"但我从来没给开源项目贡献过代码……"

"所以才要开始。"赵北辰说。"你在公司写的代码,只有公司的人能看到。你给开源项目贡献的代码,全世界的人都能看到。这是两种完全不同的影响力。"

三

王志远决定试试。

他先仔细阅读了这个项目的 CONTRIBUTING.md——贡献指南。里面写了:

  1. Fork 项目到自己的 GitHub 账号
  2. 创建一个新的分支(分支名格式:fix/issue-编号-简短描述)
  3. 修复 bug,写测试
  4. 提交 PR,描述清楚改了什么、为什么改
  5. 等待 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); // 返回周五
}

但他没有只改这两行。他还做了三件事:

  1. 写了测试用例,覆盖了"最后一天是周五/周六/周日"三种情况
  2. 检查了代码里有没有类似的问题——果然,"某个月的第一个工作日"也有同样的 bug,他一并修复了
  3. 更新了文档,在 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:

  1. The fix looks correct. But can you also handle the case where the last day falls on a holiday? We have a HolidayCalendar class that can be used.
  2. Please add a test case for the holiday scenario.
  3. Minor: the variable name lastDay could be more specific, like lastDayOfMonth.

三条评论。王志远看到第一条的时候,心里一紧——他没有考虑节假日的情况。如果月末的最后一个工作日恰好是节假日(比如国庆节),应该继续往前推。

他花了一个晚上修改了代码,加入了节假日的处理逻辑,补了测试用例,改了变量名。然后更新了 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 学术项目

核心原则:开源不只是代码,是一种协作方式。每个人都可以让世界变好一点点。

:::