Saker
Saker
发布于 2026-09-05 / 3 阅读
0
0

README 的艺术

日期:2026.9.5​

标签:technology​, basics​, cs​

内容来自 Art of README — Stephen Whitmore / hackergrrl。主要为个人总结笔记,但由于个人在总结时的实际开发经验优先,笔记最后补充的实践和实例基本是复制自本文档的中文版。笔记后附英文原文的摘录,便于对照原文。

笔记记录

重要性和目标

重要性:README 是目标群体了解这个项目的窗口。因此,README必须要解释清楚,你的程序/项目解决了什么需要,同时相比其他的海量同类项目,它的优势到底在哪里(高效在何处)。

一个好的README应该让目标群体清楚知道:

  1. 项目是什么

  2. 项目运行的实际效果

  3. 如何使用

  4. 其他相关细节

撰写原则

简洁

README≠文档,其长度应该尽可能短!保留最核心的高质量信息,言简意赅,压缩到不能再短。

补充信息可以专门写文档,不要全塞README里面。

排序:认知漏斗模型

README中的内容排布应该符合“认知漏斗模型”:最开始是最核心、最广泛的内容,依次往下越具体(对应需要了解这部分信息的读者也就越少)。

同一位开发者的README内容板块分布应该是高度一致的。也就是说具有:

  • 高度可预测的格式

  • 特定的核心板块

核心板块

此处作者以一个项目举例:

  1. 取名:名字要能做到“其义自见”。collide-2d-aabb-aabb​ 听起来是个不错的匹配,尽管它假设我知道"aabb"是什么意思。如果名字含义非常模糊或与我要找的没什么关系,我就会继续查找其它的模块了。

  2. 一行流:通过一句话简明扼要的说明了这个模块是做什么的。 collide-2d-aabb-aabb​ 的描述是:

    Determines whether a moving axis-aligned bounding box (AABB) collides with other AABBs.

    太棒了——描述了 AABB 的定义是什么,并且说明了这个模块是做什么的。现在开始评测它是否适合我的代码:

  3. 用法:在开始探究 API 文档之前,最好看看这个模块在实际应用中是什么样子。我可以快速决定用JS写的示例程序是否符合我的代码样式和我要解决的问题。人们会在很多问题上意见相左,比如 promises/callbacks 和 ES6。如果符合我的要求,我会进一步去研究细节。

  4. API:模块的名字,描述和使用方法都符合我的胃口。在这一点上我很乐意使用这个模块。我需要浏览API来确定这就是我需要的,并且很容易整合到我的代码中。API 部分应该详述模块的对象和函数,以及它们的签名,返回值,回调和事件。当类型不是特别明显的时候,也无需进行描述。注意事项要描述清楚。

  5. 安装:当我读到这儿的时候我就要开始试用这个模块了。 如果不是通用的安装说明,就需要在这儿进行描述。即使是一句简单的npm install​也好。 对于使用Node的新用户来说,放一个指向npmjs.org的链接和安装命令,可以让用户快速上手使用模块。

  6. 授权:大多数模块把这个放在最末尾,但是最好还是往前放一些;非常有可能在把这个模块整合完后才发现授权协议不合适。我通常使用 MIT/BSD/X11/ISC。如果你的协议不是很宽容,最好是放到最前面。

