-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Foundations of API Design chapter #2994
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9c1d7d1
e79081e
4a92f68
75c73cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| minutes: 2 | ||
| --- | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you intend to add something here? At the very least, a title and |
||
|
|
||
| # Foundations of API Design | ||
|
|
||
| {{%segment outline}} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| --- | ||
| minutes: 5 | ||
| --- | ||
|
|
||
| # Meaningful Doc Comments | ||
|
|
||
| ```rust,compile_fail | ||
| /// API for the client // ❌ Lacks detail | ||
| pub mod client {} | ||
|
|
||
| /// Function from A to B // ❌ Redundant | ||
| fn a_to_b(a: A) -> B {...} | ||
|
|
||
| /// Connects to the database. // ❌ Lacks detail │ | ||
| fn connect() -> Result<(), Error> {...} | ||
| ``` | ||
|
|
||
| Doc comments are the most common form of documentation developers engage with. | ||
|
|
||
| Good doc comments provide information that the code, names, and types cannot, | ||
| without restating the obvious information. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| --- | ||
| minutes: 5 | ||
| --- | ||
|
|
||
| # The Anatomy of a Doc Comment | ||
|
|
||
| 1. A brief, one-sentence summary. | ||
| 2. A more detailed explanation. | ||
| 3. Special sections: code examples, panics, errors, safety preconditions. | ||
|
|
||
| ````rust,no_compile | ||
| /// Parses a key-value pair from a string. | ||
| /// | ||
| /// The input string must be in the format `key=value`. Everything before the | ||
| /// first '=' is treated as the key, and everything after is the value. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use my_crate::parse_key_value; | ||
| /// let (key, value) = parse_key_value("lang=rust").unwrap(); | ||
| /// assert_eq!(key, "lang"); | ||
| /// assert_eq!(value, "rust"); | ||
| /// ``` | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// Panics if the input is empty. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns a `ParseError::Malformed` if the string does not contain `=`. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// Triggers undefined behavior if... | ||
| unsafe fn parse_key_value(s: &str) -> Result<(String, String), ParseError> | ||
|
|
||
| enum ParseError { | ||
| Empty, | ||
| Malformed, | ||
| } | ||
| ```` | ||
|
|
||
| <details> | ||
|
|
||
| - Idiomatic Rust doc comments follow a conventional structure that makes them | ||
| easier for developers to read. | ||
|
|
||
| - The first line of a doc comment is a single-sentence summary of the function. | ||
| Keep it concise. `rustdoc` and other tools have a strong expectation about | ||
| that: it is used as a short summary in module-level documentation and search | ||
| results. | ||
|
|
||
| - Next, you can provide a long, multi-paragraph description of the "why" and | ||
| "what" of the function. Use Markdown. | ||
|
|
||
| - Finally, you can use top-level section headers to organize your content. Doc | ||
| comments commonly use `# Examples`, `# Panics`, `# Errors`, and `# Safety` as | ||
| section titles. The Rust community expects to see relevant aspects of your API | ||
| documented in these sections. | ||
|
|
||
| - Rust heavily focuses on safety and correctness. Documenting behavior of your | ||
| code in case of errors is critical for writing reliable software. | ||
|
|
||
| - `# Panics`: If your function may panic, you must document the specific | ||
| conditions when that might happen. Callers need to know what to avoid. | ||
|
|
||
| - `# Errors`: For functions returning a `Result`, this section explains what | ||
| kind of errors can occur and under what circumstances. Callers need this | ||
| information to write robust error handling logic. | ||
|
|
||
| - **Question:** Ask the class why documenting panics is so important in a | ||
| language that prefers returning `Result`. | ||
|
|
||
| - **Answer:** Panics are for unrecoverable, programming errors. A library | ||
| should not panic unless a contract is violated by the caller. Documenting | ||
| these contracts is essential. | ||
|
|
||
| - `# Safety` comments document safety preconditions on unsafe functions that | ||
| must be satisfied, or else undefined behavior might result. They are discussed | ||
| in detail in the Unsafe Rust deep dive. | ||
|
|
||
| </details> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| --- | ||
| minutes: 15 | ||
| --- | ||
|
|
||
| # Avoiding Redundancy | ||
|
|
||
| Names and type signatures communicate a lot of information, don't repeat it in | ||
| comments! | ||
|
|
||
| ```rust,compile_fail | ||
| // Repeats name/type information. Can omit! | ||
| /// Parses an ipv4 from a str. Returns an option for failure modes. | ||
| fn parse_ip_addr_v4(input: &str) -> Option<IpAddrV4> { ... } | ||
|
|
||
| // Repeats information obvious from the field name. Can omit! | ||
| struct BusinessAsset { | ||
| /// The customer id. | ||
| let customer_id: u64, | ||
| } | ||
|
|
||
| // Mentions the type name first thing, don't do this! | ||
| /// `ServerSynchronizer` is an orchestrator that sends local edits [...] | ||
| struct ServerSynchronizer { ... } | ||
|
|
||
| // Better! Focuses on purpose. | ||
| /// Sends local edits [...] | ||
| struct ServerSynchronizer { ... } | ||
|
|
||
| // Mentions the function name first thing, don't do this! | ||
| /// `sync_to_server` sends local edits [...] | ||
| fn sync_to_server(...) | ||
|
|
||
| // Better! Focuses on function. | ||
| /// Sends local edits [...] | ||
| fn sync_to_server(...) | ||
| ``` | ||
|
|
||
| <details> | ||
|
|
||
| - Motivation: Documentation that merely repeats name/signature information | ||
| provides nothing new to the API user. | ||
|
|
||
| Additionally, signature information may change over time without the | ||
| documentation being updated accordingly! | ||
|
|
||
| - This is an understandable pattern to fall into! | ||
|
|
||
| Naive approach to "always document your code," follows this advice literally | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding the following nuance somewhere. In Google's style guide for Rust we talk about a distinction between library code and application code:
I think this distinction also applies here. Here's my quick attempt (could be a standalone slide):
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WDYT about calling out the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'll need to be put in a more to explore section, a lot of material could be written on the value one can get out of lints. |
||
| but does not follow the intent. | ||
|
|
||
| Some tools might enforce documentation coverage, this kind of documentation is | ||
| an easy fix. | ||
|
|
||
| - Be aware of the purpose of different modes of documentation: | ||
|
|
||
| - Library code will need to be documented in ways that understand the scope of | ||
| what it is used for and the breadth of people who are trying to use it. | ||
|
|
||
| - Application code has a more narrow purpose, it can afford to be more simple | ||
| and direct. | ||
|
|
||
| - The name of an item is part of the documentation of that item. | ||
|
|
||
| Similarly, the signature of a function is part of the documentation of that | ||
| function. | ||
|
|
||
| Therefore: Some aspects of the item are already covered when you start writing | ||
| doc comments! | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also: Don't be pressured to repeat information just to write a neat-looking bullet list which includes every parameter. |
||
|
|
||
| Do not repeat information for the sake of an itemized list. | ||
|
|
||
| - Many areas of the standard library have minimal documentation because the name | ||
| and types do give enough information. | ||
|
|
||
| Rule of Thumb: What information is missing from a user's perspective? Other | ||
| than name, signature, and irrelevant details of the implementation. | ||
|
|
||
| - Don't explain the basics of Rust or the standard library. Assume the reader of | ||
| doc comments has an intermediate understanding of the language itself. Focus | ||
| on documenting your API. | ||
|
|
||
| For example, if your function returns `Result`, you don't need to explain how | ||
| `Result` or the question mark operators work. | ||
|
|
||
| - If there is a complex topic involved with the functions and types you're | ||
| documenting, signpost to a "source of truth" if one exists such as an internal | ||
| document, a paper, a blog post etc. | ||
|
|
||
| - Collaborate with Students: Go through the methods in the slide and discuss | ||
| what might be relevant to an API user. | ||
|
|
||
| ## More to Explore | ||
|
|
||
| - The `#![warn(missing_docs)]` lint can be helpful for enforcing the existence | ||
| of doc comments, but puts a large burden on developers that could lead to | ||
| leaning onto these patterns of writing low-quality comments. | ||
|
|
||
| This kind of lint should only be enabled if the people maintaining a project | ||
| can afford to keep up with its demands, and usually only for library-style | ||
| crates rather than application code. | ||
|
|
||
| </details> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| --- | ||
| minutes: 10 | ||
| --- | ||
|
|
||
| # Exercise: Dialog on Details | ||
|
|
||
| Unnecessary details can sometimes be indicative of something that does need | ||
| documentation. | ||
|
|
||
| ```rust | ||
| /// Sorts a slice. Implemented using recursive quicksort. | ||
| fn sort_quickly<T: Ord>(to_sort: &mut [T]) { /* ... */ | ||
| } | ||
| ``` | ||
|
|
||
| <details> | ||
|
|
||
| - Consider the example here, we discussed in | ||
| [what and why, not how and where](what-why-not-how-where.md) that internal | ||
| details are unlikely relevant to someone reading documentation. | ||
|
|
||
| Here we're discussing a counterexample. | ||
|
|
||
| - Ask the class: Is this comment necessary for this function? | ||
|
|
||
| - Narrative: Playing the part of an intermediary between the class and the | ||
| author, such as a PM, manager, etc. tell the class that the author of this | ||
| function is pushing back. | ||
|
|
||
| - Ask the class: Why would an author of this kind of comment push back? | ||
|
|
||
| If the class asks why the author is pushing back, do not give details yet. | ||
|
|
||
| - Ask the class: Why would the caller need to know the sorting algorithm in use? | ||
|
|
||
| - Narrative: "Come back" from a meeting with the original author, explain to the | ||
| class that this function is application code that is called on untrusted data | ||
| that | ||
| [could be crafted maliciously to cause quadratic behavior during sorting](https://www.cs.dartmouth.edu/~doug/mdmspe.pdf). | ||
|
|
||
| - Ask the class: Now we have more detail, how should we comment this function? | ||
|
|
||
| The point being implementation detail vs not depends a lot on what the public | ||
| contract is (e.g., can you supply untrusted data or not), and this requires | ||
| careful judgement. | ||
|
|
||
| Consider if a comment is explaining that a for-loop is used (unnecessary | ||
| detail) or if it is explaining that the algorithms used internally have known | ||
| exploits (documentation draws attention to the wrong thing). | ||
|
|
||
| </details> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| --- | ||
| minutes: 10 | ||
| --- | ||
|
|
||
| # Library vs application docs | ||
|
|
||
| You might see elaborate documentation for fundamental APIs that repeats the\ | ||
| names and type signatures. Stable and highly reusable code can afford this with\ | ||
| a positive RoI. | ||
|
|
||
| - Library code: | ||
| - has a high number of users, | ||
| - solves a whole range of related problems, | ||
| - often has stable APIs. | ||
|
|
||
| - Application code is the opposite: | ||
| - few users, | ||
| - solves a specific problem, | ||
| - changes often. | ||
|
|
||
| <details> | ||
|
|
||
| - You might have seen elaborate documentation that repeats code, looks at the\ | ||
| same API multiple times with many examples and case studies. Context is key:\ | ||
| who wrote it, for whom, and what material it is covering, and what resources\ | ||
| did they have. | ||
|
|
||
| - Fundamental library code often has Elaborate documentation, for example,\ | ||
| the standard library, highly reusable frameworks like serde and tokio.\ | ||
| Teams responsible for this code often have appropriate resources to write and\ | ||
| maintain elaborate documentation. | ||
|
|
||
| - Library code is often stable, so the community is going to extract a\ | ||
| significant benefit from elaborate documentation before it needs to be\ | ||
| reworked. | ||
|
|
||
| - Application code has the opposite traits: it has few users, solves a specific\ | ||
| problem, and changes often. For application code elaborate documentation\ | ||
| quickly becomes outdated and misleading. It is also difficult to extract a\ | ||
| positive RoI from boilerplate docs even while they are up to date, because\ | ||
| there are only a few users. | ||
|
|
||
| </details> |
Uh oh!
There was an error while loading. Please reload this page.