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