补充:实践中其他一些好的参考原则

  1. 考虑包涵一个 背景 部分,如果你的模块依赖于重要但是不为人所熟知的抽象或生态系统。bisecting-between​的函数从它的名字上看不是特别明显,所以在 背景 部分会描述定义,并且给出具体概念和抽象的链接,以便需要的人去使用和获取。如果已经有相似的模块在npm上存在了,这儿也是一个非常适合描述建立模块的动机的地方。

  2. 积极建立连接!如果你谈及其它的模块,想法,或者其他人的时候,在相关的引用内容上加上链接,这样访客就可以很容易的得到你的模块背后的想法。极少有模块是凭空诞生的:所有的作品来源于其它作品,因此很有必要让用户追溯你的模块的历史和灵感。

  3. 包涵参数和返回值的类型的信息,当这些信息不明确的时候。 尽可能的符合约定(cb​ 代表回调函数,num​ 代表 Number​)。

  4. 在 用法 部分包含的示例代码,要在repo中以文件的形式体现 -- 例如example.js​。这样当用户clone项目后,就可以直接运行README中提及的代码。

  5. 使用徽章要慎重。经常会被滥用。它们会容易引起争论。它们在你的README中加入了视觉噪声,并且只有当用户在联网的浏览器里阅读你的markdown时才能看到徽章,因为图片是存放在互联网上的其它地方。对于每一个徽章,需要考虑:README中的徽章提供给典型读者的真实含义是什么?用一个CI徽章来显示build/test状态?这个信号更应该发邮件给维护者,或者自动创建一个issue -- 永远要考虑你的README中的数据的受众并且自问一下是否有一个流程能够让数据更好的送达到目标受众。

  6. API 文档格式没有局限。使用任何你认为是清晰的格式,但是要包含重要的细节:
    a. 参数是否可选,以及默认值
    b. 包含类型信息,如果类型不能清楚的根据约定进行体现
    c. 对于 opts​ 对象参数,描述它所接受的所有的 keys 和 values
    d. 为每个API提供一个小的调用示例,如果它们的用法不明显或是在 用法 部分没有体现。 不过,也有可能是函数太复杂了,需要进行重构,划分成更细粒度的函数,或者整体删除。
    e. 为特殊术语建立链接! 在markdown中你可以把脚注 放在文档的末尾,可以很方便的多次引用它们。这儿有一些我的API文档格式的个人偏好。

  7. 如果你的模块是无状态函数的一个小的集合,在用法 部分以 Node REPLsession 格式放一些调用和返回值的示例比运行一个示例文件更清晰。

  8. 如果你的模块提供了 CLI (command line interface)而不是 API,用命令调用的方式展示调用示例和输出。如果你创建了或更改了一个文件,cat​ 它来展示更改前后的变化。

  9. 不要忘记使用 package.json​中的关键字来盛情邀请模块探险者们。

  10. API改的越多,越要努力的去更新文档 -- 言外之意是让你的API精简并及早给出具体定义。需求一直在变化,但是我们要做的是建立一个抽象层: 模块集合本身,而不是在API中做提前的假设。当需求变更时,并且'只做一件事'不能满足要求的时候,只需写一个新的模块。'只做一件事'对npm生态系统来说能够使一个模块是有效的和有价值的,并且你的改进过程只是简单的用一个模块来替换另一个模块。

  11. 最后,请记住你的代码仓库和其中的README存在的时间要比你的代码仓库托管主机和你链接到的其它任何东西--特别是图片--的时间都要长久。所以内嵌任何对将来要获取你的作品的用户来说是重要的东西。

一些实例

以下是一些体现了本文原则的实例:

Excerpts from English Version

Importance & Purpose

A README is a module consumer's first -- and maybe only -- look into your creation. The consumer wants a module to fulfill their need, so you must explain exactly what need your module fills, and how effectively it does so.

Your job is to

  1. tell them what it is (with context)

  2. show them what it looks like in action

  3. show them how they use it

  4. tell them any other relevant details

This is your job. It's up to the module creator to prove that their work is a shining gem in the sea of slipshod modules. Since so many developers' eyes will find their way to your README before anything else, quality here is your public-facing measure of your work.

Principles

Brevity

The lack of a README is a powerful red flag, but even a lengthy README is not indicative of there being high quality. The ideal README is as short as it can be without being any shorter. Detailed documentation is good -- make separate pages for it! -- but keep your README succinct.

About the Order: Cognitive funneling

In a README it is desirable to have:

  1. a predictable format

  2. certain key elements present

Try to be consistent to save your users precious cognitive cycles.

