The one thing I always want to be careful of with questions is to make sure they don't come off condescending. One example of an anti-pattern with questions is to ask
"Why did you do <obvious mistake>?"
In that case, clearly they didn't mean to make the obvious mistake. Asking the question implies that they did it intentionally, when surely what actually happened is they overlooked the error. Instead, I just point out the mistake. "I think you forgot to check for null here." comes across way better than "Why didn't you check for null here?"
The general principle I find useful is to try to say exactly what you mean. When you spot an obvious mistake, there's not really a question to be asked. You are trying to note the mistake, so just use a statement to note the mistake. When it's less obvious, or it's a judgment call, a question is great, because that's exactly what you're trying to do: ask whether another approach might be better.
You got me. I thought it was 2004. Thanks for correcting me, now I know it's actually 2019. Pretty shocking that I'm actually 15 years older than I thought! I guess I'm a huge moron!
"It looks like this <code block/method/module> will cause XYZ to happen. Is that what you intended?"
Simple example (Python):
def foo(x):
print(x.lower())
"If x is None this will raise an exception. Is that an
expected outcome, and/or can we assume x is always non-None?"One thing I like about this approach is that it first describes your understanding of a particular code section, which helps illuminate any assumptions we might hold (and which might be incorrect). This is a good practice generally, IMHO, with or without the followup question. A guy I used to work with would make non-judgemental comments that just summarized his understanding of what was happening in the code, and it was helpful because when the comment was off, it illustrated a way in which the code was perhaps unclear or incorrect.
Of all the variants proposed in this topic, this is the only one that puts any burden whatsoever on the speaker in the first place (in a good way).
Demonstrating that you've made an actual attempt to understand what your colleague is doing is far more genuine and likely to be received as such. Unlike the mealy-mouthed carefully-phrased examples that just communicate "I care more office politics than actually ensuring this code is high quality," your approach hits a trifecta of signaling respect, actually identifying the concerning behavior of the code, and inviting all the types of productive responses:
You allow the person to graciously admit an error without particularly losing face (they can simply say "nice catch, thank you" and fix it themselves). You provide an inroad to correcting your OWN potentially false assumption -- very helpful, for example, when a junior dev whose work is being reviewed is actually in the right, but politically might be unable to demonstrate that. And your question about the mechanics opens the door to a collaborative discussion in which both sides contribute to the final codebase.
I think that's a high load question (in terms of time and energy required to answer). It's best used sparingly. Instead point out a specific problem and supply an alternative that does not have that problem.
BUT, if you are going to do that, please do it in your first pass in review, don't do it in your subsequent pass after I've already responded to your feedback. This is just inconsiderate of my time.
Instead a proper comment is "This breaks if blah happens and foo is null" at which point the submitter just fixes the bug. It achieves a) by being a neutral statement, b) naturally by making the submitter rethink their assumptions without having to apologize for that and if the reviewer was in fact wrong and the comment is incorrect, c) would happen due to the submitter explaining why this situation can't happen (and possibly adding a code comment to that effect).
E.g. "Add a comment about foo bar?"
The only real downside I run into is that sometimes the questions get answered too earnestly, and I end up with the person on the other end helpfully trying to educate me rather than engaging the question or issue directly. In every case, though, I could and should have phrased myself more clearly. It's a constant learning process, but the system works.
This approach has helped me because
a) It feels less aggressive (more like you're working through it together),
b) It prods them into thinking through what they missed, and
c) It prods for information that I might be missing. I might actually discover that I'm wrong and the other person considered more information.