├── .github └── workflows │ └── publish.yml ├── .gitignore ├── _quarto.yml ├── author ├── focused.qmd ├── handling-comments.qmd ├── img │ ├── checklists.png │ ├── finish-review.png │ ├── leaving-a-comment.png │ ├── merging.png │ ├── request-a-review.png │ └── resolve-conversation.png └── submitting.qmd ├── code-review.Rproj ├── collaboration └── index.qmd ├── index.qmd ├── issues └── index.qmd └── reviewer ├── aspects.qmd ├── comments.qmd ├── img ├── add-suggestion.png ├── add-to-batch.png ├── approve-with-comments.png ├── commit-batch.png └── write-suggestion.png ├── navigate.qmd ├── purpose.qmd ├── pushback.qmd └── speed.qmd /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: main 5 | 6 | name: Quarto Publish 7 | 8 | jobs: 9 | build-deploy: 10 | runs-on: ubuntu-latest 11 | env: 12 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 13 | steps: 14 | - name: Check out repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Set up Quarto 18 | uses: quarto-dev/quarto-actions/setup@v2 19 | 20 | - name: Render book 21 | run: quarto render 22 | 23 | - name: Deploy to GitHub Pages 24 | id: gh-pages-deploy 25 | uses: JamesIves/github-pages-deploy-action@v4 26 | with: 27 | branch: gh-pages 28 | folder: docs 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .Rdata 4 | .httr-oauth 5 | .DS_Store 6 | _book 7 | docs 8 | /.quarto/ 9 | -------------------------------------------------------------------------------- /_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: book 3 | output-dir: docs 4 | 5 | book: 6 | title: "Tidyteam code review principles" 7 | author: "Davis Vaughan" 8 | repo-url: https://github.com/tidyverse/code-review 9 | repo-branch: main 10 | repo-actions: [edit, issue] 11 | chapters: 12 | - index.qmd 13 | - collaboration/index.qmd 14 | - part: "Reviewing" 15 | chapters: 16 | - reviewer/purpose.qmd 17 | - reviewer/aspects.qmd 18 | - reviewer/navigate.qmd 19 | - reviewer/speed.qmd 20 | - reviewer/comments.qmd 21 | - reviewer/pushback.qmd 22 | - part: "Authoring" 23 | chapters: 24 | - author/submitting.qmd 25 | - author/focused.qmd 26 | - author/handling-comments.qmd 27 | - issues/index.qmd 28 | 29 | format: 30 | html: 31 | theme: cosmo 32 | 33 | editor: visual 34 | 35 | -------------------------------------------------------------------------------- /author/focused.qmd: -------------------------------------------------------------------------------- 1 | # Focused PRs {#sec-focused} 2 | 3 | ## What is a focused PR? {#sec-what-is-focused} 4 | 5 | In general, a focused PR is **one self-contained change**. 6 | This means that: 7 | 8 | - The PR makes a minimal change that addresses **just one thing**. 9 | This is sometimes just one part of a feature, rather than a whole feature at once. 10 | In general it's better to err on the side of writing PRs that are too small vs. PRs that are too large. 11 | Work with your reviewer to find out what an acceptable scope is. 12 | 13 | - Everything the reviewer needs to understand the PR is in the PR, the PR's description, the existing codebase, or a PR they've already recently reviewed. 14 | 15 | - The package will continue to work well for its users and developers after the PR is merged. 16 | 17 | - The PR is not so small that its implications are difficult to understand. 18 | If you add a new function, you should include usage of the function in the same PR so that reviewers can better understand its intended purpose. 19 | 20 | There are no hard and fast rules about how large is "too large." 100 lines is usually a reasonable size for a PR, and 1000 lines is usually too large, but it's up to the judgment of your reviewer. 21 | The number of files that a change is spread across also affects its "size." A 200-line change in one file might be okay, but spread across 50 files would usually be too large. 22 | 23 | Keep in mind that although you have been intimately involved with your code from the moment you started to write it, the reviewer often has no context. 24 | What seems like a focused PR to you might be overwhelming to your reviewer. 25 | When in doubt, write PRs that are smaller than you think you need to write. 26 | Reviewers rarely complain about getting PRs that are too small. 27 | 28 | ## Why write focused PRs? {#sec-why} 29 | 30 | Focused PRs are: 31 | 32 | - **Reviewed more [quickly](#sec-speed).** It's easier for a reviewer to find 5-10 minutes several times to review a single bug fix than to set aside an hour long block to review one large PR that implements many new features. 33 | Large PRs also have a negative compounding affect. 34 | Reviewers don't enjoy getting assigned PRs that try to do too many things at once, and often push them off repeatedly in favor of working on other smaller PRs or more interesting features. 35 | The result is that an hour long review turns into a two-day long review because of *PR dread*. 36 | 37 | - **Reviewed more thoroughly.** When many separate features are being changed at once, reviewers and authors tend to get frustrated by large volumes of detailed commentary shifting back and forth---sometimes to the point where important points get missed or dropped. 38 | 39 | - **Less likely to introduce bugs.** Since you're making fewer changes, it's easier for you and your reviewer to reason effectively about the impact of the PR and determine if a bug has been introduced. 40 | 41 | - **Less wasted work if they are rejected.** If you write a huge PR and your reviewer says that the overall direction is wrong, you've wasted a lot of work. 42 | 43 | - **Easier to merge.** Working on a large PR takes a long time, so you will likely have lots of conflicts when you eventually merge because the main branch will be past the original point of your branch. 44 | 45 | - **Easier to design well.** It's a lot easier to polish the design and code health of a single change than it is to refine all the details of multiple changes at once. 46 | 47 | - **Less blocking on reviews.** Sending self-contained portions of your overall change allows you to continue coding while you wait for your current PR to be reviewed. 48 | 49 | Note that **reviewers have discretion to reject your change outright for the sole reason of it being too large.** Usually they will thank you for your contribution but request that you somehow make it into a series of smaller changes. 50 | It can be a lot of work to split up a change after you've already written it, or require lots of time arguing about why the reviewer should accept your large change. 51 | If you have any doubts about your PR being rejected, you should discuss this with your reviewer ahead of time before you get too far into the PR to ensure that you don't do any unnecessary work. 52 | 53 | ## Separate out refactorings {#sec-refactoring} 54 | 55 | It's usually best to do refactorings in a separate PR from feature changes or bug fixes. 56 | For example, moving and renaming a function should be in a different PR from fixing a bug in that function. 57 | It is much easier for reviewers to understand the changes introduced by each PR when they are separate. 58 | It is typically also easier for git to track that you have simply moved a function if you don't make any behavioral changes in it. 59 | 60 | Small cleanups such as fixing a local variable name can be included inside of a feature change or bug fix PR, though. 61 | You can also include small changes to documentation related to code you are touching. 62 | It's up to the judgment of the reviewer to decide when a refactoring is so large that it will make the review more difficult if included in your current PR. 63 | 64 | ## Add tests {#sec-test-code} 65 | 66 | Focused PRs should include related [test code](#sec-tests). 67 | Remember that focused here refers to the idea of having a single self-contained PR, and not to minimizing the overall line count. 68 | 69 | A PR that adds or changes logic should be accompanied by new or updated tests for the new behavior. 70 | Pure refactoring PRs (that aren't intended to change behavior) should also be covered by tests. 71 | If tests for the code you are refactoring don't exist, you should add them in a separate PR before doing the refactoring. 72 | This gives you a concrete way to validate that the behavior is unchanged before and after the refactoring. 73 | 74 | For more advice on writing good tests, see [this chapter](https://r-pkgs.org/testing-design.html) of the R Packages book. 75 | 76 | ## Large PRs {#sec-large-prs} 77 | 78 | There are a few situations in which large changes that affect hundreds of lines aren't as bad: 79 | 80 | - You can usually count deletion of an entire file as a single change, because it doesn't take the reviewer very long to review. 81 | - Sometimes a large PR has been generated by an automatic refactoring tool that you trust completely, and the reviewer's job is just to verify and say that they really do want the change. With that said, the above point about ensuring that the refactored code is also well-tested still applies. 82 | 83 | Sometimes you will encounter situations not captured by the above points where it seems like your PR *has* to be large. 84 | This is very rarely true. 85 | Authors who practice writing focused PRs can almost always find a way to decompose functionality into a series of small changes. 86 | 87 | Before writing a large PR, consider whether preceding it with a refactoring-only PR could pave the way for a cleaner implementation. 88 | Talk to your teammates and see if anybody has thoughts on how to implement the functionality in focused PRs instead. 89 | 90 | If you discover a new bug while working on your PR, resisting the urge to tackle it in that PR can help your keep your PR focused. 91 | Instead, open an issue to track the bug and address it separately. 92 | 93 | If all of these options fail (which should be extremely rare) then get consent from your reviewers in advance to review a large PR, so they are warned about what is coming. 94 | In this situation, expect the review process to take longer, be vigilant about not introducing bugs, and be extra diligent about writing tests. 95 | -------------------------------------------------------------------------------- /author/handling-comments.qmd: -------------------------------------------------------------------------------- 1 | # Handling reviewer comments {#sec-handling-reviewer-comments} 2 | 3 | When you've sent a PR out for review, it's likely that your reviewer will respond with several comments on your PR. 4 | Here are some useful things to know about handling reviewer comments. 5 | 6 | ## Don't take it personally {#sec-personal} 7 | 8 | The goal of review is to maintain the quality of our packages. 9 | When a reviewer provides a critique of your code, think of it as their attempt to help you and the package rather than as a personal attack on you or your abilities. 10 | Most of the time, the reviewer is trying to help you grow as a developer. 11 | 12 | Sometimes reviewers feel frustrated and they express that frustration in their comments. 13 | This isn't a good practice for reviewers, but as a PR author you should be prepared for this. 14 | Ask yourself, "What is the constructive thing that the reviewer is trying to communicate to me?" and then operate as though that's what they actually said. 15 | 16 | **Never respond in anger to code review comments.** That is a serious breach of professional etiquette that will live forever in the code review tool. 17 | If you are too angry or annoyed to respond kindly, then walk away from your computer for a while, or work on something else until you feel calm enough to reply politely. 18 | 19 | In general, if a reviewer isn't providing feedback in a way that's constructive and polite, explain this to them in person. 20 | If you can't talk to them in person or on a video call, then send them a private message. 21 | Explain to them in a kind way what you don't like and what you'd like them to do differently. 22 | If they also respond in a non-constructive way to this private discussion, or it doesn't have the intended effect, then escalate internally as appropriate. 23 | 24 | ## Fix the code {#sec-fix-code} 25 | 26 | If a reviewer says that they don't understand something in your code, your first response should be to clarify the code itself. 27 | If the code can't be clarified, add a code comment that explains why the code is there. 28 | If a comment seems pointless, only then should your only response be an explanation in the code review tool. 29 | 30 | If a reviewer didn't understand some piece of your code, it's likely other future readers of the code won't understand either. 31 | Writing a response in the code review tool doesn't help future code readers, but clarifying your code or adding code comments does. 32 | 33 | ## Think collaboratively {#sec-think-collaboratively} 34 | 35 | Writing a PR can take a lot of work. 36 | It's often really satisfying to finally send one out for review, feel like it's done, and be pretty sure that no further work is needed. 37 | It can be frustrating to receive comments asking for changes, especially if you don't agree with them. 38 | 39 | At times like this, take a moment to step back and consider if the reviewer is providing valuable feedback that will improve the package. 40 | Your first question to yourself should always be, "Do I understand what the reviewer is asking for?" If you can't answer that question, ask the reviewer for clarification. 41 | 42 | If you understand the comments but disagree with them, it's important to think collaboratively, not combatively or defensively: 43 | 44 | > Bad: "No, I'm not going to do that." 45 | 46 | > Good: "I went with X because of \[these pros/cons\] with \[these tradeoffs\]. My understanding is that using Y would be worse because of \[these reasons\]. Can you help me understand if there is something that I am missing?" 47 | 48 | Remember, **courtesy and respect** **should always be a first priority**. 49 | If you disagree with the reviewer, find ways to collaborate: ask for clarifications, discuss pros/cons, and provide explanations of why your method of doing things is better for the package and for users. 50 | 51 | Sometimes, you might know something about the users, package, or PR that the reviewer doesn't know. 52 | [Fix the code](#sec-fix-code) where appropriate, and engage your reviewer in discussion, including giving them more context. 53 | Usually you can come to some consensus between yourself and the reviewer based on technical facts. 54 | 55 | ## Who "resolves" GitHub comments? {#sec-resolve-comments} 56 | 57 | On GitHub, there is button for `Resolve conversation` that appears under a comment thread: 58 | 59 | ![](img/resolve-conversation.png){fig-align="center" width="900"} 60 | 61 | This button will collapse the conversation, essentially marking the comment as "completed." Both the author and reviewer see this button, so who's job is it to resolve? 62 | Within the tidyteam, we typically expect the PR author to use the resolve button. 63 | One typical workflow is to add a thumbs-up emoji or a comment saying that you have handled their comment, and to then resolve the conversation. 64 | If you have a question about their comment that requires further input, then add a comment of your own and leave the thread open. 65 | Closing resolved threads can reduce the overall noise in the PR, especially if it goes through multiple rounds of review. 66 | 67 | ## Re-requesting review 68 | 69 | If your reviewer has used the [Comment or Request Changes](#sec-approve-with-comments) button in the GitHub UI, then they expect that you will re-request a [review](#sec-request-a-review) after you have addressed all of their comments. 70 | 71 | For [external contributions](#sec-external-contributions) where you might not be able to request a review, it is polite to add a comment tagging the reviewer stating that you have finished incorporating their suggestions. 72 | 73 | ## Resolving conflicts 74 | 75 | If you are having difficulty coming to an agreement with your reviewer, try the advice in the reviewer section on [conflicts](#sec-conflicts). 76 | -------------------------------------------------------------------------------- /author/img/checklists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/author/img/checklists.png -------------------------------------------------------------------------------- /author/img/finish-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/author/img/finish-review.png -------------------------------------------------------------------------------- /author/img/leaving-a-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/author/img/leaving-a-comment.png -------------------------------------------------------------------------------- /author/img/merging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/author/img/merging.png -------------------------------------------------------------------------------- /author/img/request-a-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/author/img/request-a-review.png -------------------------------------------------------------------------------- /author/img/resolve-conversation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/author/img/resolve-conversation.png -------------------------------------------------------------------------------- /author/submitting.qmd: -------------------------------------------------------------------------------- 1 | # Submitting a PR 2 | 3 | ## Writing PR descriptions {#sec-descriptions} 4 | 5 | When you submit a pull request through GitHub, you are able to include a PR title and a PR description detailing what the pull request is about. 6 | Leaving a detailed and useful description is a vital part of ensuring that your reviewer has a successful experience. 7 | There are three big reasons why writing a comprehensive PR description is useful: 8 | 9 | - You have the opportunity to orient and prepare your reviewer for your PR before they've seen any code. 10 | 11 | - Forcing yourself to describe (in words) how your PR works is actually a great way to check that it is implemented correctly. 12 | If you are having trouble describing the PR, it probably needs to be broken down into simpler pieces. 13 | 14 | - You can take advantage of GitHub specific features, including auto-closing issues, referencing related issues, checklists, and commenting on your own PR. 15 | 16 | Future developers will search for your PR based on its description. 17 | Someone in the future might be looking for your change because of a faint memory of its relevance but without the specifics handy. 18 | If all the important information is in the code and not the description, it's going to be a lot harder for them to locate your PR. 19 | 20 | ### Title 21 | 22 | The title of your pull request should be a very short summary of *what* is being done. 23 | Ideally it should be shorter than 72 characters, because this is what appears in version control history summaries and that is the typical limit before the title is truncated. 24 | It should be informative enough that future code searchers don't have to read your PR or its whole description to understand what your PR actually *did*. 25 | That is, the first line should stand alone, allowing readers to skim through code history much faster. 26 | 27 | Bad titles typically don't contain enough context about the problem the PR is trying to solve. 28 | "Fix bug" is an inadequate PR title. 29 | What bug? 30 | What did you do to fix it? 31 | Other similarly bad titles include: 32 | 33 | - "Add patch" 34 | - "Fixes #123" 35 | - "Moving code from A to B" 36 | - "Phase 1" 37 | - "Add convenience functions" 38 | 39 | Avoid restating the title of the corresponding issue in the title of the PR. 40 | Focus on summarizing what the actual change you made does, rather than what is being fixed. 41 | 42 | ### Body {#sec-body} 43 | 44 | The body of a PR description should contain the context a reviewer needs to get into a mental state where they can understand your PR. 45 | It might include: 46 | 47 | - A brief description of the problem being solved 48 | 49 | - Possible shortcomings of the approach being used 50 | 51 | - Links to other relevant issues (using GitHub [features](#sec-github-features)) 52 | 53 | - A before/after [reprex](https://reprex.tidyverse.org/) that shows what the behavior used to be vs the behavior in this PR 54 | 55 | - A before/after benchmark (using the [bench](https://bench.r-lib.org/) package) 56 | 57 | The description should give your reviewer an idea of the code they are about to see, even before they've looked at it. 58 | By outlining any potential shortcomings, you give the reviewer ideas to keep in mind while they look over your code. 59 | 60 | If your PR closes a complicated issue that involved a large amount of back and forth discussion, it is worth summarizing the outcome of that discussion in the body of the description. 61 | This is especially important if the reviewer wasn't a part of that discussion, since they'd otherwise have to wade through a large amount of comments to understand why this particular implementation was chosen. 62 | 63 | ### Reading order {#sec-reading-order} 64 | 65 | For large PRs, it can also be useful to suggest a recommended reading order to your reviewer. 66 | This might involve looking at one particular function before another, but could also be a suggestion to look at the examples or tests before looking at the implementation itself, if you feel that they would clarify the purpose of the PR. 67 | 68 | ## Examples of good PR descriptions {#sec-good-pr-descriptions} 69 | 70 | ### New feature 71 | 72 | This [vctrs PR](https://github.com/r-lib/vctrs/pull/1791) contains an example of adding a new feature to an existing function. 73 | In this case, a new `relationship` argument was added to `vctrs::vec_locate_matches()`, a function that underlies dplyr's joins, like `left_join()`. 74 | In this case, the total number of changed lines was fairly high, but that is mostly due to a combination of having to touch R code, C code, documentation, and adding new tests. 75 | The important thing here is that all of the changes are highly connected, and nothing is extraneous. 76 | 77 | There are a number of things that are useful to point out regarding this PR: 78 | 79 | - This was a case where the author felt like the reviewer might not be as "close" to the code as the author was, so a high-level review to look for any structural problems was good enough. 80 | Since an in-depth review is the default for these two colleagues, the author made sure to mention that a high-level review was fine up near the top of the description. 81 | 82 | - The description enumerates the possible options for `relationship`, along with giving examples of how it would directly tie into dplyr (i.e. providing broader context outside just this PR), and inline code examples. 83 | This gives the reviewer as much context as possible up front, and creates an extremely useful history to look back on if the PR ever needs to be revisited. 84 | 85 | - The author provided a number of [inline comments](#sec-commenting-on-your-own-pr) to call out particularly tricky parts of the code that might need more context. 86 | In particular, the author brought up [one place in the existing code](https://github.com/r-lib/vctrs/pull/1791#discussion_r1113674398) that he found confusing, which the reviewer also agreed was confusing. 87 | This was then acted upon in a [separate PR](https://github.com/r-lib/vctrs/pull/1792) (importantly, not in this one, since the change was unrelated). 88 | 89 | - The reviewer brought up [a concern of their own](https://github.com/r-lib/vctrs/pull/1791#discussion_r1114087922), which the author then provided justification for, and the "tie" went to the author since neither party felt strongly about it. 90 | 91 | ### Performance improvement 92 | 93 | This [vctrs PR](https://github.com/r-lib/vctrs/pull/1760) contains an example of a PR intended to improve the performance of the dictionary data structure used by many functions in that package. 94 | If you look at the changed lines of code, there is really only 1 changed line: 95 | 96 | ``` c 97 | const double load_adjusted_size = x_size / 0.77; 98 | ``` 99 | 100 | Changed to: 101 | 102 | ``` c 103 | const double load_adjusted_size = x_size / 0.50; 104 | ``` 105 | 106 | This is an example where even though the number of lines of changed code is very small, the PR itself required a large amount of context and justification. 107 | This context doesn't belong inside the codebase, but is important for the reviewer as they try to understand why this change was made and whether or not it was the right decision. 108 | The PR mentions: 109 | 110 | - Other sources of information justifying the change 111 | 112 | - Before/after benchmarks showing improved performance 113 | 114 | - Cases where the performance *doesn't* improve, and context about why 115 | 116 | ## GitHub features {#sec-github-features} 117 | 118 | ### Closes 119 | 120 | In the description [body](#sec-body), it is useful to mention what issue is *closed* or *fixed* by this PR. 121 | Specifically, if you mention `Closes #545` or `Fixes #545` in the body of your PR description, then GitHub will automatically close that issue when the PR is merged. 122 | This also helps provide context to both the reviewer and future developers. 123 | 124 | If you'd like to refer to another issue or PR without closing it, then using terminology like `Related to #545` or `Part of #545` is a useful way to connect the PR to the issue even if it doesn't fully resolve it (and GitHub doesn't do anything special with these phrases). 125 | 126 | ### Checklists 127 | 128 | Since GitHub supports Markdown, you can also add checklists to your PR descriptions like this: 129 | 130 | ``` markdown 131 | - [ ] Don't forget this 132 | - [ ] And this 133 | ``` 134 | 135 | Which get rendered in the GitHub UI as: 136 | 137 | ![](img/checklists.png){fig-alt="GitHub interface showing a commit message with a checklist of items each of which has an unchecked box."} 138 | 139 | Checklists are particularly useful in combination with [Draft PRs](#sec-draft-pr) where you might push a partially finished PR to GitHub (to get CI to run, or to ask for advice), but you still have a number of tweaks to make that you don't want to forget about. 140 | 141 | ### Commenting on your own PR {#sec-commenting-on-your-own-pr} 142 | 143 | GitHub allows the PR author to *also* be their own reviewer. 144 | If you open the `Files changed` menu on GitHub, you can add comments on sections of code that you've written, like this: 145 | 146 | ![](img/leaving-a-comment.png){fig-alt="GitHub UI for commenting on code. Selected lines are listed (\"Commenting on lines 35 to 37) above the text area where the commenter can write." fig-align="center" width="900"} 147 | 148 | Once you've added your "review," you can then `Comment` on the PR: 149 | 150 | ![](img/finish-review.png){fig-alt="GitHub UI for submitting a review. There is a text area to write comments, and radio buttons with three options for type of feedback submitted: Comment, Approve, and Request changes." fig-align="center" width="900"} 151 | 152 | You won't be able to `Approve`, as you can't approve your own PR. 153 | 154 | Commenting on your own PR is a great way to provide scoped comments inline with the code that they are related to. 155 | Comments like this are also great because they start a thread where a reviewer can easily respond to that point without cluttering the rest of the PR. 156 | Once the comment has been addressed, it can optionally be marked as *resolved* by the PR author and collapsed. 157 | 158 | PR comments like this should be reserved for ideas that don't belong in the codebase itself (i.e. as a code comment). 159 | They typically call the reviewer's attention to something in the PR that they might otherwise miss, or they call out a part of the code that the PR author is a little uncertain about. 160 | 161 | Here are a few real examples of tidyteam members commenting on our own PRs: 162 | 163 | - [Implement `dplyr::cross_join()`](https://github.com/tidyverse/dplyr/pull/6612) 164 | 165 | - [Refactoring classes in readxl](https://github.com/tidyverse/readxl/pull/683) 166 | 167 | ## Review before submitting 168 | 169 | PRs can undergo significant change during review. 170 | It can be worthwhile to review a PR description before submitting the PR, to ensure that the description still reflects what the PR does and that it is grammatically correct. 171 | 172 | If your codebase uses continuous integration to also run checks on your PR, you should also ensure that those are all passing before requesting a review. 173 | 174 | ## Requesting a review {#sec-request-a-review} 175 | 176 | For [close-knit collaboration](#sec-close-knit-collaboration) and when submitting a PR as an [understudy](#sec-the-understudy), it is expected that the author will use GitHub's "request a review" feature to officially ask one or more colleagues to be their reviewer. 177 | 178 | ![GitHub UI for a submitted PR. In the sidebar, there is a dropdown titled "Reviewers" which is selected, showing a list of GitHub users from whom one can be selected to review.](img/request-a-review.png) 179 | 180 | The reviewer you choose depends on their expertise with the part of the codebase your PR affects. 181 | When you are in [close collaboration](#sec-close-knit-collaboration) with someone, then they are typically the obvious choice of reviewer. 182 | If the author of the PR is also the package maintainer, the choice of reviewer may be less obvious. 183 | Typically, it is reasonable to request a high-level review from another team member, even if they aren't an expert on the package in question. 184 | For example, I am not an expert on testthat, but was requested to review [this PR](https://github.com/r-lib/testthat/pull/1760) as an [understudy](#sec-the-understudy) for its overall structure, design decisions, and clarity. 185 | 186 | Occasionally, it can be useful to select two reviewers for different purposes - one for high-level API design feedback, and one for a full in-depth review. 187 | 188 | For [external contributions](#sec-external-contributions), the author likely won't have the ability to select a reviewer. 189 | In this case, it is expected that the maintainer of the package will review the PR when they next actively work on it. 190 | If the author is a Posit employee and this PR is blocking other work, then the author should reach out to the maintainer (i.e. over Slack) to agree on an expected timeline for the review. 191 | 192 | ## Finishing a PR 193 | 194 | After you have [handled the comments](#sec-handling-reviewer-comments) from your reviewer, the PR can get merged in a variety of ways, depending on the [collaboration pattern](#sec-patterns-of-collaboration) being used. 195 | 196 | For [close-knit collaboration](#sec-close-knit-collaboration) and when working as an [understudy](#sec-the-understudy), the PR author is typically expected to merge the PR after receiving approval from the reviewer and addressing all comments. 197 | After [approval](#sec-approve-with-comments) is received, the author is no longer required to wait on further feedback from the reviewer before merging. 198 | 199 | For [external contributions](#sec-external-contributions), the author likely does not have permission to merge the PR. 200 | Instead, it is expected that the reviewer (who is likely also someone with commit rights) will merge the PR for you after all comments have been addressed. 201 | Depending on the circumstances, the reviewer may also [finish off the PR for you](#sec-finishing-off-an-external-contribution). 202 | 203 | GitHub provides various options for actually merging the PR: 204 | 205 | ![](img/merging.png){fig-alt="GitHub UI for merging. Dropdown is shown with three options: Create a merge commit; Squash and merge; and Rebase and merge." fig-align="center" width="400"} 206 | 207 | Most of the time, we use `Squash and merge`, which collapses all of the commits into a single commit that gets merged into the main branch. 208 | Because of this, we typically don't care what the commit history of the actual PR looks like, because it will likely be collapsed anyways. 209 | 210 | If the commit history of the PR has been purposefully structured in a meaningful way, we may also use `Create a merge commit` to retain the full history. 211 | 212 | After the PR has been merged, `usethis::pr_finish()` can be used to delete the local (and possibly remote) PR branch and switch back to the default branch. 213 | 214 | ## Draft PRs {#sec-draft-pr} 215 | 216 | If you aren't quite ready to submit your PR for a full review, but still want to have it up on GitHub (for CI purposes, perhaps), then you can open a [*Draft PR*](https://github.blog/2019-02-14-introducing-draft-pull-requests/). When a PR is in draft form, this is a signal to your collaborators that they don't need to worry themselves with that PR yet. 217 | 218 | Draft PRs are particularly nice for PRs that you want to get into a "final state" (with CI run and a description written), but you'd also like to sleep on it and give it one last self-review on the following day before requesting a review from a colleague. 219 | It is amazing how many small bugs and documentation typos you can find after self-reviewing a PR the day after you wrote it! 220 | This also saves time for your eventual reviewer, since you've improved the quality of the PR before they've even taken a look at it. 221 | 222 | A draft PR is also useful if you want to park a proof of concept somewhere, knowing that you will come back and clean it up in the future. 223 | 224 | ## Self-reviews 225 | 226 | While this guide mostly focuses on writing PRs with the goal of being reviewed by someone else, it is also useful to create a PR even if no one else is going to review it. 227 | This situation typically arises if you are the only one working on a package, or if you are creating a new package undergoing rapid development. 228 | Like with [draft PRs](#sec-draft-pr), opening a PR, sleeping on it, and then rereading it the following day will often reveal bugs or typos that you've missed. 229 | Functioning as your own reviewer is a great way to practice the principles outlined in this guide, even if you don't work on a team, and can prepare you for future collaborative work. 230 | 231 | Leaving a detailed PR description for yourself has more benefits than you might think. 232 | [Two of the three reasons](#sec-descriptions) for writing a good description outlined at the beginning of this section still apply - forcing yourself to describe the feature in words and using GitHub's many features. 233 | It also provides valuable historical context if someone else (likely you) needs to come back to this PR months from now to understand why a particular line was implemented a certain way. 234 | 235 | Opening a PR also forces CI to run, giving you a chance to fix any minor issues before merging it into your main branch. 236 | Since CI often takes a few minutes to run, it is typically useful to open a PR and then start working on a separate feature while that CI is running. 237 | Once the second feature is finished, you can open a PR for that one, then go back to the first PR and merge it in (possibly giving it a self-review before you do so). 238 | The [`usethis::pr_*()`](https://usethis.r-lib.org/articles/pr-functions.html) family of functions is particularly suited to this workflow. 239 | As long as you are careful to avoid merge conflicts, this allows you to efficiently work on multiple features in parallel. 240 | -------------------------------------------------------------------------------- /code-review.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | MarkdownWrap: Sentence 19 | -------------------------------------------------------------------------------- /collaboration/index.qmd: -------------------------------------------------------------------------------- 1 | # Patterns of collaboration {#sec-patterns-of-collaboration .unnumbered} 2 | 3 | Within the tidyteam group, the way we collaborate with colleagues and external contributors is highly dependent on who is working on a particular package, how closely the contributors are working together, and how often that package is updated. 4 | A large amount of the advice in this guide is independent of any specific collaboration context, but some aspects, such as the expected turnaround time of a code review and the level of detail that is expected from a review, is closely tied to it. 5 | 6 | It is important to keep in mind that package development within the tidyteam is probably a little different from software development at other companies. 7 | Packages often lie dormant for awhile as the core maintainer works on other projects, which means that sometimes PRs take longer to get reviewed than expected. 8 | Posit employees are typically already aware of this workflow, but it can be surprising to a community member submitting their first PR when they don't receive immediate feedback. 9 | If you don't see any recent activity on the `main` branch of a package, it is likely that the package is dormant, but rest assured that we will eventually get to your PR. 10 | 11 | In the following sections, we discuss three patterns of collaboration between PR author and PR reviewer that we use in the tidyteam which help us explicitly state our expectations. 12 | These patterns act as a guide and reference, rather than something that is set in stone. 13 | Developers should feel free to modify the patterns as needed - the most important thing is that you are communicating these expectations to your team and to your larger user base. 14 | 15 | In other sections of this guide, we reference these three patterns when making a distinction between them is useful. 16 | 17 | ### Close-knit collaboration {#sec-close-knit-collaboration} 18 | 19 | In a close-knit collaboration, the PR author and PR reviewer are both heavy contributors to a particular package. 20 | It is common for them to be in close contact with each other (i.e. over Slack), and they often coordinate with each other to avoid overlapping each other's work. 21 | 22 | With this pattern, reviews are typically swift to avoid blocking one another, which means it is particularly important to write [focused](#sec-focused) PRs. 23 | Reviews are also in-depth by default, since both the author and reviewer have significant amount of experience with the package, and they should communicate with each other if a superficial review is good enough. 24 | 25 | Note that close-knit collaborators aren't restricted to Posit employees. 26 | [ggplot2](https://github.com/tidyverse/ggplot2/blob/d7f22413efea3dd2a7c9effff05d4b2aa2c2d300/DESCRIPTION#L4-L22), [dtplyr](https://github.com/tidyverse/dtplyr/blob/68988ec96348e4746c1e200bca707c000f4f6bd1/DESCRIPTION#L4-L10), and [dbplyr](https://github.com/tidyverse/dbplyr/blob/7b0ed3047c0bb24b29fef58134b19b845e194a45/DESCRIPTION#L5-L10) have a number of core contributors that aren't employed by Posit that would be considered under this pattern. 27 | 28 | ### The understudy {#sec-the-understudy} 29 | 30 | An understudy typically follows a project from afar, generally submitting smaller PRs or doing superficial code review with a focus on API design, unit tests, and documentation. 31 | This allows the understudy to stay up to date with a project they may contribute to more or take over in the future. 32 | 33 | When an understudy submits a PR, review is typically swift, in-depth, and comes from a core maintainer as long as there is one actively working on that package. 34 | Understudy PRs are often smaller in scope, and can occasionally require multiple rounds of review if they aren't intimately familiar with the specifics of the package they are working on. 35 | If both the understudy and reviewer are Posit employees, discussing the review live (i.e. over Zoom) is often very beneficial for the understudy's overall understanding of the package. 36 | 37 | When an understudy reviews a PR, review is expected to be swift, and the PR assigned to the understudy for review should not require an in-depth knowledge of the package. 38 | If the understudy isn't available to review, it is fine to merge the PR without waiting for a review, especially if it is blocking some other work. 39 | 40 | An understudy may be an intern at Posit, a motivated community member, or another tidyteam member. 41 | 42 | ### External contributions {#sec-external-contributions} 43 | 44 | An external contribution is a PR authored by someone who isn't a regular contributor to a package. 45 | This may be a one-off PR from a community member or from another tidyteam member that notices a small bug or feature that is missing from a package they don't typically work on. 46 | For example, I am not a regular contributor to usethis, but noticed a small non-critical bug and [sent in a PR for it](https://github.com/r-lib/usethis/pull/1786). 47 | 48 | When an external contributor authors a PR, they should not expect an immediate review. 49 | If the package is in a dormant state, it is possible that the PR won't be looked at for weeks or even months at a time. 50 | When the reviewer eventually does look at the PR, if they have comments that need to be addressed, then they should feel free to make a judgement call on whether or not to request that the PR author make the changes, or to just [finish off the PR themselves](#sec-finishing-off-an-external-contribution). 51 | No matter what the reviewer decides, they should thank the author for their contributions. 52 | 53 | If the external contributor is a Posit employee and the PR fixes a bug or adds a feature that is a blocker for one of their projects, then the PR author should coordinate with the reviewer to ensure that the PR can be reviewed and merged in a timely manner. 54 | If the PR is a blocker, it is important for the package maintainer to respect the deadlines of the PR author, even if they aren't currently working on the package. 55 | -------------------------------------------------------------------------------- /index.qmd: -------------------------------------------------------------------------------- 1 | # Welcome {.unnumbered} 2 | 3 | At Posit, we use code review to maintain the quality of our packages and products. 4 | Code review is also a great opportunity for newer contributors to learn and improve from constructive feedback provided by more experienced package developers. 5 | 6 | The principles in this guide cover various aspects of the code review process. 7 | They are written with the *tidyteam* in mind, which make up the combination of the [tidyverse](https://www.tidyverse.org/), [tidymodels](https://www.tidymodels.org/), and [mlops](https://vetiver.rstudio.com/) groups, but are generally applicable to a large amount of the software development we do at Posit. 8 | 9 | This guide functions as: 10 | 11 | - A resource for new contributors, whether they are community members or Posit employees. 12 | 13 | - A linkable source of truth when there are questions about our code review process. 14 | 15 | We mostly use [GitHub](https://github.com/) as the hosting platform for our code, so many details of this guide are specific to GitHub's tooling. 16 | Additionally, we have a number of R packages that facilitate working on R packages and authoring/reviewing pull requests, such as [`devtools`](https://devtools.r-lib.org/) and [`usethis`](https://usethis.r-lib.org/). 17 | In particular, the family of [`usethis::pr_*()`](https://usethis.r-lib.org/articles/pr-functions.html) functions are extremely useful for managing pull requests on R packages. 18 | 19 | We will use the abbreviation PR to mean a *pull request*. 20 | 21 | ## Acknowledgements 22 | 23 | This guide is a forked and modified version of Google's [code review principles](https://google.github.io/eng-practices/review/). 24 | -------------------------------------------------------------------------------- /issues/index.qmd: -------------------------------------------------------------------------------- 1 | # Issues {#sec-issues .unnumbered} 2 | 3 | ## How to write an issue {#sec-how-to-write-an-issue} 4 | 5 | GitHub issues are often the first step in the PR process, since many of our PRs are reactions to bug reports and feature requests we receive. 6 | Because of this, it is worth spending a little time discussing what makes a high-quality issue. 7 | 8 | While it might seem like opening an issue with just the error message you received is "good enough" for the package maintainer to figure it out, this typically isn't the case in practice. 9 | Maintainers generally need enough context to reproduce the *exact* issue themselves before working on a solution. 10 | Crafting a great issue is a critical part of helping *me* (the maintainer) help *you* (the issue opener). 11 | 12 | Great issues are composed of two parts: 13 | 14 | - Context around what you are trying to accomplish. 15 | 16 | - A minimal reproducible example of the problem. 17 | 18 | ### Context {#sec-issue-context} 19 | 20 | Issues come in all shapes and sizes, and package maintainers have to work through many of them in a single day, so providing context around exactly what was expected to happen vs what actually happened is extremely helpful. 21 | Useful context takes the form of a complete paragraph including: 22 | 23 | - The overarching type of problem you are experiencing - such as a bug, performance regression, or a missing feature. 24 | 25 | - The specific functions involved. 26 | 27 | - Any relevant package versions. 28 | 29 | - Links to any relevant issues or pull requests. 30 | 31 | This [dplyr issue](https://github.com/tidyverse/dplyr/issues/6674) is a great example of providing useful context: 32 | 33 | > Using `case_when` in a `mutate` call with a grouping variable is much, much slower in v1.1.0 compared to v1.0.10. 34 | > The code works but it's causing a tremendous slowdown in many of the packages I maintain. 35 | 36 | The issue author goes on to provide a full before and after benchmark [reprex](#sec-issue-reprex) (which is excellent!), but the context was actually so comprehensive that we could have reproduced this ourselves without it. 37 | 38 | ### Reprex {#sec-issue-reprex} 39 | 40 | A **repr**oducible **ex**ample, or reprex, is the most important part of a high-quality issue. 41 | To create a reprex, you'll use the [reprex](https://reprex.tidyverse.org/) package. 42 | To learn how to use the package, [read this article](https://reprex.tidyverse.org/articles/learn-reprex.html). 43 | If you have the time, you should also watch Jenny Bryan's webinar (linked from that article) that discusses how to use the package. 44 | 45 | Without a reprex, it is typically extremely difficult for maintainers to reproduce an issue, which significantly decreases the chance of it getting resolved in a favorable way. 46 | 47 | As you get into the habit of creating reprexes for your issues, you'll find that the process of creating the reprex often leads you to solve the problem on your own. 48 | Because reprex forces you to run your example in a new R session, effectively giving you a "fresh start," you'll commonly discover that you had something "weird" in your main R session that was causing the problem to begin with. 49 | 50 | Once you've gotten comfortable with creating reprexes, you should next work on making them *minimal*. 51 | Creating a minimal reprex is a bit of an artform, and involves stripping down an existing reprex to the minimum amount of packages and code needed to reproduce the issue. 52 | Sometimes this requires going a level deeper into the code to figure out where the underlying problem is coming from. 53 | For example, with the dplyr issue mentioned in the [context](#sec-issue-context) section, [Hadley further refined](https://github.com/tidyverse/dplyr/issues/6674#issuecomment-1412175315) the original reprex by removing the `mutate()`, `group_by()`, and data frame from the example to show that the problem was directly related to an unexpected amount of overhead in `case_when()`. 54 | Creating minimal reprexes allows us to quickly focus in on the exact problem, resulting in faster PRs, [like this one](https://github.com/tidyverse/dplyr/pull/6711), to resolve them. 55 | 56 | If your reprex involves some kind of performance regression, we recommend that you use the [bench](https://bench.r-lib.org/) package to include a before and after reprex. 57 | This involves installing a version of the package before the regression (with `devtools::install_version()`), running a reprex that involves a call to `bench::mark()` which demonstrates the issue, and then rerunning that same reprex with the faulty version of the package installed. 58 | Hadley's [minimal reprex](https://github.com/tidyverse/dplyr/issues/6674#issuecomment-1412175315) mentioned above is a great example of this. 59 | 60 | ## New issues vs closed issues 61 | 62 | If you find that you are having a problem that looks similar to an existing closed issue, we in the tidyteam greatly prefer that you open a new issue rather than commenting on an existing closed one. 63 | Some repositories have even set up a GitHub Actions [workflow](https://github.com/tidymodels/.github/blob/main/lock.yaml) that locks old issues, preventing this. 64 | It is typically much easier for us to keep track of open issues, so the chance of your issue being resolved is much higher if you open a new one with full context, a reprex, and a link to the issue that you believe is related. 65 | 66 | ## Issue first, PR second {#sec-issue-first-pr-second} 67 | 68 | If you are an [external contributor](#sec-external-contributions) to a package and feel that you have found a bug, it is highly recommended that you open an issue *first* before attempting a PR. 69 | This gives the maintainer a chance to confirm that what you are seeing is actually an issue, and that they are willing to accept a PR for it. 70 | Importantly, it avoids the tough situation of a PR author spending a bunch of time on a PR only for it to be closed by the reviewer because they didn't believe it was a bug or didn't agree with the approach taken by the author. 71 | 72 | For existing issues, it is typically still worth adding a comment on the issue asking if the maintainer would accept a PR for it. 73 | They might have plans to revamp that section of the codebase entirely, which may invalidate your PR altogether. 74 | Even if they say yes, remember that you might not get an immediate review if the maintainer isn't actively working on the package, as mentioned in the [patterns of collaboration](#sec-external-contributions) section, but we still value all contributions! 75 | -------------------------------------------------------------------------------- /reviewer/aspects.qmd: -------------------------------------------------------------------------------- 1 | # Aspects of a PR {#sec-aspects} 2 | 3 | ## Design {#sec-design} 4 | 5 | The most important thing to cover in a review is the overall design of the PR. 6 | Do the interactions of various pieces of code in the PR make sense? 7 | Does this change belong in this package, or somewhere upstream? 8 | Does it integrate well with the rest of the ecosystem? 9 | 10 | If you're [collaborating closely](#sec-close-knit-collaboration) with the PR author, hopefully you aligned on the design aspect of the PR before they worked on it. 11 | If not, it is often worth pausing and having a separate conversation about the overall design rather than directly reviewing their PR, as that conversation might result in the PR being completely rewritten or closed altogether. 12 | If you have to close an author's PR because it doesn't align with the overall design of the package, encourage them to [open an issue](#sec-issue-first-pr-second) first next time, so they don't waste their time doing work that may be rejected. 13 | 14 | ## Functionality 15 | 16 | Does this PR do what the author intended? 17 | Is what the author intended good for the users of this code? 18 | The "users" are usually both end-users (when they are affected by the change) and developers (who will have to "use" this code in the future). 19 | 20 | Mostly, we expect authors to test PRs well-enough that they work correctly by the time they get to code review. 21 | However, as the reviewer you should still be thinking about edge cases, trying to think like a user, and making sure that there are no bugs that you see just by reading the code. 22 | In R, typical edge cases to watch out for include checking what happens if the user passes in an object of an unexpected type, or what happens if they pass in an object of zero length. 23 | 24 | An important part of validating the functionality of a PR is trying it out locally. 25 | This is particularly important if the PR has a user-facing impact, such as a new function or argument. 26 | Within the tidyteam, we generally check out PRs locally using `usethis::pr_fetch()`. 27 | This is a great time to try out edge cases in the code. 28 | It also makes it very easy to create a [reprex](https://reprex.tidyverse.org/) that you can share with the PR author in a GitHub comment if you do find an issue. 29 | 30 | ## Complexity 31 | 32 | Is the PR more complex than it should be? 33 | Check this at every level of the PR---are individual lines too complex? 34 | Are functions too complex? 35 | "Too complex" usually means "can't be understood quickly by code readers." A way to check for this is to try and "run" the function in your head from top to bottom. 36 | If it is exceedingly difficult to do this, the PR might be too complex. 37 | 38 | Complexity can also mean that developers are likely to introduce bugs when they try to call or modify that code in the future. 39 | 40 | A particular type of complexity is **over-engineering**, where authors have made the code more generic than it needs to be, or added functionality that isn't presently needed by the system. 41 | Reviewers should be especially vigilant about over-engineering. 42 | Encourage the author to solve the problem they know needs to be solved *now*, not the problem that the author speculates *might* need to be solved in the future. 43 | The future problem should be solved once it arrives and you can see its actual shape and requirements in the physical universe. 44 | 45 | ## Tests {#sec-tests} 46 | 47 | In general, if a PR fixes a bug or adds a feature, it should be accompanied by a unit test. 48 | If there is a corresponding issue or PR on GitHub that is linked to the test, it is useful for future forensic purposes to include the issue number in the test description, like: 49 | 50 | ``` r 51 | test_that("`my_function()` throws an error when given strings (#553)") 52 | ``` 53 | 54 | Recent versions of the RStudio IDE will automatically generate a hyperlink from this issue number to the corresponding URL on GitHub, which make these extremely valuable for looking up historical context of a particular bug. 55 | 56 | Make sure that the tests in the PR are correct, sensible, and useful. 57 | Tests do not test themselves, and we rarely write tests for our tests---a human must ensure that tests are valid so they should be as straightforward and self-contained as possible. 58 | 59 | Tests should also strive to be as minimal as possible. 60 | When a user opens an [issue](#sec-issues) with a bug report, chances are that they have included an example that is tailored to their use case, but it likely isn't a *minimal* example of the bug in question. 61 | When you write a test for the bug fix, it is worth spending time making the example as minimal as possible so it isolates the bug and is easy to understand in the future. 62 | 63 | For an in-depth analysis of test design, see [the testing chapter](https://r-pkgs.org/testing-design.html) of R Packages. 64 | 65 | Cross-package integration tests are more difficult, but the [tidymodels](https://www.tidymodels.org/) ecosystem accomplishes this using a separate GitHub-only R package named [extratests](https://github.com/tidymodels/extratests) that is run on a daily basis. 66 | 67 | ## Naming {#sec-naming} 68 | 69 | Did the author make an attempt to pick names that are consistent with the rest of the package? 70 | In general, we also prefer longer names that fully describe the functionality rather than shortened names that require special knowledge of special acronyms. 71 | 72 | ## Comments {#sec-code-comments} 73 | 74 | Code comments are useful when they explain *why* some code exists, rather than *what* the code is doing. 75 | Useful comments contain information that can't possibly be in the code, like the reasoning behind a decision. 76 | One example of a good code comment is [this one](https://github.com/tidyverse/tidyr/blob/0764e65fad777b71aa2a81ffd5447d04a61f8d5e/R/chop.R#L270-L275) which is part of code that powers `tidyr::unnest()`: 77 | 78 | ``` r 79 | 80 | 81 | col <- list_unchop(col, ptype = col_ptype) 82 | 83 | if (is_null(col)) { 84 | # This can happen when both of these are true: 85 | # - `col` was an empty list(), or a list of all `NULL`s. 86 | # - No ptype was specified for `col`, either by the user or by a list-of. 87 | col <- unspecified(0L) 88 | } 89 | 90 | 91 | ``` 92 | 93 | It describes the exact combination of inputs required for a very rare branch of code to run. 94 | Without the *context* provided by that comment, it can be a little difficult to know why that branch is there. 95 | Particularly challenging comments like this one should come with tests that can provide concrete examples of the issue, like [these](https://github.com/tidyverse/tidyr/blob/0764e65fad777b71aa2a81ffd5447d04a61f8d5e/tests/testthat/test-chop.R#L231-L249) in tidyr: 96 | 97 | ``` r 98 | test_that("unchopping a bare empty list results in unspecified()", { 99 | df <- tibble(x = integer(), y = list()) 100 | expect <- tibble(x = integer(), y = unspecified()) 101 | 102 | expect_identical(unchop(df, y), expect) 103 | expect_identical(unchop(df, y, keep_empty = TRUE), expect) 104 | }) 105 | 106 | test_that("unchopping a bare fully `NULL` list results in unspecified()", { 107 | df <- tibble(x = 1:2, y = list(NULL, NULL), z = list(NULL, NULL)) 108 | expect <- tibble(x = integer(), y = unspecified(), z = unspecified()) 109 | expect_identical(unchop(df, c(y, z)), expect) 110 | }) 111 | ``` 112 | 113 | Note that comments are different from [documentation](#sec-documentation) of functions, which should instead express the purpose of a piece of code, how it should be used, and how it behaves when used. 114 | 115 | ## Style 116 | 117 | Tidyteam R packages follow our [style guide](https://style.tidyverse.org/). 118 | Two packages support this style guide, [styler](https://styler.r-lib.org/), which allows you to restyle files or packages, and [lintr](https://lintr.r-lib.org/), which performs automated checks to ensure that you conform to the style guide. 119 | 120 | Admittedly, many of us don't use these tools religiously. 121 | The important thing is that if there is a point of contention about style in a PR, we can point to the style guide to resolve it. 122 | 123 | The author of the PR should not include major style changes combined with other changes. 124 | It makes it hard to see what is being changed in the PR and makes merges more complex. 125 | If the author wants to reformat the whole file, request that they send you just the reformatting as one PR, and then send another PR with their functional changes after that. 126 | 127 | ## Consistency 128 | 129 | In some cases, the style guide makes recommendations rather than declaring requirements. 130 | In these cases, it's a judgment call (typically by the reviewer / maintainer) whether the new code should be consistent with the recommendations or the surrounding code. 131 | 132 | Consistency is also heavily related to [naming](#sec-naming), and the reviewer should ensure that the author has chosen function names, argument names, and local variable names that are consistent with the rest of the codebase. 133 | Consistent naming makes code significantly easier to understand, because it lowers your cognitive burden if you know that, say, `loc` always stands for a positive integer value corresponding to a vector index. 134 | This is particularly helpful in R, since static typing isn't available to provide this information for you. 135 | 136 | ## Documentation {#sec-documentation} 137 | 138 | If a PR changes how users interact with the package, check to see that it also updates any associated documentation. 139 | This includes both R package documentation and updating the [pkgdown](https://pkgdown.r-lib.org/) reference index if a new function is added. 140 | 141 | It is generally also good practice to provide a news bullet in a `NEWS.md` file that is associated with the change. 142 | This is another place where providing the GitHub issue or PR number is useful, like: 143 | 144 | ``` markdown 145 | * `my_function()` now checks that `x` is a numeric value (#565). 146 | ``` 147 | 148 | For more on writing good news bullets, see the [style guide](https://style.tidyverse.org/news.html#bullets). 149 | 150 | If there isn't a GitHub issue that corresponds to your bug fix or feature, you can be caught in a chicken-and-egg scenario where you'd like a GitHub PR number to link to, but you haven't opened the PR yet. 151 | One way to resolve this is to: 152 | 153 | - Go ahead and open the PR without the commit changing the `NEWS.md` file. 154 | 155 | - Add the news bullet and link to the now open PR. 156 | 157 | - Push with a commit message like `NEWS bullet`. 158 | 159 | Occasionally it is useful to document *internal* functions that aren't seen by users. 160 | This can be useful for other developers to reference as they use common internal helpers. 161 | One example of this is `tidyr:::df_append()`, which includes this [internal function documentation](https://github.com/tidyverse/tidyr/blob/0764e65fad777b71aa2a81ffd5447d04a61f8d5e/R/append.R#L1) marked with `@noRd`: 162 | 163 | ``` r 164 | #' Append new columns (`y`) to an existing data frame (`x`) 165 | #' 166 | #' @details 167 | #' If columns are duplicated between `x` and `y`, then `y` columns are 168 | #' silently preferred. 169 | #' 170 | #' @param x A data frame. 171 | #' @param y A named list of columns to append. Each column must be the same size 172 | #' as `x`. 173 | #' @param after One of: 174 | #' - `NULL` to place `y` at the end. 175 | #' - A single column name from `x` to place `y` after. 176 | #' - A single integer position (including `0L`) to place `y` after. 177 | #' @param remove Whether or not to remove the column corresponding to `after` 178 | #' from `x`. 179 | #' 180 | #' @returns 181 | #' A bare data frame containing the columns from `x` and any appended columns 182 | #' from `y`. The type of `x` is not maintained. It is up to the caller to 183 | #' restore the type of `x` with `reconstruct_tibble()`. 184 | #' 185 | #' @noRd 186 | df_append <- function(x, y, after = NULL, remove = FALSE) { 187 | # ... 188 | } 189 | ``` 190 | 191 | ## Every line {#every-line} 192 | 193 | In general, you should look at every line of code that you have been assigned to review. 194 | Some things like data files, generated code, or large data structures you can scan over sometimes, but don't scan over a human-written function or block of code and assume that what's inside of it is okay. 195 | Obviously some code deserves more careful scrutiny than other code---that's a judgment call that you have to make---but you should at least be sure that you *understand* what all the code is doing. 196 | 197 | If reviewing every line is too hard and is slowing down the review, then you should let the author know and ask them to clarify it (possibly providing suggestions of your own, if possible). 198 | This also applies to code that you understand, but it took a long time for you to figure it out (or if you had to run it locally to understand it). 199 | If you can't understand the code, it is likely that other reviewers won't be able to either! 200 | Taking a moment to add a [comment](#sec-comments) requesting that the author clarify a confusing section of code will improve the overall quality of the package and help future developers. 201 | 202 | If you understand the code but you don't feel qualified to do some part of the review, it is perfectly acceptable (and preferred!) that you let the author know, and suggest that they request a review from another colleague who has more expertise in that area. 203 | 204 | ## Context 205 | 206 | It is often helpful to look at the PR in a broad context. 207 | Usually the code review tool on GitHub will only show you a few lines of code around the parts that are being changed. 208 | Sometimes you have to look at the whole file to be sure that the change actually makes sense. 209 | For example, you might see only four new lines being added, but when you look at the whole file, you see those four lines are in a 50-line helper that now really needs to be broken up into smaller functions. 210 | 211 | It's also useful to think about the PR in the context of the package as a whole. 212 | Is this PR improving the code health of the package or is it making the package more complex, less tested, etc.? 213 | PRs should *always* maintain or improve the overall health of the package. 214 | Most systems become complex through many small changes that add up, so it's important to prevent even small complexities in new changes if possible. 215 | 216 | Sometimes the timing of the PR isn't right. 217 | It is possible that the package may need restructuring before a PR can be accepted. 218 | You can generally recognize this when the PR has a large number of changes to parts of the package that are seemingly unrelated to the purpose of the PR itself. 219 | In those cases, it is often worth suggesting that the author submit a few smaller [focused](#sec-focused) PRs first that prepare the package for the larger PR. 220 | This reduces the complexity of the larger PR, making it easier to understand and faster to review. 221 | 222 | ## Compliments {#good-things} 223 | 224 | If you see something nice in the PR, tell the author, especially when they addressed one of your comments in a great way. 225 | Code reviews often just focus on mistakes, but they should offer encouragement and appreciation for good practices as well. 226 | It's sometimes even more valuable, in terms of mentoring, to tell an author what they did right than to tell them what they did wrong. 227 | -------------------------------------------------------------------------------- /reviewer/comments.qmd: -------------------------------------------------------------------------------- 1 | # Writing review comments {#sec-comments} 2 | 3 | ## Courtesy {#sec-courtesy} 4 | 5 | In general, it is important to be courteous and respectful while also being very clear and helpful to the author whose code you are reviewing. 6 | One way to do this is to be sure that you are always making comments about the *code* and never making comments about the *author*. 7 | You don't always have to follow this practice, but you should definitely use it when saying something that might otherwise be upsetting or contentious. 8 | For example: 9 | 10 | Bad: "Why did **you** use threads here when there's obviously no benefit to be gained from concurrency?" 11 | 12 | Good: "The concurrency model here is adding complexity to the system without any actual performance benefit that I can see. Because there's no performance benefit, it's best for this code to be single-threaded instead of using multiple threads." 13 | 14 | ## Explain why {#sec-why} 15 | 16 | One thing you'll notice about the "good" example from above is that it helps the author understand *why* you are making your comment. 17 | You don't always need to include this information in your review comments, but sometimes it's appropriate to give a bit more explanation around your intent, the best practice you're following (like the design or style guide), or how your suggestion improves code health. 18 | 19 | ## Giving guidance {#sec-guidance} 20 | 21 | **In general it is the author's responsibility to fix a PR, not the reviewer's.** You are not required to do detailed design of a solution or write code for the author. 22 | 23 | This doesn't mean the reviewer should be unhelpful. 24 | In general you should strike an appropriate balance between pointing out problems and providing direct guidance. 25 | Pointing out problems and letting the author make a decision often helps the author learn, resulting in higher quality PRs in the future. 26 | It can also result in a better solution, because the author is typically closer to the code than the reviewer is. 27 | 28 | One example of this is to simply point out a section of code that confused you, or which took a long time to understand (like with a *why* [code comment](#sec-code-comments)). 29 | This isn't a direct call to action, but prompts the author to take another look at the code in section to see if it can be further simplified or clarified. 30 | 31 | Another way to demonstrate an issue is to provide the author a [reprex](https://reprex.tidyverse.org/) that demonstrates that there is still a bug in their PR. 32 | The author can then decide how to resolve it. 33 | 34 | However, sometimes direct instructions, suggestions, or even code are more helpful. 35 | The primary goal of code review is to get the best PR possible. 36 | A secondary goal is improving the skills of PR authors so that they require less and less review over time. 37 | 38 | Remember that people learn from reinforcement of what they are doing well and not just what they could do better. 39 | If you see things you like in the PR, comment on those too! 40 | Examples: author cleaned up a messy algorithm, added exemplary test coverage, or you as the reviewer learned something from the PR. 41 | Just as with all comments, include why you liked something, further encouraging the author to continue good practices. 42 | 43 | ## GitHub suggestions 44 | 45 | For very small tweaks, like typos or additions to comments, GitHub provides a feature known as [*suggestions*](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#adding-line-comments-to-a-pull-request). 46 | As a reviewer, you can add suggestions using the workflow shown below, and the author can accept them directly in the GitHub UI and commit them into the PR (which they can then pull locally to get everything up to date if they have more work to do). 47 | 48 | ![](img/add-suggestion.png){fig-alt="Screenshot of GitHub UI for review comment box. The tooltip for the file-diff icon is open and reads \"Add a suggestion, \"." fig-align="center" width="900"} 49 | 50 | ![](img/write-suggestion.png){fig-alt="Screenshot of GitHub UI for commenting on a changed file for a pull request. The markup for making a suggestion is shown in the comment box: three backticks before the word suggestion begin the suggested code change, which is followed by three closing backticks." fig-align="center" width="900"} 51 | 52 | Note that if your suggestion touches documentation, then the author will have to merge, pull locally, and run `devtools::document()`. 53 | If you notice this situation, it is typically polite to mention to the author that they will need to `document()` locally after committing the suggestions, because that is particularly hard to remember. 54 | 55 | As a reviewer, you can add multiple suggestions and the author can *batch* multiple suggestions into a single commit. 56 | The author must first hit `Files changed` to switch to the viewer mode, at which point the suggestion will show up with an additional option - `Add suggestion to batch`. 57 | 58 | ![](img/add-to-batch.png){fig-alt="Screenshot of a GitHub interface for review comment with a suggestion. The tooltip over \"Add suggestion to batch\" button is open and reads \"Add this suggestion to a batch that can be applied as a single commit.\""} 59 | 60 | After the author has finished adding suggestions to the batch, they can `Commit suggestions` en masse using the button at the top of the screen. 61 | 62 | ![](img/commit-batch.png){fig-alt="Screenshot of GitHub interface for committing suggestions open. User can supply a title and description and select the \"Commit changes\" button to apply multiple changes."} 63 | 64 | ## Label comment severity {#sec-label-comment-severity} 65 | 66 | Consider labeling the severity of your comments, differentiating required changes from guidelines or suggestions. 67 | 68 | Here are some examples: 69 | 70 | > Nit: This is a minor thing. 71 | > 72 | > Optional (or Consider): I think this may be a good idea, but it's not strictly required. 73 | > 74 | > FYI: I don't expect you to do this in this PR, but you may find this interesting to think about for the future. 75 | 76 | This makes review intent explicit and helps authors prioritize the importance of various comments. 77 | It also helps avoid misunderstandings; for example, without comment labels, authors may interpret all comments as mandatory, even if some comments are merely intended to be informational or optional. 78 | 79 | ## Approve with comments {#sec-approve-with-comments} 80 | 81 | When you've finished leaving review comments, GitHub provides a number of options for finalizing your review. 82 | The most common kind of PR review within the tidyteam is known as "approved with comments." It involves leaving a few minor comments on a PR, while also hitting the `Approve` button on the GitHub review UI: 83 | 84 | ![](img/approve-with-comments.png){fig-alt="Screenshot of options in the GitHub review UI. Radio buttons for: Comment, Approve, and Request changes." fig-align="center" width="600"} 85 | 86 | This gives the PR author permission to merge the PR as soon as they have addressed the comments without needing to request another round of review. 87 | This is done when either: 88 | 89 | - The reviewer is confident that the author will appropriately address all the reviewer's remaining comments. 90 | - The remaining changes are minor and don't *have* to be done by the author. 91 | 92 | Approving with comments is largely about the experience of the PR author. 93 | [Close-knit collaborators](#sec-close-knit-collaboration) often approve each other's work after just one round of review, because both parties have significant experience working on the package and the PR is likely to be mostly correct from the beginning. 94 | For [understudies](#sec-the-understudy) and [external contributions](#sec-external-contributions) from new contributors, it is common for reviewers to use the `Comment` option instead, which is a signal to the author that they should request another round of review when they have finished addressing your comments. 95 | It is important to remember to be patient with new developers; they are going to need multiple rounds of detailed reviews early on, but putting in this extra effort up front tends to lead to faster PRs in the future as the author learns more about what is expected from their PRs. 96 | 97 | ## Requesting changes {#sec-request-changes} 98 | 99 | GitHub also includes a third option when submitting a review, `Request changes`. 100 | This is typically reserved for more drastic changes that absolutely require another round of review. 101 | This option is rare to see in [close-knit collaboration](#sec-close-knit-collaboration), but is somewhat common when the author is an [understudy](#sec-the-understudy) and with [external contributions](#sec-external-contributions), where the author likely doesn't have as much expertise as the reviewer. 102 | 103 | ## Finishing off an external contribution {#sec-finishing-off-an-external-contribution} 104 | 105 | Sometimes when we receive [external contributions](#sec-external-contributions), we decide that rather than leaving comments for the external contributor, it is more efficient for us as the reviewer (and usually package maintainer) to just finish off the PR and merge it for them. 106 | This typically happens when we come back to work on a package after letting it lie dormant for a few months, and we find that we have a few PRs from external contributors. 107 | Sometimes these PRs can sit for awhile while no one is actively working on the package, and it might not make much sense to leave comments for the PR author weeks after they sent the PR in. 108 | Using `usethis::pr_fetch()` and `usethis::pr_push()`, you can instead just take over the PR, fix any minor issues, and merge it in. 109 | Make sure that if you do this, you also thank the author for their contributions! 110 | 111 | It is also appropriate to inform a PR author that you'll finish off a PR if you have already gone through 1-2 rounds of PR review and there are still a few minor edits that need to be made before the PR is ready to be merged. 112 | -------------------------------------------------------------------------------- /reviewer/img/add-suggestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/reviewer/img/add-suggestion.png -------------------------------------------------------------------------------- /reviewer/img/add-to-batch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/reviewer/img/add-to-batch.png -------------------------------------------------------------------------------- /reviewer/img/approve-with-comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/reviewer/img/approve-with-comments.png -------------------------------------------------------------------------------- /reviewer/img/commit-batch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/reviewer/img/commit-batch.png -------------------------------------------------------------------------------- /reviewer/img/write-suggestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/code-review/7ce6c9e709197ffbafc79da84a0341f48976a5fe/reviewer/img/write-suggestion.png -------------------------------------------------------------------------------- /reviewer/navigate.qmd: -------------------------------------------------------------------------------- 1 | # Navigating a PR in review {#sec-navigate} 2 | 3 | ## Summary 4 | 5 | Now that you know [what to look for](#sec-aspects), what's the most efficient way to manage a review that's spread across multiple files? 6 | Here are three steps to tackle most code reviews: 7 | 8 | 1. Before looking at any code, ask yourself if the rationale behind the change make sense. 9 | 2. Look at the most important part of the change first. Is it well-designed overall? 10 | 3. Look at the rest of the PR in an appropriate sequence. 11 | 12 | ## Take a broad view of the change {#sec-broad} 13 | 14 | Before looking at any code, you should familiarize yourself with the GitHub issue / bug report that the PR is resolving, and read over the PR [description](#sec-descriptions) to better orient yourself. 15 | Does this change even make sense? 16 | You can save everyone time if the answer is *no* by stopping the review there and responding with an explanation of why the change isn't necessary. 17 | Ideally, when performing [close-knit collaboration](#sec-close-knit-collaboration), you will have discussed this ahead of time to avoid the author putting in unnecessary work (and feeling bad if their work is rejected). 18 | For [external contributions](#sec-external-contributions), occasionally you do have to reject PRs if they don't fit the overall [design](#sec-design) of the package. 19 | You should do this nicely, thanking them for their contribution, and requesting that next time they [open an issue first](#sec-issue-first-pr-second) and ask if you'd accept a PR for that issue, which would have saved them time if you had an opportunity to preemptively say no. 20 | 21 | For example, you might say: "Looks like you put some good work into this, thanks! However, we're actually going in the direction of removing the `foo()` helper that you're modifying here, and so we don't want to make any new modifications to it right now. In the future, it is best if you open an issue first to highlight the bug or feature and ask if we'd accept a pull request for it at this time." 22 | 23 | If you get more than a few PRs that represent changes you don't want to make, you should consider re-working your team's development process or the posted process for [external contributors](#sec-external-contributions) so that there is more communication before PRs are written. 24 | It's better to tell people "no" before they've done a ton of work that now has to be thrown away or drastically re-written. 25 | 26 | ## Examine the main parts of the PR 27 | 28 | Find the file or files that are the "main" part of this PR. 29 | Often, there is one file that has the largest number of logical changes. 30 | Ideally, the PR description will point you to this if there is any ambiguity. 31 | Look at these major parts first. 32 | This helps give context to all of the smaller parts of the PR, and generally accelerates doing the code review. 33 | If the PR is too large for you to figure out which parts are the major parts, ask the author to provide a [recommended reading order](#sec-reading-order), or ask them to [split up the PR into multiple focused PRs](#sec-focused). 34 | 35 | If you see some major design problems with this part of the PR, you should send those comments immediately, even if you don't have time to review the rest of the PR right now. 36 | In fact, reviewing the rest of the PR might be a waste of time, because if the design problems are significant enough, a lot of the other code under review is going to disappear and not matter anyway. 37 | 38 | This is also a great time to use `usethis::pr_fetch()` to pull the PR down locally and try it out. 39 | Chances are that the main part of the PR has to do with the main UI changes, so this is a nice time to check that the changes in the PR line up with the output you get from running the code locally. 40 | 41 | ## Look through the rest of the PR 42 | 43 | Once you've confirmed there are no major design problems with the PR as a whole, try to figure out a logical sequence to look through the files while also making sure you don't miss reviewing any file. 44 | Usually after you've looked through the major files, it's simplest to just go through each file in the order that the code review tool presents them to you. 45 | Sometimes it's also helpful to read the tests first before you read the main code, because then you have an idea of what the change is supposed to be doing. 46 | 47 | For R code, you can generally ignore the generated documentation files ending in `.Rd` while reviewing the PR. 48 | GitHub will collapse these by default. 49 | 50 | When you've finished your review, if you've checked out the PR locally you can remove it with `usethis::pr_forget()`. 51 | -------------------------------------------------------------------------------- /reviewer/purpose.qmd: -------------------------------------------------------------------------------- 1 | # The purpose of code review {#sec-purpose} 2 | 3 | The primary purpose of code review is to ensure that the overall "code health" of our packages is improving over time. 4 | In order to accomplish this, a series of trade-offs have to be balanced. 5 | 6 | First, developers must be able to make progress on their tasks. 7 | If a reviewer makes it very difficult for *any* change to go in, then authors are discouraged from making improvements in the future. 8 | 9 | On the other hand, it is the duty of the reviewer to make sure that each PR is of such a quality that the overall code health of the package is not decreasing as time goes on. 10 | This can be tricky, because codebases often degrade through small decreases in code health over time. 11 | This isn't necessarily anyone's fault. 12 | It is a natural process resulting from the accumulation of technical debt over time, which must continually be fought against. 13 | 14 | Additionally, a reviewer often has ownership and responsibility over the code they are reviewing. 15 | When the reviewer is also the maintainer of the software, keeping the codebase consistent and maintainable is an even higher priority, because the reviewer is the one that has to maintain the code going forward. 16 | 17 | With those tradeoffs in mind, we get the following rule as the standard we expect of code reviews: 18 | 19 | **In general, reviewers should favor approving a PR once it is in a state where it definitely improves the overall code health of the system being worked on, even if the PR isn't perfect.** 20 | 21 | A key point here is that there is no such thing as "perfect" code---there is only *better* code. 22 | Reviewers should not require the author to polish every tiny piece of a PR before granting approval. 23 | Rather, the reviewer should balance out the need to make forward progress compared to the importance of the changes they are suggesting. 24 | Instead of seeking perfection, what a reviewer should seek is *continuous improvement*. 25 | A PR that, as a whole, improves the maintainability, readability, and understandability of the package shouldn't be delayed for days or weeks because it isn't "perfect." 26 | 27 | Reviewers should *always* feel free to leave comments expressing that something could be better, but if it's not very important, note in the comment that this is just a point of polish that the author can choose to ignore. 28 | 29 | ## Mentoring {#sec-mentoring} 30 | 31 | At Posit, we believe that code review is an important part of on-boarding new developers. 32 | As a reviewer, you are encouraged to leave comments that help an author learn something new. 33 | Linking out to sections of the style guide, design guide, or this code review guide are ways to point to a concrete source of truth that the PR author can read to learn how to align their contributions with tidyteam standards. 34 | 35 | Pair programming is also an excellent way to perform a code review. 36 | One way to do this is to have the reviewer live-review a pull request, which often teaches the author what the reviewer looks for when they review PRs, and gives both parties a way to have an immediate discussion about any tricky design points. 37 | It is generally useful to document any insights from the pair programming session as a comment in the code review. 38 | -------------------------------------------------------------------------------- /reviewer/pushback.qmd: -------------------------------------------------------------------------------- 1 | # Handling pushback {#sec-pushback} 2 | 3 | Sometimes an author will push back on a code review. 4 | Either they will disagree with your suggestion or they will argue that you are being too strict in general. 5 | 6 | ## Who is right? {#sec-who-is-right} 7 | 8 | When an author disagrees with your suggestion, first take a moment to consider if they are correct. 9 | Often, they are closer to the code than you are, and so they may have more insights about that particular section. 10 | Does their argument make sense? 11 | Does it make sense from a code health perspective? 12 | If so, let them know that they are right and let the issue drop. 13 | 14 | However, authors are not always right. 15 | In this case, the reviewer should further explain why they believe that their suggestion is correct. 16 | A good explanation demonstrates both an understanding of the author's reply, and additional information about why the change is being requested. 17 | Good explanations are additionally reinforced by facts or external official resources like the design or style guide. 18 | 19 | In particular, when the reviewer believes their suggestion will avoid a drop in code health, then they should continue to advocate for the change if they believe the resulting code quality improvement justifies the additional work requested. 20 | On the other hand, if the PR already improves the overall health of the package, it isn't worth arguing every little stylistic point. 21 | **Code improvements often happen in small steps**. 22 | 23 | Sometimes it takes a few rounds of explaining a suggestion before it really sinks in. 24 | Just make sure to always stay [polite](#sec-courtesy) and let the author know that you *hear* what they're saying, you just don't *agree*. 25 | 26 | ## Upsetting authors {#upsetting_developers} 27 | 28 | Reviewers sometimes believe that the author will be upset if the reviewer insists on an improvement. 29 | Sometimes authors do become upset, but it is usually brief and they become grateful later that you helped them improve the quality of their code. 30 | Usually, if you are [polite](#sec-courtesy) in your comments, authors actually don't become upset at all, and the worry is just in the reviewer's mind. 31 | 32 | ## Cleaning it up later {#later} 33 | 34 | A common source of push back is that authors (understandably) want to get things done. 35 | They don't want to go through another round of review just to get this PR in. 36 | So they say they will clean something up in a later PR, and thus you should approve *this* PR now. 37 | Some developers are very good about this, and will immediately write a follow-up PR that fixes the issue. 38 | However, experience shows that as more time passes after an author writes the original PR, the less likely this clean up is to happen. 39 | In fact, usually unless the author does the clean up *immediately* after the present PR, it never happens. 40 | This isn't because the author is irresponsible, but because they have a lot of work to do and the cleanup gets lost or forgotten in the press of other work. 41 | Thus, it is usually best to insist that the author clean up their PR *now*, before the code is in the package and "done." 42 | 43 | Note that PR cleanup is different from the case where a PR exposes a bug that is technically unrelated to that PR. 44 | In the spirit of [keeping PRs small and focused](#sec-focused), in those cases it is best to open an [issue](#sec-issues) with a reproducible example demonstrating the issue, so that it is tracked and can be addressed after merging the current PR. 45 | 46 | ## Resolving conflicts {#sec-conflicts} 47 | 48 | In any conflict on a code review, the first step should always be for the author and reviewer to try to come to consensus, based on the contents of this guide. 49 | 50 | When coming to consensus becomes especially difficult, it can help to have a face-to-face meeting or a video conference, instead of just trying to resolve the conflict through code review comments. 51 | (If you do this, though, make sure to record the results of the discussion as a comment on the PR, for future readers.) 52 | 53 | If that doesn't resolve the situation, the next step is to escalate. 54 | Often the escalation path leads to having a third team member offer their own review of the review comments, or having a broader team discussion for particularly complicated issues. 55 | **Don't let a PR sit around because the author and the reviewer can't come to an agreement.** 56 | 57 | Remember to respect your colleagues even when you disagree. 58 | You both want to generate the best results possible, and just happen to have different beliefs about how to get there. 59 | -------------------------------------------------------------------------------- /reviewer/speed.qmd: -------------------------------------------------------------------------------- 1 | # Speed of reviews {#sec-speed} 2 | 3 | ## Why should code reviews be fast? {#why} 4 | 5 | The speed of an individual developer is important, but the speed at which a *team* of developers can produce high quality code is even more important. 6 | When code reviews are slow, several things happen: 7 | 8 | - **Authors start to protest the code review process.** If a reviewer only responds every few days, but requests major changes to the PR each time, that can be frustrating and difficult for the author. 9 | If the reviewer requests the *same* substantial changes (changes which really do improve code health), but responds *quickly* every time the author makes an update, the frustration tends to disappear. 10 | 11 | - **The velocity of the team as a whole is decreased.** Yes, the individual who doesn't respond quickly to the review gets other work done. 12 | However, new features and bug fixes for the rest of the team are delayed by days, weeks, or months as each PR waits for review. 13 | 14 | - **Code health can be impacted.** When reviews are slow, authors are actually incentivized to submit *fewer* PRs and to make each one *larger*. 15 | If you think that it is going to take days to get a single small change reviewed, you are going to be pressured to include *one more thing* in your PR just to avoid another multi-day review process. 16 | To ensure that each PR only improves the health of the package, code review must be fast enough that authors don't feel hamstrung by it. 17 | 18 | ## What do we mean by "fast"? 19 | 20 | Rather than suggesting average turnaround times for a PR review (such as 2-4 hours), we feel that it is more useful to categorize PRs under different [patterns of collaboration](#sec-patterns-of-collaboration). 21 | This recognizes the fact that each PR comes with its own context - a time sensitive bug fix that blocks another colleague is going to be reviewed and merged faster than a small typo in a dormant package that isn't actively being worked on. 22 | 23 | The most important thing is for the author and reviewer to have similar expectations around the amount of time a PR review may take. 24 | If there is confusion around this, the author and reviewer should discuss this with each other, especially if they are both Posit employees, to ensure that they are on the same page about which [pattern](#sec-patterns-of-collaboration) their style of collaboration falls under (keeping in mind that the patterns can be modified as needed). 25 | 26 | If you are feeling overwhelmed by the amount of PRs that you are in charge of, it is worth reviewing your PR process. 27 | If you have so many PRs to review that you can't get to your own work, then you might need to distribute the reviews across your team more evenly. 28 | Not speaking up when you are overwhelmed by PRs can in turn slow down an entire team! 29 | 30 | ## Speed vs interruption {#sec-interruption} 31 | 32 | There is one time where the consideration of personal velocity trumps team velocity. 33 | **If you are in the middle of a focused task, such as writing code, don't interrupt yourself to do a code review.** Research has shown that it can take a long time for a developer to get back into a smooth flow of development after being interrupted. 34 | So interrupting yourself while coding is actually *more* expensive to the team than making a PR author wait a bit for a code review. 35 | If you want to read more about this, Paul Graham's post on [Maker vs Manager](http://www.paulgraham.com/makersschedule.html) is very good. 36 | 37 | Instead, wait for a break point in your work before you respond to a request for review. 38 | This could be when your current coding task is completed, after lunch, returning from a meeting, coming back from the breakroom, etc. 39 | Most "deep work" sessions last around 2-3 hours, so batching your code reviews and handling multiple of them at once after a focused session often works well. 40 | 41 | ## Detailed review 42 | 43 | With all of this discussion about speed, it can be tempting to just do a surface level review and respond with "LGTM." Resist this temptation! 44 | The whole [purpose of code review](#sec-purpose) is to ensure that the code health of the package is improving, and you are unlikely to catch potential bugs with a surface level review. 45 | 46 | PRs vary greatly in their complexity and sufficiently carrying out the review can take a non-trivial amount of time and cognitive effort. 47 | In general: 48 | 49 | - **Small PRs** take anywhere from **5-15 minutes** and don't typically require checking the code out locally. 50 | This should be the majority of code reviews that tackle small bugs or documentation changes. 51 | Experienced reviewers should be able to "run" the code related to these changes in their heads. 52 | 53 | - **Medium PRs** take up to **30 minutes** and often involve using `usethis::pr_fetch()` and exploring the code locally. 54 | This typically includes PRs that implement new features or small UI changes. 55 | 56 | - **Large PRs** can take up to **1 hour**, and occasionally even longer. 57 | These should be very rare, and are reserved for large refactorings, especially if there are unavoidable behavior changes mixed in. 58 | Large PRs are typically undesirable because they slow the velocity of the entire team, so typically the reviewer should ask the author if the PR can be [split into multiple smaller focused PRs](#sec-focused). 59 | If you don't have time to review a large PR and it can't be made any smaller, provide some broad comments on the design of the PR, and consider requesting another colleague to take a second look. 60 | 61 | ## Cross time zone reviews {#sec-cross-time-zone} 62 | 63 | Dealing with time zone differences can be challenging, but when the author and reviewer are [working together closely](#sec-close-knit-collaboration) it is reasonable for the reviewer to ensure that they provide feedback before the author starts work the next day. 64 | If more rapid turnaround times are needed, then the author should communicate that with the reviewer and attempt to batch multiple PRs in parallel, or set aside a predetermined time during the day when both the author and reviewer are online for review. 65 | --------------------------------------------------------------------------------