The review checklist
นี่คือ checklist ที่โมดูลนี้สร้างมาตลอด: หนึ่งหน้า หกกลุ่ม ทุกข้อเขียนเป็นคำถามที่ตอบได้จริงจากการอ่าน diff — ไม่ใช่คำเตือนคลุมเครือให้ “ระวังไว้” แต่ละคำถาม link กลับไปบท (ในโมดูลนี้หรือบทก่อนหน้า) ที่อธิบายว่าทำไมมันถึงสำคัญ พร้อมโค้ดที่ compile ผ่านแต่ผิดที่มันจับได้
Boundaries & assertions
หัวข้อที่มีชื่อว่า “Boundaries & assertions”- ทุก
as: มัน assert shape ที่ถูก validate จริงอยู่ใกล้ ๆ หรือ assertion นี้คือสิ่งเดียวที่กันบรรทัดนี้จากการพัง? — Typing APIs & Boundaries - ทุก
any— ไม่ว่าจะระบุตรง ๆ หรือรั่วมาจาก return type ที่ infer จาก import ที่ไม่มี type: มันหยุดตรงไหน แล้วอะไรข้าม boundary นั้นไปโดยไม่ถูกตรวจเลย? — Typing APIs & Boundaries - ทุก
!: คำกล่าวอ้างว่าไม่ null มี runtime check รองรับอยู่ใกล้ ๆ หรือแค่หวังไว้เฉย ๆ? — Nullability,never& Errors - ทุก
@ts-ignoreหรือ@ts-expect-errorที่ไม่ระบุ code: มันซ่อน error อะไรอยู่ แล้วถ้าลบออกจะยัง reproduce error นั้นได้อยู่ไหม? — Why TypeScript - ทุก
catch (e):eถูก narrow (instanceof Error) ก่อนอ่าน.message/.stackหรือเปล่า? — Typing APIs & Boundaries - ทุก
Record<string, any>หรือobjectเปล่า ๆ ที่ทำหน้าที่แทนข้อมูลจริง: interface ที่ concrete จะจับ typo หรือ shape ที่ไม่ตรงที่โค้ดนี้พึ่งพาอยู่ได้ไหม? — Typing APIs & Boundaries
Nullability & defaults
หัวข้อที่มีชื่อว่า “Nullability & defaults”- ทุก optional field ที่เช็คด้วย
if (x):0,"", หรือfalseที่ถูกต้อง ต้องแยกจาก “field หายไป” หรือเปล่า? — Narrowing - ทุก
||ที่ใช้ใส่ default: ควรเป็น??เพื่อให้ falsy value ที่ตั้งใจไว้รอดหรือเปล่า? — Nullability,never& Errors - ทุก
Readonly<T>/readonly T[]: โค้ดรอบ ๆ สมมติ (ผิด ๆ) ว่าความคุ้มครองนี้ไปถึง nested object ด้วยหรือเปล่า? — Typing APIs & Boundaries - ทุก object literal ระดับ module ที่ใช้เป็น “default” ที่แชร์กัน: มันเคยถูก caller แก้แทนที่จะ copy หรือเปล่า? — Typing APIs & Boundaries
- ทุก call ไปหา
asyncfunction หรืออะไรก็ตามที่คืนPromise: มันถูกawait,.catch, หรือทิ้งไปตรง ๆ ด้วยvoidหรือเปล่า? — Performance & Pitfalls - ทุก
Promise.all(...): array ถูกส่ง inline (เก็บ type ของแต่ละตำแหน่งไว้) หรือสร้างเป็นตัวแปรก่อน (ที่อาจ widen เป็น union แบบเงียบ ๆ)? — Performance & Pitfalls - ทุก
switch/if-chain บน union: มี branchdefaultที่ assign ส่วนที่เหลือให้ตัวแปร type เป็นneverหรือเปล่า? — Nullability,never& Errors - ทุก method ที่อ่านออกมาจาก instance แล้วส่งต่อไปที่อื่นแบบ bare reference (
obj.methodไม่มี.bindไม่มี arrow): body ของมันใช้thisหรือเปล่า? —this, Polymorphism & Binding
Generics & variance
หัวข้อที่มีชื่อว่า “Generics & variance”- ทุก parameter ของ function ที่ type เป็น
T[]/Array<T>ที่แค่อ่านอย่างเดียว: ใช้readonly T[]แทนได้ไหม เพื่อปิดช่อง covariance บวกpush? — Generic Classes & Interfaces — Variance - ทุก type predicate ที่เขียนเอง (
x is T): body ของ function เช็คทุก field ที่ predicate กล่าวอ้างจริงหรือแค่ returntrue? — Narrowing - ทุก generic
interface/classที่มี implementer เดียวและ type parameter ใช้ตำแหน่งเดียว: มันคุ้มกับ abstraction ที่ใส่ไปหรือเป็นแค่ indirection ที่ไม่มี reuse payoff? — Generic Functions & Constraints - ทุก
Omit/Pick/Excludeที่ใช้กับสิ่งที่อาจเป็น union: เคยถูกเช็คแล้วว่ามันกระจายตามที่ผู้เขียนสมมติไว้จริงหรือเปล่า? — The Utility Types - ทุก recursive conditional type ที่ลึกมากบน input ที่ผู้ใช้ควบคุมได้: เคยเช็คกับ depth limit ที่วัดไว้ของ compiler หรือเปล่า? — Recursive Types
Erasure & config
หัวข้อที่มีชื่อว่า “Erasure & config”- ทุก
enum(numeric หรือ string): runner จริงของ codebase นี้ (node --experimental-strip-types,esbuild,swc) รองรับมันไหม หรือ compile ผ่านแค่ตอนใช้tsc? — Objects, Arrays & Tuples - ทุก parameter property (
constructor(public x: number)): คำถามเดียวกัน — runtime ที่ใช้รันไฟล์นี้จริง ๆ รองรับ emit ที่มันต้องการหรือเปล่า? — Build & Emit - ทุก
const enum: เคยถูก import ข้ามไฟล์หรือข้าม package ที่ single-file transpiler จะ process ทีละไฟล์เองหรือเปล่า? — Build & Emit - ทุก
import { X }ธรรมดาที่Xถูกใช้เป็น type อย่างเดียว: ควรเป็นimport type { X }หรือเปล่า? —tsconfigDeep Dive
Tooling
หัวข้อที่มีชื่อว่า “Tooling”tsc --noEmit --strictถูกรันกับ diff นี้จริง ๆ หรือเปล่า — ไม่ใช่แค่สมมติว่าสะอาดเพราะ editor ไม่ขึ้นเส้นสีแดง? — Verification Tools- typed rule set ของ
typescript-eslintถูกรันหรือเปล่า ไม่ใช่แค่ default ที่ไม่ typed ที่ starter config ส่วนใหญ่ติดตั้งมาให้? — Verification Tools - สำหรับ package ที่ publish:
@arethetypeswrong/cliรายงานปัญหา resolution หรือexportsอะไรไหม? — Verification Tools knipรายงาน export ที่ diff นี้ควรลบออก หรือ dependency ที่ไม่ต้องใช้แล้วไหม? — Verification Tools
The short version
หัวข้อที่มีชื่อว่า “The short version”ปักไว้บนสุดของ PR template — checklist เดียวกัน ย่อเหลือบรรทัดเดียวต่อกลุ่ม:
[ ] Boundaries -- every as/any/!/@ts-ignore/catch(e): asserted, or actually checked?[ ] Nullability -- every falsy-check/||/Readonly/shared-default: does 0 survive, is it deep, is it copied?[ ] Async -- every promise call: awaited? Promise.all inline? every union switch: never-checked? every bare method ref: bound?[ ] Generics -- every T[] param: readonly enough? every predicate: really checked? every generic: earning its keep? every Omit/Pick: distributes? every recursion: under the limit?[ ] Erasure -- every enum/parameter property/const enum: does the actual runtime support it? every type-only import: import type?[ ] Tooling -- tsc --strict run? typed eslint run? attw/knip run on a package?และ grep เดียวที่เผยส่วนใหญ่ของ diff ที่ควรช้าลงให้ดู:
grep -nE 'as |: any|!\.|!;|!\)|@ts-|JSON\.parse|\.json\(\)|enum |Object\.keys' <changed-files>Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”หกกลุ่ม หนึ่งหน้า:
1. Boundaries & assertions -- ทุกจุดที่ type กับความจริงได้รับอนุญาตให้ต่างกัน2. Nullability & defaults -- ทุกจุดที่ falsy value หรือ object ที่แชร์กันจัดการผิดได้ง่าย3. Async -- ทุกจุดที่ Promise หรือ union จัดการผิดแบบเงียบ ๆ ได้4. Generics & variance -- ทุกจุดที่ abstraction คุ้มค่าหรือเปิดช่อง unsoundness5. Erasure & config -- ทุกจุดที่ "compile ผ่าน" กับ "รันได้" อาจต่างกัน6. Tooling -- มีอะไรนอกจากการอ่านของตัวเองตรวจสิ่งนี้จริงหรือเปล่า?
ทุกคำถามตอบได้จากการอ่าน diff -- ไม่มีข้อไหนต้องรันโปรแกรมก่อนถึงจะตอบได้Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ได้อะไร | ประโยชน์ | ต้นทุน |
|---|---|---|
| checklist หกกลุ่มตายตัว | coverage สม่ำเสมอทุก reviewer ทุก PR ไม่ใช่ “อะไรก็ตามที่นึกได้วันนี้” | checklist ที่ทำตามแบบเครื่องจักรโดยไม่เข้าใจว่าทำไมแต่ละข้อถึงสำคัญ กลายเป็นแค่ tick กล่อง |
| คำถามแทนคำเตือน | แต่ละข้อมีคำตอบ yes/no ที่ชัดเจน อ่านได้จาก diff | บางคำถาม (variance, distribution, recursion depth) ต้องใช้พื้นฐานจากบทที่ link ถึงจะตอบถูก |
| short version บรรทัดเดียวต่อกลุ่ม | เร็วพอจะ paste ลง PR template แล้วใช้จริงทุกครั้ง | ย่อจนอาจเสีย “ทำไม” ไป — เก็บ full version link ไว้ใกล้ ๆ |
| grep one-liner เดียว | จับจุดที่เป็น syntax hot spot ส่วนใหญ่ได้ในไม่กี่วินาที ก่อนอ่าน logic แม้แต่บรรทัดเดียว | เจอจาก grep ไม่ได้แปลว่าเป็น bug อัตโนมัติ — ทุกจุดที่เจอยังต้องใช้วิจารณญาณที่ checklist ที่เหลือฝึกไว้ |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “checklist แทนวิจารณญาณได้” ทุกข้อในนี้ยังต้องอ่านโค้ดรอบ ๆ จุดนั้นจริง ๆ — checklist บอกแค่ว่า ต้องดูตรงไหน ไม่ได้บอกว่าจะเจออะไร
- “grep ไม่เจออะไร แปลว่า diff สะอาด” grep list จับแค่ syntax pattern; หลาย pattern ในแคตาล็อกของโมดูลนี้ (mutable default ที่แชร์กัน, type predicate ที่โกหก,
Omitบน union) ไม่ทิ้ง token ให้ match เลย - “หกกลุ่ม แปลว่าต้องอ่านหกรอบแยกกัน” ในทางปฏิบัติ การอ่าน diff รอบเดียวตอบคำถามส่วนใหญ่ได้พร้อมกัน — หกกลุ่มจัดระเบียบว่าต้องมองหาอะไร ไม่ใช่ต้องอ่านกี่รอบ
- “checklist นี้ใช้กับโค้ดที่ AI เขียนเท่านั้น” ทุกคำถามในนี้ใช้กับ PR ของคนได้เท่ากัน — โค้ดที่ AI เขียนแค่ผลิตการละเมิดในปริมาณที่เยอะกว่าและมี comment ที่ดูมั่นใจกว่า