在过去的几年里,我一直试图将我的工作集中在创建我认为是Perfect Commit的东西上。这是一个包含以下所有内容的单一提交:
- 实施:一个单一的、有重点的改变
- 证明实施有效的测试
- 反映更改的更新文档
- 指向提供更多上下文的问题线程的链接
作为软件工程师,我们的工作不是编写新软件:而是将更改应用于现有软件。
提交是我们的主要工作单元。它值得被深思熟虑和小心对待。
执行
每次提交都应该改变一件事情。
这里对“事物”的定义是故意模糊的!
目标是有一些可以轻松查看的内容,并且在将来使用git blame
或git bisect等工具重新访问时可以清楚地理解。
我喜欢保持我的提交历史是线性的,因为我发现这让以后更容易理解。这进一步强化了每个提交作为一个单一的、有针对性的更改的价值。
如果出现问题,原子提交也更容易干净地恢复 – 或者樱桃挑选到其他分支。
测试
测试的最终目标是提高您的生产力。如果你的测试实践让你慢下来,你应该考虑改进它们的方法。
从长远来看,这种生产力的提高来自于获得做出改变的自由,并保持对你的改变没有破坏其他东西的信心。
但测试也有助于在短期内提高生产力。
您如何知道所做的更改何时完成并准备好提交?当新的测试通过时,它就准备好了。
我发现这减少了我花在猜测自己和质疑我是否做得足够多并考虑所有边缘情况的时间。
如果没有测试,您的更改很可能会破坏其他一些可能不相关的功能。您的提交可能会因数小时繁琐的手动测试而受阻。或者你可以YOLO并得知你后来弄坏了一些重要的东西!
如果您已经有了良好的测试实践,那么编写测试的时间就会大大减少。
向包含大量现有测试的项目添加新测试很容易:您通常可以找到一个现有测试,其中 90% 的模式已经为您制定好了。
如果您的项目根本没有测试,那么为您的更改添加测试将需要更多的工作。
这就是为什么我以通过测试开始我的每一个项目的原因。这个测试是什么并不重要 – assert 1 + 1 == 2
很好!关键是建立一个测试框架,这样你就可以运行一个命令(对我来说通常是pytest
)来执行测试套件——而且你有一个明显的地方可以在未来添加新的测试。
我几乎所有的新项目都使用这些 cookiecutter 模板。他们配置了一个测试框架,其中包含一个通过测试和 GitHub Actions 工作流程,以便从一开始就进行所有测试。
我不是测试优先开发的大力倡导者,在这种开发中,测试是在代码本身之前编写的。我关心的是包含测试的开发,最终提交将测试和实现捆绑在一起。我在How to cheat at unit tests with pytest 和 Black中写了更多关于我的测试方法。
文档
如果您的项目定义了要在项目之外使用的 API,则需要对其进行记录。在我的工作中,这些项目通常是以下之一:
- 提供旨在导入其他项目的代码的 Python API(模块、函数和类)
- Web API——现在通常是基于 HTTP 的 JSON——提供其他应用程序使用的功能
- 命令行界面工具,例如使用Click或Typer或argparse实现的工具。
此文档必须与代码本身位于同一存储库中,这一点至关重要。
这很重要,原因有很多。
文档只有在人们信任的情况下才有价值。人们只有在知道它保持最新时才会信任它。
如果您的文档位于某个单独的 wiki 中,那么它们很容易过时 – 但更重要的是,任何人都很难快速确认文档是否与代码同步更新。
文档应该是版本化的。人们需要能够找到他们正在使用的软件的特定版本的文档。将其保存在与代码相同的存储库中,可以免费同步版本控制。
文档更改应以与您的代码相同的方式进行审查。如果它们位于同一个存储库中,您可以在代码审查过程中捕获需要反映在文档中的更改。
理想情况下,应该测试文档。我写了关于我使用文档单元测试的方法。使用测试框架执行文档中的示例代码也是一个好主意。
与测试一样,从头开始编写文档比逐步修改现有文档要多得多。
我的许多提交都包含只有一两句话的文档。这不会花很长时间来写,但随着时间的推移它会增加一些非常全面的东西。
面向最终用户的文档如何?我自己还在想办法。我创建了我的截图工具来帮助自动化保持屏幕截图最新的过程,但我还没有找到我有信心的最终用户文档的个人习惯和风格。
指向问题的链接
每个完美的提交都应该包含一个链接到一个伴随该更改的问题线程。
有时我什至会在写提交信息之前几秒钟打开一个问题,只是为了给自己一些我可以从提交本身链接到的东西!
我喜欢问题线程的原因是,它们为正在进行的更改的评论和背景提供了有效的无限空间。
我的大多数问题线程都是我自言自语——有时还有几十个问题评论,都是我写的。
问题线程中可以包含的内容包括:
- 背景:改变的原因。我尝试将其包含在开场评论中。
- 改变前的比赛状态。我会经常链接到当前版本的代码和文档。如果我几天后返回一个未解决的问题,这非常有用,因为它使我不必重复最初的研究。
- 链接到事物。这么多链接!改变的灵感、相关文档、关于 Slack 或 Discord 的对话、在 StackOverflow 上找到的线索。
- 说明潜在设计和错误开始的代码片段。使用
```python ... ```
块在问题注释中突出显示语法。 - 决定。你是怎么考虑的?你做了什么决定?作为程序员,我们每天要做出数百个微小的决定。记下来!然后,您将永远不会发现自己忘记了最初的推理而在将来重新提起诉讼。
- 截图。之前的样子,之后的样子。动画截图更精彩!我使用LICEcap来生成快速 GIF 屏幕截图或 QuickTime 来捕捉视频——这两者都可以直接放入 GitHub 问题评论中。
- 原型。我会经常粘贴从 Python 控制台会话复制的几行代码。有时我什至会粘贴一段 HTML 和 CSS,或者添加 UI 原型的屏幕截图。
关闭问题后,我想添加最后一条评论,链接到更新的文档,最好是新功能的现场演示。
问题比提交信息更有价值
我在我的提交信息中经历了几年的写作阶段,试图尽可能多地捕捉背景背景和思考。
当我开始在提交中捆绑更新的文档时,我的提交消息变得更短了——因为通常我之前包含在提交消息中的大部分材料现在都在该文档中。
当我扩展编写问题线程的实践时,我发现对于大多数情况来说,它们比提交消息本身更适合。它们支持嵌入式媒体,更容易被发现,即使在提交完成后我也可以继续扩展它们。
今天,我的许多提交消息都是单行摘要和问题的链接!
冗长的提交消息的最大好处是保证它们与存储库本身一样长。如果您要以我在此处描述的方式使用问题线程,那么考虑它们的长期存档价值至关重要。
我希望这会引起争议!我主张在这里放弃 Git 的核心思想之一——每个存储库都应该包含一个完整的、分散的历史记录,当有人克隆一个 repo 时,它会被完整地复制。
我理解那种哲学。我在这里要说的是,我自己的经验是,放弃该要求会导致我的整体生产力净增加。其他人可能会得出不同的结论。
如果这太冒犯你了,欢迎你构建一个更完美的提交,在扩展的提交消息中也包含背景信息和额外的上下文。
我喜欢 GitHub Issues 的原因之一是它包含一个全面的 API,可用于提取所有数据。我使用我的github-to-sqlite 工具来维护我的问题的持续存档并将问题评论作为 SQLite 数据库文件。
并非每个提交都需要“完美”
我发现我的绝大多数工作都符合这种模式,但也有例外。
某些文档或评论的错字修复?寄过去就好了
不值得记录的错误修复?仍然捆绑实施和测试以及指向问题的链接,但无需更新文档 – 特别是如果它们已经描述了预期的无错误行为。
但总的来说,我发现以实现、测试、文档和问题链接为目标几乎涵盖了我的所有工作。这是一个非常好的默认模型。
一些例子
以下是我遵循此模式的提交的一些示例:
- 将 Docker 映像升级到 Python 3.11以获取数据集 #1853 – 一个非常小的变化,但仍然包括测试、文档和问题链接。
- sqlite-utils 模式现在为sqlite-utils #299提供可选表
- 用于shot-scraper #96的shot-scraper html 命令
- s3-credentials 的 s3-credentials put-objects 命令#68
- datasette-gunicorn #1 的初始实现– 这是对该存储库的第一次提交,但我仍然捆绑了测试、文档、实现和问题链接。
原文: http://simonwillison.net/2022/Oct/29/the-perfect-commit/#atom-everything