Verification tools
ไม่มีเครื่องมือด้านล่างตัวไหนที่ทับซ้อนกันสมบูรณ์ และไม่มีตัวไหนที่ใกล้สมบูรณ์ด้วยตัวเองเลย — บรรทัด “จับได้โดย” ของแต่ละ entry ทั้ง 28 ตัวใน AI Bug Catalog เอ่ยชื่อเครื่องมือด้านล่างตัวใดตัวหนึ่งเจาะจง (ถ้ามี) ที่จะจับมันได้ และ entry ส่วนใหญ่ในจำนวน 28 ตัวนั้นไม่เอ่ยชื่อเครื่องมือตัวไหนเลย ณ จุดที่บั๊กถูกใส่เข้าไป ผลลัพธ์ทุกอย่างด้านล่างเป็นของจริง จับสดสำหรับโมดูลนี้กับโปรเจกต์ scratch แบบทิ้งขว้างบน Mac เครื่องนี้ (Flutter 3.47.5, Dart 3.13.4) ไม่ใช่ probe ที่ใช้ร่วมกันของคอร์ส
flutter analyze — พิสูจน์ type และ syntax ไม่เคยพิสูจน์พฤติกรรมตอน runtime
หัวข้อที่มีชื่อว่า “flutter analyze — พิสูจน์ type และ syntax ไม่เคยพิสูจน์พฤติกรรมตอน runtime”นี่คือ gate ที่ถูกที่สุดและเร็วที่สุดในบันไดทั้งหมดนี้ และสำคัญที่ต้องแม่นยำว่ามันตัดอะไรออกไปได้จริง ๆ ConsumerWidget ที่ไม่มี ProviderScope อยู่เหนือมัน, ListView ที่กำลังจะ overflow constraint ความสูงของมัน, และ closure ของ compute() ที่กำลังจะพังตอน runtime ล้วน analyze ผ่านสะอาดสมบูรณ์:
$ flutter analyze lib/proof/a1_provider_scope.dart lib/proof/c1_unbounded_listview.dartNo issues found!flutter analyze เป็น gate จริง มีประโยชน์ ไม่มีต้นทุน สำหรับบั๊กคนละประเภทจริง ๆ (ชื่อ parameter สะกดผิด, argument type ผิด, method ที่ไม่มีอยู่จริง) เทียบกับสิ่งที่มีรูปร่างเป็น runtime แทบทั้งหมดใน catalog ของโมดูลนี้ — ดู entry 5, 18, 19, 24, 25, 27, และ 28 สำหรับ diagnostic ที่มันจับได้จริง
โหมดเข้มของ analysis_options.yaml — ช่องว่างจริงและเป็นปัจจุบันในสิ่งที่เปิดใช้งานโดย default
หัวข้อที่มีชื่อว่า “โหมดเข้มของ analysis_options.yaml — ช่องว่างจริงและเป็นปัจจุบันในสิ่งที่เปิดใช้งานโดย default”สองข้อค้นพบแยกกัน ทั้งคู่พิสูจน์สดสำหรับโมดูลนี้:
flutter_lints 6.0.0 ไม่มี rule prefer_const_constructors แบบทั่วไป — ยืนยันจากการอ่าน rule list ของ flutter.yaml ในแพ็กเกจเองตรง ๆ (มีแค่ prefer_const_constructors_in_immutables) และจากการรัน flutter analyze ก่อน/หลังจริง:
$ flutter analyze lib/proof/h1_prefer_const.dart # flutter_lints 6 aloneNo issues found!
$ flutter analyze lib/proof/h1_prefer_const.dart # same file, prefer_const_constructors: true addedinfo • Use 'const' with the constructor to improve performance. Try adding the 'const' keyword tothe constructor invocation • prefer_const_constructorsvery_good_analysis 11.0.0 ในทางตรงกันข้าม มี prefer_const_constructors อยู่ — ยืนยันจากการอ่าน rule list ของ analysis_options.11.0.0.yaml ที่ shipped จริงตรง ๆ บรรทัด 131
flag โหมดเข้มสามตัวของ language: — strict-casts, strict-inference, strict-raw-types — เป็น option ของ analyzer ไม่ใช่ lint rule ปิดอยู่โดย default และพิสูจน์ครบแล้วพร้อม diagnostic จริงใน Project and Tooling โมดูลนี้ไม่ได้พิสูจน์ซ้ำ แค่อ้างอิงไขว้เป็นส่วนหนึ่งของบันไดเดียวกัน
flutter_lints / riverpod_lint / custom_lint — ข้อค้นพบเรื่อง version resolution จริงและเป็นปัจจุบัน
หัวข้อที่มีชื่อว่า “flutter_lints / riverpod_lint / custom_lint — ข้อค้นพบเรื่อง version resolution จริงและเป็นปัจจุบัน”เรื่องนี้คุ้มค่าที่จะพูดให้แม่นยำ เพราะมันขัดแย้งตรง ๆ กับสมมติฐานทั่วไปที่ว่า riverpod_lint ยังรันอยู่บน custom_lint: ณ วันที่คอร์สนี้เช็ค dependency เอง (2026-09-29) riverpod_lint เวอร์ชันล่าสุด (3.1.9) ไม่ได้พึ่ง custom_lint เลยแม้แต่น้อย การอ่าน pubspec.yaml ของมันเองยืนยันเรื่องนี้ — dependency ของมันคือ analysis_server_plugin, analyzer, และ analyzer_plugin ไม่มี custom_lint/custom_lint_builder อยู่ที่ไหนเลย riverpod_lint ย้ายไปใช้ native analyzer-plugin system ของ Dart ราว ๆ เวอร์ชัน 3.1.0
การพยายามติดตั้ง custom_lint และ riverpod_lint ด้วยกัน ที่เวอร์ชันล่าสุดของทั้งคู่ resolve ไม่ผ่านเลย:
$ flutter pub get # custom_lint: ^0.8.1, riverpod_lint: ^3.1.9Because riverpod_lint >=3.1.4-dev.1 depends on analyzer_plugin ^0.14.0 and custom_lint >=0.7.4depends on analyzer_plugin ^0.13.0, riverpod_lint >=3.1.4-dev.1 is incompatible with custom_lint>=0.7.4.การ pin riverpod_lint เวอร์ชันเก่ากว่า (3.1.0) เพื่อหลบความขัดแย้งนั้น resolve ได้จริง — แต่ต้อง pin riverpod/flutter_riverpod ลงไปที่ 3.1.0 ด้วย ห่างไกลจากเป้าหมาย 3.4.3 ของคอร์สนี้มาก:
$ flutter pub get # custom_lint: ^0.8.1, riverpod_lint: 3.1.0+ riverpod 3.1.0 (3.4.3 available)+ riverpod_lint 3.1.0 (3.1.9 available)Changed 58 dependencies!วิธีตั้งค่าที่ถูกต้องและเป็นปัจจุบันจริง ๆ ไม่ใช้ custom_lint กับ riverpod_lint เลย README ของ riverpod_lint เองอธิบาย native plugin mechanism ตรง ๆ:
analyzer: plugins: - riverpod_lint
plugins: riverpod_lint: 3.1.9ตั้งค่าแบบนี้ (ไม่ต้องมี dependency ของ custom_lint ที่ไหนเลย) dart analyze สร้างผลลัพธ์จริงของ riverpod_lint — จับสดสำหรับโมดูลนี้ บน ConsumerWidget ที่ไม่มี ProviderScope:
$ dart analyzewarning - lib/main.dart:4:3 - Flutter applications using Riverpod must contain a ProviderScope atthe root of their widget tree. - missing_provider_scopedart fix --dry-run — แสดงสิ่งที่แก้ได้ด้วยเครื่องจักร โดยไม่ apply จริง
หัวข้อที่มีชื่อว่า “dart fix --dry-run — แสดงสิ่งที่แก้ได้ด้วยเครื่องจักร โดยไม่ apply จริง”ผลลัพธ์จริงสำหรับการ deprecate ของ cacheExtent จาก entry 24 ของ AI Bug Catalog:
$ dart fix --dry-run lib/proof/g3_cache_extent.dartComputing fixes in g3_cache_extent.dart (dry run)...
1 proposed fix in 1 file.
g3_cache_extent.dart deprecated_member_use - 1 fix
To fix an individual diagnostic, run one of: dart fix --apply --code=deprecated_member_use lib/proof/g3_cache_extent.dartคุ้มค่าที่จะสังเกตให้แม่นยำ: dart fix แจ้งว่า diagnostic deprecated_member_use มีทางแก้ที่เสนอไว้ — มันไม่ได้บอกผู้อ่านเองว่าทางแก้เชิงกลไกนั้น (การเปลี่ยนชื่อตรง ๆ) ถูกต้องจริงหรือไม่ entry 24 ใน catalog คือกรณีนี้เป๊ะ: cacheExtent: 500 → scrollCacheExtent: 500 “แก้” คำเตือน deprecation ด้วยกลไก แต่สร้าง error ใหม่ที่ต่างออกไป (ScrollCacheExtent เป็น sealed class ไม่ใช่ double) — dart fix --apply เพียงลำพังไม่รู้เรื่องนั้น reviewer ยังต้องตรวจสอบเอง
flutter test — พิสูจน์พฤติกรรม รวมถึง --coverage และ golden test
หัวข้อที่มีชื่อว่า “flutter test — พิสูจน์พฤติกรรม รวมถึง --coverage และ golden test”ทุก dual-assert test และ fail-then-pass test ใน AI Bug Catalog คือการพิสูจน์ด้วย flutter test มีอีกสองโหมดที่ควรรู้:
--coverage สร้าง coverage/lcov.info จริง เป็นรายบรรทัด:
$ flutter test --coverage test/proof/a4_late_uninitialized_test.dart00:00 +2: All tests passed!
$ cat coverage/lcov.infoSF:lib/proof/a4_late_uninitialized.dartDA:6,2LF:1LH:1end_of_recordDA:6,2 หมายความว่าบรรทัด 6 ถูกเรียกใช้สองครั้งตลอดการรัน test — จำนวนการรันจริงต่อบรรทัด ไม่ใช่การประมาณ
Golden test เปรียบเทียบ pixel ที่ render จริงของ widget กับไฟล์ PNG อ้างอิงที่บันทึกไว้ สร้างแล้วทำให้มันพังจริง ๆ จับสดสำหรับโมดูลนี้:
$ flutter test --update-goldens test/proof/golden_demo_test.dart✓ golden: a simple badge matches the reference PNG00:00 +1: All tests passed!
$ # change the badge's color, then re-run WITHOUT --update-goldens:$ flutter test test/proof/golden_demo_test.dartGolden "badge.png": Pixel test failed, 0.83%, 4000px diff detected.Failure feedback can be found at .../test/proof/failuresgolden test จับ visual regression ได้ทุกแบบ — รวมถึงแบบที่ assertion ไหนก็ไม่มีวันคิดจะเช็ค — แต่เปราะบางโดยธรรมชาติข้าม rendering environment ที่ต่างกัน (font hinting, anti-aliasing ระดับ OS) harness ของคอร์สนี้เองไม่สร้างหรือเช็คไฟล์ golden สำหรับ fence ของบทเรียน ด้วยเหตุผลเรื่อง portability นี้เป๊ะ
flutter build web --wasm — portability gate จริง
หัวข้อที่มีชื่อว่า “flutter build web --wasm — portability gate จริง”พิสูจน์สดสำหรับโมดูลนี้: snippet dart:io/Platform.isIOS เป๊ะจาก entry 19 ของ AI Bug Catalog compile ผ่านสะอาดภายใต้ทั้งเป้าหมาย JS แบบ default และ --wasm:
$ flutter build web --release✓ Built build/web
$ flutter build web --wasm --release┌─ New feature ──────────────────────────────────────────────────────────────┐│ WebAssembly compilation is new. Understand the details before deploying ││ to production. See https://flutter.dev/to/wasm for more information. │└──────────────────────────────────────────────────────────────────────────┘✓ Built build/webทั้งสอง build สำเร็จโดยไม่มี warning เรื่อง dart:io เลยสักคำ — --wasm เป็น portability gate สำหรับ compile target ที่ต่างออกไป ไม่ใช่ analyzer ที่เข้มขึ้น มันไม่จับบั๊กที่มีอยู่แค่ตอน runtime ได้มากไปกว่า web build แบบ default เลย
DevTools — เขียนเป็นเอกสาร ไม่ได้รัน (ไม่มี device ใน environment นี้)
หัวข้อที่มีชื่อว่า “DevTools — เขียนเป็นเอกสาร ไม่ได้รัน (ไม่มี device ใน environment นี้)”ตามข้อจำกัดหลักของโมดูลนี้ (ไม่มี emulator/device อยู่ใน environment นี้) widget inspector, layout explorer, และหน้าจอ performance/memory ของ DevTools ไม่ได้ถูก screenshot ที่นี่ สิ่งที่ DevTools จะเพิ่มเติมจากทุกอย่างข้างบน ตาม docs ของมันเอง: widget-tree inspector แบบสด (จะแสดงการขาด ProviderScope จริง ๆ ในต้นไม้ ไม่ใช่แค่ข้อความ crash ที่ตามมา), layout explorer ที่แสดง constraint โดยตรงเป็นภาพ (จะแสดงความสูงไม่มีขอบเขตของ entry 9 เป็นภาพก่อนที่มัน throw), และ memory view ที่จะแสดง Ticker ที่รั่วของ entry 22 สะสมไปตามเวลา ไม่ใช่แค่ assertion ตอน dispose ครั้งเดียวที่ catalog นี้จับไว้
DartPad — ฝังไว้ที่อื่นในคอร์สนี้ ไม่ซ้ำที่นี่
หัวข้อที่มีชื่อว่า “DartPad — ฝังไว้ที่อื่นในคอร์สนี้ ไม่ซ้ำที่นี่”component DartPad.astro ของคอร์สนี้ (ฝังไว้ใน dart-language-tour และ widgets-and-the-tree) ดูแลแยกต่างหากจากโมดูลนี้ snippet ส่วนใหญ่ใน catalog นี้ต้องการ package นอก whitelist ของ DartPad (flutter_riverpod, go_router) หรือการรัน flutter test เต็มรูปแบบที่ DartPad ทำไม่ได้ บทนี้จึงไม่พยายามฝัง DartPad ของตัวเอง
harness ของคอร์สนี้เอง: tools/verify-snippets.mjs
หัวข้อที่มีชื่อว่า “harness ของคอร์สนี้เอง: tools/verify-snippets.mjs”ทุก fence ในโมดูลนี้ที่ประกาศ path จริงในบรรทัดแรก (// lib/ai-bug-catalog/<name>.dart, // test/ai-bug-catalog/<name>_test.dart) ถูกเก็บรวบรวมและตรวจสอบโดย harness ของ repo นี้เอง — อ่าน comment ส่วนหัวของ tools/verify-snippets.mjs (ราว 150 บรรทัดแรก) ก่อนที่จะเชื่อคำอธิบายใดก็ตามเกี่ยวกับมันแบบมือสอง รวมถึงคำอธิบายนี้ด้วย ผลลัพธ์จริง รันสดกับ catalog ของโมดูลนี้เป๊ะ:
$ npm run -s verifyreading-flutter: 91 / 1 / 6 / 19 # collected / skipped-no-path / expect-error / tests
$ npm run -s verify -- --test reading-flutter/ai-bug-catalogreading-flutter/ai-bug-catalog: 16/16 passedfence ที่บรรทัดแรกมี @expect-error (buggy snippet ของ entry 5, 14, 27, และ 28 ใน catalog) จงใจไม่ถูกเก็บรวบรวม — harness ข้ามมันไปทั้งหมด และเนื้อหาบทเรียนพกพาผลลัพธ์จริงที่จับแยกต่างหากมาแทน เหมือนกับทุกโมดูลอื่นในคอร์สนี้เป๊ะ
ข้อจำกัดที่ระบุไว้ตรง ๆ ของ harness นี้ อ่านจาก header ของมันเอง: มันไม่มีโหมด --build — flutter build web/apk ต่อบทเรียนถูกประเมินว่าช้าเกินไปสำหรับการตรวจสอบตามปกติ และนี่เป็นช่องว่างที่บันทึกไว้และยอมรับแล้ว ไม่ใช่การมองข้าม (การพิสูจน์ flutter build web --release/--wasm ของบทนี้เองสำหรับ entry 19 และ 26 รันด้วยมือในโปรเจกต์ scratch ด้วยเหตุผลนี้เป๊ะ ไม่ได้รันผ่าน harness) output ของ codegen (build_runner, riverpod_generator, ไฟล์ .g.dart) ก็ไม่ถูกสร้างโดย harness เช่นกัน — บทเรียนที่ต้องการ generated code จะ ship เนื้อหา .g.dart เป็น fence จริงของตัวเองและบอกไว้ในเนื้อหาว่ามันถูก generate มา catalog ของโมดูลนี้ไม่มี entry ที่พึ่ง codegen เลย ข้อจำกัดนี้จึงไม่กระทบที่นี่โดยตรง
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”flowchart TD
subgraph Static["Static (no run needed)"]
Analyze["flutter analyze<br/>types/syntax only, never runtime behavior"]
Fix["dart fix --dry-run<br/>mechanically fixable, not necessarily CORRECTLY fixed"]
Lint["riverpod_lint (native plugin)<br/>missing_provider_scope and more"]
end
subgraph Dynamic["Needs a real run"]
Test["flutter test<br/>proves behavior; --coverage counts real executions"]
Golden["flutter test --update-goldens<br/>catches ANY visual regression, brittle across environments"]
Build["flutter build web / --wasm<br/>proves it COMPILES, never that it's safe to RUN"]
end
subgraph NotRun["Documented, not run here"]
DevTools["DevTools<br/>no device in this environment"]
DartPad["DartPad<br/>most snippets need packages outside its whitelist"]
end
subgraph Harness["This course's own harness"]
Verify["npm run verify / --test<br/>collects real path-commented fences only"]
end