Free

CLAUDE.md ที่ดีไม่ต้องอธิบายฟีเจอร์ — เขียนแค่สิ่งที่ Claude อ่านโค้ดก็มองไม่เห็น

CLAUDE.md ที่ดีไม่ใช่ README — มันเก็บ invariant ที่ Claude อ่านโค้ดไม่ออก. 6 ข้อต้องเขียน, 4 ข้อที่ควรเลี่ยง, 5 คำถาม.


โปรเจกต์ Pickful ของผมมีระบบคณะลูกขุนแบบชุมชนกระจายศูนย์ ชำระเงินคริปโตแบบ x402 Sign-In with Ethereum มัลติดาต้าเบส พุชเรียลไทม์ — ล้วนเป็นสแต็กที่เพิ่งโผล่มาในช่วง 1-2 ปีที่ผ่านมา Claude ส่งมอบฟีเจอร์เหล่านี้เร็วและสะอาด

แต่เปิด CLAUDE.md ของโปรเจกต์ดู คุณจะเจอว่า: ระบบคณะลูกขุน และ x402 — ไม่ถูกเอ่ยถึงแม้แต่ครั้งเดียว

นี่ไม่ใช่ลืม จุดมุ่งหมายของ CLAUDE.md ไม่เคยเป็น "อธิบายฟีเจอร์" หน้าที่มันคือบันทึกสิ่งที่ Claude ไม่มีทางอนุมานได้จากการอ่านโค้ด

การเขียนคำอธิบายฟีเจอร์ = ละลาย token

คนที่เขียน CLAUDE.md ครั้งแรกมักปฏิบัติต่อมันเหมือน README — อธิบายทุกฟีเจอร์หลัก:

  • "ระบบคณะลูกขุนให้ผู้ใช้รายงานเนื้อหา; ที่ถูกรายงานจะเข้าสู่การโหวตสาธารณะ คณะลูกขุนถูกเลือกจาก..."
  • "การชำระเงิน x402 ทริกเกอร์การโอน on-chain ผ่านโค้ดสถานะ HTTP 402..."
  • "ไลก์ให้คะแนน; ที่ 400 คะแนนผู้ใช้กลายเป็น VIP..."

เนื้อหาแบบนี้ Claude เปิด topic_review_service.rb / x402.rb / like_points_service.rb แล้วอ่านได้แม่นยำกว่าที่คุณเขียน คำอธิบายลอจิกธุรกิจหนึ่งพันคำ Claude อ่านจากโค้ดเสียไปไม่กี่ร้อย token และไม่มีการเบี่ยงเบนทางการตีความ — โค้ดคือข้อเท็จจริง คำอธิบายคือข้อมูลมือสอง

ที่ Claude สะดุดจริง ๆ คือ 6 หมวดต่อไปนี้

6 หมวดที่ช่วยชีวิตจริง ๆ

1. ทางเลือกสถาปัตยกรรมที่สวนสัญชาตญาณ

CLAUDE.md ของ Pickful มีบรรทัดแบบนี้:

Propshaft (not Sprockets)
ImportMap (no JavaScript bundler)
Hotwire: Turbo Frames, Turbo Streams, Stimulus
Lexxy gem overrides ActionText:
  config.lexxy.override_action_text_defaults = false

ทุกบรรทัด ต่อต้านการเดาแบบดีฟอลต์ เห็นโปรเจกต์ Rails สมมุติฐานดีฟอลต์ของ Claude คือ:

  • Static asset ผ่าน Sprockets (ความเฉื่อยของโปรเจกต์เก่า)
  • JS ใช้ Webpacker หรือ esbuild
  • ฟรอนต์คือ React หรือส่วนผสม Stimulus + Turbo
  • Rich text คือ ActionText แท้ ๆ

หากไม่เขียนบรรทัดพวกนี้ใน CLAUDE.md ขอให้ Claude เพิ่มฟีเจอร์ JS ใหม่ มีโอกาสสูงเขาจะติดตั้ง Webpacker แก้ package.json เขียน config bundler — ผิดหมด และผิดเงียบ (แอปยังวิ่งได้ แต่ pipeline ของ asset ถูกปนเปื้อน)

บรรทัดพวกนี้ใน CLAUDE.md กำลังบอก Claude ว่า: อย่าเดา ตัดสินใจแล้ว

2. การแบ่ง multi-database

PostgreSQL with 4 separate databases:
- primary - Main application data
- cache   - Solid Cache storage
- queue   - Solid Queue jobs
- cable   - Action Cable subscriptions

