วิธีอ่านโค้ด Flutter ที่คุณไม่ได้เขียนเอง
ทุกโมดูลก่อนหน้านี้สอนให้คุณ เขียน widget, state, navigation, และโค้ด platform ส่วนโมดูลนี้สอนอีกครึ่งหนึ่ง: การอ่าน StatefulWidget, ConsumerWidget, ค่า config ของ go_router, หรือทั้งฟีเจอร์ที่คนอื่น — หรือ AI — เขียนไว้ แล้วตัดสินว่ามันถูกต้องจริงหรือไม่ AI assistant ยื่นหน้าจอที่ render ได้สวยงามตั้งแต่รันครั้งแรก และความสำเร็จนั้นแหละคือกับดัก: emulator แสดงผลถูกต้อง, flutter analyze เงียบสนิท, และ demo ทำงานได้ — ในขณะที่โค้ดแอบสมมติว่า widget จะไม่มีวัน dispose กลางคัน, list จะไม่มีวันสลับลำดับ, isolate boundary จะไม่มีวันเจอ BuildContext, หรือ route หนึ่งจะไม่มีวันถูกเข้าก่อน sibling ของมัน เครื่องมือของ Flutter แทบไม่ตรวจสอบ พฤติกรรม เลย flutter analyze พิสูจน์แค่ว่า type ตรงกัน มันไม่มีความเห็นว่า setState หลัง await ปลอดภัยหรือไม่, ขาด key หรือเปล่า, หรือ ListView มีที่ให้วางความสูงหรือไม่
มีสามอย่างที่ทำให้เรื่องนี้แย่ลงเฉพาะกับ Flutter สามอย่าง อย่างแรก Flutter และ Dart เปลี่ยนรูปแบบจริง ๆ กลางช่วงข้อมูลฝึกสอน: WillPopScope ถูก deprecate ไปเป็น PopScope, StateNotifierProvider/StateProvider ย้ายไปอยู่หลัง import legacy.dart ของ Riverpod, class modifiers กับ sealed class เปลี่ยนวิธีเขียน state union ไปเลย และ material_ui/cupertino_ui เริ่มแยกตัวออกจาก package:flutter/material.dart — ทำให้ snippet Flutter ที่ “ทันสมัย” จำนวนมากบนอินเทอร์เน็ตอธิบาย API ที่ compile ได้บน SDK เก่า แต่ขึ้น diagnostic จริงบน Flutter 3.47.5 / Dart 3.13 ที่คอร์สนี้ pin ไว้ อย่างที่สอง ตัวอย่าง Flutter สาธารณะจำนวนมากเป็นระดับ demo — หน้าจอเดียวที่ปรับให้ “render ได้ครั้งเดียว” ไม่ใช่แอปจริงที่ปรับให้ “รอดจากการหมุนจอ, rebuild, และ widget ที่ถูก dispose กลาง await” — ดังนั้น pattern ที่ไม่เป็นอันตรายใน gist 30 บรรทัด (ไม่มีการเช็ค mounted, ไม่มี key, สร้าง Future ใหม่ใน build()) จึงถูกก็อปไปแปะในโค้ดที่รับความเสี่ยงนี้ไม่ได้ อย่างที่สาม ทางแก้ที่เร็วที่สุดที่ AI assistant มักเลือกหลังจาก layout crash มักเป็นทางที่ใกล้ที่สุด ไม่ใช่ทางที่ถูกต้อง — เช่นห่อด้วย Expanded เพราะข้อความ error พูดถึงคำนั้น โดยไม่เช็คว่ามี Flex ให้มันเป็นลูกหรือเปล่า
flowchart TD A["1. pubspec & ข้อจำกัด SDK<br/>สมมติ Flutter/Dart เวอร์ชันไหน, package เวอร์ชันไหน?"] --> B["2. แนวทาง State<br/>Provider, Riverpod, Bloc, setState -- และต่อสายไว้จริงหรือเปล่า (ProviderScope)?"] B --> C["3. รูปทรง widget tree<br/>key บน children ที่สลับลำดับได้, โอกาสใช้ const, การใช้ GlobalKey"] C --> D["4. Lifecycle & async gap<br/>setState หลัง dispose, setState ระหว่าง build, ขาด mounted"] D --> E["5. Layout constraints<br/>ความสูงไม่มีขอบเขต, Expanded นอก Flex, overflow"] E --> F["6. Navigation<br/>ลำดับ route ของ go_router, go เทียบกับ push, redirect + refreshListenable"] F --> G["7. Platform & isolate<br/>dart:io บน web, MissingPluginException, compute() + BuildContext"] G --> H["8. Test, lint & release config<br/>lint อะไรทำงานจริง, print ใน release, --wasm, coverage"]
1. pubspec & ข้อจำกัดของ SDK
หัวข้อที่มีชื่อว่า “1. pubspec & ข้อจำกัดของ SDK”ก่อนดูอะไรเกี่ยวกับตัวโค้ด ให้เช็คก่อนว่าโค้ดนี้สมมติ SDK และ package เวอร์ชันอะไร — เพราะ API ของ Flutter เอง (PopScope, material_ui, การแยก legacy.dart ของ Riverpod) และพฤติกรรมการจับคู่ route ของ go_router เปลี่ยนไปตามเวอร์ชันที่คอร์สนี้ใช้ตรง ๆ และ snippet ที่เขียนบน SDK เก่ากว่าอาจยัง parse หรือแม้แต่ analyze ผ่านได้ แต่พอเจอ SDK 3.47.5 จริง ๆ กลับเจอ deprecation หรือชื่อที่หาไม่เจอ
- สิ่งที่ต้องมองหา:
environment: sdk:ในpubspec.yamlรองรับ Dart 3.13 จริงหรือเปล่า (หรือดูเหมือนก็อปมาจากโปรเจกต์ Dart 2 หรือ Dart 3 รุ่นแรก ๆ)? โค้ด importpackage:flutter_riverpod/flutter_riverpod.dartแล้วเรียกStateProvider/StateNotifierProviderตรง ๆ หรือเปล่า — ทั้งที่ API พวกนี้ย้ายไปอยู่หลัง import แยกlegacy.dartใน Riverpod 3 แล้ว?pubspec.yamlpincustom_lintกับriverpod_lintคู่กันโดยไม่เช็คว่า resolve ได้จริงหรือเปล่า (บ่อยครั้งไม่ได้ — พิสูจน์แล้วใน Verification Tools)? - คำถามที่ควรถาม: import นี้, named parameter นี้, class นี้ จะ resolve ได้จริงบน SDK และ package เวอร์ชันที่ประกาศไว้จริงหรือเปล่า — หรือแค่ดูสมเหตุสมผลเพราะรูปร่างของ API ไม่ได้เปลี่ยนแม้ว่า entry point จะเปลี่ยนไปแล้ว?
- บทที่อธิบายเรื่องนี้: กลุ่ม State & lifecycle ใน AI Bug Catalog สำหรับ diagnostic จริง
undefined_functionที่StateProviderสร้างขึ้นโดยไม่มีlegacy.dart; Project and Tooling สำหรับโครงสร้างpubspec.yamlแบบเต็ม
2. แนวทาง State
หัวข้อที่มีชื่อว่า “2. แนวทาง State”เมื่อยืนยัน SDK แล้ว ให้เช็คว่าโค้ดอ้างว่าใช้แนวทาง state-management แบบไหน และต่อสายไว้จริงหรือเปล่า — ความล้มเหลวที่พบบ่อยที่สุดของ Riverpod ไม่ใช่คำตอบผิด แต่คือ crash ทันทีที่ ConsumerWidget พยายามอ่านอะไรก็ตาม เพราะ widget ตัวเดียวที่ทำให้ state อ่านได้เลยไม่เคยถูกเพิ่มเข้ามา
- สิ่งที่ต้องมองหา: มี
ProviderScopeอยู่เหนือConsumerWidget/ref.watchตัวแรกหรือเปล่า? provider แบบ.familyจับคู่กับ.autoDisposeหรือเปล่า (.familyที่ไม่จับคู่จะรั่วสร้าง controller ทีละตัวต่อค่าพารามิเตอร์ ตลอดไป)?ref.readถูกเรียกในbuild()(ไม่ reactive แบบเงียบ ๆ) หรือref.watchถูกเรียกใน callback (throw)? โค้ดหยิบStateNotifierProvider/StateProviderจาก tutorial Riverpod 2 มาใช้ ทั้งที่ Riverpod 3 ของคอร์สนี้ย้ายทั้งคู่ไปไว้หลัง importlegacy.dartแบบชัดเจนแล้วหรือเปล่า? - คำถามที่ควรถาม: ถ้า widget ตัวนี้เป็น
ConsumerWidgetตัวแรกที่ build หลังrunApp()มีอะไรอยู่เหนือมันในต้นไม้ที่สร้างProviderContainerที่refของมันต้องการจริง ๆ หรือเปล่า — หรือมันทำงานได้เพราะบังเอิญเท่านั้น เพราะหน้าจอบรรพบุรุษบางหน้าเผอิญเพิ่ม scope ไว้แล้ว? - บทที่อธิบายเรื่องนี้: กลุ่ม State & lifecycle ใน AI Bug Catalog (ขาด
ProviderScope,StateProviderแบบ legacy) และ Verification Tools สำหรับผลลัพธ์จริงของriverpod_lintrulemissing_provider_scope; Provider and Riverpod สำหรับกลไกแบบเต็ม
3. รูปทรงของ widget tree: key และ const
หัวข้อที่มีชื่อว่า “3. รูปทรงของ widget tree: key และ const”เมื่อยืนยันว่า state ต่อสายไว้แล้ว ให้เช็ครูปทรงของ widget tree เอง — Flutter จับคู่ widget กับ State ของมันด้วย type และตำแหน่ง เว้นแต่จะบอกเป็นอย่างอื่น และ AI assistant ที่ไม่เคยต้อง debug list ที่สลับลำดับ จะไม่มีสัญชาตญาณว่าเมื่อไหร่ค่า default นี้จะแอบผูก state ผิดแถวแบบเงียบ ๆ
- สิ่งที่ต้องมองหา: list ของ stateful widget ที่สร้างด้วย
for/.map()มีkeyหรือเปล่า ในเมื่อลำดับของมันเปลี่ยนได้ (ลาก-เรียงใหม่, sort, filter)?GlobalKeyตัวเดียวกันถูกใช้ซ้ำข้าม sibling widget แทนที่จะใช้UniqueKey()/ValueKeyต่อรายการหรือเปล่า? widget ที่เป็นค่าคงที่ตอน compile ถูกทำเครื่องหมายconstจริงหรือเปล่า — และชุด lint ของโปรเจกต์จับตัวที่ไม่ใช่ const ได้หรือเปล่า (flutter_lints6 ไม่มี ruleprefer_const_constructorsแบบทั่วไป มีแค่ตัวแคบกว่าคือ_in_immutables)? - คำถามที่ควรถาม: ถ้าลำดับของ list นี้เปลี่ยนใน frame ถัดไป — เรียงใหม่, ลบ, filter — state ท้องถิ่นของแต่ละแถว (checkbox, สถานะขยาย/ยุบ, ตำแหน่ง cursor ของ text field) เดินทางไป พร้อมกับ ข้อมูลของมันหรือเปล่า หรือติดอยู่กับตำแหน่งบนจอแล้วไปจบที่แถวผิด?
- บทที่อธิบายเรื่องนี้: กลุ่ม Widget tree & keys ใน AI Bug Catalog พิสูจน์ด้วย test การเรียงใหม่จริงและ
FlutterErrorจริงข้อความDuplicate keys found; Keys สำหรับกลไกแบบเต็ม
4. Lifecycle & async gap
หัวข้อที่มีชื่อว่า “4. Lifecycle & async gap”บั๊กประเภทที่พบบ่อยที่สุดของโค้ด Flutter ที่ AI สร้างไม่ใช่คำตอบผิดเลย — แต่เป็นคำตอบที่ ถูกต้อง ซึ่งคำนวณเสร็จหลังจาก widget ที่ขอมันหายไปแล้ว เพราะไม่มีอะไรเช็ค mounted ระหว่าง await กับ setState
- สิ่งที่ต้องมองหา: ทุก
setStateที่เข้าถึงได้จาก.then(), การทำงานต่อหลังawait,Timer, หรือStreamlistener เช็คmountedก่อนหรือเปล่า?build()ของ widget หนึ่งเรียกsetStateบน widget อื่น แบบ synchronous จากภายในbuild()ของตัวมันเองหรือเปล่า (กับดัก “cross-sibling”)? field แบบlateเคยถูกอ่านบน code path ที่ method ซึ่งกำหนดค่าให้มันอาจยังไม่ทำงานหรือเปล่า? - คำถามที่ควรถาม: ถ้า user นำทางออกไป, เลื่อน widget นี้ออกจาก list ที่สร้างแบบ lazy, หรือ dispose
Stateobject นี้ ก่อน ที่Future/Streamevent ที่ค้างอยู่พอดีจะ resolve มีอะไรหยุด callback ที่ตามมาไม่ให้แตะStateที่ไม่มีอยู่แล้วหรือเปล่า? - บทที่อธิบายเรื่องนี้: กลุ่ม State & lifecycle ใน AI Bug Catalog พิสูจน์ด้วย error จริง
setState() called after dispose()และsetState() or markNeedsBuild() called during buildที่จับสด ๆ สำหรับโมดูลนี้; setState and Lifecycle สำหรับลำดับ lifecycle แบบเต็ม
5. Layout constraints
หัวข้อที่มีชื่อว่า “5. Layout constraints”Layout crash ของ Flutter เกือบทั้งหมดมีรูปแบบเดียวกันเมื่อรู้กติกาแล้ว — แกนที่อยากมีขนาดเท่ากับเนื้อหาของมันถูกวางไว้ในที่ที่ให้พื้นที่ไม่จำกัดในแกนเดียวกันนั้น — และโค้ดที่ AI สร้างมักหยิบทางแก้แรกที่พูดถึงชื่อ widget ที่ถูกต้อง (Expanded) โดยไม่เช็คว่ามี Flex ให้มันเป็นลูกหรือเปล่า
- สิ่งที่ต้องมองหา:
ListView/GridView/scrollable อื่นถูกวางตรง ๆ ในColumn/Rowโดยไม่มีอะไรจำกัดขนาดแกนหลักของมันหรือเปล่า? (ในทางกลับกัน —Columnข้างในListViewไม่ต้องการอะไรเพิ่ม; scrollable ให้ children ของมัน cross-axis ที่มีขอบเขตและ main-axis ที่ไม่มีขอบเขตอยู่แล้ว ซึ่งตรงกับที่Columnที่มีขนาดตามธรรมชาติต้องการพอดี)Expanded/Flexibleถูกใช้เป็นลูกของสิ่งที่ไม่ใช่Row/Column/Flexหรือเปล่า?Rowของ children ที่มีความกว้างคงที่ไม่มีExpanded/Flexible/scrolling เป็นทางออกสำหรับเนื้อหาที่กว้างกว่า viewport หรือเปล่า? - คำถามที่ควรถาม: เดินจาก widget นี้ขึ้นไปหาบรรพบุรุษที่ใกล้ที่สุดที่ให้ constraint แบบมีขอบเขตในแกนที่ overflow หรือ crash มีอยู่จริงหรือเปล่า — และถ้าขนาดตามธรรมชาติของ widget นี้เกินกว่าที่มีอยู่จริง มี flex factor, scrollable, หรือ clip คั่นอยู่ระหว่างพวกมันหรือเปล่า?
- บทที่อธิบายเรื่องนี้: กลุ่ม Layout ใน AI Bug Catalog พิสูจน์ด้วย error จริง
Vertical viewport was given unbounded height,Incorrect use of ParentDataWidget, และRenderFlex overflowed; Constraints and Layout สำหรับโมเดล constraints-down-sizes-up แบบเต็ม
6. Navigation
หัวข้อที่มีชื่อว่า “6. Navigation”การจับคู่ route ของ go_router ไวต่อลำดับการประกาศในแบบที่ type system ไม่จับได้เลย และ callback redirect ของมัน default จะทำงานซ้ำเฉพาะตอนเกิด navigation event เท่านั้น — ทั้งสองอย่างเป็นจุดที่ config การ routing ที่ AI สร้าง compile ได้, รันได้, แล้วทำผิดแบบเงียบ ๆ
- สิ่งที่ต้องมองหา: route ที่มีพารามิเตอร์ (
/pets/:id) ถูกประกาศ ก่อน route แบบ static ที่เฉพาะเจาะจงกว่าในระดับ segment เดียวกัน (/pets/new) หรือเปล่า?context.go()ถูกใช้ในจุดที่ user ต้องกดย้อนกลับได้หรือเปล่า (มันแทนที่ stack ไม่ใช่ push เข้าไป)? callbackredirectอ่าน state ภายนอก (auth flag) โดยไม่ได้ชี้refreshListenableของ router ไปที่สิ่งที่แจ้งเตือนเมื่อ state นั้นเปลี่ยนหรือเปล่า? - คำถามที่ควรถาม: สำหรับตาราง route นี้ ในลำดับการประกาศนี้ route ไหน match กับ
/pets/newก่อนจริง ๆ — และถ้าค่าที่redirectนี้อ่านเปลี่ยนไปโดยไม่มี navigation event เกิดขึ้นพร้อมกัน มีอะไรบอกให้ router ประเมินใหม่หรือเปล่า? - บทที่อธิบายเรื่องนี้: กลุ่ม Navigation ใน AI Bug Catalog พิสูจน์ด้วย test route-order ที่ misroute จริง, การเปรียบเทียบความลึกของ stack ระหว่าง
go/pushจริง, และ testredirectที่ค้างหน้า login จริง; Declarative Routing และ Navigator and Routes สำหรับทั้งสองกลไกแบบเต็ม
7. Platform & isolate
หัวข้อที่มีชื่อว่า “7. Platform & isolate”โค้ดที่ต้องรันเหมือนกันทั้งบนมือถือ, desktop, และ web หรือที่ offload งานไป isolate แยก คือจุดที่โมเดลความคิด “Dart เดียว, runtime เดียว” ของ AI assistant พังชัดเจนที่สุด — บั๊กบางส่วนเกิดตอน compile ส่วนใหญ่เงียบจนกว่าจะเจอ platform เฉพาะหรือค่าที่ถูก capture เฉพาะ
- สิ่งที่ต้องมองหา:
dart:io/Platform.isIOS/Fileถูกใช้แบบไม่มีเงื่อนไขในโค้ดที่ต้อง build สำหรับ web ด้วยหรือเปล่า (มัน compile ผ่านสำหรับ web ได้สบาย — ทุก member แค่ throwUnsupportedErrorทันทีที่มันรันจริง)? การเรียกMethodChannel.invokeMethodสมมติว่ามี native handler ลงทะเบียนไว้ทุก platform ที่แอป ship ไปหรือเปล่า? closure ของcompute()captureBuildContext,Zone, หรืออะไรก็ตามที่ผูกกับ binding ของ isolate นี้เองหรือเปล่า (ข้อมูลล้วน ๆ และแม้แต่บาง closure ก็ข้าม isolate boundary ได้สำเร็จใน Dart รุ่นปัจจุบัน — แต่อะไรที่เข้าถึงได้จาก Element tree ยังทำไม่ได้อยู่ดี)? - คำถามที่ควรถาม: ถ้า function นี้รันบน platform เดียว (หรือ isolate เดียว) ที่ไม่เคยถูกทดสอบจริง ๆ มาก่อน ทุกค่าที่มันแตะยังมีความหมายอยู่ที่นั่นหรือเปล่า — หรือมันทำงานได้แค่เพราะทุก environment ที่โค้ดนี้ถูกเขียนและทดสอบด้วยบังเอิญมีสมมติฐานร่วมกันที่โค้ดไม่เคยระบุไว้?
- บทที่อธิบายเรื่องนี้: กลุ่ม Platform ใน AI Bug Catalog พิสูจน์ด้วย
flutter build web --releaseจริงที่ compiledart:ioผ่านสบาย,MissingPluginExceptionจริง, และข้อความIllegal argument in isolate message: object is unsendableจริงที่จับได้จาก closure ของcompute()ที่ captureBuildContext; Platform Channels และ Isolates and compute() สำหรับทั้งสองกลไกแบบเต็ม
8. Test, lint & release config
หัวข้อที่มีชื่อว่า “8. Test, lint & release config”ช่วงสุดท้ายคือการเช็คว่า tooling ของโปรเจกต์เองจับสิ่งข้างต้นได้จริงหรือเปล่า — flutter analyze สีเขียวพิสูจน์น้อยกว่าที่มันดูเหมือนจะพิสูจน์มาก และ print() ที่ “แน่นอนว่า” ต้องถูกตัดออกจาก release build ก็ไม่ได้เป็นแบบนั้น
- สิ่งที่ต้องมองหา: lint package อะไรถูก include จริง (
flutter_lints6 แคบกว่าชื่อเสียงของมัน — ไม่มีprefer_const_constructorsแบบทั่วไป เป็นต้น) และriverpod_lint/custom_lintresolve คู่กันได้จริงที่เวอร์ชันที่ pin ไว้หรือเปล่า (บ่อยครั้งไม่ได้ ตามที่คอร์สนี้เช็คเอง)? มีอะไรใน release pipeline ตัดprint()ออกหรือเปล่า (ไม่มี โดย default — มันรอดเข้าไปใน release web bundle แบบคำต่อคำ พิสูจน์แล้วสำหรับโมดูลนี้)?flutter build web --wasmเคยถูกใช้เป็น portability gate หรือเปล่า หรือมีแค่ default build แบบ JS? - คำถามที่ควรถาม: ถ้า CI ของโปรเจกต์นี้รันแค่
flutter analyzeแล้วถือว่าจบ บั๊กที่โมดูลนี้พิสูจน์แล้วข้อไหนจะยัง ship ออกไปอยู่ดี — และanalysis_options.yamlของโปรเจกต์ enable rule ตามที่ README ของมันอ้างจริงหรือเปล่า? - บทที่อธิบายเรื่องนี้: กลุ่ม Tooling & release ใน AI Bug Catalog พิสูจน์ด้วยการเทียบ lint package ของ
flutter_lints6 จริง และ stringprint()จริงที่พบคำต่อคำในmain.dart.jsของ release build; Verification Tools สำหรับบันไดเครื่องมือแบบเต็มและสิ่งที่แต่ละตัวพิสูจน์ได้จริง; Testing, Build and Release, และ Production Concerns สำหรับแต่ละหัวข้อแบบเต็ม
โมดูลนี้มีอะไรต่อ
หัวข้อที่มีชื่อว่า “โมดูลนี้มีอะไรต่อ”- The AI Bug Catalog — pattern เฉพาะเจาะจง แต่ละอันมีโค้ดที่ AI มักเขียน, ผลลัพธ์จริงที่จับสดสำหรับโมดูลนี้ (analyzer diagnostic,
flutter testที่ fail, หรือ runtime error), และทางแก้ - Review Checklist — pattern เดียวกันในรูปแบบ checklist หน้าเดียวแปดกลุ่ม เรียงตามลำดับการอ่านนี้ แต่ละบรรทัดลิงก์ไปยัง catalog entry และเครื่องมือที่พิสูจน์มัน
- Verification Tools — บันไดเครื่องมือจริง:
flutter analyze, โหมดเข้มของanalysis_options.yaml,flutter_lints/riverpod_lint/custom_lint(พร้อมข้อค้นพบเรื่อง version resolution จริงและเป็นปัจจุบัน),dart fix --dry-run,flutter test(--coverage, golden test),flutter build web --wasmเป็น portability gate, DevTools, DartPad, และ harness ของคอร์สนี้เอง - Worked review สามบท — Worked Review 1, Worked Review 2, Worked Review 3 — นำลำดับการอ่านนี้ไปใช้กับฟีเจอร์เต็ม ๆ ที่ AI สร้าง (แอป todo แบบ Riverpod codegen กับ
StatefulShellRouteของgo_router, แอปสื่อที่ครอบคลุม sliver/isolate/chunked upload, และแอปข้ามแพลตฟอร์มที่ครอบคลุมdart:io/web, platform channel, และ release build) บทเหล่านี้เขียนและดูแลแยกจากบทนี้ — ถ้าลิงก์ด้านล่าง 404 ระหว่างที่งานนั้นยังไม่เสร็จ ลำดับการอ่านและ catalog ด้านบนยังใช้ได้ด้วยตัวเอง - ทุกบท Phase 3 ก่อนหน้านี้ในคอร์สมี section
## Reading AI-generated codeของตัวเองพร้อม<SpotTheBug>อยู่แล้ว — catalog ของโมดูลนี้ไม่ซ้ำกับสิ่งเหล่านั้น มันรวบรวม pattern ชุดใหม่ที่กว้างกว่ารอบลำดับการอ่านเดียวกัน เริ่มจาก สารบัญคอร์ส ถ้าต้องการเส้นทางแบบบทต่อบทแบบเต็มแทน
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”ลำดับการอ่านโค้ด Flutter ที่คุณไม่ได้เขียนเอง:
1. pubspec & ข้อจำกัด SDK -> Flutter/Dart เวอร์ชันไหน, package เวอร์ชันไหน?2. แนวทาง State -> Provider/Riverpod/Bloc -- ต่อสายไว้จริงหรือเปล่า?3. รูปทรง widget tree -> key บน children ที่สลับลำดับได้, const, GlobalKey4. Lifecycle & async gap -> setState หลัง dispose, setState ระหว่าง build5. Layout constraints -> ความสูงไม่มีขอบเขต, Expanded นอก Flex6. Navigation -> ลำดับ route ของ go_router, go เทียบ push, redirect7. Platform & isolate -> dart:io บน web, MissingPluginException, compute()8. Test, lint & release -> lint อะไรทำงานจริง, print ใน release, --wasm
"มัน render ได้ดีบน emulator" ตอบคำถามพวกนี้แทบไม่ได้เลย -- catalog entry ส่วนใหญ่ของโมดูลนี้ render ได้สะอาดแล้วพังตอนหมุนจอ, rebuild, เรียงใหม่, navigate, หรือ releaseความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “ถ้า
flutter analyzeสะอาด แสดงว่าโค้ดถูกต้อง” พิสูจน์แล้วตลอดทั้ง catalog ของโมดูลนี้:ConsumerWidgetที่ไม่มีProviderScopeอยู่เหนือมัน,ListViewที่กำลังจะ overflow constraint ของมัน, และ closure ของcompute()ที่กำลังจะพังตอน runtime ล้วน analyze ผ่านสะอาดหมด —flutter analyzeพิสูจน์แค่ว่า type และ import resolve ได้ ไม่เคยพิสูจน์ว่า runtime invariant คงอยู่ - “มัน render ถูกต้องครั้งหนึ่งแล้ว แสดงว่าบั๊กแก้แล้ว” ตรงกันข้ามต่างหากที่ใกล้ความจริงกว่าสำหรับโมดูลนี้ส่วนใหญ่: การขาดเช็ค
mounted, การขาด key, และความผิดพลาดเรื่องลำดับ route ล้วน render ถูกต้อง ในรอบแรกสุด — มันแสดงราคาที่ต้องจ่ายก็ต่อเมื่อมีการหมุนจอ, เรียงใหม่, navigate รอบที่สอง, หรือ widget ถูก dispose กลางคัน - “โค้ด Flutter ที่ AI สร้างแค่ต้องการ prompt ที่ใหม่กว่า” ปัญหาไม่ใช่แค่ความล้าสมัย —
StateProviderยังมีอยู่จริงใน Riverpod 3 แค่อยู่หลัง import ที่ต่างออกไป โค้ดที่ล้มเหลวมักจะ อธิบาย API จริงที่ยังมีอยู่ แค่ไม่ใช่ตัวที่มันคิดว่ากำลังใช้อยู่ - “ความสำเร็จแบบเงียบ ๆ ปลอดภัยกว่า error” ตรงกันข้ามต่างหากที่ใกล้ความจริงกว่าใน catalog นี้:
dart:ioบน web compile ผ่านโดยไม่มี warning สักอันและ throw ก็ต่อเมื่อการเช็คPlatform.isIOSรันจริงในเบราว์เซอร์ — build ที่ปฏิเสธการ compile ไปเลยจะเป็นความล้มเหลวที่ปลอดภัยกว่า