谈谈Github上如何交流(3): 如何管理issue

我听过不少人凭借爱好开源了自己的项目后, 却对 issue 太乱感到困扰, 甚至想干脆直接禁用 issue. 其实, 任何项目达到一定规模后, 如果不对 issue 进行适当管理, 都会使 issue 信噪比过低, 失去原本的功能.

这篇文章主要从 maintainer 的角度说说, 在具备规模的项目中管理 issue 的一些方法和原则.

Issue Template

任何具备一定规模的项目都应该使用 issue template. Issue template 位于项目的.github/ISSUE_TEMPLATE 目录, 包含两种文件:

  1. 每个 template 有一个 markdown 文件, 对应一类 issue. 其中描述需要用户提供的信息.

    还可以为这个 issue template 自动配置 issue label. 然而由于 template 是用户选择的, 这种方式得到的 issue label 噪音较大, 可能还需要 maintainer 纠正. (我的策略是仅对 "feature request" 和 "documentation issue" 自动 label)

  2. 可选的config.yml 全局配置文件. 有用的配置包括:

    • blank_issues_enabled: 是否允许用户不使用 template 自己写 issue.
    • contact_links: maintainer 用它将用户引导到其他地方 (论坛, discussions 等).

Github 近期在测试 issue form, 是 issue template 的升级版, 有了更好看的 UI 和丰富的输入类型. 可惜我一直还没有测试机会.

Issues vs. Questions

常见的 issue 有如下两大类:

  1. Unexpected behavior / bug: 报告 bug 以及有可能是 bug 的 unexpected behavior.
  2. Feature request / enhancement: 对项目的代码, 文档, 注释等的各类 improvement/enhancement.

除了这些之外, 用户常常还想问各种其他问题, 譬如 "怎么用 XXX", "我这样做对不对", "项目里这段 code 是干嘛用的" 等等. 暂且将它们称为 "question". 我认为, 大的 (issue 很多的) 开源项目中 issue 里不应包含这些 "question", issue 应当 不超过上面的两类.

为什么? 当 issue 很多的时候, "question" 与两大类 "issue" 有些本质的不同, 会导致 issue 难以管理:

  1. 无法定义 close: issue 的设计是有 open/close 的状态的, 但 question 往往是 open-ended, 只能以提问者是否满意来定义 close. 然而提问者是否满意是很主观, 不可控的: 提问者可能水平不高不理解答案, 可能会有 follow up 的跑题的问题, 甚至可能问题都不成立让人无从回答. 处理这些问题, 会带来大量无法标记 open/close 状态的 issue, 影响项目的管理. 相反, 上文提到的两大类 issue 是可以较为客观的定义 close 的: 一旦一个 bug / feature request 被解决, maintainer 一般都可以自主决定是否将 issue 关闭.
  2. Not Actionable: Maintainer 希望实现 "每个 open issue 都是一个 todo item", 以方便对项目的管理. 然而, question 以提问者为中心. 从开发者的角度来看, 并没有明确的 action items.
  3. 太宽泛: 两大类 issue 都可以设定很清晰的格式 / 模板, 因此 maintainer 可以通过 issue 的类别和模板来声明自己的义务范围 (后文会详细介绍). 然而, question 的范围太广了, 用户的问法常常会让人不明白到底想问什么, 或者不明白该怎么处理.
    例如, 有一类我很头疼的问题是 "这个项目能不能做 xxx?". 这种过于自由的问法让 maintainer 不明白这个问题到底是:
    • feature request: 建议未来在项目里实现 xxx, 还是
    • 寻求写代码指导: 希望使用这个项目自己实现 xxx 但不知道怎么做 --- 这往往不是 maintainer 的义务范围.

总而言之, question 大多以用户为中心, 处理它们的沟通成本更高, 而对项目的 contribution 却更低. 混杂在以项目为中心的另两类更重要的 issue 中会分散 maintainer 的精力. 因此很多大的项目都希望将 question 剥离出 issue.

然而, 用户确实有问问题或进行其他交流的需求, 这样的需求可以用 github discussions / 论坛来满足.

Github Discussions / 论坛

Github 近两年推出了 "discussions" 版块. Discussions 在功能 / UI 上与 issues 有所区别, 各方面都更像传统的论坛: 例如没有 open/close/assign 的状态, 可以 "顶帖", 可以 "mark as answer", 等等. 简单来说, github discussions 就是提供一个 简化版的论坛.

在内容上, github 并没有给 discussions 和 issues 定义明确的边界, 这个边界由每个项目自己定义: Maintainer 应通过 issue category 和 issue template 来 声明自己愿意支持解决的 issue 有哪些 (例如 bug report, feature request), 并告知用户 "其他" 讨论 / Question 可以发到 discussions 中. 如果发错了地方, maintainer 可以通过 github 提供的按钮一键在 issue/discussion 之间转换.

