Measuring Modularity (การวัดความเป็นโมดูล)
เนื่องจาก modularity มีความสำคัญมาก สถาปนิกจึงต้องการเครื่องมือที่ช่วยให้เข้าใจมันได้ดีขึ้น โชคดีที่นักวิจัยได้สร้าง metric ที่ไม่ขึ้นกับภาษา (language-agnostic) ไว้หลากหลายเพื่อจุดประสงค์นี้ เราจะเน้นที่แนวคิดหลักสามอย่าง ได้แก่ cohesion , coupling และ connascence
Cohesion (ความเชื่อมโยงภายใน)
Cohesion หมายถึงระดับที่ส่วนต่าง ๆ ของ module หนึ่งควรถูกบรรจุไว้ใน module เดียวกัน กล่าวอีกนัยหนึ่งคือ มันวัดว่าส่วนต่าง ๆ เหล่านั้นเกี่ยวข้องกันมากแค่ไหน module ที่มี cohesion ในอุดมคติคือ module ที่ทุกส่วนถูกแพ็กไว้ด้วยกัน การแยกส่วนเหล่านั้นออกเป็นชิ้นเล็ก ๆ จะต้องอาศัยการผูก (coupling) ส่วนต่าง ๆ เข้าด้วยกันผ่านการเรียกข้าม module เพื่อให้ได้ผลลัพธ์ที่เป็นประโยชน์ นิทานเตือนใจของ modularity ที่เกี่ยวข้องกับ cohesion ปรากฏชัดในคำพูดต่อไปนี้จากหนังสือ Structured Design (Pearson, 2008):
การพยายามแบ่ง module ที่มี cohesion สูงออกจากกันจะยิ่งเพิ่ม coupling และลดความสามารถในการอ่านโค้ดลงเท่านั้น
Larry Constantine
นักวิทยาศาสตร์คอมพิวเตอร์ได้กำหนดสเปกตรัมของ cohesion ไว้ เรียงจากดีที่สุดไปแย่ที่สุด:
Functional cohesion
ทุกส่วนของ module เกี่ยวข้องกันหมด และ module มีทุกสิ่งที่จำเป็นต่อการทำงานของมันครบถ้วน
Sequential cohesion
module สองตัวมีปฏิสัมพันธ์กัน โดยตัวหนึ่งส่งข้อมูลออกมาเป็น input ให้อีกตัวหนึ่ง
Communicational cohesion
module สองตัวก่อตัวเป็นห่วงโซ่การสื่อสาร ซึ่งแต่ละตัวประมวลผลข้อมูลและ/หรือมีส่วนร่วมใน output บางอย่าง ตัวอย่างเช่น ตัวหนึ่งเพิ่ม record ลงในฐานข้อมูล ส่วนอีกตัวสร้างอีเมลตามข้อมูลนั้น
Procedural cohesion
module สองตัวต้องรันโค้ดตามลำดับที่กำหนดไว้
Temporal cohesion
module เกี่ยวข้องกันโดยอาศัยความสัมพันธ์ด้านเวลา (timing) ตัวอย่างเช่น หลายระบบมีรายการงานที่ดูไม่เกี่ยวข้องกันซึ่งต้อง initialize ตอนระบบเริ่มทำงาน งานที่ต่างกันเหล่านี้ถือว่ามี temporal cohesion ต่อกัน
Logical cohesion
ข้อมูลภายใน module เกี่ยวข้องกันในเชิงตรรกะแต่ไม่ใช่เชิงฟังก์ชัน ตัวอย่างเช่น ลองนึกถึง module ที่แปลงข้อมูลจาก text, serialized object หรือ stream ให้เป็นรูปแบบอื่น การทำงานของมันเกี่ยวข้องกัน แต่ฟังก์ชันแต่ละตัวก็ต่างกันมาก ตัวอย่างที่พบได้บ่อยของ cohesion ประเภทนี้มีอยู่ในแทบทุกโปรเจกต์ Java ในรูปของ package StringUtils ซึ่งเป็นกลุ่มของ static method ที่ทำงานกับ String แต่ไม่มีความเกี่ยวข้องกันในแง่อื่นเลย
Coincidental cohesion
องค์ประกอบภายใน module ไม่เกี่ยวข้องกันเลยนอกจากอยู่ในไฟล์ source เดียวกัน นี่คือรูปแบบ cohesion ที่แย่ที่สุด
แม้จะมีหลายรูปแบบ แต่ cohesion ก็ยังเป็น metric ที่แม่นยำน้อยกว่า coupling บ่อยครั้งที่ระดับ cohesion ของ module หนึ่ง ๆ ถูกกำหนดโดยดุลยพินิจของสถาปนิกแต่ละคน ลองพิจารณานิยาม module นี้:
Customer Maintenance
-
add customer -
update customer -
get customer -
notify customer -
get customer orders -
cancel customer orders
สองรายการสุดท้ายควรอยู่ใน module นี้หรือไม่? หรือควรให้นักพัฒนาสร้าง module แยกออกมาสองตัว? หน้าตามันจะเป็นแบบนี้:
Customer Maintenance
-
add customer -
update customer -
get customer -
notify customer
Order Maintenance
-
get customer orders -
cancel customer orders
โครงสร้างไหนถูกต้องกันแน่? เหมือนเดิม—มันขึ้นอยู่กับสถานการณ์:
-
นี่คือสอง operation เดียวของ
Order Maintenanceใช่หรือไม่? ถ้าใช่ อาจสมเหตุสมผลกว่าที่จะรวม operation เหล่านั้นกลับเข้าไปในCustomer Maintenance -
Customer Maintenanceคาดว่าจะขยายใหญ่ขึ้นมากหรือไม่? ถ้าใช่ นักพัฒนาอาจควรมองหาโอกาสที่จะแยกพฤติกรรมบางอย่างออกไปเป็น module อื่น (หรือ module ใหม่) -
Order Maintenanceต้องรู้ข้อมูลของCustomerมากจนการแยก module ทั้งสองออกจากกันจะต้องอาศัย coupling ระดับสูงเพื่อให้ทำงานได้หรือไม่? (นี่ย้อนกลับไปที่คำพูดของ Larry Constantine ก่อนหน้านี้)
คำถามเหล่านี้คือตัวอย่างของการวิเคราะห์ trade-off ซึ่งเป็นแก่นแท้ของงานสถาปนิกซอฟต์แวร์
นักวิทยาศาสตร์คอมพิวเตอร์ได้พัฒนา metric เชิงโครงสร้างที่ดีสำหรับวัด cohesion นั่นคือ Lack of Cohesion (ซึ่งค่อนข้างน่าประหลาดใจ เมื่อพิจารณาว่า cohesion เป็นคุณลักษณะที่ค่อนข้างเป็นเรื่องส่วนตัว) ชุด metric ที่มีชื่อเสียงชื่อ Chidamber and Kemerer Object-Oriented Metrics Suite วัดแง่มุมเฉพาะของระบบซอฟต์แวร์เชิงวัตถุ ชุดนี้รวม code metric ทั่วไปหลายตัว เช่น Cyclomatic Complexity (ดูที่ “Cyclomatic Complexity” ) และ coupling metric สำคัญหลายตัวที่กล่าวถึงใน “Coupling”
Chidamber และ Kemerer ยังได้พัฒนา metric ชื่อ Lack of Cohesion in Methods (LCOM) ซึ่งวัด cohesion เชิงโครงสร้างของ module เวอร์ชันแรกสุดปรากฏใน Equation 3-1
Equation 3-1. LCOM, version 1
L C O M \= | P | - | Q | , if | P | > | Q | 0 , otherwise
ในสมการนี้ P จะเพิ่มขึ้นทีละ 1 สำหรับทุก method ที่ไม่ได้เข้าถึง field ที่ใช้ร่วมกันตัวใดตัวหนึ่ง ส่วน Q จะลดลงทีละ 1 สำหรับ method ที่ เข้าถึง field ที่ใช้ร่วมกันนั้น หากสูตรนี้ทำให้คุณสับสน เราก็เข้าใจดี เพราะมันซับซ้อนขึ้นเรื่อย ๆ ตามกาลเวลา เวอร์ชันที่สอง ซึ่งเปิดตัวในปี 1996 (จึงมีชื่อว่า LCOM96B ) ปรากฏใน Equation 3-2
Equation 3-2. LCOM96B
L C O M 96 b \= 1 a ∑ j \= 1 a m - μ ( A j ) m
เราจะไม่มาไล่แกะตัวแปรและตัวดำเนินการใน Equation 3-2 เพราะคำอธิบายเป็นข้อความต่อไปนี้เข้าใจง่ายกว่ามาก โดยสรุป metric LCOM เผยให้เห็น coupling แบบบังเอิญ (incidental) ภายในคลาส นิยามที่เข้าใจง่ายกว่าของ LCOM คือ “ผลรวมของชุด method ที่ไม่ได้ใช้ field ร่วมกัน”
ลองนึกถึงคลาสหนึ่งที่มี private field a และ b method จำนวนมากเข้าถึงแค่ a และอีกจำนวนมากเข้าถึงแค่ b ผลรวม ของชุด method ที่ไม่ได้ใช้ field ร่วมกัน ( a และ b ) จะมีค่าสูง ดังนั้นคลาสนี้จึงมีคะแนน LCOM สูง ซึ่งบ่งชี้ถึง lack of cohesion in methods ที่มีนัยสำคัญ
ลองพิจารณาสามคลาสที่แสดงใน Figure 3-1 ในภาพนี้ field จะปรากฏเป็นตัวอักษรเดี่ยวภายในรูปแปดเหลี่ยม และ method จะปรากฏเป็นบล็อก ใน Class X คะแนน LCOM ต่ำ บ่งชี้ถึง cohesion เชิงโครงสร้างที่ดี ในขณะที่ Class Y ขาด cohesion แต่ละคู่ field/method ใน Class Y สามารถแยกออกไปอยู่ในคลาสของตัวเองได้โดยไม่กระทบพฤติกรรมของระบบ ส่วน Class Z แสดง cohesion แบบผสม โดยคู่ field/method สุดท้ายสามารถถูก refactor ออกไปเป็นคลาสแยกได้
Figure 3-1. metric LCOM ที่ field แสดงเป็นรูปแปดเหลี่ยม และ method แสดงเป็นสี่เหลี่ยม
metric LCOM มีประโยชน์ต่อสถาปนิกที่กำลังวิเคราะห์โค้ดเบสเพื่อช่วยในการปรับโครงสร้างใหม่ ย้ายระบบ หรือทำความเข้าใจโค้ดเบส คลาส utility ที่ใช้ร่วมกันมักเป็นปัญหาปวดหัวเวลาย้ายสถาปัตยกรรม การใช้ metric LCOM ช่วยให้สถาปนิกหาคลาสที่ถูก coupled กันแบบบังเอิญและไม่ควรจะถูกรวมเป็นคลาสเดียวตั้งแต่แรกได้
software metric หลายตัวมีข้อบกพร่องร้ายแรง และ LCOM ก็ไม่มีข้อยกเว้น สิ่งที่ metric นี้หาได้มีเพียง lack of cohesion เชิง โครงสร้าง เท่านั้น มันไม่มีทางบอกได้ว่าชิ้นส่วนต่าง ๆ เข้ากันในเชิงตรรกะหรือไม่ นี่สะท้อนกลับไปยังกฎข้อที่สองของสถาปัตยกรรมซอฟต์แวร์ของเรา นั่นคือ ทำไม สำคัญกว่า อย่างไร
Coupling (การผูกติดกัน)
โชคดีที่เรามีเครื่องมือที่ดีกว่าสำหรับวิเคราะห์ coupling ในโค้ดเบส เครื่องมือเหล่านี้ตั้งอยู่บนพื้นฐานของทฤษฎีกราฟบางส่วน เพราะการเรียกและการ return ของ method ก่อตัวเป็น call graph ซึ่งสามารถวิเคราะห์ทางคณิตศาสตร์ได้ หนังสือของ Edward Yourdon และ Larry Constantine ชื่อ Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design (Prentice-Hall, 1979) ได้นิยามแนวคิดหลักหลายอย่าง รวมถึง metric afferent coupling และ efferent coupling Afferent coupling วัดจำนวนการเชื่อมต่อ ขาเข้า (incoming) มายัง code artifact หนึ่ง (component, class, function และอื่น ๆ) Efferent coupling วัดการเชื่อมต่อ ขาออก (outgoing) ไปยัง code artifact อื่น ๆ มีเครื่องมือสำหรับแทบทุกแพลตฟอร์มที่ช่วยให้สถาปนิกวิเคราะห์คุณลักษณะ coupling ของโค้ดได้