Topic 12 กระสุนส่องวิถี

พร้อม ยิง แล้วค่อยเล็ง…

Anon

เรามักจะพูดถึงการยิงเป้าเวลาพัฒนา Software กัน แน่นอนว่าเราไม่ได้ยิงอะไรจริงๆ ที่สนามยิงปืน แต่มันก็ยังเป็น Metaphor ที่มีประโยชน์และเห็นภาพได้ชัดเจนมาก โดยเฉพาะอย่างยิ่ง มันน่าสนใจที่จะพิจารณาว่าจะ ยิงยังไง ให้โดนเป้าในโลกที่ซับซ้อนและเปลี่ยนแปลงอยู่ตลอดเวลา

คำตอบนั้นแน่นอนว่าขึ้นอยู่กับลักษณะของอาวุธที่คุณใช้เล็งอยู่ อาวุธหลายชนิดให้คุณเล็งได้แค่ครั้งเดียว แล้วก็ต้องรอดูว่าโดนเป้าหรือเปล่า แต่มันมีวิธีที่ดีกว่านั้น

คุณคงเคยดูหนัง รายการทีวี หรือเกมที่มีฉากคนยิงปืนกล ในฉากแบบนี้ คุณมักจะเห็นวิถีกระสุนเป็นเส้นแสงสว่างพาดผ่านอากาศ เส้นแสงเหล่านั้นมาจากกระสุนส่องวิถีนั่นเอง

กระสุนส่องวิถีจะถูกบรรจุแทรกเป็นระยะๆ ปนไปกับกระสุนปกติ เมื่อยิงออกไป ฟอสฟอรัสในตัวมันจะติดไฟและทิ้งวิถีที่ลุกสว่างไว้ตั้งแต่ปากกระบอกปืนไปจนถึงจุดที่มันโดน ถ้ากระสุนส่องวิถีโดนเป้า กระสุนปกติก็โดนเป้าเช่นกัน ทหารใช้กระสุนแบบนี้เพื่อปรับการเล็งให้แม่นขึ้น นี่คือ Feedback แบบ Real-time ที่ Pragmatic และเกิดขึ้นภายใต้สภาวะจริง

หลักการเดียวกันนี้ใช้ได้กับโปรเจกต์เช่นกัน โดยเฉพาะเวลาที่คุณกำลังสร้างอะไรบางอย่างที่ไม่เคยมีใครสร้างมาก่อน เราใช้คำว่า Tracer bullet development เพื่อสื่อภาพให้เห็นถึงความจำเป็นในการได้รับ Feedback ทันทีภายใต้สภาวะจริง ในขณะที่เป้าหมายยังคงเคลื่อนที่อยู่ตลอดเวลา

เหมือนกับพลปืน คุณกำลังพยายามยิงเป้าในความมืด เพราะ User ของคุณไม่เคยเห็นระบบแบบนี้มาก่อน Requirement ของพวกเขาจึงอาจจะยังคลุมเครือ เพราะคุณอาจต้องใช้ Algorithm เทคนิค ภาษา หรือ Library ที่คุณไม่คุ้นเคย คุณจึงต้องเจอกับสิ่งที่ไม่รู้จำนวนมาก และเพราะโปรเจกต์ต้องใช้เวลากว่าจะเสร็จ คุณแทบจะการันตีได้เลยว่า Environment ที่คุณทำงานอยู่จะเปลี่ยนไปก่อนที่งานจะเสร็จ

วิธีตอบสนองแบบคลาสสิกคือการเขียน Spec ของระบบให้ละเอียดยิบ ผลิตเอกสารเป็นตั้งๆ ระบุทุก Requirement ผูกมัดทุกสิ่งที่ไม่รู้ และกำหนดกรอบของ Environment ไว้ตายตัว แล้วก็ยิงปืนด้วยการคำนวณล่วงหน้าแบบ Dead Reckoning คำนวณครั้งใหญ่ครั้งเดียวไว้ก่อน แล้วค่อยยิงแล้วหวังว่าจะโดน

แต่ Pragmatic Programmer มักจะชอบใช้สิ่งที่เทียบเท่ากับกระสุนส่องวิถีในโลกของ Software มากกว่า

Code That Glows in the Dark (โค้ดที่เรืองแสงอยู่ในความมืด)

