====== 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