ข้ามไปยังเนื้อหา

Review checklist

นี่คือ checklist ที่ควรไล่ดู — ด้วยตา บน diff ไหนก็ได้ ของคุณเองหรือของ AI — หลังจากเครื่องมือใน Verification Tools รันแล้วสะอาด มันจงใจทำให้อยู่ในหนึ่งหน้ากระดาษพิมพ์: checklist ที่ไม่มีใครเปิดดูจริงตอนรีวิวไม่มีค่าอะไรเลย ทุกบรรทัดลิงก์ไปบทที่อธิบายว่า ทำไม และลิงก์ไปหมายเลข pattern ที่ตรงกันใน AI Bug Catalog ที่มันช่วยป้องกัน

  • default argument ใช้ mutable literal (= [], = {}, = set()) แทนที่จะเป็น = None หรือเปล่า? Catalog #1 ดู Functions
  • lambda หรือ nested function ใน loop close over ตัวแปร loop เอง แทนที่จะเป็นค่าของมันในแต่ละรอบหรือเปล่า? Catalog #2 ดู Closures
  • function ที่ดูเหมือน read-only จริง ๆ mutate argument ที่เป็น list/dict ในที่เดิม (.sort(), .append(), x[i] = ...) โดยไม่บอกไว้เลยหรือเปล่า? Catalog #3 ดู Lists & Tuples
  • [[...] * n] * m (หรือ container ของ container ที่ทำซ้ำด้วย *) สร้าง inner object ที่แชร์กันตัวเดียว แทนที่จะเป็นตัวอิสระต่อกันหรือเปล่า? Catalog #4 ดู Lists & Tuples
  • class attribute ที่ประกาศระดับ class (ไม่ใช่ใน __init__) เป็น mutable object ที่ถูกแชร์ทุก instance หรือเปล่า? Catalog #15 ดู Classes
  • มีการใช้ is ในจุดที่ควรเป็น == — เทียบค่าแทนที่จะเช็ค singleton (None/True/False) หรือเปล่า? Catalog #5 ดู Variables & Types
  • มี bare except:, except Exception: pass หรือ (ใน async code) except BaseException คั่นอยู่ระหว่าง await กับ .cancel() ของ task หรือเปล่า? Catalog #6 ดู Exceptions
  • มี raise e อยู่ใน except ... as e: ที่ตั้งใจใช้ bare raise หรือเปล่า? Catalog #7 ดู Exceptions
  • assert validate external input (argument, user input, API response) แทนที่จะเป็น internal invariant หรือเปล่า? มันหายไปภายใต้ -O Catalog #8 ดู Exceptions
  • ทุก call ที่คืน coroutine ถูก await จริงหรือถูกส่งให้ asyncio.create_task/asyncio.gather หรือเปล่า? Catalog #9 ดู asyncio basics
  • ผลลัพธ์ของ asyncio.create_task(...) ทุกตัวถูกเก็บไว้ที่ไหนสักแห่งที่มี lifetime ยาวกว่า function ที่สร้างมันหรือเปล่า? Catalog #10 ดู Tasks & gather
  • มี async def function ตัวไหนเรียก blocking function (time.sleep, synchronous HTTP client, blocking file I/O) แทนที่จะเป็นเวอร์ชัน await-based ของมันหรือเปล่า? Catalog #11 ดู GIL & threads
  • shared mutable state ถูกอ่านแล้วเขียนคาบเกี่ยว await โดยไม่มี asyncio.Lock หรือเปล่า? Catalog #12 ดู Tasks & gather
  • ทุกการเรียก multiprocessing.Pool/Process อยู่หลัง if __name__ == "__main__": หรือเปล่า? Catalog #13 ดู multiprocessing
  • class นิยาม __eq__ โดยไม่นิยาม __hash__ ด้วย (หรือยอมรับโดยตั้งใจว่า instance จะ unhashable) หรือเปล่า? Catalog #14 ดู Dunder methods
  • มีการใช้ Any ในจุดที่มี type จริงให้ใช้ได้ หรือมี # type: ignore ที่ไม่มี error code เฉพาะเจาะจงหรือเปล่า? Catalog #16 ดู Type hints
  • parameter หรือ return value ที่เป็น X | None (หรือ Optional[X]) ถูกใช้โดยไม่มีการเช็ค None ก่อนหรือเปล่า? Catalog #17 ดู Type hints
  • list[Subtype] ถูกส่งไปในจุดที่คาดหวัง list[Supertype] หรือเปล่า — และ Sequence/Iterable (covariant) ควรเป็น parameter type ที่ถูกต้องแทน list หรือเปล่า? Catalog #18 ดู Type hints
  • โค้ดเรียก datetime.utcnow() (deprecated, naive) แทนที่จะเป็น datetime.now(timezone.utc) หรือเปล่า? Catalog #19 ดู stdlib tour
  • ทุกการเรียก open() อยู่ใน with block และส่ง encoding= อย่างชัดเจนหรือเปล่า? Catalog #20 ดู stdlib tour
  • มีการเรียก subprocess ตัวไหนใช้ shell=True กับ string ที่สร้างจาก external input หรือเปล่า? Catalog #21 ดู stdlib tour
  • ทุก @patch("...") target string ตรงกับที่โค้ดที่ถูกทดสอบ import ชื่อนั้นมาจริง ๆ หรือเปล่า — module ที่เรียกใช้ ไม่ใช่ module ที่นิยาม? Catalog #22 ดู pytest
  • ทุก dependency ถูก pin หรือเปล่า และ pip-audit/เครื่องมือ audit ของ uv ถูกรันกับ lockfile เมื่อไม่นานมานี้หรือเปล่า? มีการติดตั้งด้วย sudo pip ที่ไหนหรือเปล่า? Catalog #23 ดู venv, pip & uv
  • โค้ดที่เป็น library เรียก print() ในจุดที่ควรเป็น logging — อะไรก็ตามที่ผู้เรียกต้อง filter, redirect หรือปิดเสียงได้ — หรือเปล่า? Catalog #24 ดู Logging

