- การมีส่วนร่วมในการออกแบบอย่างแท้จริงของระบบขนาดใหญ่ ทำได้เฉพาะวิศวกรที่ลงมือจัดการโค้ดนั้นโดยตรงเท่านั้น และคำแนะนำเชิงนามธรรมส่วนใหญ่มักแทบไม่มีความหมาย
- คำแนะนำด้านการออกแบบแบบทั่วไป (generic design) มักถูกเสนอโดยที่ไม่รู้จัก codebase แต่เข้าใจเพียงแค่โดเมน
- ในงานจริง ข้อจำกัดที่เป็นรูปธรรมและการรักษาความสอดคล้อง สำคัญกว่าหลักการออกแบบมาก และหัวใจสำคัญคือการเข้าใจสถานะปัจจุบันของโค้ด
- ในการ ออกแบบระบบใหม่หรือกำหนดทิศทางเทคโนโลยีทั้งบริษัท หลักการออกแบบทั่วไปยังพอมีประโยชน์อยู่บ้าง
- แต่ โครงสร้างการออกแบบที่ยึดสถาปนิกเป็นศูนย์กลางและแยกขาดจากหน้างานมักล้มเหลวง่าย และผู้ที่เสนอแบบควรต้องรับผิดชอบต่อผลลัพธ์
ข้อจำกัดของการออกแบบซอฟต์แวร์แบบทั่วไป
- การออกแบบระบบขนาดใหญ่จำเป็นต้องมี ความเข้าใจเชิงลึกในรายละเอียดที่เป็นรูปธรรมของโค้ด
- คำแนะนำเชิงนามธรรมแทบไม่ช่วยในการแก้ปัญหาจริง
- ในงานจริง ความสอดคล้อง (consistency) สำคัญกว่า ‘การออกแบบที่ดี’
- เนื่องจาก codebase จริงมีความซับซ้อนและก่อให้เกิดผลลัพธ์ที่คาดเดาไม่ได้ รูปแบบการ implement ที่เลือกใช้ได้เพื่อให้แก้ไขอย่างปลอดภัยจึงมีจำกัด
- codebase ขนาดใหญ่ที่ใช้ร่วมกันมักอยู่ใน สถานะกึ่งกลาง ที่มีหลายแนวทางการออกแบบปะปนกันอยู่เสมอ ดังนั้นสภาพการเชื่อมโยงของโค้ดในปัจจุบันจึงสำคัญกว่าเป้าหมายเชิงอุดมคติ
- ระบบส่วนใหญ่ไม่สามารถ rewrite ใหม่ทั้งหมดได้ จึงต้องพึ่งพา ความสอดคล้องภายในและความรอบคอบของวิศวกร
ลักษณะของการออกแบบซอฟต์แวร์แบบเฉพาะเจาะจง
- การพูดคุยเรื่องการออกแบบที่มีประสิทธิภาพเกิดขึ้นในการสนทนาระหว่างวิศวกรไม่กี่คนที่ดูแลระบบนั้นทุกวัน
- ประเด็นที่คุยกันไม่ใช่หลักการทั่วไป แต่เป็น บริบทที่เฉพาะเจาะจง เช่น โครงสร้างรายละเอียดของระบบหรือการไหลของข้อมูล
- ตัวอย่างเช่น ไม่ใช่การถกเถียงว่า “DRY ดีหรือไม่” แต่เป็น การอภิปรายทางเทคนิคอย่างละเอียด เช่น “จะใส่ฟีเจอร์นี้ไว้ใน subsystem A ได้ไหม” หรือ “ข้อมูล B เข้าถึงได้ใน context C หรือไม่”
- การมีส่วนร่วมที่สำคัญมักมาจากการ แก้ความเข้าใจผิดเล็ก ๆ หรือผลกระทบเชิงรายละเอียดของการเปลี่ยนโค้ด
กรณีที่คำแนะนำการออกแบบแบบทั่วไปมีประโยชน์
- เมื่อออกแบบโปรเจกต์ใหม่ตั้งแต่ต้น หลักการทั่วไปมีประโยชน์เพราะยังไม่มีข้อจำกัดที่เฉพาะเจาะจง
- เมื่อเลือกได้ยากระหว่างหลายแนวทางการ implement หลักการทั่วไปสามารถทำหน้าที่เป็น เกณฑ์ตัดสิน (tie-breaker)
- ยังช่วยในเรื่อง การรักษาความสอดคล้องในระดับทั้งบริษัท ซึ่งเป็นหนึ่งในบทบาทอย่างเป็นทางการของ software architect
- แม้ในการเลือกเทคโนโลยีวงกว้างอย่าง cloud vs on-premise, AWS vs Azure ก็สามารถอ้างอิงหลักการทั่วไปได้ แต่ก็ยัง ละเลยข้อจำกัดที่เป็นรูปธรรมไม่ได้
สถาปนิกและปัญหา ‘local minima’
- หลายบริษัทตกอยู่ใน โครงสร้างการออกแบบเชิงนามธรรมที่ยึดสถาปนิกเป็นศูนย์กลาง ทั้งที่ไม่มีประสบการณ์หน้างาน
- วิธีนี้ดูเหมือนมีประสิทธิภาพภายนอก แต่ในความเป็นจริงกลับก่อให้เกิด แบบออกแบบที่วิศวกรหน้างานนำไป implement ไม่ได้
- เพราะสถาปนิกไม่ได้ลงมือ implement เอง จึงขาด ความรับผิดชอบต่อผลลัพธ์ (skin in the game)
- ถ้าการออกแบบสำเร็จก็รับเครดิต แต่ถ้าล้มเหลวก็มักโทษทีมปฏิบัติการ
- โครงสร้างแบบนี้มีแนวโน้มจะ เสริมกิจกรรมการออกแบบเชิงพิธีการ มากกว่าสร้างคุณค่าจริง
บทสรุปและข้อเสนอ
- การพูดคุยด้านการออกแบบที่มีประโยชน์ต้องเป็นบทสนทนาเชิงรูปธรรมในระดับหน่วยของโค้ด และผู้ออกแบบต้องคุ้นเคยกับ codebase อย่างแท้จริง
- หลักการสถาปัตยกรรมแบบทั่วไป ควรถูกจำกัดไว้กับการออกแบบระบบใหม่ การช่วยตัดสินใจรายละเอียดของระบบเดิม และการกำหนดทิศทางเทคโนโลยีในระดับบริษัท
- ผู้ที่เสนอการออกแบบโปรเจกต์ต้องรับผิดชอบต่อทั้งความสำเร็จและความล้มเหลวของมัน และวิศวกรที่ลงมือแตะโค้ดจริงควรเป็นผู้มีบทบาทหลักในการออกแบบ
- ด้วยวิธีนี้ ผู้ที่เข้าใจระบบจริงและสามารถนำมันขึ้น deploy ได้ จึงจะได้รับการยอมรับว่าเป็นผู้ออกแบบตัวจริง
4 ความคิดเห็น
> ในงานจริง ข้อจำกัดที่เป็นรูปธรรมและการรักษาความสอดคล้องสำคัญกว่าหลักการออกแบบมาก และหัวใจสำคัญคือการเข้าใจสถานะปัจจุบันของโค้ด
เป็นความเชื่อที่ผมยึดถือมาตลอด เลยรู้สึกอบอุ่นใจเลยครับ
ถึงว่าเดี๋ยวนี้กูรูทั้งหลายถึงพูดกันว่า พวกมือใหม่กลับใช้เอเจนต์ได้เก่งกว่ามาก แบบนี้นี่เอง ของที่เคยทำเงินให้มานานก็เลยยังไม่ยอม unlearn กัน
ถ้าปรับปรุงกระบวนการทำให้สถาปัตยกรรมสมบูรณ์ขึ้น ก็น่าจะสามารถแก้ปัญหาที่ยกประเด็นไว้ได้เพียงพอไม่ใช่หรือครับ?
ความคิดเห็นจาก Hacker News
อธิบายสถานการณ์ที่ในการประชุมทีมไม่ได้คุยกันทำนองว่า “DRY ดีกว่าไหมหรือ WET ดีกว่า” แต่กลับเป็นการคุยเรื่อง dependency ที่ซับซ้อน เช่น “จะเอาฟีเจอร์นี้ใส่ใน subsystem A ได้ไหม? ไม่ได้ เพราะฝั่งนั้นไม่มีข้อมูล B แล้วถ้าจะเปิดเผยข้อมูลนั้นก็ต้องเขียน D ใหม่...”
บอกว่าเป็นภาพที่คุ้นเคยมาก พร้อมล้อเล่นด้วยการไล่ชื่อระบบสมมติหลาย ๆ ชื่อ และปิดท้ายด้วย ลิงก์ YouTube
ทำงานพัฒนามา 30 ปีแล้ว แต่แทบไม่เคยเห็นกรณีที่มี ความพยายามอย่างสม่ำเสมอด้านการออกแบบและสถาปัตยกรรม จริง ๆ
‘สถาปนิก’ ส่วนใหญ่ไม่ได้ออกแบบเอง แต่เป็นโครงสร้างที่นักพัฒนาระดับอาวุโสออกแบบแล้วค่อยรับฟีดแบ็ก
ถ้าระยะเวลาทำงานเฉลี่ยอยู่ที่ 2 ปี คนก็จะออกแบบทั้งที่เข้าใจเพียงบางส่วนของระบบ และสถาปนิกก็มักแค่คอยอนุมัติ
‘Generic Software Design’ มีประโยชน์ในการกำหนดทิศทางการลงมือทำ เพราะช่วยให้มี ภาษาและกรอบร่วมกัน ทำให้แก้ปัญหาได้ง่ายขึ้น
แต่การลงมือทำจริงอาจต่างจากแผน และตามทฤษฎีการเขียนโปรแกรมของ Naur ความรู้ที่แท้จริงของระบบอยู่ใน หัวของนักพัฒนา
คำพูดที่ว่า “ใน codebase ขนาดใหญ่ ความสอดคล้องสำคัญกว่าการออกแบบที่ดี” ก็คือกับดักของ คำแนะนำแบบเหมารวม นั่นเอง
ถ้าเน้นแต่ความสอดคล้อง นิสัยแย่ ๆ ก็จะถูกรักษาไว้เหมือนเดิม
ฝั่งหนึ่งคือ ‘สถาปนิก’ ที่ไม่เข้าใจโลกความจริง อีกฝั่งคือ Real Programmer ที่หมกมุ่นกับการ optimize รายละเอียดเท่านั้น
ทั้งสองสุดโต่งล้วนเป็นปัญหา และคนตัดสินใจต้องเข้าใจทั้งรายละเอียดและบริบท
มีการอ้างถึง The Story of Mel ที่เกี่ยวข้องกับประเด็นนี้
เห็นด้วยกับประโยคที่ว่า “คนที่ออกแบบควรต้องรับผิดชอบต่อความสำเร็จและความล้มเหลวของโปรเจกต์”
เรื่องนี้ควรใช้กับ คนที่เลือก methodology การพัฒนา ด้วย Scrum master ไม่ได้มีความรับผิดชอบเท่ากับหัวหน้าวิศวกร
แอปที่ดีที่สุดที่ฉันเคยร่วมทำมีลักษณะร่วมกันอยู่ 3 ข้อ
อิสระนี้ช่วยยกระดับทั้งความพึงพอใจของนักพัฒนาและคุณภาพของผลิตภัณฑ์
จากประสบการณ์ของฉัน การ ยึดติดกับความสอดคล้องมากเกินไปใน codebase ขนาดใหญ่ กลับเป็นความผิดพลาด
เพราะแต่ละโมดูลมีข้อกำหนดต่างกัน ดังนั้นกลยุทธ์การทดสอบหรือการตั้งชื่อก็ควรต่างกันด้วย
ในกรณีที่เหมาะที่สุด นักพัฒนาควรเป็น ผู้ใช้จริง ของซอฟต์แวร์ที่ตัวเองสร้างด้วย
แบบนั้นจึงจะสัมผัสข้อผิดพลาดหรือความไม่สะดวกได้โดยตรง และมีแรงจูงใจที่จะปรับปรุง
การมี สถาปนิกซอฟต์แวร์แยกต่างหาก ที่ไม่เข้าร่วมการบำรุงรักษาเป็นเรื่องไม่สมจริง
โปรเจกต์เปลี่ยนตลอดเวลา ดังนั้นบทบาทที่คอยสั่งการจากระยะไกลจึงไม่ได้ช่วยอะไร