กลับไปที่บทความ
Engineering Git Code Review Team Culture DevOps

Code Review และ Commit Hygiene ที่ช่วยได้จริง

พลากร วรมงคล
7 มกราคม 2569 11 นาที

“Conventional Commits, PR ขนาดเล็ก, วัฒนธรรมการ Review และ PR Templates ที่คนจะกรอกจริง — วิธี Review โค้ดเพื่อให้ Developer คนต่อไป (รวมถึงตัวคุณในอนาคต) ยังสามารถทำงานได้เร็ว”

Teams ส่วนใหญ่ไม่ได้มีปัญหาเรื่อง code review พวกเขามีปัญหาเรื่องขนาดของ PR ปัญหาเรื่อง commit message และปัญหาวัฒนธรรมการเงียบ ที่สวมเสื้อโค้ทคลุมว่าเป็นปัญหา code review แก้ที่ Input แล้ว review จะถูกลง ปล่อยทิ้งไว้ก็ไม่มี Process ใดช่วยคุณได้

บทความนี้คือเวอร์ชันที่มีจุดยืนชัดเจน ไม่มีพิธีรีตอง: เป็นเพียงไม่กี่นิสัยที่ทำให้การ review มีประโยชน์จริงต่อ reviewer ต่อผู้เขียน และต่อ Engineer ผู้โชคร้ายที่ต้องอ่าน git blame ในอีกสองปีข้างหน้า

TL;DR

  • โค้ดถูกอ่านมากกว่าถูกเขียนหลายเท่า — review คือจุดที่คุณจ่าย (หรือปฏิเสธที่จะจ่าย) ดอกเบี้ยของต้นทุนการอ่านนั้น
  • ใช้ Conventional Commits ให้ subject บอกว่า อะไร และ body บอกว่า ทำไม
  • ทำ PR ให้อยู่ราว 200 บรรทัด โดยมีเพดานนิ่มอยู่ที่ราว 400 — แตกออกด้วย stacked PR หรือ refactor เตรียมการ
  • กำหนด review-response SLO เป็นชั่วโมง และทำให้การทวงซ้ำเป็นเรื่องที่ทำได้ในทางวัฒนธรรม
  • ใส่ Prefix ให้ comment ว่า nit:, question:, suggestion: หรือ blocking: เพื่อขจัดความกำกวม
  • ใช้ PR template สั้น ๆ ที่มีหมวด Risk และให้ CI จัดการ lint, types, secrets และ commit-lint
  • Review ที่โค้ด ไม่ใช่ที่ผู้เขียน — และอย่าปล่อยให้ nit ที่ formatter แก้ได้กิน review time ของมนุษย์

ทำไมหัวข้อนี้ถึงคุ้มค่าที่จะลงทุน

โค้ดถูกอ่านบ่อยกว่าถูกเขียนมาก ทุกการเปลี่ยนแปลงที่ team ส่งออกไปจะถูกเขียนเพียงครั้งเดียว แล้วถูกอ่าน — ใน review, ใน git blame, ใน incident post-mortem, ในการ onboarding, ในการ refactor — ตราบใดที่โค้ดยังมีชีวิตอยู่ ต้นทุนส่วนใหญ่ของ codebase อยู่ตรงการอ่านเหล่านั้น

Code review คือจุดที่คุณจ่ายดอกเบี้ยของต้นทุนการอ่านนั้น หรือปฏิเสธที่จะจ่าย Review pipeline ที่ดีจะเปลี่ยนงานในวันนี้ให้กลายเป็น History ที่อ่านได้ ส่วน pipeline ที่แย่จะเปลี่ยนมันให้กลายเป็นการขุดค้นทางโบราณคดี

