Table of Contents
Engineering Knowledge Learned from PR Reviews
Overview
This document summarizes practical engineering knowledge learned from real PR reviews. The focus is how reviewers think, why comments matter, and how to apply the lessons in future development.
Goal:
Write code that passes review smoothly
Design systems that scale and are maintainable
Think like a senior engineer, not just an implementer
1. Clean Architecture: Layer Responsibility
Knowledge
Each layer has a single responsibility. Business logic must not depend on delivery mechanisms (HTTP, gRPC, protobuf).
❌ Bad Example
Usecase handling HTTP concerns:
if err != nil { return makeErrorResponse(http.StatusBadRequest, "invalid request") }
✅ Good Example
Usecase returns domain error only:
return errors.WithStack(ErrInvalidRequest)
Handler converts error to HTTP response:
if err != nil { return http.StatusBadRequest, err.Error() }
Why Reviewers Care
Usecase should work for HTTP, gRPC, batch jobs
Easier testing and reuse
Clear separation of concerns
How to Apply
Ask before coding:
“If this was no longer HTTP, would this code still belong here?”
2. Do Not Leak Protobuf / DTO into Business Logic
Knowledge
Transport formats change frequently. Domain and usecase must remain stable.
❌ Bad Example
func Execute(req *pb.GetCouponRequest) { if req.Mode == pb.Mode_LIST { ... } }
✅ Good Example
Domain model:
type CouponQuery struct { Mode CouponMode }
Usecase:
func Execute(q CouponQuery) ([]Coupon, error) { ... }
Handler mapping:
q := CouponQuery{ Mode: mapProtoMode(req.Mode), }
Why Reviewers Care
Protobuf changes should not break business logic
Domain models should be framework-agnostic
How to Apply
Rule of thumb:
If pb. appears in usecase → wrong layer
3. Error Handling Is About Observability
Knowledge
Errors are signals, not just messages.
❌ Bad Example
return fmt.Errorf("invalid request")
✅ Good Example
var ErrInvalidRequest = errors.New("invalid request") return errors.WithStack(ErrInvalidRequest)
Why Reviewers Care
Stack traces in logs
Easier root-cause analysis
Error comparison using errors.Is
How to Apply
Always consider:
Can this error be debugged in production?
4. Validation Must Stop at the Boundary
Knowledge
Invalid input should never reach usecase.
❌ Bad Example
if req.Mode != "list" && req.Mode != "history" { return ErrInvalidRequest }
✅ Good Example
Declarative validation:
type Request struct { Mode string validate:"oneof=list history" }
Why Reviewers Care
Cleaner business logic
Validation rules are readable and centralized
How to Apply
Perform validation in:
Handler
Validator layer
Value Object constructors
5. Domain Models Should Represent Meaning
Knowledge
Domain models represent concepts, not API fields.
❌ Bad Example
CouponID CouponCode CouponName
✅ Good Example
ID Code Name
Why Reviewers Care
Less noise
Easier reasoning
Domain-first design
How to Apply
Ask:
“Would this name make sense without protobuf?”
6. Pointer vs Value Must Be Intentional
Knowledge
Pointers imply mutability and shared state.
❌ Bad Example
[]*Coupon
Used everywhere without reason.
✅ Good Example
[]Coupon
Use pointers only when:
Object is large
Mutation is required
Identity matters
Why Reviewers Care
Avoid side effects
Clear API intent
How to Apply
Be able to explain:
“Why is this a pointer?”
7. External Systems Are Hostile
Knowledge
Never trust external APIs.
❌ Bad Example
resp, _ := client.Do(req) return resp.Body.Data
✅ Good Example
if resp.StatusCode != http.StatusOK { return ErrExternalService } if resp.Body == nil { return ErrInvalidResponse }
Why Reviewers Care
External services fail
Partial responses happen
How to Apply
Always validate:
Status code
Required fields
Nil / empty values
8. Backward Compatibility Over Cleanliness
Knowledge
APIs are long-term contracts.
Example
Proto field numbers are not sequential, but not changed to avoid breaking clients.
Why Reviewers Care
Breaking changes affect other teams
Stability > aesthetics
How to Apply
Before changing:
Schema
API
Contract Ask:
“Who will this break?”
9. Refactoring During Review Is a Positive Signal
Knowledge
Responding to reviews with refactoring shows maturity.
Bad Reaction
“Out of scope”
“Existing code does this”
Good Reaction
Refactor
Explain trade-offs
Improve architecture
Why Reviewers Care
Ownership mindset
Long-term thinking
10. Tests Are Negotiable, Bugs Are Not
Knowledge
Not all feedback blocks merge.
Priority
Bugs → Must fix
Architecture → Must fix
Test refactor → Follow-up OK
Why Reviewers Care
Shipping value matters
Balance quality and speed
Final Meta-Lessons
After these reviews, you learned how to:
Think like a reviewer
Predict feedback before submitting PRs
Design maintainable systems
Communicate design intent clearly