我们以 PyTorch 为例. 在 PyTorch 的 issue 列表点击 "new issue" 后, 进入 PyTorch 的 issue 类别 页面.

可以看到:

  1. PyTorch issue 就只包含上文提到的两大类: bug 与 feature (只是细分成了更多类).

    实践上把 documentation 细分出一类是很有用的. 因为 documentation 的勘误到底是属于 "bug" 还是 "enhancement" 可能会有歧义. Documentation 被细分后, maintainer 就可以将 "bug" 定义为狭义的代码 bug, 将 "enhancement" 定义为 "feature request", 使得类别的定义更清晰.

  2. 所有 "其他讨论" 都通过最后一行的按钮被引导到 PyTorch 的官方 Discourse 论坛上. 曾经, PyTorch 甚至专门有一个 "question" issue template 的内容就是 "不要发 question, 请用论坛". 由于避免了 question, PyTorch issue 始终维持了高质量的技术讨论, 也达到了管理开发任务的 "tracker" 功能.

    Github discussions 的定位就是一个项目自带的简易论坛, 毕竟不是所有项目都有资源自己搭建一个论坛.

再以 TensorFlow 做个反面教材: 我由于曾经是深度 TF1 用户, 在早期还是很喜欢看它的 github. 然而 TensorFlow 长期没有对 issue 进行分流. 可以观察到大约在 18 年前后, 估计由于 issue 的噪声太大, 性价比太低, TensorFlow issues 里已经很少再有 core developer 回复, 导致真正有价值的 issue 也更难以得到重视了. 我就多次需要靠手动 at 对应领域我认识的 developer 才能有人回应我报的 bug. 直到 2021 年, TensorFlow 才终于开始在 issue template 里把用户引导至自建 Discourse 论坛.

最后还是要提醒: discussions / 论坛仅适用于规模较大, 问题较多的项目. 对小项目, 额外一个讨论平台引入的 overhead 可能得不偿失.

Maintainer 的义务范围

第一篇文章中说到, maintainer 自己决定自己有哪些义务, 决定自己的 commitment, 也即自己愿意对用户提供哪些 "support". 很多 maintainer 与用户沟通上的问题, 源于没有划清自己的义务范围. 一旦这条线划清了, maintainer 就无需为乱七八糟的 issue 头疼: 项目不 support 的问题不必操心, 关闭或者移至 discussions 都可以.

  1. Maintainer 应该通过 issue template 的选项表明哪些类 issue 是允许的. 可以通过blank_issues_enabled: false 来禁用 "无 template" 的 issue. 可以通过contact_links 引导 "其他问题" 到别的地方. 如果用户依然发了不支持的 issue, 可以以 "不支持" 为由关闭 / 移至 discussions.

  2. Issue template 的内容里可以更清楚的声明哪些常见情形是不支持的, 例如:

    • Bug report template 可以声明 "bug report 必须要包含复现步骤".
    • Detectron2 的 issue template 声明了 "你自己的模型 train 不好我们不管". 但凡有人报告自己的模型性能不好 / 不收敛, 我就直接引用这句话然后关闭 (为了引用方便, 我依然使用了 saved replies).
  3. 用户应该认识到 "支持 / support" 到底是什么意思:

    • "We don't support X" 更多是关于服务范围的声明, 而不是关于项目功能的声明: "We don't support X" 不代表 "X doesn't work". "We don't support X" 的意思是 "We won't help you about X", 也即 "我们不管 X 能不能用".
      • 例如 detectron2 一直以来都在 windows 上可用甚至还有 CI 测试, 但是从不 "support windows". 对于与 windows 有关的 issue 我们也就不提供帮助.
    • 开源社区中的 "support" 一词大多数时候都是这个含义.
      • 例如 Ubuntu 的 LTS (Long-term support) 中 "support" 的意思, 官方是这么解释的: "commitment to update, patch and maintain the software".
  4. 对于 maintainer 职责之外的 issue, 即使 maintainer 个人愿意帮助, 也可以立刻关闭 / 移至 discussion, 再进行评论. 这样的情况下, 我一般会关闭 issue 并说:

    Because of ABC, this issue is unsupported/unrelated, therefore closing the issue.

    I think doing XYZ might solve/help the issue.

    在这里, "close issue" 表明了 issue 不被支持, 这样提前避免用户由于 "得到了评论" 而对于 support 有不切实际的预期. 也避免了 (其他) maintainer 在下次处理 issue 列表时再看一次.

    同时, 也在不需要花自己太多时间的前提下给了简单的建议, 但至于是否能解决问题我就不再管了.

处理 Bugs/Unexpected Issues

这一节说说对于 bugs/unexpected issues 的常见处理流程和注意事项.

使用 Issue Template: 上篇文章中说了用户报告 unexpected issues 时需要提供的几类信息: expectation, unexpected observation, environment, reproducible example. Maintainer 应该使用 issue template 来告知 / 引导用户提供这些信息.