โพสต์นี้มีจุดยืนชัดเจนและตั้งอยู่บนพื้นฐานความเป็นจริง ไม่ใช่เรื่องของพิธีกรรม แต่เป็นเรื่องของไม่กี่นิสัยที่ทำให้ review มีประโยชน์จริง — ต่อ reviewer ต่อผู้เขียน และต่อคนที่ต้องไปแก้ Production bug ในอีกสองปีข้างหน้า

สาม Failure Mode ของการทำ PR Review

Teams ส่วนใหญ่ที่ไม่พอใจกับการ review มักติดอยู่ในรูปแบบหนึ่งในสามแบบนี้ แต่ละแบบมีอาการเฉพาะตัว และมีวิธีแก้ที่รู้กันอยู่แล้ว

ใหญ่เกินไป PR ลงมาพร้อม diff 1,200 บรรทัดกระจายอยู่ใน 40 ไฟล์ Reviewers อ่านผ่าน ๆ กด approve แล้วก็ภาวนา อาการคือ LGTM แบบประทับตรายาง และ test suite ที่บางครั้งจับ regression ได้ บ่อยครั้งที่จับไม่ได้ วิธีแก้ไม่ใช่ความอดทนที่เข้มแข็งกว่าเดิมจาก reviewer — แต่คือการมีเพดานขนาด PR ที่ team ตกลงและบังคับใช้ร่วมกัน

ช้าเกินไป PR ค้างอยู่ใน review หลายวัน ผู้เขียนต้องสลับ context, rebase, push ใหม่, อธิบายใหม่ อาการคือ branch เก่าค้าง, merge conflict และ Morale ที่ค่อย ๆ เลือดไหลออก วิธีแก้คือ review-response SLO ที่วัดเป็นชั่วโมง ไม่ใช่วัน และการให้สิทธิ์ทางวัฒนธรรมในการทวงซ้ำ

หละหลวมเกินไป Review ประกอบด้วยคำว่า “nice!” และ Rocket emoji สามตัว ข้อบกพร่องหลุดออกไป อาการคือจำนวน escaped bug ที่เพิ่มขึ้น และตำนานที่งอกใหม่ว่า “อย่าไปแตะไฟล์นั้น” วิธีแก้ไม่ใช่ reviewer ที่ดุขึ้น — แต่คือ checklist ที่ตกลงร่วมกันว่าทุก review ต้องตรวจอะไรจริง ๆ และต้องเขียนไว้

ส่วนที่เหลือของบทความนี้คือสิ่งที่ควรใส่ลงในแต่ละช่อง

Commit Hygiene

Commit คือหน่วยของคำอธิบาย ถ้าเพื่อนร่วมทีมรัน git log --oneline แล้วบอกไม่ได้ว่าเกิดอะไรขึ้น commit นั้นล้มเหลว — ไม่ว่าโค้ดจะทำงานได้หรือไม่

Conventional Commits

Conventional Commits คือไวยากรณ์ง่าย ๆ สำหรับ subject line:

<type>(<scope>): <short summary>

<body>

<footer>

Type ที่คุ้มค่าที่จะใช้:

  • feat — feature ใหม่ที่ผู้ใช้เห็นได้
  • fix — การแก้บั๊ก
  • refactor — พฤติกรรมเหมือนเดิม โครงสร้างดีขึ้น
  • chore — tooling, dependencies, งานบ้านน่าเบื่อ
  • docs — เอกสารอย่างเดียว
  • test — เพิ่มหรือแก้ test อย่างเดียว
  • perf — ปรับปรุง performance โดยไม่เปลี่ยนพฤติกรรม

บางครั้งคุณจะเห็น build, ci, style, revert ใช้ได้ถ้า team เห็นว่ามีประโยชน์ อย่ามาเถียงกันเรื่องนี้

Scope เป็น optional และระบุพื้นที่ของโค้ด: feat(auth): ..., fix(billing): ... Scope คุ้มค่าตั้งแต่วินาทีที่มีคนแตะ repo มากกว่าหนึ่งคน เพราะมันทำให้ git log --grep และ release notes ใช้นำทางได้จริง

