Good documentation will lead to quicker adoption and happier users. It should also lead to fewer requests for help and support. Documentation also serves to draw a clear line between public and private APIs.
There is no one-size-fits-all approach to good documentation. Whether a documentation is considered good or not still remains a highly subjective thing after all.
With that in mind, we still try to set up a rough guideline on how to create solid documentation for the software that we produce.
We use Sphinx
to create documentation for
all Python projects. Sphinx is the de facto documentation generator in the Python
eco-system because it provides lots of nice features specific to documenting Python
code.
We tend to host our documentation using the ReadTheDocs service.
The README.md
file should always have a link to the hosted documentation.
Documentation that contains broken examples is very annoying for the reader. Therefore it is
required to have a dedicated CI job that runs doctest
to validate the correctness of the
documentation.
Code examples can be written using .. doctest::
code blocks to ensure they are executed as
regular code during the doctest
CI run.
Examples that use this feature are effectively structured as if they were written in an interactive Python session.
Example:
.. doctest::
>>> current_vm = chain.get_vm()
>>> account_db = current_vm.state.account_db
>>> account_db.get_balance(SOME_ADDRESS)
10000000000000000000000
Sometimes, writing code examples using only the .. doctest::
directive can feel a bit limiting
and effectively clash with other desired properties of the example (e.g. order, brevity). There's
another effective way to guarantee examples in the documentation continue to work as the
underlying software evolves.
We can write examples as standalone code examples that are tested through unit tests and then use
the .. literalinclude::
directive to take fine-grained control over the exposed parts that get
exposed in the documentation.
Example:
.. literalinclude:: ../../../trinity-external-plugins/examples/peer_count_reporter/peer_count_reporter_plugin/plugin.py
:language: python
:pyobject: PeerCountReporterPlugin
:end-before: def configure_parser
The following structure serves as a rough generalization on how to break down the documentation in different sections. It also achieves a side navigation that looks clean and makes it easy to navigate.
General
- Introduction
- Quickstart
- Release Notes
Fundamentals
- API
- Guides
- Cookbook (optional)
Community
- Contributing
- Code of Conduct
This is the main landing page. It should contain:
- Abstract (What is this?)
- Goals (What do we want?)
- Status (Where are we?)
- Further reading (Links)
- Quickstart
- Source Code
- Communication channels
- Getting involved
The Quickstart is a very simple hands-on guide, sketching a happy path scenario for those who have just discovered the project. It demos how to install the software and how to use it in the most simplest way possible.
It should also contain links to tell the reader what to explore next. Notice that the Quickstart is really just a guide (see Guides section further down) that is referenced in the side navigation as well as in the introduction to be exposed more prominently.
The release notes inform the reader about the progress of the project. They are continuously written as new releases are published.
API docs are the first level of documentation. They are the closest to actual source code and simply document how an API works. They do not necessarily give a good perspective about the bigger picture though.
Public APIs are expected to be annotated with docstrings as seen in the following example.
def add_transaction(self,
transaction: BaseTransaction,
computation: BaseComputation,
block: BaseBlock) -> Tuple[Block, Dict[bytes, bytes]]:
"""
Add a transaction to the given block and
return `trie_data` to store the transaction data in chaindb in VM layer.
Update the bloom_filter, transaction trie and receipt trie roots, bloom_filter,
bloom, and used_gas of the block.
:param transaction: the executed transaction
:param computation: the Computation object with executed result
:param block: the Block which the transaction is added in
:return: the block and the trie_data
"""
Docstrings are written in reStructuredText and allow certain type of directives.
The :param:
and :return:
directives are being used to describe parameters as well as the return
value. Usage of :type:
and :rtype:
directives is discouraged as sphinx automatically reads
and displays the types from the source code type definitions making any further use of
:type:
and :rtype:
obsolete and unnecessarily verbose.
Whenever other APIs in the form of classes or methods are mentioned we should use the linking syntax as it will allow the reader to jump to the corresponding API docs.
Example: :class:
`~eth_typing.misc.Address`
We use imperative, present tense to describe APIs: “return” not “returns”
One way to test if we have it right is to complete the following sentence.
If we call this API it will: __________________________
If that sounds right, it probably is.
Sphinx can generated the API docs directly from the embedded docstrings in the source code. The
rst
files for API docs are mostly concerned about how the order of the described types or short
intro paragraphs.
Chain
=====
BaseChain
---------
.. autoclass:: eth.chains.base.BaseChain
:members:
Chain
-----
.. autoclass:: eth.chains.base.Chain
:members:
While solid API documentation is very important for those who know what they are looking for, it does not cover all the essential aspects of the entire documentation spectrum.
Often, a narrative style documentation that walks the user through specific use cases is more user friendly. This is especially true for those who are not yet familiar with the software and lack a good feel for existing APIs.
Each narrative is written from a particular point of view and it is important to remain consistent in its use.
There are two main styles of writing that are generally used for technical documentation and there really is no right or wrong when it comes to picking one over the other.
Here are two prominent examples:
React uses second-person "you" form
Rust uses first-person "we" form
The second-person "you" form is more casual as it directly speaks to the reader whereas the first-person "we" form is more formal and more popular in scientific writing.
We use the first-person "we" form only.
Examples:
Correct
We can send the transaction after we sign it.
Wrong
You can send the transaction after you sign it.
Wrong
I can send the transaction after I sign it.
It's tempting to write guides that drop the user right within a solution that highlights exciting features. While this may make a lot of sense for the writer, often readers will find themselves wondering: "Wait! Why are we doing this in the first place?"
Therefore, it's a good idea for guides to be written in a problem-driven manner whenever possible. We want the reader to first understand why they want to read a section of documentation. What is the problem that we will learn how to fix or what are the possible use cases for the features that we are about to learn?
Between concise API docs and lengthy guides, there's often room for a third format of documentation. A cookbook is a collection of small recipes that demo the use of APIs through short examples. Things that do not need a full grown guide but also aren't entirely clear by just looking at the API docs are perfect candidates to go into a cookbook.
Writing cookbooks takes less effort compared to writing guides and it's an effective tactic to start collecting short examples in a cookbook and later turn some of them into more in-depth guides.
This section should describe how to get involved.
- Setting up a work environment
- Running the tests
- Sending PRs
- Developer communication channels
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone. Therefore, every project documentation siteshould contain this code of conduct.