เขียนเรียบ ๆ แต่ช่วยประหยัดทั้งคืน Rails 8 มี multi-DB เป็นดีฟอลต์ซึ่งเป็นพฤติกรรมใหม่ Claude ไม่ไปเช็คเองว่าคุณใช้กี่ DB migration ที่ดูไม่เกี่ยวข้องไปลงผิด DB ที่ dev ไม่ error (ทั้งสี่เป็น PostgreSQL schema ผ่านได้ทุกที่) แต่ที่ production ตาราง job ของ Solid Queue แอบเข้า backup ของ primary หรือ model ของ primary ไป query DB cache — บักแบบนี้ต้องใช้เวลาหลายวันจึงโผล่

สองบรรทัดใน CLAUDE.md vs. หนึ่งวันไล่บักที่ production

3. "ข้อตกลงที่มองไม่เห็น" ของ URL routing

/p-{slug} - Short post URLs (4-5 char alphanumeric)
/t-{slug} - Topic URLs (3-4 char alphanumeric)
/s-{code} - Short URL redirects (3-4 char alphanumeric)
/r-{referral} - Referral links

เส้นทางเอง Claude เห็นใน routes.rb แต่ ข้อตกลงเรื่องความยาว (4-5 ตัว, 3-4 ตัว) ฝังอยู่ใน logic สร้าง slug ของ model หรือ service ขอ Claude ให้เพิ่ม short link ชนิดใหม่ เขามีโอกาสสร้าง slug 6 ตัว สไตล์ UUID หรือเลขล้วน — หลุดจากภาษาภาพของทั้งระบบ

คุณสมบัติของ "ข้อตกลง" แบบนี้: ฝ่าฝืนไม่ error แต่คนอ่านโค้ดคนถัดไปจะรู้สึกว่าอะไรบางอย่างเพี้ยน ต้องเขียน

4. เกณฑ์ธุรกิจฝังตัวในโค้ด

VIP status at 400+ points
Posts with 15+ likes are "hot" posts