Key Elements (as examples)

  1. Name -- self-explanatory names are best. collide-2d-aabb-aabb​ sounds promising, though it assumes I know what an "aabb" is. If the name sounds too vague or unrelated, it may be a signal to move on.

  2. One-liner -- having a one-liner that describes the module is useful for getting an idea of what the module does in slightly greater detail. collide-2d-aabb-aabb​ says it

    Determines whether a moving axis-aligned bounding box (AABB) collides with other AABBs.

    Awesome: it defines what an AABB is, and what the module does. Now to gauge how well it'd fit into my code:

  3. Usage -- rather than starting to delve into the API docs, it'd be great to see what the module looks like in action. I can quickly determine whether the example JS fits the desired style and problem. People have lots of opinions on things like promises/callbacks and ES6. If it does fit the bill, then I can proceed to greater detail.

  4. API -- the name, description, and usage of this module all sound appealing to me. I'm very likely to use this module at this point. I just need to scan the API to make sure it does exactly what I need and that it will integrate easily into my codebase. The API section ought to detail the module's objects and functions, their signatures, return types, callbacks, and events in detail. Types should be included where they aren't obvious. Caveats should be made clear.

  5. Installation -- if I've read this far down, then I'm sold on trying out the module. If there are nonstandard installation notes, here's where they'd go, but even if it's just a regular npm install​, I'd like to see that mentioned, too. New users start using Node all the time, so having a link to npmjs.org and an install command provides them the resources to figure out how Node modules work.

  6. License -- most modules put this at the very bottom, but this might actually be better to have higher up; you're likely to exclude a module VERY quickly if it has a license incompatible with your work. I generally stick to the MIT/BSD/X11/ISC flavours. If you have a non-permissive license, stick it at the very top of the module to prevent any confusion.

Bonus: other good practices

Outside of the key points of the article, there are other practices you can follow (or not follow) to raise your README's quality bar even further and maximize its usefulness to others:

  1. Consider including a Background section if your module depends on important but not widely known abstractions or other ecosystems. The function of bisecting-between is not immediately obvious from its name, so it has a detailed Background section to define and link to the big concepts and abstractions one needs to understand to use and grok it. This is also a great place to explain the module's motivation if similar modules already exist on npm.

  2. Aggressively linkify! If you talk about other modules, ideas, or people, make that reference text a link so that visitors can more easily grok your module and the ideas it builds on. Few modules exist in a vacuum: all work comes from other work, so it pays to help users follow your module's history and inspiration.

  3. Include information on types of arguments and return parameters if it's not obvious. Prefer convention wherever possible (cb probably means callback function, num probably means a Number, etc.).

  4. Include the example code in Usage as a file in your repo -- maybe as example.js. It's great to have README code that users can actually run if they clone the repository.

  5. Be judicious in your use of badges. They're easy to abuse. They can also be a breeding ground for bikeshedding and endless debate. They add visual noise to your README and generally only function if the user is reading your Markdown in a browser online, since the images are often hosted elsewhere on the internet. For each badge, consider: "what real value is this badge providing to the typical viewer of this README?" Do you have a CI badge to show build/test status? This signal would better reach important parties by emailing maintainers or automatically creating an issue. Always consider the audience of the data in your README and ask yourself if there's a flow for that data that can better reach its intended audience.

  6. API formatting is highly bikesheddable. Use whatever format you think is clearest, but make sure your format expresses important subtleties:

    a. which parameters are optional, and their defaults

    b. type information, where it is not obvious from convention

    c. for opts object parameters, all keys and values that are accepted

    d. don't shy away from providing a tiny example of an API function's use if it is not obvious or fully covered in the Usage section. However, this can also be a strong signal that the function is too complex and needs to be refactored, broken into smaller functions, or removed altogether

    e. aggressively linkify specialized terminology! In markdown you can keep footnotes at the bottom of your document, so referring to them several times throughout becomes cheap. Some of my personal preferences on API formatting can be found here

  7. If your module is a small collection of stateless functions, having a Usage section as a Node REPL session of function calls and results might communicate usage more clearly than a source code file to run.

  8. If your module provides a CLI (command line interface) instead of (or in addition to) a programmatic API, show usage examples as command invocations and their output. If you create or modify a file, cat it to demonstrate the change before and after.

  9. Don't forget to use package.json keywords to direct module spelunkers to your doorstep.

  10. The more you change your API, the more work you need to exert updating documentation -- the implication here is that you should keep your APIs small and concretely defined early on. Requirements change over time, but instead of front-loading assumptions into the APIs of your modules, load them up one level of abstraction: the module set itself. If the requirements do change and 'do-one-concrete-thing' no longer makes sense, then simply write a new module that does the thing you need. The 'do-one-concrete-thing' module remains a valid and valuable model for the npm ecosystem, and your course correction cost you nothing but a simple substitution of one module for another.

  11. Finally, please remember that your version control repository and its embedded README will outlive your repository host and any of the things you hyperlink to -- especially images -- so inline anything that is essential to future users grokking your work.


评论