กระสุนส่องวิถีได้ผลเพราะมันทำงานอยู่ใน Environment เดียวกันและภายใต้เงื่อนไขเดียวกันกับกระสุนจริง มันไปถึงเป้าหมายอย่างรวดเร็ว พลปืนจึงได้ Feedback ทันที และในทางปฏิบัติ มันก็เป็นวิธีแก้ปัญหาที่ค่อนข้างถูกด้วย

เพื่อให้ได้ผลแบบเดียวกันใน Code เราจะมองหาบางอย่างที่พาเราจาก Requirement ไปสู่บางส่วนของระบบสุดท้ายได้อย่างรวดเร็ว มองเห็นได้ชัดเจน และทำซ้ำได้

มองหา Requirement ที่สำคัญ ตัวที่นิยามว่าระบบนี้คืออะไร มองหาจุดที่คุณยังไม่มั่นใจ และจุดที่คุณเห็น Risk ใหญ่ที่สุด แล้วจัดลำดับความสำคัญในการพัฒนาให้ส่วนเหล่านี้เป็นส่วนแรกที่คุณลงมือเขียน Code

Tip 20 ใช้กระสุนส่องวิถีเพื่อค้นหาเป้าหมาย

จริงๆ แล้ว ด้วยความซับซ้อนของโปรเจกต์สมัยนี้ ที่เต็มไปด้วย External Dependency และ Tool มากมาย กระสุนส่องวิถียิ่งมีความสำคัญมากขึ้นไปอีก สำหรับเรา กระสุนส่องวิถีนัดแรกสุดก็คือแค่ สร้างโปรเจกต์ขึ้นมา เขียน “hello world!” แล้วเช็กให้แน่ใจว่ามัน Compile และรันได้ จากนั้นเราก็จะมองหาจุดที่ยังไม่แน่นอนในภาพรวมของ Application แล้วเพิ่ม Skeleton ที่จำเป็นเข้าไปเพื่อให้มันทำงานได้

ลองดูไดอะแกรมต่อไปนี้ ระบบนี้มี Architectural Layer อยู่ห้าชั้น เรามีความกังวลอยู่บ้างว่าแต่ละชั้นจะ Integrate เข้าด้วยกันยังไง เราจึงมองหา Feature ง่ายๆ ที่ทำให้เราทดสอบการทำงานร่วมกันของทุกชั้นได้ เส้นทแยงมุมแสดงเส้นทางที่ Feature นั้นวิ่งผ่าน Code เพื่อให้มันทำงานได้ เราแค่ต้อง Implement ส่วนที่แรเงาเข้มในแต่ละ Layer ส่วนที่เป็นลายหยักๆ จะทำทีหลัง

A figure shows a set of bars to represent different entities.

ภาพแสดงแท่งแนวตั้งห้าแท่ง แต่ละแท่งมีส่วนที่ถูกไฮไลต์ไว้ มีเส้นทแยงมุมลากผ่านบริเวณที่ไฮไลต์ของแต่ละแท่ง ส่วนที่ไม่ได้ไฮไลต์แสดงถึง "ส่วนที่ยังไม่ได้ Implement" แท่งเหล่านี้แทน User Interface, Authorization, Business Logic, Data Model และ Database ตามลำดับ

ครั้งหนึ่งเราเคยรับทำโปรเจกต์ Client-Server Database Marketing ที่ซับซ้อน ส่วนหนึ่งของ Requirement คือความสามารถในการระบุและรัน Temporal Query ฝั่ง Server เป็น Database เชิงสัมพันธ์และ Database เฉพาะทางหลายตัว ส่วน UI ฝั่ง Client เขียนด้วยภาษา A (สมมติชื่อ) ใช้ Library ชุดหนึ่งที่เขียนด้วยภาษาอื่นเพื่อเชื่อมต่อกับ Server Query ของ User จะถูกเก็บไว้ที่ Server ในรูปแบบ Notation คล้าย Lisp ก่อนที่จะถูกแปลงเป็น SQL ที่ Optimize แล้วก่อนรันจริง มีสิ่งที่ไม่รู้และ Environment ที่แตกต่างกันเยอะมาก และไม่มีใครมั่นใจนักว่า UI ควรจะทำงานยังไง