Subject กับ Body: ที่ที่ “ทำไม” อยู่

Subject ตอบคำถามว่า อะไร Body ตอบคำถามว่า ทำไม Commit ที่ body เขียนว่า “fixes the bug” คือ commit ที่ไม่มี body เพราะคุณบอกไปแล้วใน subject

Body คือที่ที่คุณเขียนถึง:

  • ทำไมจึงเลือกการเปลี่ยนแปลงนี้แทนอีกสามวิธีที่คุณพิจารณา
  • คุณลองอะไรไปก่อนหน้านี้แล้วไม่ได้ผล
  • ข้อจำกัดที่ไม่ชัดเจนซึ่งผู้อ่านควรรู้ (เช่น “column นี้ต้องคง nullable ไว้จนกว่า backfill ใน #482 จะเสร็จ”)
  • ลิงก์ไปยัง ticket, incident หรือ design doc

ในอีกหกเดือน body คือสิ่งที่จะช่วยกอบกู้คุณจากตัวคุณเอง

กฎ Atomic Commit

หนึ่งการเปลี่ยนแปลงเชิงตรรกะต่อหนึ่ง commit ถ้าคุณต้องเขียนคำว่า “and” ใน subject line แสดงว่าคุณมีสอง commit

Refactor ที่เปิดทางให้ feature คือสอง commit: refactor ก่อน (พฤติกรรมเหมือนเดิม, test ผ่าน) แล้วค่อย feature ทับลงไป (test เปลี่ยน) Reviewers จะอ่าน refactor ในฐานะการจัดเรียงล้วน ๆ และอ่าน feature ในฐานะพฤติกรรมใหม่ล้วน ๆ ทั้งสองอย่างเข้าใจง่ายกว่าตอนที่อยู่แยกกัน เมื่อเทียบกับการยัดรวมเข้าด้วยกัน

นี่ยังหมายความว่าแต่ละ commit สามารถ revert ได้อย่างปลอดภัย ถ้า feature ทำให้ Production พัง ก็ git revert commit ของ feature — ไม่ใช่ refactor ที่อยู่ข้างใต้

เมื่อไรควร Squash และเมื่อไรไม่ควร

Squash เมื่อ commit ระหว่างทางเป็นแค่เสียงรบกวน: “WIP”, “fix typo”, “respond to review”, “revert last thing” สิ่งเหล่านี้คือร่องรอยของการทำงาน ไม่ใช่ของการคิด History ที่ merge เข้าไปควรอ่านได้เหมือนการคิด ไม่ใช่เหมือนการทำงาน

อย่า squash เมื่อ commit ระหว่างทางแต่ละตัวเล่าเรื่องราวจริง — เช่น pattern refactor-แล้ว-feature ข้างต้น หรือการ migrate หลายขั้นตอนที่แต่ละขั้นมีความหมายในตัวเอง การ squash สิ่งเหล่านั้นคือการทำลายข้อมูลที่ผู้อ่านในอนาคตจำเป็นต้องใช้

หลักทั่วไป: ถ้าคุณเขียน Conventional-Commit subject เดี่ยว ๆ ที่สะอาดและอธิบาย PR ทั้งก้อนได้ ก็ squash ถ้าเขียนไม่ได้ ก็อย่า squash

Commit ที่ดี, Commit ที่แย่

Commit ที่แย่:

fixed stuff

Commit ที่ดี:

fix(billing): treat 402 from Stripe as retryable, not fatal

We were bubbling 402 "card declined on idempotency retry" up as a
payment failure, which double-charged ~3% of customers who clicked
"pay" twice within the 5s window. Stripe returns 402 with a
`previous_response` field in that case and the original charge is
the source of truth.

This change inspects `previous_response` and short-circuits to the
original receipt when present. Added a contract test against
Stripe's test-mode idempotency replay.

Refs: INC-2214

เวอร์ชันแย่ไม่ได้บอกอะไรกับผู้อ่านคนต่อไปเลย เวอร์ชันดีบอกพวกเขาว่า: อะไรเปลี่ยน, ทำไม, มันป้องกันอะไร และจะ verify อย่างไร Commit ตัวที่สองคุ้มค่าที่จะกลับไปอ่านอีก ส่วนตัวแรกคุ้มค่าที่จะลบทิ้ง

ขนาดของ PR

ตั้งเป้าให้ diff อยู่ราว 200 บรรทัด เป็นเป้า โดยมีเพดานนิ่มอยู่ราว 400 ตัวเลขเหล่านี้เป็นค่าประมาณ — diff 600 บรรทัดที่ 90% เป็นโค้ดที่ generate ขึ้น หรือเป็นการย้ายไฟล์ล้วน ๆ ก็โอเค แต่ diff 200 บรรทัดที่แตะ subsystem สิบตัว ไม่โอเค

ทำไมถึงเป็นช่วงนี้? เพราะคุณภาพของการ review ตกฮวบเมื่อ diff เกินขนาดที่ reviewer สามารถเก็บไว้ในหัวได้ เกินไม่กี่ร้อยบรรทัดเมื่อไร comment จะเริ่มเบาบาง ปัญหาจะถูกมองข้าม และการ review จะกลายเป็นการกระทำของความไว้วางใจมากกว่าการตรวจสอบ สิ่งนี้สังเกตได้ใน team ใด ๆ ก็ตามที่ลอง plot ความหนาแน่นของ review comment เทียบกับขนาด PR

เมื่อการเปลี่ยนแปลงใหญ่จริง ๆ คุณมีสองวิธีที่สะอาดในการแตกมันออก:

Stacked PRs แตกงานเป็น PR ต่อเนื่องกัน แต่ละตัว merge (หรืออย่างน้อยถูก review และ approve) อยู่บน PR ก่อนหน้า เครื่องมืออย่าง Graphite หรือ workflow stacked-branch ที่มีอยู่ใน Git ช่วยได้ การ branch แยกจาก branch ของตัวเองที่กำลัง review อยู่แล้วเปิด PR ใหม่ก็ช่วยได้เช่นกัน

Preparation PR Merge no-op refactor หรือ scaffold ก่อน — เพิ่ม interface, แนะนำ module ใหม่, ย้ายไฟล์ — แล้วค่อยเปิด PR ของ feature ทับบน base ที่ refactor แล้ว Reviewers จะอ่าน scaffold ได้โดยไม่มีเสียงรบกวนของ feature และ PR ของ feature ก็จะเล็กเพราะระบบท่อร้อยส่วนใหญ่ของมันเข้าไปอยู่แล้ว

คำถามที่ควรถามก่อนเปิด PR ใหญ่ ๆ ไม่เคยใช่ “reviewer จะรับมือไหวไหม?” แต่คือ “นี่แตกได้ไหม?” คำตอบเกือบทุกครั้งคือได้

ความเร็วของ Review เทียบกับความลึก

Review pipeline มีปุ่มสองตัว: review เกิดขึ้นเร็วแค่ไหน และ review ตรวจโค้ดอย่างละเอียดแค่ไหน Teams ส่วนใหญ่มัก optimize ตัวหนึ่งโดยเสียอีกตัวหนึ่งไปโดยไม่รู้ตัว

เป้าที่ใช้ได้สำหรับ team ส่วนใหญ่: ตอบครั้งแรกภายในวันทำงาน, ตัดสินใจ merge ภายใน 24 ชั่วโมงสำหรับ PR ทั่วไป ไม่ใช่ทันที — reviewer ก็ต้องใช้เวลาคิด — แต่ก็ไม่ใช่หลายวัน PR ที่ค้างอยู่สามวันทำให้ผู้เขียนเสียมากกว่าสามวัน เพราะพวกเขาเดินหน้าไปที่อื่นแล้ว, branch เน่าแล้ว และการกลับมาก็เป็นการสลับ context ที่แพง

ความลึกและความเร็วไม่ได้ขัดแย้งกันเมื่อ PR เล็ก เหตุผลที่ review ช้าเกือบทุกครั้งคือ PR ใหญ่ จึงทำให้ reviewer ผัดวันประกันพรุ่งจนกว่าจะ “มีเวลาดูอย่างละเอียด” หด PR ลงแล้ว same-day review จะกลายเป็นเรื่องปกติ

ทำให้ SLO ชัดเจน เขียนมันลงไป ให้สิทธิ์ทางวัฒนธรรมแก่ผู้เขียนในการทวงซ้ำหลังเงียบไปสองสามชั่วโมง — ไม่ใช่เพราะ reviewer ขี้เกียจ แต่เพราะความเงียบนั้นกำกวม และความกำกวมก็แพง

Review Comment ที่ช่วย เทียบกับที่ทำร้าย

Review comment ที่ดีต้องเฉพาะเจาะจง, ทำได้จริง และเกี่ยวกับโค้ด ส่วนตัวที่แย่จะกำกวม, เป็นความเห็นที่นำเสนอเหมือนข้อเท็จจริง หรือเกี่ยวกับตัวผู้เขียน

ใส่ Prefix แสดงเจตนาให้ comment ของคุณ นี่คือนิสัยที่ให้ leverage สูงที่สุดที่ team หนึ่งจะรับเอาได้:

  • nit: — ความชอบส่วนตัวเรื่องสไตล์ จะเอาก็ได้ ไม่เอาก็ได้
  • question: — ฉันไม่เข้าใจ อธิบายมาแล้วฉันอาจไม่มีคำขออะไรเพิ่ม
  • suggestion: — ทางเลือกที่เป็นรูปธรรม น่าพิจารณา
  • blocking: — สิ่งนี้ต้องเปลี่ยนก่อน merge และนี่คือเหตุผล

“nit:” ไม่ใช่คำที่อ่อนแอ แต่เป็นคำที่แม่นยำ มันบอกผู้เขียนว่า “คุณ ignore ได้ ฉันจะไม่เถียง” ถ้าไม่มีมัน ทุก comment จะรู้สึกเหมือน blocker และ review จะกลายเป็นสงครามสนามเพลาะ

วลีบางอย่างที่ช่วยได้:

  • “Is there a reason this isn’t X? X would avoid the null case below.” — เชื้อเชิญให้ผู้เขียนให้บริบท ไม่ฟันธง
  • “suggestion: extracting this to a helper would make the retry logic reusable in the webhook handler.” — เป็นรูปธรรม พร้อมเหตุผลรองรับ
  • “blocking: this reads req.body.userId without a check and will 500 on malformed input. Either validate upstream or guard here.” — บอกอะไร, บอกทำไม, บอกวิธี unblock

วลีที่ทำร้าย:

  • “Why did you do it this way?” — กล่าวหา ไม่มีทางเลือกอื่นเสนอให้
  • “This is wrong.” — ไม่อาจพิสูจน์ผิดได้ ไม่เปิดทางออกใด
  • “I would never write it like this.” — เกี่ยวกับผู้เขียน ไม่ใช่เกี่ยวกับโค้ด

หลักการพื้นฐาน: review โค้ด ไม่ใช่ผู้เขียน สมมติว่าผู้เขียนมีเหตุผลสำหรับทุกการเลือก และถามถึงตัวที่คุณไม่เข้าใจก่อนจะฟันธงว่ามันผิด ครึ่งหนึ่งของเวลามีเหตุผลที่ดีอยู่ อีกครึ่งหนึ่ง ผู้เขียนค้นพบมันด้วยตัวเองในวินาทีที่พยายามอธิบาย

PR Templates ที่คนกรอกจริง

PR template ส่วนใหญ่ถูกข้ามเพราะมันถามสิ่งที่ผู้เขียนตอบไม่ได้หรือไม่สนใจ Template ที่ดีจะถามเฉพาะข้อมูลที่ reviewer ต้องการจริง ๆ และถามในแบบที่ผู้เขียนตอบได้ในสองนาที

Template ที่ใช้งานได้จริงในทางปฏิบัติ:

## What

<One or two sentences. What does this change do from a user's
or caller's perspective?>

## Why

<Link to the ticket, incident, or design doc. If none, explain
the motivation in one paragraph.>

## How

<Notes on the approach — especially anything non-obvious, any
alternative you rejected, any follow-up you explicitly deferred.>

## Testing

- [ ] Unit tests added / updated
- [ ] Integration or e2e coverage considered
- [ ] Manually verified (describe briefly)

## Risk

<What could go wrong? What's the blast radius if this is buggy?
Is there a feature flag, a rollback plan, or a staged rollout?>

## Checklist

- [ ] PR is under ~400 lines of diff, or split with reason
- [ ] Commit messages follow Conventional Commits
- [ ] No secrets, credentials, or `.env` files in the diff
- [ ] Docs updated if behaviour changed

หมวด Risk คือหมวดที่ template ส่วนใหญ่ละทิ้ง และเป็นหมวดที่ reviewer อยากได้มากที่สุด มันบังคับให้ผู้เขียนคิดถึง Failure Mode ก่อนที่จะขอให้คนอื่นมาคิดให้

ทำให้ template สั้น Templates ที่ขอข้อมูลสิบสองหมวดจะถูกลบทิ้งตั้งแต่ PR แรก

Pre-Merge Check ที่สำคัญ

CI gate ควรจับปัญหาที่มนุษย์จับได้แย่ เพื่อให้มนุษย์ใช้ review time ไปกับปัญหาที่พวกเขาจับได้ดี

ตัวที่คุ้มค่าที่จะใส่:

  • Lint สไตล์, บั๊กชัด ๆ, import ที่ไม่ถูกใช้ มนุษย์ไม่ควร review สิ่งเหล่านี้
  • Type check สำหรับภาษาที่ typed, type check ที่สะอาดไม่มีข้อต่อรอง
  • Unit และ Integration test รวมถึงตัวที่ PR เพิ่มเข้ามา Test ที่ flaky ต้องถูกแก้หรือกักกัน ไม่ใช่ทน
  • Secret scanning gitleaks, trufflehog หรือเทียบเท่า Credentials ที่ commit ลง Git ยังเป็นสาเหตุของ incident ที่พบบ่อยที่สุด ที่ป้องกันได้ง่ายมาก
  • Dependency และ SBOM diff เมื่อ package-lock.json หรือ go.sum เปลี่ยน ให้แสดงว่ามีอะไรใหม่ npm audit มีเสียงรบกวนเยอะ; diff ของ lockfile บวก known-vulns check ก็มักเพียงพอ
  • Commit-lint บังคับใช้รูปแบบ Conventional Commit ที่เวลา CI ไม่ใช่ที่เวลา review

นี่คือ GitHub Action ขั้นต่ำที่บังคับใช้ Conventional Commits ในทุก PR:

name: Commit Lint

on:
  pull_request:
    types: [opened, synchronize, edited]

jobs:
  commitlint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install --no-save @commitlint/cli @commitlint/config-conventional
      - name: Lint commits in PR
        run: |
          npx commitlint \
            --from ${{ github.event.pull_request.base.sha }} \
            --to ${{ github.event.pull_request.head.sha }} \
            --config .commitlintrc.json

พร้อม .commitlintrc.json ดังนี้:

{ "extends": ["@commitlint/config-conventional"] }

สำหรับการ review metadata ของ PR แบบ bot-driven — ขนาด, description ที่ขาด, test ที่ขาด — Danger.js คือเครื่องมือที่ใช้กันทั่วไป กฎเดี่ยว ๆ ที่บังคับใช้ norm เรื่องขนาด PR:

// dangerfile.js
import { danger, warn, fail } from "danger";

const bigPR = 400;
const changed = danger.github.pr.additions + danger.github.pr.deletions;

if (changed > bigPR) {
  warn(
    `:elephant: This PR is ${changed} lines. Consider splitting it — ` +
    `reviews lose signal above ~${bigPR} lines. If this is unavoidable ` +
    `(generated code, file moves), note the reason in the description.`,
  );
}

const body = danger.github.pr.body ?? "";
if (!/##\s*Risk/i.test(body)) {
  fail("PR description is missing the **Risk** section.");
}

Danger เปล่งประกายเมื่อกฎพูดง่ายแต่ตรวจด้วยตาน่าเบื่อ อย่าใช้มันมาคุมเรื่องรสนิยม

สิ่งที่ไม่ควรทำใน Review

พฤติกรรมบางอย่างของ reviewer ทำลายคุณค่าจริงจัง จับตัวเองให้ทันถ้าทำสิ่งเหล่านี้:

จับผิดสไตล์เล็ก ๆ น้อย ๆ ที่ formatter ควรจัดการ ถ้า team ของคุณมี Prettier, Black, gofmt หรือเทียบเท่า การถกเถียงเรื่อง space-vs-tab และ trailing-comma เป็นปัญหาของ formatter มนุษย์ไม่ควรเสีย review time ไปกับสิ่งเหล่านี้

ถกเถียงเรื่อง Architecture กลาง PR ถ้าใครใช้เวลาสามวันสร้าง feature และคุณกำลังเสนอแนวทางที่แตกต่างเชิงโครงสร้าง บทสนทนานั้นควรเกิดขึ้นก่อนที่โค้ดจะถูกเขียน ยกข้อกังวลขึ้น แต่อย่าจับ PR เป็นตัวประกัน — เปิด ticket ตามต่อสำหรับ refactor ครั้งหน้าทำ design review ให้เร็วขึ้น

การประจาน “ยังไม่รู้อีกหรือว่าเราชอบ early return?” “นี่คือครั้งที่สามแล้วที่ฉันบอก” นั่นไม่ใช่ review นั่นคือการลงโทษ ถ้ามีคนทำผิดเดิมซ้ำ ๆ ระบบต้องเป็นตัวจับ — lint rule, pairing session, convention ที่จดไว้ — ไม่ใช่ reviewer ที่มีความจำยาว

Drive-by opinion Reviewer ที่ทิ้ง comment ห้าตัวแล้วหายไปสองวันแย่กว่าไม่มี reviewer ผูกมัดกับการ review หรือไม่ก็ส่งต่อให้คนอื่น

Rubber-Duck PR

เปิด draft PR ก่อนที่คุณจะคิดว่าเสร็จ Push สิ่งที่คุณมี อ่าน diff ของตัวเองเหมือนคุณเป็น reviewer แล้วแก้สิ่งที่ชัดเจนก่อน

มันทำสองอย่าง หนึ่ง คุณจับ comment ครึ่งหนึ่งที่จะถูกส่งมาหาคุณได้ก่อนที่ใครจะต้องพิมพ์มัน สอง คุณได้รู้สึกถึงรูปร่างของ PR ที่แท้จริง — ขนาด, เรื่องราว, รอยต่อ — ในจุดที่การแตกมันยังถูกอยู่

Draft PR ยังเป็นที่ที่เหมาะในการถามคำถามเชิงโครงสร้าง: “ฉันคิดจะวางสิ่งนี้ใน services/billing — ตรงกับตำแหน่งของ fees ไหม?” คำถามบรรทัดเดียวบน draft ถูกกว่า PR 400 บรรทัดในโมดูลผิด

ทำเครื่องหมายว่าเป็น draft พูดว่า “ยังไม่พร้อมให้ review เต็ม อยากได้ sanity check เรื่องแนวทาง” เพื่อนร่วมทีมส่วนใหญ่ยินดีอ่านผ่าน ๆ คนที่ไม่อยากจะบอกคุณอย่างสุภาพให้กลับมาตอนที่พร้อม

สรุปเช็คลิสต์

ก่อนกด Merge ผู้เขียนและ reviewer ควรตอบ “ใช่” กับทุกข้อต่อไปนี้ได้:

  • Subject line เป็นไปตาม Conventional Commits Body อธิบาย ทำไม สำหรับการเปลี่ยนแปลงที่ไม่ใช่เรื่องเล็ก
  • PR ถูกแตกที่ขอบเขต atomic — แต่ละ commit หาก revert จะทำให้ repo ยังอยู่ในสภาพใช้งานได้
  • Diff อยู่ใต้เพดานขนาดของ team หรือ description อธิบายว่าทำไมไม่อยู่
  • ความเสี่ยงถูกระบุชื่อ มีแผน rollback หรือ feature flag หาก blast radius กว้าง
  • Test ครอบคลุมพฤติกรรมใหม่และบั๊กที่ถูกแก้ Test ที่ flaky ถูกแก้ ไม่ใช่ retry ซ้ำ
  • ไม่มี secret, credentials หรือข้อมูลส่วนบุคคลใน diff
  • PR description ยังถูกต้องอยู่ — หาก implementation เปลี่ยนระหว่าง review สรุปก็ต้องเปลี่ยนตาม
  • ทุก comment blocking: ถูกแก้ไข ทุก comment nit: ถูกจัดการหรือรับทราบ

ไม่มีอะไรในนี้เป็นวีรกรรม มันคือเพียงสุขลักษณะเล็ก ๆ ที่ทำซ้ำได้ ซึ่งทำให้ PR ครั้งต่อไป — ของคุณหรือของเพื่อนร่วมทีม — ถูกลงเล็กน้อยในการ review สะสมไปหนึ่งปีแล้วมันคือความแตกต่างระหว่าง codebase ที่คนยินดีจะเปิด กับ codebase ที่คนหลีกเลี่ยงเงียบ ๆ

Code review ไม่ใช่ภาษีของการ ship แต่เป็นจุดที่ team ตัดสินใจอย่างชัดเจนว่า codebase ที่อยากอยู่ในอีกหกเดือนข้างหน้าควรเป็นแบบไหน

อ่านเพิ่มเติม

  • Conventional Commits 1.0.0 — spec ฉบับเต็ม อ่านเสร็จในประมาณห้านาที
  • Google’s Code Review Developer Guide — มาตรฐานที่ reviewer ของ Google ถูกฝึกมา โดยเฉพาะส่วนที่ว่าด้วย CL ขนาดเล็กและความเร็วของ reviewer
  • commitlint — drop-in enforcement ของ Conventional Commits ที่เวลา CI
  • Danger.js — bot-driven check ของ PR metadata (ขนาด, description ที่ขาด, test ที่ขาด)
  • Working Effectively with Legacy Code — Michael Feathers (2004) ทำอย่างไรเมื่อ diff ที่คุณกำลัง review อยู่ในโค้ดที่ไม่เคยถูก review ตั้งแต่แรก

Comments powered by Giscus are not yet configured. Set PUBLIC_GISCUS_REPO_ID and PUBLIC_GISCUS_CATEGORY_ID in apps/web/.env to enable.

PV

เขียนโดย พลากร วรมงคล

Software Engineer Specialist ประสบการณ์กว่า 20 ปี เขียนเกี่ยวกับ Architecture, Performance และการสร้างระบบ Production

เพิ่มเติมเกี่ยวกับผม

บทความที่เกี่ยวข้อง