🐛 หา bug ในโค้ดนี้

def get_config(overrides: dict | None = None) -> dict:
config = {"retries": 3, "timeout": 10}
if overrides:
config |= overrides
return config
base = get_config()
custom = get_config({"retries": 5})
print(base)
print(custom)
เฉลย

โค้ดนี้ถูกต้องอยู่แล้ว — มันอยู่ตรงนี้เพื่อทดสอบว่าคุณไล่ checklist แบบท่องจำหรืออ่านโค้ดจริง overrides: dict | None = None ใช้ None เป็น default ไม่ใช่ mutable literal ดังนั้น catalog #1 ไม่เกี่ยวข้อง: config = {...} เป็น dict ใหม่ที่ถูกสร้างข้างใน function body ทุกครั้งที่เรียก และ config |= overrides merge เข้าไปใน dict ใหม่นั้น ไม่ใช่เข้าไปในอะไรที่แชร์ข้าม call การไล่ checklist ควรบอกคุณว่า ต้องดูตรงไหน ไม่ใช่ยัด bug ที่ไม่มีจริงให้คุณ — base พิมพ์ {'retries': 3, 'timeout': 10} และ custom พิมพ์ {'retries': 5, 'timeout': 10} อย่างถูกต้อง แยกจากกัน

Mutability: mutable default arg? loop-var closure? mutates caller's list? shared [[...]]*n rows? shared class attr?
Errors: is vs ==? bare/broad except (incl. BaseException across await)? raise e vs raise? assert validating input?
Async: every coroutine awaited/tasked? task reference kept? blocking call inside async def? shared state without Lock? Pool guarded by __main__?
Typing: __eq__ without __hash__? bare Any / uncoded type: ignore? Optional narrowed before use? list invariance respected?
Resources: utcnow()/naive datetime? open() with encoding=? subprocess shell=True? patch target matches import site? deps pinned + audited?
Tooling: print() instead of logging in library code?
'does every asyncio.create_task result get stored somewhere' อยู่กลุ่มไหน และทำไมถึงเป็นกลุ่มนั้นไม่ใช่ 'Typing'?
ทำไม checklist ถึงถามเจาะจงว่า '@patch target ตรงกับที่โค้ดที่ทดสอบ import ชื่อมาหรือเปล่า' แทนที่จะถามแค่ 'มี mock อยู่หรือเปล่า'?
checklist บอกให้เช็คว่าการเรียก Pool/Process อยู่หลัง if __name__ == "__main__": หรือเปล่า ทำไมเรื่องนี้ถึงสำคัญกว่าบน platform บางตัว?
ทำไม 'มีการใช้ Any ในจุดที่มี type จริงให้ใช้ได้' ถึงอยู่ใน checklist นี้ ทั้งที่ mypy --strict และ pyright รายงาน zero error บนไฟล์ที่ใช้ Any แบบนี้เหมือนกันทั้งคู่?
catalog pattern #1 (mutable default argument) เกิดขึ้นที่นี่หรือเปล่า?
def get_config(overrides=None):
    config = {"retries": 3}
    if overrides:
        config |= overrides
    return config