นี่คือโอกาสทองที่จะใช้ Tracer Code เราสร้าง Framework สำหรับ Front End, Library สำหรับ Represent Query และโครงสร้างสำหรับแปลง Query ที่เก็บไว้ให้กลายเป็น Query เฉพาะของแต่ละ Database จากนั้นเราก็เอาทุกอย่างมาต่อกันแล้วเช็กว่ามันทำงานได้ สำหรับ Build แรกนั้น สิ่งที่เราทำได้คือส่ง Query ที่ List ทุกแถวในตารางออกมาเท่านั้น แต่มันก็พิสูจน์แล้วว่า UI คุยกับ Library ได้ Library Serialize และ Unserialize Query ได้ และ Server ก็ Generate SQL จากผลลัพธ์ได้ ในหลายเดือนต่อมา เราค่อยๆ เติมเต็มโครงสร้างพื้นฐานนี้ โดยเพิ่ม Functionality ใหม่ผ่านการต่อเติมแต่ละ Component ของ Tracer Code ไปพร้อมๆ กัน เมื่อ UI เพิ่ม Query Type ใหม่ Library ก็ขยายตาม และการ Generate SQL ก็ซับซ้อนขึ้น

Tracer Code ไม่ใช่ของที่เขียนแล้วทิ้ง คุณเขียนมันเพื่อเก็บไว้ใช้จริง มันมี Error Checking, โครงสร้าง, Documentation และ Self-checking ครบเหมือน Production Code ทุกชิ้น เพียงแต่มันยังทำงานได้ไม่ครบทุกฟังก์ชัน อย่างไรก็ตาม เมื่อคุณเชื่อมต่อ Component ต่างๆ ของระบบแบบ End-to-end ได้สำเร็จแล้ว คุณก็สามารถเช็กได้ว่าตอนนี้ใกล้เป้าหมายแค่ไหน แล้วปรับตามความจำเป็น พอคุณเข้าเป้าแล้ว การเพิ่ม Functionality ก็จะง่ายขึ้นมาก

การพัฒนาแบบ Tracer สอดคล้องกับแนวคิดที่ว่าโปรเจกต์ไม่มีวันเสร็จสมบูรณ์จริงๆ ครับ จะมีการเปลี่ยนแปลงและ Function ที่ต้องเพิ่มอยู่เสมอ มันเป็นแนวทางแบบ Incremental

ทางเลือกแบบดั้งเดิมคือแนวทาง Heavy Engineering ที่แบ่ง Code ออกเป็น Module แล้วเขียนแต่ละ Module แยกกันไปในสุญญากาศ จากนั้นค่อยเอา Module มารวมเป็น Subassembly แล้วรวมกันต่อไปเรื่อยๆ จนวันหนึ่ง Application ทั้งหมดก็เสร็จสมบูรณ์ จากนั้นถึงจะเอา Application ทั้งชิ้นไปให้ User ดูและทดสอบได้

แนวทาง Tracer Code มีข้อดีอยู่หลายอย่าง:

User ได้เห็นอะไรที่ทำงานได้จริงตั้งแต่เนิ่นๆ

ถ้าคุณสื่อสารได้ชัดเจนว่ากำลังทำอะไรอยู่ (ดู Topic 52, ​ Delight Your Users ​ ) User ของคุณจะรู้ว่ากำลังเห็นอะไรบางอย่างที่ยังไม่สมบูรณ์ พวกเขาจะไม่ผิดหวังที่ Functionality ยังไม่ครบ แต่จะดีใจมากที่เห็นความคืบหน้าที่จับต้องได้ของระบบ พวกเขายังมีโอกาสได้มีส่วนร่วมไปพร้อมกับความคืบหน้าของโปรเจกต์ ทำให้รู้สึกเป็นเจ้าของมากขึ้น User กลุ่มนี้เองที่มักจะเป็นคนบอกคุณว่าแต่ละ Iteration ใกล้เป้าหมายแค่ไหน

Developer ได้โครงสร้างไว้ทำงานต่อ

กระดาษเปล่าที่ไม่มีอะไรเขียนเลยคือสิ่งที่น่ากลัวที่สุด ถ้าคุณคิด End-to-end Interaction ของ Application ออกมาครบแล้วและใส่ลงใน Code จริง ทีมของคุณก็ไม่ต้องคิดทุกอย่างขึ้นมาจากอากาศบางๆ อีกต่อไป ทำให้ทุกคนทำงานได้มีประสิทธิภาพขึ้น และช่วยให้ Consistency ดีขึ้นด้วย

