Cutting the elephant
เมื่อเร็วๆนี้ ได้สนทนาธรรมกับ ท่านอนุชิต คุยกันเรื่อง architecture และ pattern ของ software ว่า ในช่วงเริ่ม project ใหม่ๆ ควรเน้นการเขียน end-to-end test ก่อน เพราะว่าเราไม่รู้ architecture ของระบบจะมีโครงสร้างเป็นอย่างไร ไม่มีใครรู้ด้วย การที่ไม่รู้นี่แหละคือความเสี่ยง เราจึงต้องทำให้เรียบง่ายเข้าไว้ ไม่มโนเอาเองว่า จะแบ่งโครงสร้างอย่างไร จะต้องให้เห็น pattern ก่อนแล้วจึงค่อยๆ refactor
ส่วนมากแล้วพวกเรามักจะนำ pattern หรือ tools ที่กำหนด architecture ต่างๆมา apply ใช้ตอนเริ่มทำ project ไม่ว่าจะเป็นจากที่เรียนมา หรือจาก project เดิมที่เคยทำหรือเคยเห็น ซึ่งมันก็ช่วยได้มากในช่วงแรก ทำให้ทำงานได้เร็ว มองเห็นทิศทางที่จะไป เพราะมี pattern ให้ทำตาม แต่ทำไปทำมานานวันเข้า มี requirement ใหม่เข้ามาเรื่อยๆ แล้วก็พบว่า architecture ที่เป็นอยู่ไม่เหมาะกับ application ของเรา ส่งผลให้จะแก้โค้ดทีก็ลำบาก อยากร้องไห้บ้าง อยากทุบคอมทิ้งบ้าง
ที่เป็นปัญหาหลักๆก็คือ ส่วนมากพวกเราจะชอบจัดกลุ่มตั้งแต่เริ่มแรก “หั่น” ของออกเป็นส่วนๆ และออกแบบว่าแต่ละส่วนสื่อสารกันอย่างไร แล้วก็ภูมิใจกับตัวเองว่า architecture ที่เราออกแบบนั้นงดงามเหลือเกิน คำถามคือ รู้ได้ยังไงวะว่ามันถูก ถ้าออกแบบมาแล้วมันใช้ได้ดีก็โชคดีไป แต่ก็มีความเสี่ยงสูงว่า ของที่เราทำไว้ จะไม่ match กับของที่เราคิดว่าจะได้ใช้ด้วยกัน เปรียบเสมือนชิ้นส่วนที่ต่อกันไม่สนิท เราก็ต้องเอาเทปกาวมาแปะๆ ผลลัพธ์ก็คือของที่ได้ออกมาก็จะรูปร่างแปลกๆ
กรณีที่เห็นได้ส่วนใหญ่ก็คือ มีคนที่สร้างของที่ generic มากๆมาตั้งแต่แรก พอถามว่าสร้างทำไม คำตอบที่ได้คือ “ก็ในอนาคตจะได้ใช้กับตัวอื่นได้ด้วย” แบบนี้เขาเรียกว่า reuse ก่อน use
อีกกรณี คือการนำ pattern หรือ tools เข้ามา apply คือ จริงๆแล้วเราต้องเข้าใจปัญหาใน context ของเราก่อน แล้วค่อยเลือก tool ที่เหมาะสมมาใช้
tool ต่างๆที่เขาปล่อยออกมาตาม community เขาเคยใช้เพื่อแก้ปัญหาใน context ของเขาแล้วมันดี เขาก็เลยแบ่งปันให้ทุกๆคนได้ใช้กัน แต่บางครั้งเราก็ไปใช้ของเขาด้วยเหตุผลว่า ใครๆก็ใช้กัน ใครๆก็ทำกัน หรือมีคนใช้เยอะ น่าจะดี ซึ่งเหตุผลแบบนี้มีความเสี่ยงที่ tool ที่เรานำมาใช้จะสร้างปัญหาให้มากกว่าปัญหาที่แก้ไปอีก
ยกตัวอย่างกันตรงๆเลย เช่น จะเอา business logic ไปไว้ใน store procedure เพราะว่าเป็น best practice ด้าน security หรือ ใช้ redux เพราะว่าเป็น library ที่ popular หรือการทำ microservices แม้กระทั่ง การทำ single page application แบ่ง frontend กับ backend
ไม่ได้บอกว่าตัวอย่างข้างบนนั้นไม่ดี แต่การทำให้ระบบซับซ้อนเกินความจำเป็น มีความเสี่ยงสูงที่จะเป็น waste เราอาจจะไม่ได้ใช้ของที่เราเขียนมา หรือต้องฝืนใช้ด้วยท่าแปลกๆ เพราะว่าเสียดายอุตส่าห์ลง effort สร้างไปแล้ว ทำให้มี cost ที่ต้องจ่ายเพิ่มมากขึ้น สุดท้ายแล้วก็ส่งผลต่อคุณภาพของ software และส่งผลกับ maintainability ในระยะยาว แล้วถ้าทำไปเรื่อยๆ ก็อาจจะไปถึงจุดที่ อยากจะทำให้ดีขึ้นก็ทำได้ยาก ทนอยู่กับมันไปก็ทรมาณ เพราะฉะนั้นเราต้องทำให้เรียบง่ายเข้าไว้
ทั้งนี้ ไม่ได้บอกว่าเราไม่ต้องออกแบบโครงสร้างอะไรเลย ถ้าเราไม่จัดระเบียบอะไรเลยก็จะกลายเป็นว่า มีของกองอยู่รวมกันมากเกินไป ก็ทำให้ส่งผลต่อคุณภาพของ software อีก เพียงแต่ว่าเราต้องมีเหตุผลว่า ทำไปทำไม มีความจำเป็นแค่ไหนที่เราเพิ่มความซับซ้อนนี้เข้าไปโปรแกรม design pattern ต่างๆเกิดขึ้นเพื่อแก้ไขปัญหา ต้องให้มีปัญหาก่อนถึงนำมาใช้ เช่นเดียวกันถ้าเราไปออกแบบ architecture ว่าจะต้องมีระบบย่อย แบบนี้ๆ หลากหลายส่วน ต้องตั้งคำถามก่อนว่า แก้ไขปัญหาอะไร จำเป็นแค่ไหน มีท่าที่เรียบง่ายกว่านี้ไหม
“Good patterns come from refactoring, not design” — Aki
software architecture ควรจะถูก drive มาจาก constraint หรือ requirement ที่เราเพิ่มเข้าไประบบ เราจะสร้าง abstraction ก็ต่อเมื่อเรามองเห็น pattern ของสิ่งที่มันเป็นแล้ว ลักษณะแบบนี้เรียกว่า Emergent design ระบบจะถูกสร้างขึ้นมาเพื่อรองรับ business logic จริงๆ สิ่งที่เราต้องทำคือ คอยสังเกตสุขภาพของโค้ดอยู่สม่ำเสมอ และ refactor ออกมาเป็นส่วนๆ
ประเด็นคือเราจะต้องเรียนรู้ว่าจะ “หั่น” ของตอนไหน ซึ่งต้องอาศัยการฝึกฝนและประสบการณ์ เราสามารถฝึกได้ด้วยการทำ Kata ทำไปเรื่อยๆ จนเริ่มมองเห็น pattern ลองผิดลองถูก ลองฝึก refactor
ดังนั้น เมื่อต้องการที่จะ delay การออกแบบออกไปในช่วงแรกๆของโปรเจ็ค จึงจำเป็นต้องมี test ในระดับ high level มากๆ ก่อน เพื่อที่เราจะสามารถลองผิดลองถูกในการ refactor ได้