In your role as lead developer and architect, it is your place to double check reviews and enforce code quality. In general, it's ok to have more than one reviewer on a PR. Ideally, nobody owns the code, and nobody owns the review; it's on you to cultivate this attitude.
Rather than viewing your reviews in conflict, your two reviews provide overlapping coverage. If one reviewer accepts and one requests changes, the change is not accepted.
Ask yourself how this got through review, and how it could be prevented. Is it a lack of clear policies? Is it a lack of automation (that is, people are being asked to do and review too much manually)? Is it a lack of time? Is it a lack of training/expertise?
Consider the people and their personalities, they are not interchangeable cogs. Some people come from very strict workplaces which squash their initiative and devalue anything but getting the minimum done; this has to be unlearned, and it's your job to communicate that it is both safe and desirable. Others might be junior developers and are struggling to just get the minimum to work and don't yet appreciate longer-term maintenance issues. Some people might get very prickly and take any critique personally; a back-and-forth in review comments might not be constructive, one-on-one time might work better. Others will be happy to receive the careful attention and teaching from their lead.
If you see the same patterns of unacceptable changes over and over again, you may wish to have a training session with the team.
Do
- Be factual.
- Critique the code, not the people.
- Offer concrete issues.
- Point to existing policy.
- Automate as much as possible.
- Be willing formally Request Changes.
- Be willing to do some work yourself, at least this time.
- Suggest (not require) small improvements.
- Use this as a teachable moment.
- Offer solutions.
- Consider the return on investment.
- Make it as easy as possible to implement your changes.
- Keep yourself open to differing points of view.
Don't
- Make up new policy on the spot, nobody likes a moving target.
- Request changes based on subjective values.
- Require small improvements.
- Set up a conflict between meeting a deadline and making minor improvements.
- Rely on your position of authority.
- Be afraid of conflict.
- Burn goodwill over small, subjective changes.
Edge cases were not considered
This one is the most straight-forward; there's a bug. Offer a clear edge case which breaks the code, possibly with a test. If it's acceptable to commit to someone else's PR (my preference), and the problem is complicated, you may commit and push a test and fix. Then people can review and learn from your work.
You may wish to develop a list of patterns of edge cases that are easily forgotten. For example, slurping in all the data and then processing it risks consuming too much memory; always read and process in chunks.
You may wish to develop utility classes and functions to do common, but subtly complex, tasks.
There are inconsistent changes: Not all code received the proposed changes - potentially leading to inconsistent program behavior and bugs.
For example, they were tasked to fix a bug and did it for A, B, and D, but missed C and E (possibly because only A, B, and D were explicitly mentioned in the instructions). Or they were asked to change the formatting of, for example, how money is displayed but only did it in a few places and missed others.
Similar to missing edge cases, these are demonstrable problems. You can point out the additional places where the fix needs to be applied, or the feature needs to be made consistent.
This can be a teachable moment. You can show your mindset when approaching a task. Rather than fixing only the obvious bug, or only doing the letter of the task, you can show that it's not only ok but expected to go beyond what's in the task and hunt down more places the bug might live, and that consistency of presentation and behavior is valued.
Depending on the people involved, you may wish to do practically all the work to demonstrate how to do the work. Or you may simply give them a sketch and encourage their own initiative, "here's a spot that also has the bug, look for others that are similar".
You may do work to demonstrate better patterns and acceptable initiative. For example, if formatting code is duplicated and scattered around, show them how to consolidate the code into a better pattern, and also demonstrate that this sort of work is not only acceptable, but encouraged.
The change does not achieve the goal of the overarching business requirement driving this work
If this is a high priority requirement, request changes; the PR should not be merged.
Was this made clear in the instructions? If yes, offer a scenario they need to cover (preferably one that can be turned into an acceptance test) and request changes.
If no, make a new issue with the new instructions and mention it in a comment on the PR. The author may consider incorporating the issue into their PR, or they may do it separately. If you wrote the instructions, take responsibility; maybe even pick up the additional work yourself.
If this is a common problem, you may need to consider how instructions are being written. Are issues too large? Are the instructions ambiguous? Are the instructions missing requirements? Is the author not comprehending the instructions?
Changes / Additions are not designed in a maintainable way
The code works, but there might be maintenance issues in the future. They can get subjective; you don't want to be making seemingly arbitrary decisions about other people's code. You also don't want to be seen to slowing people down on trivial changes when they're working on a deadline. If it is within your power, you may reassure the author they will be given extra time.
If there's an existing policy they are violating, point to it and request changes. If there's no existing policy, allow this PR through and discuss a policy change with the team.
As much as possible, use static analysis tools to enforce and auto-correct these maintenance rules. This gives a concrete and tangible set of rules, a concrete way to change them, and it makes it as easy as possible to follow them; it turns the subjective into the objective. If there is no rule for this, open a new PR to add one and discuss the policy change with your team.
You can use curiosity. "This looks like it's doing the same thing as , could they be turned into one method?" You might find out they're actually different. Or "I'm having trouble understanding what this block of code does, could you extract it into its own method? Then it'll also be easier to unit test."
Consider using it as a teachable moment. Often when people are unfamiliar with a language they will write more than they have to. You can leave a comment such as "here's a better way to implement that" and explain why it's better. For example, if I saw code like this:
sum = 0
array.each { |item| sum += item }
I might comment they can use sum = array.sum
; it's more concise, easier to understand at a glance, less bug prone, and faster. If they don't now the language well, I might link to the appropriate documentation. If your review system supports it, submit this as a patch so it's minimal work to accept the change. I would not block the PR over this.
Often I will notice ways in which code can be refactored to make it more maintainable. Sometimes it is worth requesting this change if it is egregious, for example, if there is code duplication. For minor violations or complicated refactorings, consider writing a follow up issue; the author can decide to incorporate the refactorings into their PR, or they can open a new PR.
I like having "refactor" or "cleanup" or "follow up" or "style" tags for issues and set aside some time at the end of a working period once deadline pressure is off to take care of them. You can plan some time at the end of a working period to do these cleanup and minor bug fixes.