คุณมี Integration Platform ใช้งาน

เมื่อระบบเชื่อมต่อกันแบบ End-to-end แล้ว คุณก็มี Environment ให้เพิ่ม Code ใหม่เข้าไปได้ทันทีที่ผ่าน Unit Test แทนที่จะพยายาม Integrate ทีเดียวแบบ Big-bang คุณจะได้ Integrate ทุกวัน (บ่อยครั้งหลายครั้งต่อวันด้วยซ้ำ) ผลกระทบของการเปลี่ยนแปลงแต่ละครั้งจะเห็นได้ชัดเจนขึ้น และการ Interact กันก็จำกัดวงแคบลง ทำให้ Debug และ Test ได้เร็วและแม่นยำขึ้น

คุณมีอะไรให้ Demo ได้เสมอ

Sponsor ของโปรเจกต์และผู้บริหารระดับสูงมักจะอยากดู Demo ในเวลาที่ไม่สะดวกที่สุดเสมอ แต่ถ้าคุณมี Tracer Code คุณจะมีอะไรให้พวกเขาดูอยู่ตลอดครับ

คุณรับรู้ความคืบหน้าได้ดีขึ้น

ในการพัฒนาแบบ Tracer Code Developer จะจัดการ Use Case ทีละอัน พอทำเสร็จอันหนึ่งก็ไปต่ออันถัดไป ทำให้วัดผลงานและแสดงความคืบหน้าให้ User เห็นได้ง่ายขึ้นมาก เพราะแต่ละงานย่อยมีขนาดเล็กลง คุณจึงหลีกเลี่ยงการสร้าง Code ก้อนใหญ่แบบ Monolithic ที่ถูกรายงานว่า "เสร็จไปแล้ว 95%" อยู่แบบนั้นสัปดาห์แล้วสัปดาห์เล่า

Tracer Bullets Don't Always Hit Their Target (กระสุนส่องวิถีก็ไม่ได้โดนเป้าเสมอไป)

กระสุนส่องวิถีจะแสดงให้เห็นว่าคุณกำลังยิงโดนอะไรอยู่ ซึ่งอาจไม่ใช่เป้าหมายเสมอไป จากนั้นคุณก็ปรับการเล็งไปเรื่อยๆ จนกว่าจะโดนเป้า นั่นแหละคือประเด็นสำคัญ

Tracer Code ก็เหมือนกัน คุณใช้เทคนิคนี้ในสถานการณ์ที่คุณยังไม่มั่นใจ 100% ว่ากำลังมุ่งหน้าไปทางไหน อย่าแปลกใจถ้าการลองครั้งแรกๆ จะยังพลาดเป้าอยู่บ้าง อาจเป็นเพราะ User บอกว่า “นี่ไม่ใช่สิ่งที่ผมหมายถึง” หรือข้อมูลที่คุณต้องการยังไม่พร้อมใช้งานตอนที่ต้องการ หรืออาจมีปัญหาเรื่อง Performance เกิดขึ้น ดังนั้นก็แค่ปรับสิ่งที่คุณมีอยู่ ให้เข้าใกล้เป้าหมายมากขึ้น และขอบคุณที่คุณเลือกใช้แนวทาง Lean Development เพราะ Code ก้อนเล็กมี Inertia ต่ำ ปรับเปลี่ยนได้ง่ายและรวดเร็ว คุณจะสามารถเก็บ Feedback จาก Application แล้วสร้าง Version ใหม่ที่แม่นยำขึ้นได้อย่างรวดเร็วและประหยัด และเพราะ Component หลักทุกตัวของ Application ถูก Represent อยู่ใน Tracer Code ของคุณ User จึงมั่นใจได้ว่าสิ่งที่พวกเขาเห็นอยู่นั้นอ้างอิงจากความเป็นจริง ไม่ใช่แค่ Spec บนกระดาษ

Tracer Code versus Prototyping (Tracer Code เทียบกับ Prototyping)

คุณอาจจะคิดว่าแนวคิด Tracer Code นี้ก็แค่การทำ Prototyping ในชื่อที่ดูล้ำกว่าเดิม แต่จริงๆ แล้วมันต่างกัน กับ Prototype คุณมุ่งสำรวจแค่บางแง่มุมของระบบสุดท้าย และกับ Prototype ตัวจริง คุณจะโยนสิ่งที่ประกอบกันขึ้นมาชั่วคราวทิ้งไป แล้วเขียน Code ใหม่อย่างถูกต้องโดยอาศัยบทเรียนที่ได้เรียนรู้มา

