The Limitations of Metrics (ข้อจำกัดของ metric)
แม้วงการนี้จะมี code-level metric ไม่กี่ตัวที่ให้ข้อมูลเชิงลึกที่มีคุณค่า แต่เครื่องมือของเรานั้นหยาบมากเมื่อเทียบกับเครื่องมือวิเคราะห์ในสาขาวิศวกรรมอื่น ๆ แม้แต่ metric ที่มาจากโครงสร้างของโค้ดโดยตรงก็ยังต้องอาศัยการตีความ ตัวอย่างเช่น Cyclomatic Complexity (ดูที่ “Cyclomatic Complexity” ) วัดความซับซ้อนในโค้ดเบส แต่ metric นี้ไม่สามารถแยกแยะระหว่างความซับซ้อนแบบ essential (โค้ดซับซ้อนเพราะปัญหาที่แท้จริงซับซ้อน) กับความซับซ้อนแบบ accidental (โค้ดซับซ้อนเกินความจำเป็น) แทบทุก code-level metric ต้องอาศัยการตีความ แต่ก็ยังมีประโยชน์ที่จะสร้าง baseline สำหรับ metric สำคัญอย่าง Cyclomatic Complexity เพื่อให้สถาปนิกประเมินได้ว่าโค้ดเบสเข้าข่ายแบบไหน เราจะพูดถึงการตั้งค่า test แบบนี้ใน “Governance and Fitness Functions”
หนังสือ Structured Design ของ Yourdon และ Constantine ตีพิมพ์ในปี 1979 ก่อนที่ภาษาเชิงวัตถุจะได้รับความนิยม โดยเน้นไปที่ construct แบบ structured programming เช่น function (ไม่ใช่ method) หนังสือยังนิยาม coupling ประเภทอื่น ๆ ที่ล้าสมัยไปแล้วเนื่องจากการออกแบบภาษาโปรแกรมสมัยใหม่ การเขียนโปรแกรมเชิงวัตถุได้นำแนวคิดเพิ่มเติมมาซ้อนทับ afferent และ efferent coupling รวมถึงคำศัพท์ที่ประณีตกว่าในการอธิบาย coupling ที่เรียกว่า connascence
Connascence
หนังสือของ Meilir Page-Jones ชื่อ What Every Programmer Should Know about Object-Oriented Design (Dorset House, 1996) ได้สร้าง ภาษา ที่ประณีตยิ่งขึ้นเพื่ออธิบาย coupling ประเภทต่าง ๆ ในภาษาเชิงวัตถุ Connascence ไม่ใช่ coupling metric อย่าง afferent และ efferent coupling—แต่มันเป็นภาษาที่ช่วยให้สถาปนิกอธิบาย coupling ประเภทต่าง ๆ ได้แม่นยำขึ้น (และเข้าใจผลลัพธ์ทั่วไปบางอย่างของ coupling แต่ละประเภท)
component สองตัวจะเรียกว่า connascent ถ้าการเปลี่ยนแปลงในตัวหนึ่งจำเป็นต้องทำให้อีกตัวถูกแก้ไขตามด้วย เพื่อรักษาความถูกต้องโดยรวมของระบบ Page-Jones แบ่ง connascence ออกเป็นสองประเภท: static และ dynamic
Static connascence (Connascence แบบ static)
Static connascence หมายถึง coupling ระดับ source code (ตรงข้ามกับ coupling ตอนรันไทม์ ซึ่งกล่าวถึงใน “Dynamic connascence” ) สถาปนิกมอง static connascence เป็น ระดับ ของการถูก coupled ผ่านทาง afferent หรือ efferent coupling มี static connascence หลายประเภทได้แก่:
Connascence of Name
component หลายตัวต้องตกลงกันเรื่องชื่อของ entity หนึ่ง ๆ
ชื่อ method และพารามิเตอร์ของ method เป็นวิธีที่พบบ่อยที่สุดที่โค้ดเบสถูก coupled และเป็นวิธีที่พึงประสงค์ที่สุด โดยเฉพาะเมื่อพิจารณาเครื่องมือ refactoring สมัยใหม่ที่ทำให้การเปลี่ยนชื่อทั้งระบบเป็นเรื่องเล็กน้อย ตัวอย่างเช่น นักพัฒนาไม่เปลี่ยนชื่อ method ในโค้ดเบสที่ใช้งานอยู่แบบมือเปล่าอีกต่อไป แต่ใช้เครื่องมือสมัยใหม่ refactor ชื่อ method แทน ซึ่งจะเปลี่ยนแปลงชื่อนั้นตลอดทั้งโค้ดเบสให้เอง
Connascence of Type
component หลายตัวต้องตกลงกันเรื่อง type ของ entity หนึ่ง ๆ
connascence ประเภทนี้หมายถึงแนวโน้มทั่วไปในภาษาแบบ statically typed หลายภาษาที่จำกัด variable และพารามิเตอร์ให้เป็น type เฉพาะ อย่างไรก็ตาม ความสามารถนี้ไม่ได้จำกัดเฉพาะภาษาแบบ statically typed เท่านั้น บางภาษาแบบ dynamically typed ก็มี typing แบบเลือกได้เช่นกัน โดยเฉพาะ Clojure และ Clojure Spec
Connascence of Meaning
component หลายตัวต้องตกลงกันเรื่องความหมายของค่าหนึ่ง ๆ เรียกอีกชื่อว่า Connascence of Convention
กรณีที่พบบ่อยและชัดเจนที่สุดของ connascence ประเภทนี้ในโค้ดเบสคือการใช้ตัวเลข hardcode แทนที่จะใช้ constant ตัวอย่างเช่น เป็นเรื่องปกติในบางภาษาที่จะนิยามไว้ที่ไหนสักแห่งว่า int TRUE = 1; int FALSE = 0 ลองนึกภาพปัญหาที่จะเกิดขึ้นถ้ามีใครสลับค่าสองตัวนี้
Connascence of Position
component หลายตัวต้องตกลงกันเรื่องลำดับของค่า
นี่เป็นปัญหาที่เกี่ยวกับค่าพารามิเตอร์ในการเรียก method และ function แม้แต่ในภาษาที่มี static typing ตัวอย่างเช่น หากนักพัฒนาสร้าง method void updateSeat(String name, String seatLocation) แล้วเรียกด้วยค่า updateSeat("14D", "Ford, N") ความหมายก็ไม่ถูกต้อง แม้ว่า type จะถูกต้องก็ตาม
Connascence of Algorithm
component หลายตัวต้องตกลงกันเรื่องอัลกอริทึมหนึ่ง ๆ
กรณีทั่วไปของ Connascence of Algorithm เกิดขึ้นเมื่อนักพัฒนานิยาม security hashing algorithm ที่ต้องรันและให้ผลลัพธ์เหมือนกันทั้งฝั่ง server และ client เพื่อยืนยันตัวตนผู้ใช้ เห็นได้ชัดว่านี่คือระดับ coupling ที่สูงมาก—หากรายละเอียดของอัลกอริทึมฝั่งใดฝั่งหนึ่งเปลี่ยนไป การจับมือ (handshake) ก็จะใช้งานไม่ได้อีกต่อไป
Dynamic connascence (Connascence แบบ dynamic)
connascence อีกประเภทที่ Page-Jones นิยามไว้คือ dynamic connascence ซึ่งวิเคราะห์การเรียกในขณะรันไทม์ ประเภทของ dynamic connascence มีดังนี้:
Connascence of Execution
ลำดับการทำงานของ component หลายตัวมีความสำคัญ
ลองพิจารณาโค้ดนี้:
email = new Email();
email.setRecipient("[email protected]");
email.setSender("[email protected]");
email.send();
email.setSubject("whoops");
โค้ดนี้จะทำงานไม่ถูกต้อง เพราะคุณสมบัติบางอย่างต้องถูกกำหนดตามลำดับที่เฉพาะเจาะจง
Connascence of Timing
จังหวะเวลาการทำงานของ component หลายตัวมีความสำคัญ
กรณีทั่วไปของ connascence ประเภทนี้คือ race condition ที่เกิดจาก thread สองตัวทำงานพร้อมกัน ซึ่งส่งผลต่อผลลัพธ์ของการทำงานร่วมกันนั้น
Connascence of Values
ค่าหลายตัวขึ้นอยู่กับกันและกันและต้องเปลี่ยนแปลงไปด้วยกัน
ลองพิจารณากรณีที่นักพัฒนานิยามสี่เหลี่ยมผืนผ้าด้วยการกำหนดจุดสี่จุดเพื่อแทนมุมของมัน เพื่อรักษาความถูกต้องของโครงสร้างข้อมูล นักพัฒนาไม่สามารถเปลี่ยนจุดใดจุดหนึ่งแบบสุ่มได้โดยไม่พิจารณาผลกระทบต่อจุดอื่น ๆ เพื่อรักษารูปทรงของสี่เหลี่ยมผืนผ้าไว้
กรณีที่พบบ่อยและเป็นปัญหามากกว่าเกี่ยวข้องกับธุรกรรม (transaction) โดยเฉพาะในระบบแบบกระจาย ในระบบที่ออกแบบด้วยฐานข้อมูลแยกกัน เมื่อมีคนต้องอัปเดตค่าตัวเดียวในทุกฐานข้อมูล ค่าเหล่านั้นต้องเปลี่ยนไปพร้อมกันทั้งหมดหรือไม่เปลี่ยนเลย
Connascence of Identity
component หลายตัวต้องอ้างอิงถึง entity เดียวกัน
ตัวอย่างทั่วไปของ Connascence of Identity คือ component อิสระสองตัวที่ต้องแชร์และอัปเดตโครงสร้างข้อมูลร่วมกัน เช่น distributed queue
Connascence properties (คุณสมบัติของ connascence)
Connascence คือกรอบการวิเคราะห์ (analysis framework) สำหรับสถาปนิกและนักพัฒนา และคุณสมบัติบางอย่างของมันช่วยให้เราใช้มันได้อย่างชาญฉลาด คุณสมบัติของ connascence เหล่านี้ได้แก่:
Strength
สถาปนิกกำหนด ความแข็งแกร่ง (strength) ของ connascence ในระบบหนึ่ง ๆ จากความง่ายที่นักพัฒนาสามารถ refactor coupling นั้นได้ connascence บางประเภทพึงประสงค์กว่าประเภทอื่นอย่างชัดเจน ดังแสดงใน Figure 3-5 การ refactor ไปสู่ connascence ประเภทที่ดีกว่าสามารถปรับปรุงคุณลักษณะ coupling ของโค้ดเบสได้
สถาปนิกควรเลือก static connascence มากกว่า dynamic เพราะนักพัฒนาสามารถตรวจสอบได้ด้วยการวิเคราะห์ source code ธรรมดา ๆ และเพราะเครื่องมือสมัยใหม่ทำให้การปรับปรุง static connascence เป็นเรื่องเล็กน้อย ตัวอย่างเช่น Connascence of Meaning สามารถปรับปรุงได้ด้วยการ refactor เป็น Connascence of Name โดยการสร้าง named constant แทนค่า magic value
Figure 3-5. ความแข็งแกร่ง ของ connascence สามารถเป็นแนวทางที่ดีในการ refactor
Locality
locality ของ connascence ในระบบหนึ่ง ๆ วัดว่า module ต่าง ๆ อยู่ใกล้กัน (proximal) แค่ไหนในโค้ดเบส โค้ดที่ใกล้กัน (proximal code) (โค้ดใน module เดียวกัน) มักมี connascence จำนวนมากและรูปแบบที่แข็งแกร่งกว่าโค้ดที่แยกจากกันมากกว่า (อยู่คนละ module หรือคนละโค้ดเบส) กล่าวอีกนัยหนึ่งคือ connascence รูปแบบที่บ่งชี้ว่า coupling ไม่ดีเมื่อ component อยู่ห่างกัน กลับไม่เป็นปัญหาเมื่อ component อยู่ใกล้กันมากกว่า ตัวอย่างเช่น หากคลาสสองตัวใน module เดียวกันมี Connascence of Meaning มันจะสร้างความเสียหายต่อโค้ดเบสน้อยกว่าถ้าคลาสทั้งสองอยู่คนละ module
สถาปนิกส่วนใหญ่ยังไม่ตระหนักถึงความสำคัญของข้อสังเกตนี้เมื่อผู้เขียนตีพิมพ์มันครั้งแรก ในภาษาสมัยใหม่ เขากำลังเสนอว่าสถาปนิกควรจำกัดขอบเขตของรายละเอียดการ implement (coupling สูง) ให้แคบที่สุดเท่าที่จะทำได้จริง ซึ่งเป็นคำแนะนำเดียวกับที่ได้มาจากแนวคิด bounded context ของ domain-driven design (DDD) ข้อสังเกตทางสถาปัตยกรรมนั้นเหมือนกัน—จำกัด implementation coupling Meilir Page-Jones อธิบายหลักการออกแบบที่ดีไว้ ซึ่งภายหลังถูกนำกลับมาเผยแพร่อย่างสมบูรณ์มากขึ้นผ่าน DDD (ดูที่ “Domain-Driven Design’s Bounded Context” ใน Chapter 7 )
เป็นความคิดที่ดีที่จะพิจารณา strength และ locality ไปพร้อมกัน connascence รูปแบบที่แข็งแกร่งกว่าภายใน module เดียวกันถือเป็น “code smell” น้อยกว่า connascence แบบเดียวกันที่กระจายตัวออกไป
Degree
degree ของ connascence เกี่ยวข้องกับขนาดของผลกระทบจากการเปลี่ยนคลาสใน module หนึ่ง ๆ—การเปลี่ยนแปลงนั้นกระทบคลาสไม่กี่ตัวหรือหลายตัว? connascence ที่มี degree ต่ำกว่าต้องการการเปลี่ยนแปลงคลาสและ module อื่นน้อยกว่า จึงสร้างความเสียหายต่อโค้ดเบสน้อยกว่า กล่าวอีกนัยหนึ่งคือ การมี dynamic connascence สูงไม่ใช่เรื่องเลวร้ายนัก ถ้าสถาปนิกมี module อยู่ไม่กี่ตัว แต่โค้ดเบสมักโตขึ้นเรื่อย ๆ ทำให้ปัญหาเล็ก ๆ กลายเป็นปัญหาที่ใหญ่ขึ้นตามไปด้วยในแง่ของการเปลี่ยนแปลง
ใน What Every Programmer Should Know about Object-Oriented Design Page-Jones เสนอแนวทางสามข้อสำหรับใช้ connascence เพื่อปรับปรุง modularity ของระบบ:
-
ลด connascence โดยรวมให้น้อยที่สุด ด้วยการแบ่งระบบออกเป็น องค์ประกอบ ที่ถูกห่อหุ้ม (encapsulated)
-
ลด connascence ที่เหลืออยู่ซึ่งข้ามขอบเขตการห่อหุ้ม (encapsulation boundary) ให้น้อยที่สุด
-
เพิ่ม connascence ภายในขอบเขตการห่อหุ้มให้มากที่สุด
Jim Weirich ผู้บุกเบิกด้านสถาปัตยกรรมซอฟต์แวร์ในตำนานที่ทำให้แนวคิด connascence กลับมาได้รับความนิยมอีกครั้ง ได้เสนอกฎยอดเยี่ยมสองข้อจากการบรรยายเรื่อง “Connescence Examined” ในงาน Emerging Technologies for the Enterprise conference ปี 2012:
Rule of Degree: เปลี่ยน connascence รูปแบบที่แข็งแกร่งให้กลายเป็นรูปแบบที่อ่อนกว่า
Rule of Locality: ยิ่งระยะห่างระหว่าง software element เพิ่มขึ้น ให้ใช้ connascence รูปแบบที่อ่อนลง
สถาปนิกได้ประโยชน์จากการเรียนรู้เรื่อง connascence ด้วยเหตุผลเดียวกับที่การเรียนรู้เรื่อง design pattern มีประโยชน์ นั่นคือ connascence ให้ภาษาที่แม่นยำกว่าในการอธิบาย coupling ประเภทต่าง ๆ ตัวอย่างเช่น สถาปนิกสามารถบอกใครสักคนว่า “เราต้องการ service ที่มีได้แค่หนึ่ง instance เท่านั้น” หรือจะบอกว่า “เราต้องการ Singleton service” ก็ได้ design pattern แบบ Singleton ห่อหุ้มบริบทและคำตอบของปัญหาที่พบบ่อยไว้อย่างกระชับด้วยชื่อง่าย ๆ
ในทำนองเดียวกัน เมื่อทำ code review สถาปนิกสามารถบอกนักพัฒนาว่า “อย่าใส่ magic string constant ไว้กลางการประกาศ method แยกมันออกมาเป็น constant แทน” หรือจะพูดว่า “คุณมี Connascence of Meaning อยู่ตรงนี้ ให้ refactor เป็น Connascence of Name ”