Skriven av ledningen för apputvecklingen.
Code reviews¶
Reviews are a shared responsibility. The goal is not to find fault. It is to improve the code together and catch problems before they reach the main branch. Be specific, be constructive, and distinguish between blockers and suggestions. Code reviews are also important for sharing knowledge.
Who should review my code? It might be tempting to ask a group leader or the tech lead to review, but we want you to find another developer to the largest extent possible. This is to encourage knowledge sharing, so that you aren't the only one who has seen that piece of code.
You might be asked to review a pull request. This can seem daunting at first if you don’t have a lot of experience. But don’t worry, it is not the end of the world if some bad code gets merged, we can always fix it later. It is usually when something goes wrong that you learn the most! Read on to see how a review should be carried out.
Some tips when reviewing a pull request:
- Sit next to the person you are reviewing if possible! This way you can ask them any questions that come up directly. Sometimes just talking about the code helps you find any mistakes
- Check out the code and test it locally. In the end the most important part is that it works as it should.
- Make sure that the code follows our Code style.
What to Look For¶
Correctness
- Does the code do what the PR description claims it does? (Test it!)
- Are edge cases handled (empty inputs, nulls, boundary values)?
- Could the change introduce a regression elsewhere?
Logic and Control Flow
- Are there off-by-one errors or incorrect loop bounds?
- Are error paths handled correctly, or is an error silently swallowed?
Safety and Robustness
- Are
unwrap()/expect()or similar exit calls justified? Could they panic in production? - Is input from external sources validated before use?
- Are resource lifetimes and ownership correct?
Tests
- Are new behaviors covered by tests?
- Do existing tests still pass, and do they still test what they claim to test?
- Are tests testing behavior, not implementation details?
Readability
- Can a future reader understand the code without the PR description?
- Are names clear and consistent with the surrounding code?
- Is there dead code or commented-out blocks that should be removed?
Commit hygiene
- Does the commit history follow the conventions in this document?
- Are commits atomic; each one a single logical change?
- Do any commits carry a
Co-Authored-ByAI trailer? If so, apply extra scrutiny to that code. Pay closer attention to edge cases, logic, and whether the implementation actually matches the intent. AI agents tend to things in many places for a single fix. Make sure the structure of the change makes sense.
Leaving Review Comments¶
All comments must be resolved before a PR may merge. "Resolved" means the author has either addressed the comment or explicitly replied explaining why the change was not made; not simply dismissed.
Please preface comments with one of the following so that the author knows the severity of your comment.
- Blocker: a correctness issue, safety problem, or clear violation of project conventions. Must be fixed.
- Suggestion: an improvement worth considering. The author may resolve it by
making the change or by replying with a reasoned explanation of why the
current approach is preferred. Prefix with
nit:for minor style observations. - Question: you need more context before you can assess the code. Must be answered before merge. Please phrase this as a genuine question ("Why is X done this way rather than Y?" or "How exactly does Z work?"). This kind of observation may warrent further documentation (if the reviewer is asking the question, it's likely to be asked about again when reading the code).
Approve when you are confident the code is correct and consistent with the codebase, even if you have left suggestions (resolve if not critical). Do not withhold approval over personal style preferences.
Example Pull Request and Review¶
Below is an example of a PR and the kind of review it should receive.
PR: [API] (Update): Add support for filtering activities by date range¶
Description
Add a date_range query parameter to the activity listing endpoint, allowing
callers to restrict results to a given start/end date. Integrates with the
existing filter chain.
Diff (simplified)
// api/src/routes/activities.rs
pub fn filter_by_date_range(
mut self,
start: Option<NaiveDate>,
end: Option<NaiveDate>,
) -> Self {
if start.is_none() && end.is_none() {
return self;
}
self.filters.push(Filter::DateRange { start, end });
self
}
// api/src/filters.rs
Filter::DateRange { start, end } => {
if let Some(s) = start {
query = query.filter(date_col.ge(s));
}
if let Some(e) = end {
query = query.filter(date_col.le(e));
}
}
// api/tests/activity_tests.rs
#[test]
fn test_date_range_filter() {
let start = NaiveDate::from_ymd(2024, 1, 1);
let end = NaiveDate::from_ymd(2024, 12, 31);
let q = QueryBuilder::new()
.filter_by_date_range(Some(start), Some(end))
.build();
assert!(q.to_sql().contains("date_col"));
}
Review¶
[Blocker] (executor.rs) Inverted date range not rejected
Nothing prevents the caller from passing start > end. This would produce a
logically impossible filter that silently returns zero rows. Either document
that this is the caller's responsibility, or add a guard:
Filter::DateRange { start, end } => {
if let (Some(s), Some(e)) = (start, end) {
debug_assert!(s <= e, "start must not be after end");
}
// ...
}
If this should be a hard error rather than an assertion, return a Result from
filter_by_date_range instead.
[Blocker] (query_tests.rs) Test does not verify filtering behaviour
The test checks that "date_col" appears somewhere in the SQL string. This is
testing an implementation detail, not the behaviour. A refactor that renames the
column would break the test without breaking the feature.
Consider testing with an in-memory database and asserting on which rows are
returned, or at minimum assert that the SQL contains the expected >= and <=
predicates with the correct date values.
[Suggestion] (routes/activities.rs) Consider a dedicated DateRange type
Using two Option fields means callers can easily pass (None, None) by
accident, which the early return silently discards. A named type makes the
intent clearer and prevents the degenerate case at the type level:
pub struct DateRange {
pub start: NaiveDate,
pub end: NaiveDate,
}
pub fn filter_by_date_range(mut self, range: Option<DateRange>) -> Self { ... }
[Suggestion nit] (query_tests.rs) from_ymd is deprecated
NaiveDate::from_ymd was deprecated in chrono 0.4.23. Use
from_ymd_opt(...).unwrap() instead?
Author Response¶
Re: [Blocker] Inverted date range not rejected
Good catch. I've added a guard in filter_by_date_range that returns an Err
when start > end, and updated the callers to propagate the error. A hard error
feels right here since an inverted range is almost certainly a bug on the
caller's side, not an intended edge case.
Re: [Blocker] Test does not verify filtering behaviour
Agreed, the test was effectively useless. Replaced it with an integration test that seeds an in-memory SQLite database with three activities (before, within, and after the range) and asserts that only the in-range row is returned. The SQL string assertion is gone.
Re: [Suggestion] Consider a dedicated DateRange type
I've introduced the DateRange struct as suggested. It also gives us a natural
place to put the start <= end invariant check, so I moved the validation there
instead of inside the filter chain. The signature is now
filter_by_date_range(mut self, range: Option<DateRange>) -> Result<Self, FilterError>.
Re: [Suggestion nit] from_ymd is deprecated
Fixed, switched to from_ymd_opt(...).unwrap() throughout the test file.
Approval and Merge¶
After a PR has been approved, the author of the PR is responsible for merging it, provided they have sufficient permissions. Use rebase. Also update the examples with links to your code if it contains patterns useful elsewhere!
[Approved]
All blockers addressed. Resolving nit suggestion that you can take or leave on a follow-up.
PR rebased into main with the commit (a PR could have several commits though!):