ตัวอย่างเช่น สมมติว่าคุณกำลังทำ Application ที่ช่วยผู้ขนส่งคำนวณว่าจะบรรจุกล่องขนาดแปลกๆ ลงในตู้คอนเทนเนอร์ยังไงให้ลงตัวที่สุด นอกจากปัญหาอื่นๆ แล้ว User Interface ก็ต้องใช้งานง่าย และ Algorithm ที่ใช้หาการบรรจุที่เหมาะสมที่สุดก็ซับซ้อนมาก

คุณอาจจะ Prototype User Interface สำหรับ End User ด้วย UI Tool คุณเขียน Code แค่พอให้ Interface ตอบสนองต่อ Action ของ User ได้ พอพวกเขาเห็นด้วยกับ Layout แล้ว คุณอาจจะโยนมันทิ้งแล้วเขียนใหม่ คราวนี้ใส่ Business Logic เข้าไปด้วย โดยใช้ภาษาเป้าหมายจริง ในทำนองเดียวกัน คุณอาจอยาก Prototype Algorithm หลายตัวที่ใช้บรรจุของจริงๆ คุณอาจเขียน Functional Test ด้วยภาษาระดับสูงที่ยืดหยุ่นอย่าง Python แล้วเขียน Low-level Performance Test ด้วยภาษาที่ใกล้เครื่องมากขึ้น ไม่ว่าจะแบบไหน เมื่อคุณตัดสินใจได้แล้ว คุณก็เริ่มใหม่และเขียน Algorithm ใน Environment สุดท้ายจริงๆ ที่เชื่อมต่อกับโลกจริง นี่คือ Prototyping และมันมีประโยชน์มากครับ

ส่วนแนวทาง Tracer Code นั้นแก้ปัญหาคนละแบบ คุณต้องรู้ว่า Application ทั้งหมดทำงานประสานกันยังไง คุณอยากแสดงให้ User เห็นว่า การ Interact ต่างๆ จะทำงานยังไงในทางปฏิบัติ และอยากให้ Developer มี Architectural Skeleton ไว้แขวน Code ต่อ ในกรณีนี้ คุณอาจสร้าง Tracer ที่ประกอบด้วย Algorithm การบรรจุตู้คอนเทนเนอร์แบบง่ายๆ (อาจจะแบบใครมาก่อนได้ก่อน) กับ User Interface ง่ายๆ ที่ใช้งานได้จริง พอทุก Component ใน Application เชื่อมต่อกันหมดแล้ว คุณก็มี Framework ไว้โชว์ User และ Developer ของคุณ เมื่อเวลาผ่านไป คุณก็ค่อยๆ เพิ่ม Functionality ใหม่เข้าไปใน Framework นี้ เติมเต็ม Routine ที่ยังเป็น Stub อยู่ แต่ตัว Framework จะยังคงอยู่เหมือนเดิม และคุณจะรู้ว่าระบบจะยังทำงานแบบเดียวกับตอนที่ Tracer Code ตัวแรกเสร็จสมบูรณ์

ความแตกต่างนี้สำคัญพอที่จะขอย้ำอีกครั้ง Prototyping สร้าง Code ที่ใช้แล้วทิ้ง ส่วน Tracer Code นั้นกระชับแต่สมบูรณ์ และกลายเป็นส่วนหนึ่งของ Skeleton ของระบบสุดท้าย ลองคิดว่า Prototyping คือการลาดตระเวนและเก็บข่าวกรอง ที่เกิดขึ้นก่อนที่กระสุนส่องวิถีนัดแรกจะถูกยิงออกไป

Related Sections Include (ส่วนที่เกี่ยวข้อง)

  • Topic 13, ​ Prototypes and Post-it Notes ​
  • Topic 27, ​ Don't Outrun Your Headlights ​
  • Topic 40, ​ Refactoring ​
  • Topic 49, ​ Pragmatic Teams ​
  • Topic 50, ​ Coconuts Don't Cut It ​
  • Topic 51, ​ Pragmatic Starter Kit ​
  • Topic 52, ​ Delight Your Users ​