การจัดระเบียบข้อมูล
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”กลุ่ม organizing data ว่าด้วย รูปร่าง ของค่าที่โปรแกรมขนไปมา และ เส้นทาง ที่ code ใช้เข้าถึงค่าเหล่านั้น โปรแกรมจะชัดเจนได้มากที่สุดเท่าที่ข้อมูลชัดเจนเท่านั้น เมื่อ field หนึ่งถูกแตะได้จากทุกที่ เมื่อเลข 0.07 ดิบ ๆ ลอยผ่านสามโมดูลโดยไม่มีชื่อ หรือเมื่อ string ธรรมดาแอบแบกกฎการจัดรูปแบบของตัวเองไว้ ข้อมูลนั้นกำลังทำงานต่อต้านคุณ refactoring กลุ่มนี้ขีดขอบเขตล้อมข้อมูล ตั้งชื่อให้ และปล่อยให้ข้อมูลโตขึ้นพร้อม behavior ที่เรียกร้องมาตลอดอย่างเงียบ ๆ
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”ข้อมูลที่ยุ่งเหยิงโผล่มาในรูปแบบเล็ก ๆ ที่เกิดซ้ำ ๆ ค่าคงที่ตัวเดียวกันปรากฏในห้าไฟล์โดยไม่มีใครจำได้ว่าแปลว่าอะไร field ที่เป็น list ถูกส่งต่อออกไป แล้ว caller ไกล ๆ ก็ push ลงไป ทำลาย invariant ที่เจ้าของไม่เคยตกลงด้วย เบอร์โทรถูกเก็บเป็น string ทุกการ validate และการจัดรูปแบบจึงถูก copy-paste ซ้ำที่จุดใช้งานทุกแห่ง แต่ละอย่างไม่ร้ายแรงเลย แต่พอรวมกันก็ทำให้ codebase เปราะ เพราะเปลี่ยน วิธีเก็บ ข้อมูลทีเดียว แรงกระเพื่อมจะไปถึงทุกจุดที่อ่านข้อมูลนั้นโดยตรง
| Code Smell | อาการที่บ่งชี้ | ท่าที่แนะนำ |
|---|---|---|
| Primitive Obsession | primitive ที่สะสม validation และ formatting ไว้รอบตัว | Replace Primitive with Object |
| Magic Number / String | ค่าคงที่ลอยตัวที่ไม่มีชื่อ โผล่ซ้ำในหลายไฟล์ | Replace Magic Literal |
| Mutable Data | field ลิสต์ถูก mutate จากข้างนอก ทำให้ invariant พัง | Encapsulate Collection |
| Switch Statement | type code ขับเคลื่อน switch ที่กระจายอยู่ในหลาย function | Replace Type Code with Subclasses |
| Global Data | ตัวแปรที่ถูกอ่านและเขียนจากทุกที่โดยไม่มีจุดควบคุม | Encapsulate Variable |
flowchart TD
A["Data that is awkward to work with"] --> B{"What is wrong?"}
B -->|"A field is read and written everywhere"| C["Encapsulate Variable"]
B -->|"A bare number or string has no name"| D["Replace Magic Literal"]
B -->|"A list or map field is mutated from outside"| E["Encapsulate Collection"]
B -->|"A primitive is sprouting behaviour"| F["Replace Primitive with Object"]
B -->|"A type code drives branching logic"| G["Replace Type Code with Subclasses"]
C --> H["Data with a clear, guarded shape"]
D --> H
E --> H
F --> H
G --> H โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”refactoring สิบท่า แต่ละท่ากระชับความสัมพันธ์ระหว่างข้อมูลกับ code ที่ใช้ข้อมูลนั้น
- Encapsulate Variable — ห่อหุ้มการเข้าถึงข้อมูลโดยตรงไว้หลัง function accessor เพื่อให้คุณมีจุดเดียวสำหรับตรวจสอบความถูกต้อง บันทึก log หรือเปลี่ยนวิธีแทนค่าในภายหลัง
- Replace Magic Literal — ให้ชื่อเชิงสัญลักษณ์แก่ตัวเลขหรือ string ที่ไม่มีคำอธิบาย เพื่อให้ค่าคงที่อธิบายความหมายของตัวเอง และเปลี่ยนแปลงได้ในจุดเดียว
- Encapsulate Collection — หยุดเปิดเผย field ลิสต์หรือแมปที่เปลี่ยนแปลงได้ คืนค่าเป็น view แบบอ่านอย่างเดียว และเสนอ method add กับ remove อย่างชัดเจน เพื่อให้เจ้าของยังควบคุมเนื้อหาได้
- Replace Primitive with Object — เมื่อค่า primitive เริ่มดูดกฎ validate และกฎจัดรูปแบบเข้ามา ให้ยกระดับเป็น value type เล็ก ๆ ที่ถือทั้งข้อมูลและ behavior
- Replace Type Code with Subclasses — เมื่อ field Type Code ขับเคลื่อนการแตกกิ่งแบบ
switchให้แทนที่ logic เงื่อนไขด้วย polymorphism ที่กระจายอยู่ทั่วลำดับชั้น class ขนาดเล็ก - Self-Encapsulate Field — ให้แม้แต่การอ่านและเขียน field ภายใน class ตัวเองก็วิ่งผ่าน getter และ setter ของ field นั้น subclass และการ validate จะได้มีจุดเดียวให้เกาะ
- Split Variable — เมื่อตัวแปรหนึ่งถูกกำหนดค่าใหม่เพื่อหมายถึงสองสิ่งที่ต่างกัน ให้แต่ละจุดประสงค์มีตัวแปรที่กำหนดค่าครั้งเดียวเป็นของตัวเอง
- Rename Field — เมื่อชื่อ field ของ record หรือ struct ไม่ตรงกับข้อมูลอีกต่อไป ให้เปลี่ยนชื่อแล้วอัปเดตทุกจุดที่ใช้ ชื่อจะได้กลับมาบอกความจริง
- Change Reference to Value (and back) — ทำให้ object ขนาดเล็กเป็น value ที่ไม่เปลี่ยนแปลงและเปรียบเทียบกันด้วยเนื้อหา เมื่อการแบ่งปันไม่ได้ให้ประโยชน์อะไร หรือกลับท่าตรงข้ามเมื่อหลาย ๆ ที่ต้องแบ่งปัน instance เดียวที่อัปเดตได้
- Replace Type Code with State/Strategy — เมื่อ Type Code ขับเคลื่อน behavior และเปลี่ยนแปลงได้ขณะ runtime ให้ถือ object State หรือ Strategy ที่สลับเปลี่ยนได้ แทนที่จะใช้ subclass ตายตัว
แต่ละบทเรียนแสดงรูปแบบก่อน-และ-หลังเดียวกันในภาษา TypeScript, Python, Go และ Rust จากนั้นจึงเดินผ่านกลไกที่ปลอดภัยและเป็นลำดับ — โดยรัน test ระหว่างทุกขั้นตอน