สองเลขนี้อยู่ที่ใดที่หนึ่งในโค้ด (User#vip?, scope Post#hot?) ปัญหาคือเมื่อ Claude แก้อะไรที่เกี่ยว — ปรับรางวัลคะแนน เพิ่มแจ้งเตือน "ใกล้เป็น VIP" เขียน cron ปักหมุด hot post — เขาจะไม่ปรับเกณฑ์ที่จุดอื่น ๆ ให้ตรงกันโดยอัตโนมัติ

ผล: คุณให้รางวัล 500 คะแนนสำหรับ task หนึ่ง แต่ copy บอก "กำลังจะเป็น VIP" (จริง ๆ 400 ก็พอ); หรือทำ seed data ให้ฟีเจอร์ใหม่ด้วยจำนวนไลก์น้อยเกินไป จนไม่เคยแตะเกณฑ์ 15

ความสามารถโค้ดของ Claude แข็ง แต่เขาไม่มี ความรู้สึกเรื่องตัวเลขในระดับระบบทั้งหมด การใส่เกณฑ์สำคัญใน CLAUDE.md ทำให้ทุกครั้งที่เริ่มคุยเขารู้ตั้งแต่ต้นว่า "400 กับ 15 เป็นตัวเลขพิเศษ"

5. ป้ายบอกทางของสแต็ก auth/authz

- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules

หน้าที่ของบรรทัดนี้คือ นำทาง ไม่ใช่บรรยาย

หากไม่มี เมื่อ Claude ต้องเพิ่มการตรวจสิทธิ์ใหม่ มีสามความเป็นไปได้:

  • เขียน unless current_user.admin? ตรง ๆ ใน controller
  • ขุดเศษของ CanCan เก่า ๆ ที่เลิกใช้แล้วขึ้นมา
  • ประดิษฐ์ method authorize? ของตัวเองใน model

มี "Pundit policies in app/policies/" เขียนไว้ Claude ไปที่ app/policies/ เพิ่มไฟล์ policy ทุกครั้ง — สไตล์สม่ำเสมอ

หนึ่งบรรทัดตัด "งานนักสืบ" ของ Claude ออกทุกครั้ง

6. ข้อจำกัดภายนอกระดับโปรเจกต์

Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers

เวลาเพิ่มฟีเจอร์ใหม่ ดีฟอลต์ของ Claude คือ:

  • เพิ่มแค่ string ภาษาอังกฤษ
  • เขียนเทสต์ด้วย Minitest + fixtures (ดีฟอลต์ของ Rails)

แต่โปรเจกต์ของคุณจริง ๆ ต้องการ:

  • คำแปลใน 3 locales
  • RSpec + FactoryBot ไม่ใช่ fixtures

การฝ่าฝืน "ข้อจำกัดภายนอกระดับโปรเจกต์" แบบนี้จะสร้างงานตกค้างจำนวนมาก — แปลที่ต้องเพิ่ม เทสต์ที่ต้องเขียนใหม่ เขียนใน CLAUDE.md คือการตอกตะปู "สิ่งที่ต้องทำทุกครั้ง" ให้เรียบร้อยครั้งเดียว

ด้านตรงข้าม: เขียนพวกนี้คือละลายเวลา

สำคัญพอกับ "ต้องเขียน" คือ "อย่าเขียน" หมวดต่อไปนี้ เห็น = ลบ:

1. บรรยายฟีเจอร์

"ระบบคณะลูกขุน: ผู้ใช้สามารถรายงานเนื้อหาที่ละเมิดกฎ เนื้อหาที่รายงานเข้าสู่ขั้นตอนโหวตสาธารณะ คณะลูกขุนถูกเลือกจาก..."

→ Claude เปิด topic_review_service.rb แล้วอ่านแม่นกว่าที่คุณเขียน ยัดสิ่งนี้เข้าทุกการสนทนาใหม่คือละลาย token ล้วน ๆ

2. สิ่งที่อ่านจากโครงสร้างไดเรกทอรี / Gemfile ได้ทันที

"app/models/ เก็บ ActiveRecord model", "ใช้ Rails 8", "DB คือ PostgreSQL"

→ Claude กวาดตา root ของโปรเจกต์และ Gemfile ครั้งเดียวก็รู้

3. ความรู้เขียนโปรแกรมทั่วไป

"Controllers should be thin, delegate to services", "เลี่ยง N+1", "เขียนเทสต์ให้ฟีเจอร์หลัก"

→ อยู่ใน training data ของ Claude อยู่แล้ว เขียนก็ต่อเมื่อโปรเจกต์คุณ ผิดปกติ เท่านั้น — เช่น "เราจงใจไม่ใช้ service layer; ลอจิกอยู่ใน controller"

4. บริบทของงานปัจจุบัน

"ตอนนี้เรากำลัง refactor ระบบชำระเงิน; โฟกัสที่..."

→ นี่คือบริบทการสนทนา ไม่ใช่ข้อเท็จจริงของโปรเจกต์ ยัดลงใน CLAUDE.md จะปนเปื้อนทุกการสนทนาอื่น

ตรวจสอบภาคสนาม: CLAUDE.md ของผมเองก็ตัดครึ่งได้

วางหลักฐาน "ผมทำได้" ไว้หน้าคำเทศนา หลังเขียนส่วนข้างบน ผมเอา CLAUDE.md ของ Pickful — 238 บรรทัด — ผ่าน 5 คำถามของตัวเอง ผลลัพธ์: ประมาณครึ่งหนึ่งคือเสียเปล่า

ที่ต้องตัด (~120 บรรทัด):

ส่วนใหญ่ของบล็อก dev commands (70 บรรทัด → 10): bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman ล้วนเป็นคำสั่ง Rails มาตรฐาน อยู่ใน training ของ Claude แล้ว เก็บแค่สามที่เฉพาะของโปรเจกต์: bin/jobs (Solid Queue worker), bin/importmap pin (เฉพาะ ImportMap), bin/kamal deploy

รายการ Core Domain Models (35 บรรทัด → ลบทิ้ง): ไล่รายชื่อ 20 model กับบทบาทคือตัวอย่างคลาสสิกของ "CLAUDE.md สไตล์ README" — Claude รัน ls app/models/ หรืออ่านไฟล์ model หนึ่งไฟล์ก็รู้ ยัดทุกการสนทนาคือเสียเปล่าล้วน

รายการมาตรฐานใน Tech Stack (28 บรรทัด → 8): Rails 8 / Devise / Pundit / Tailwind / pg_search ล้วนอ่านจาก Gemfile ได้ทันที เก็บเฉพาะที่สวนสัญชาตญาณ: Propshaft / ImportMap / Lexxy / x402-rails / Grover

ความรู้เขียนโปรแกรมทั่วไปที่กระจัดกระจาย: "Controllers should be thin", "Use app/jobs/ for async processing", request specs vs. model specs ทดสอบอะไร — Claude ทำโดยดีฟอลต์อยู่แล้ว เปลือง token

ที่เหลือ ~100 บรรทัด: ข้อตกลง URL routing, การแบ่ง 4 DB, เกณฑ์ VIP 400 / Hot 15, override Lexxy, ทางเลือกสถาปัตยกรรมแบบต้านดีฟอลต์, ป้าย Pundit, รายการ locale, FactoryBot (ไม่ใช่ fixtures)

แต่สำคัญกว่า: invariants อะไรที่ยังไม่ได้เขียน?

ระหว่างตรวจสอบ ผมตระหนักถึงหลายข้อที่ควรเพิ่มแต่ไม่เคยเพิ่ม:

  • ตัวเลขที่ฝังในระบบคณะลูกขุน (เกณฑ์การโหวต คุณสมบัติการเป็นลูกขุน) — ไม่ปรากฏใน CLAUDE.md เลย
  • ถ้า x402 มี chain id, ที่อยู่สัญญา, env var ที่จำเป็น — Claude ไม่ไป grep ไฟล์ config และจะคิดค่าขึ้นเอง
  • กฎข้อจำกัดเฉพาะใน service ของ point-trading / Referral

238 บรรทัดบีบเหลือ 100–120 บรรทัด บวกเพิ่ม 5–10 บรรทัดของ invariants ที่พลาดไป — นั่นจึงใกล้กับ "ความหนาแน่นที่ถูกต้อง" ของ CLAUDE.md

นี่ไม่ใช่ tutorial นี่คือการตรวจสอบโปรเจกต์ของผมเอง — ผมก็เขียนไม่ถูกเหมือนกัน รูปแบบที่ถูกของ CLAUDE.md คือ ลบไปเรื่อย ๆ และเพิ่มไปเรื่อย ๆ — โปรเจกต์ที่แก่ขึ้นหน่อย CLAUDE.md ควรสั้นลงและแน่นขึ้น

5 คำถามก่อนใส่กฎลงไป

ทุกครั้งที่อยากจะเพิ่มอะไรใน CLAUDE.md ผมจะผ่าน checklist ดังต่อไปนี้:

  1. Claude อ่าน 3 ไฟล์อนุมานกฎนี้ได้หรือไม่? ได้ — อย่าเขียน ให้เขาอ่านเอง
  2. กฎนี้สวนสัญชาตญาณหรือไม่? (เกณฑ์ผิดปกติ การเลือกไลบรารีนอกกระแส การตั้งค่าที่ขัดกับดีฟอลต์อัปสตรีม) สวน — ต้องเขียน
  3. เป็น invariant ระดับทั้ง codebase หรือแค่ไฟล์เดียว? ไฟล์เดียว — ใส่ comment ในโค้ด ไม่ต้องขึ้นมา CLAUDE.md
  4. ฝ่าฝืนกฎนี้จะทำให้ Claude ผิดแบบเงียบหรือไม่? (ไม่ error แต่ความหมายผิด — DB ผิด แปลขาด policy ข้าม) ใช่ — ต้องเขียน
  5. อธิบายใน 3 บรรทัดได้หรือไม่? ไม่ได้ — คุณเองยังไม่ตกผลึก ยังไม่ต้องเขียน

กฎที่ผ่านทั้ง 5 เก็บไว้; ข้อใดที่ตอบไม่ได้ — ลบหรือเขียนใหม่

สรุปหนึ่งบรรทัด

การใช้ CLAUDE.md ที่ถูกไม่ใช่ "แนะนำโปรเจกต์" แต่คือ บีบอัดความรู้ซ่อนเร้นระหว่างคุณกับ codebase ที่ Claude ไม่มีทางเติมให้ได้จากการอ่านโค้ด — ทางเลือกผิดปกติ เกณฑ์มองไม่เห็น ข้อตกลงที่ขัดกับดีฟอลต์ ข้อจำกัดภายนอกระดับโปรเจกต์

ทุกบรรทัดที่คุณเพิ่มต้องตอบคำถามนี้: "สิ่งนี้ Claude อ่านจากโค้ดออกไม่ได้หรือ?" ไม่ได้ — เก็บ ได้ — ลบ

CLAUDE.md ที่เขียนแบบนี้มักสั้นกว่าฉบับร่างแรกเกินครึ่ง แต่มีค่าต่อการสนทนาแต่ละครั้งมากกว่าคำอธิบายฟีเจอร์หลายพันคำ และมัน "ยังไม่เสร็จ" ตลอด — ทุกฟีเจอร์ใหม่ที่ส่งไป จะเผยให้เห็น invariant อีกอันที่ควรอยู่ในนั้น และย่อหน้าเก่าอีกย่อหน้าที่ตอนนี้ตัดได้ ลบและเพิ่ม ลบและเพิ่ม — นั่นคือการบำรุงรักษาประจำวันของ CLAUDE.md