Detectron2 的 "unexpected problems" issue template 可以作为参考. Facebook AI Research 的其他一些 project 也参考了这个 template (如 pytorch3d, vissl).

检查必要的信息: 还是有不少用户不尊重 issue template, 不提供需要的信息. 以下几个方案可能有帮助:

  • 使用 github 的 saved replies 功能, 一键发送常用回复. 我的 saved replies 里就包含这样一句话:

    If you need help to solve an unexpected issue you observed, please include details following the XXX issue template (link).

  • 手动评论还是麻烦的, 所以我实现了一个 github bot 来检测一些特别明显的信息缺失, 并自动评论.
  • 我和 bot 都会为缺少信息的 issue 打上 "needs-more-info" 标签. 这个标签可以告诉其他 maintainer 不必再查看这个 issue. Maintainer 也可以以这个标签为依据在一段时间后关闭 issue.
  • 未来的 issue form, 有希望通过更严格的格式来缓解这个问题.

分析, 解决 issue: 任何一个有足够信息的 unexpected issue, 应该 有且仅有 如下几种结果:

  • Issue 不存在或无法确认 (例如: Expectation 不正确, 程序 working as expected, 用户自己错了, 无法 reproduce 等等):
    • Maintainer 应解释原因并关闭 issue
    • Maintainer 应考虑是否有提升用户体验的机会, 来避免类似的问题被重复. 包括:
      • 优化文档: 让用户更容易发现正确的信息, 有正确的 expectation
      • 优化程序的 logging: 让用户理解程序的行为
      • 加入一些 early check 来更早的发现 error
      • 提供更清晰的 error message
      • 让 error message 更 actionable -- 不仅说哪里错了, 还告诉用户该怎么办
      • 例如, 1, 2, 3, 4 就是我看到用户在 issue 中的困惑后, 对 logging/error 的一些微小优化. 任何系统的文档 / logging 都永远有提升空间, 用户体验必须经过这样的迭代才能得到提升.
      • 一个很有用的技巧是, 在回复这类 issue 时, maintainer 应尽量尝试仅通过 直接引用 log 或文档 来回答. 如果做不到, 那常常能发现 log 或文档的不足之处.
  • Issue 重复
    • 应关闭并链接到另一个 issue, 把同一个问题的对话集中到一处
    • 没有什么检测 duplicate 的好办法, 希望未来 NLP 技术能有所帮助
  • Issue 由于项目以外的原因产生 (环境, 依赖)
    • Maintainer 依据其严重性决定是否关闭 issue
    • 虽然 issue 不来自项目自身, maintainer 应考虑是否值得加入 workaround / warning 来增加项目的可用性
    • 对于依赖的问题, maintainer 应指向上游依赖的对应 issue. 如果上游没有这个 issue, maintainer 应向上游报告
  • Issue 来自于项目自身的 bug
    • Issue 应永远保持 open, 直到被修复
  • Issue 可以确认存在, 但无法判断原因
    • Issue 应永远保持 open, 直到发现原因后变为以上几种情况之一

可以看到, 以上几种结果基本都是对项目有 contribution 的. 甚至即使 issue 最终不存在, maintainer 也可能从 unexpected issues 中看到提升用户体验的机会. 因此 unexpected issues / bugs 对项目有很大价值.

各类 bot

介绍一些管理 issue 的 bot:

  • 上面提到过的检查 issue 是否包含必要信息的 bot. 然而为了用户体验, 这个 bot 是 precision-driven 的, 只检测最明显的情况, recall 并不高.

  • 自动关闭 "needs-more-info" 的 issue: 如果 issue 有了 "needs-more-info" 的标签, 等待用户提供必要的信息, 却长时间没有 update, 就会被 bot 自动关闭. 当有了 update 时, 标签会被这个 workflow 自动移除.

  • 自动锁定古老 issue: 如果项目一直在活跃开发, 那么一个古老的, 已解决的 bug 很可能没有任何值得 follow up 的信息: 即使类似的 bug 又出现了, 大概率也和旧的 bug 没什么关系. 那么可以对此类 issue 设定为静默一年后自动锁定 (禁止评论).

  • 自动 label: Github 支持按照 issue template 来自动 label, 但是那样的粒度太粗. 如果对于特定类的 issue 能够根据内容来精准匹配的话, 也可以用这个 bot 添加 label. 但是需要注意自然语言处理是很困难的, 给这个 bot 写规则并不容易.

  • 自动订阅 label: 巨型项目中, 开发者想要自动 subscribe 特定模块相关的 issue. 这个 bot 按照 issue 的 label 自动添加 "@username" 来 subscribe 感兴趣的开发者.

  • Stale bot: 自动关闭一段时间没有 activity 的 issue. 这个 bot 很常见, 但 不应该被使用, 因为没有 activity 不代表 issue 解决了. 参考:

    注意这里假设了 issue 和 question 是被区分开的. 如果 question 也被包括在 issue 里, 自动关闭 question 是可以接受的.

Comments