Replace Loop with Pipeline
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”loop ที่เดินบน collection ข้ามบางสมาชิก แปลงบางตัว แล้วสะสมผลลัพธ์ คือการทำงานหลายอย่างพันกันอยู่ collection pipeline คลายปมนั้นออก แต่ละ operation — filter, map, reduce — กลายเป็นขั้นตอนแยกกันที่มีชื่อ และข้อมูลไหลผ่านตามลำดับ ผู้อ่านตามข้อมูลไป แทนที่จะต้องคอยตาม accumulator ที่เปลี่ยนค่าได้กับ index ส่วน behavior เหมือนเดิมทุกอย่าง เปลี่ยนแค่รูปทรงจาก “วน loop ยังไง” เป็น “แต่ละขั้นทำอะไร”
อาการของปัญหา
หัวข้อที่มีชื่อว่า “อาการของปัญหา”คุณเห็น for loop ที่มีตัวแปรผลลัพธ์ประกาศคร่อมอยู่ข้างบน มี continue หรือ if คอยกั้นอยู่ข้างใน และมีการแปลงค่าฝังตรงกลาง จะเข้าใจได้ต้องรันในหัว พร้อมจำ state ปัจจุบันของ accumulator ไปด้วย loop เดียวนี้เอา การเลือก ว่าสมาชิกไหนนับ การแปลงค่า และ การรวมผล มายำรวมกัน สามแนวคิดที่ต่างกันโดยไม่มีรอยต่อให้เห็น
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”งานคือเก็บชื่อลูกค้า premium ใน region หนึ่ง แบบ imperative เป็น loop เดียวที่รวม guard การอ่าน field และการ append ไว้ด้วยกัน ส่วนแบบ pipeline คือ filter แล้ว filter แล้ว map
// Beforefunction premiumNames(customers: Customer[], region: string): string[] { const names: string[] = []; for (const c of customers) { if (c.region !== region) continue; if (!c.isPremium) continue; names.push(c.name.toUpperCase()); } return names;}
// Afterfunction premiumNames(customers: Customer[], region: string): string[] { return customers .filter((c) => c.region === region) .filter((c) => c.isPremium) .map((c) => c.name.toUpperCase());}# Beforedef premium_names(customers, region): names = [] for c in customers: if c.region != region: continue if not c.is_premium: continue names.append(c.name.upper()) return names
# Afterdef premium_names(customers, region): in_region = (c for c in customers if c.region == region) premium = (c for c in in_region if c.is_premium) return [c.name.upper() for c in premium]// Beforefunc PremiumNames(customers []Customer, region string) []string { names := []string{} for _, c := range customers { if c.Region != region { continue } if !c.IsPremium { continue } names = append(names, strings.ToUpper(c.Name)) } return names}
// After — Go has no built-in pipeline, so keep one loop with named stepsfunc PremiumNames(customers []Customer, region string) []string { names := []string{} for _, c := range customers { inRegion := c.Region == region isPremium := c.IsPremium if inRegion && isPremium { names = append(names, strings.ToUpper(c.Name)) } } return names}// Beforefn premium_names(customers: &[Customer], region: &str) -> Vec<String> { let mut names = Vec::new(); for c in customers { if c.region != region { continue; } if !c.is_premium { continue; } names.push(c.name.to_uppercase()); } names}
// Afterfn premium_names(customers: &[Customer], region: &str) -> Vec<String> { customers .iter() .filter(|c| c.region == region) .filter(|c| c.is_premium) .map(|c| c.name.to_uppercase()) .collect()}กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- หา collection ที่ loop เดินผ่าน และระบุว่าผลลัพธ์ที่สร้างออกมาคืออะไร
- แปล logic แต่ละส่วนของ loop เป็นขั้นตอนหนึ่งขั้น guard แบบ
continueกลายเป็นfilterการแปลงค่าต่อสมาชิกกลายเป็นmapการสะสมเป็นค่าเดียวกลายเป็นreduce(หรือsum,joinและพวกพ้อง) - ต่อ pipeline ทีละขั้น จะปล่อย loop เดิมไว้ข้าง ๆ ไว้เทียบก็ได้
- แทนที่ loop ด้วย pipeline ที่ต่อเสร็จแล้ว แล้ว return ผลลัพธ์ออกไป
- รัน test ผลลัพธ์ต้องตรงกับ loop เป๊ะ รวมถึงเคส input ว่างด้วย
- ในภาษาที่ไม่มี pipeline ในตัวอย่าง Go อย่าฝืนสร้างขึ้นมา ใช้ loop เดียวต่อไปแต่ยกแต่ละเงื่อนไขขึ้นเป็น boolean ที่มีชื่อชัด ๆ ขั้นตอนจะได้ยังอ่านเป็นแนวคิดที่แยกจากกัน
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”หยิบ pipeline มาใช้เมื่อ loop ปน filter, transform และ accumulate เข้าด้วยกัน การซอยเป็นขั้น ๆ ทำให้แต่ละเจตนาชัด และอ่านเส้นทางของข้อมูลไล่จากบนลงล่างได้ ยิ่งได้ผลเมื่อ operation เป็น pure และไม่ขึ้นกับลำดับ
ข้อแลกเปลี่ยนคือ loop ที่มี break ออกกลางทาง มี index ที่มีความหมาย หรือมี side effect ใน body พอแปลงเป็น pipeline แล้วมักออกมาอึดอัดหรือชวนเข้าใจผิด และ chain ที่ยาวเกินไปก็กลายเป็นปริศนาชุดใหม่ได้เหมือนกัน pipeline บางตัวยังจอง collection กลางเพิ่มด้วย ใช้ตรงจุดที่ทำให้ชัดขึ้นเท่านั้น และอย่าลืมข้อสังเกตเรื่อง Go ข้างบน — loop ที่ซอยเป็นขั้นตอนมีชื่อคือของเทียบเท่าที่เข้ากับสไตล์ Go มากกว่า
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Replace Loop with Pipeline เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| loop ทำหลายขั้น: filter + transform + reduce | loop มี side effect (I/O, mutation) ใน body |
| ต้องการ chain operation หลายตัวเป็น readable flow | ทีมไม่คุ้นกับ functional style — อ่านยากกว่า loop |
| logic ใน loop อ่านยากเพราะมีหลาย condition | performance สำคัญมาก — pipeline สร้าง intermediate collection |
⚠️ ไม่ควร Replace Loop with Pipeline เมื่อ:
- loop ต้องใช้ index ของ element (
for i, v in enumerate(...))- loop มีการ break กลางคัน — pipeline ไม่รองรับ early exit แบบง่าย
- ต้องการความเร็วสูงสุด — pipeline สร้าง allocation เพิ่ม