ความเชื่อมแน่น (Cohesion) เป็นเมตริกที่แสดงว่าองค์ประกอบภายในโมดูลหรือคลาสหนึ่งมีความสัมพันธ์กันอย่างใกล้ชิดเพียงใด ตาม Wikipedia ความเชื่อมแน่นสูงเป็นลักษณะของโมดูลที่ออกแบบมาอย่างดี ซึ่งเมธอดและฟิลด์ทั้งหมดทำงานในงานเดียว ความเชื่อมแน่น ส่งผลโดยตรงต่อความสามารถในการบำรุงรักษาโค้ด และตรงกันข้ามกับการเชื่อมโยง (coupling) — ความสัมพันธ์ระหว่างโมดูล
ประเด็นสำคัญ
ความเชื่อมแน่น เป็นเมตริกที่ประเมินว่าเมธอด ฟิลด์ และคุณสมบัติภายในคลาสหรือโมดูลหนึ่งเชื่อมต่อกันอย่างมีตรรกะเพียงใด โมดูลที่มีความเชื่อมแน่นสูงทำงานเดียวและมีเฉพาะองค์ประกอบที่จำเป็นเพื่อบรรลุงานนั้น โมดูลที่มีความเชื่อมแน่นต่ำพยายามทำหลายสิ่งพร้อมกัน — เมธอดของมันมีความสัมพันธ์ทางความหมายที่อ่อนแอ
ในบริบทของการเขียนโปรแกรมเชิงวัตถุ ความเชื่อมแน่นเกี่ยวข้องอย่างใกล้ชิดกับหลักการความรับผิดชอบเดียว (S) หากคลาสมีความรับผิดชอบที่ชัดเจนหนึ่งอย่าง ความเชื่อมแน่นของมันมักจะสูง หากคลาสจัดการทั้ง UI ตรรกะทางธุรกิจ และเครือข่ายพร้อมกัน — ความเชื่อมแน่นต่ำ และคลาสดังกล่าวควรถูกแบ่งออกเป็นหลายคลาสแยกกันที่มีความรับผิดชอบที่แคบลง
การเข้าใจความเชื่อมแน่น ช่วยนักพัฒนาในการตัดสินใจปรับโครงสร้างโค้ด เมื่อคุณเห็นเมธอดในคลาสที่ไม่ใช้ฟิลด์ใดๆ ของคลาส นั่นเป็นสัญญาณของความเชื่อมแน่นต่ำ เมธอดนั้นอาจอยู่ในตำแหน่งที่ไม่เหมาะสมในคลาส หรือคลาสถูกออกแบบมาไม่ดี การพยายามทำให้มีความเชื่อมแน่นสูงเป็นความพยายามอย่างต่อเนื่องในการปรับปรุงสถาปัตยกรรมในทุกระดับของโค้ด
ในวิศวกรรมซอฟต์แวร์ มีการจำแนกระดับความเชื่อมแน่นเจ็ดระดับ เรียงจากแย่ที่สุดไปยังดีที่สุด การเข้าใจสเกลนี้ช่วยให้คุณประเมินคุณภาพของโมดูลอย่างเป็นกลางและกำหนดทิศทางสำหรับการปรับโครงสร้างโค้ด ยิ่งระดับสูงเท่าใด โค้ดก็จะยิ่งบำรุงรักษาและเข้าใจได้ง่ายมากขึ้นเท่านั้น
โดยบังเอิญ — ระดับที่แย่ที่สุด ซึ่งองค์ประกอบในโมดูลถูกจัดกลุ่มแบบสุ่มโดยไม่มีความเชื่อมต่อทางตรรกะ ตัวอย่าง: คลาส Utilities ที่มีเมธอดสำหรับจัดรูปแบบวันที่ ส่งอีเมล และคำนวณส่วนลด คลาสดังกล่าวไม่สามารถเข้าใจได้หากไม่ได้อ่านเมธอดทั้งหมด และการเปลี่ยนเมธอดหนึ่งอาจทำให้เมธอดอื่นเสียหายเพียงเพราะอยู่ร่วมกัน
เชิงตรรกะ — องค์ประกอบทำงานที่เกี่ยวข้องกันเชิงตรรกะแต่แตกต่างกันโดยพื้นฐาน คลาสที่มีเมธอด parseJSON, parseXML และ parseCSV เชื่อมต่อกันเชิงตรรกะด้วยหัวข้อ “การแยกวิเคราะห์” แต่แต่ละเมธอดทำงานที่แตกต่างกันโดยพื้นฐาน ปัญหา: เมื่อเพิ่มรูปแบบใหม่ (YAML) คลาสจะใหญ่ขึ้นและอินเทอร์เฟซของมันพองตัว
เชิงเวลา — องค์ประกอบถูกจัดกลุ่มตามเวลาดำเนินการ คลาส AppInitializer ที่ตั้งค่าฐานข้อมูล โหลดการกำหนดค่า และเริ่มต้นการวิเคราะห์ — ทั้งหมดนี้เกิดขึ้นเมื่อเริ่มต้นแอป แต่งานต่างๆ ไม่เกี่ยวข้องกัน ควรแบ่งพวกมันเป็น Initializer แยกกันสำหรับแต่ละพื้นที่ความรับผิดชอบ
เชิงกระบวน — เกิดขึ้นเมื่อองค์ประกอบถูกรวมเข้าด้วยกันโดยลำดับการดำเนินการ โมดูล “การดำเนินการตามคำสั่งซื้อ” มีเมธอด validateCart, processPayment และ sendConfirmation — แต่ละเมธอดถูกเรียกตามลำดับหลังจากเมธอดก่อนหน้า ซึ่งดีกว่าความเชื่อมแน่นแบบโดยบังเอิญหรือเชิงตรรกะ แต่ก็ยังไม่เหมาะ: แต่ละขั้นตอนสามารถถูกแยกออกเป็นโมดูลแยกต่างหากได้
เชิงสื่อสาร — องค์ประกอบทำงานกับข้อมูลเดียวกัน คลาส UserService ที่มีเมธอด getUser, updateUser และ deleteUser ถูกรวมเข้าด้วยกันโดยเอนทิตี User ร่วมกัน ซึ่งดีกว่าความเชื่อมแน่นเชิงกระบวนอย่างมีนัยสำคัญ: คลาสมีโดเมนที่ชัดเจน คลาส Repository ส่วนใหญ่ในโปรเจกต์มือถือมีความเชื่อมแน่นเชิงสื่อสาร
เชิงหน้าที่ — ระดับสูงที่สุด ซึ่งทุกองค์ประกอบในโมดูลมีส่วนร่วมในการดำเนินงานเดียว คลาส PasswordValidator ที่มีเมธอด validate เดียวที่ตรวจสอบความยาว การมีอยู่ของอักขระ และความซับซ้อนของรหัสผ่านเป็นตัวอย่างของความเชื่อมแน่นเชิงหน้าที่ หากคลาสดังกล่าวเปลี่ยนแปลง นั่นเป็นเพราะกฎการตรวจสอบรหัสผ่านเปลี่ยนแปลงไปเท่านั้น
การบรรลุความเชื่อมแน่นเชิงหน้าที่ เป็นเป้าหมายหลักของการปรับโครงสร้างสถาปัตยกรรม แต่ละคลาสควรมีเหตุผลในการเปลี่ยนแปลงเพียงเหตุผลเดียว ในการพัฒนาแอปมือถือ ความเชื่อมแน่นเชิงหน้าที่ทำได้โดยการแยก Use Cases, View ที่กำหนดเอง, ฟอร์แมตเตอร์ และตัวตรวจสอบ แต่ละคลาสดังกล่าวเป็นส่วนประกอบที่สมบูรณ์พร้อมพื้นที่ความรับผิดชอบที่ชัดเจน
ความเชื่อมแน่นและการเชื่อมโยง เป็นสองด้านของคุณภาพเดียวกัน ยิ่งความเชื่อมแน่นภายในโมดูลสูงเท่าใด การเชื่อมโยงระหว่างโมดูลก็ยิ่งต่ำลงเท่านั้น ระบบที่ออกแบบมาอย่างดีพยายามทั้งความเชื่อมแน่นภายในสูงและการเชื่อมโยงภายนอกที่หลวม หลักการนี้ได้รับการยอมรับว่าเป็นพื้นฐานในวิศวกรรมซอฟต์แวร์ตั้งแต่ปี 1970
ความสัมพันธ์ความเชื่อมแน่น-การเชื่อมโยง สามารถคิดได้ว่าเป็นความสมดุล หากนักพัฒนาเสียสละความเชื่อมแน่นโดยรวมงานหลายอย่างในคลาสเดียว โมดูลข้างเคียงจะได้รับการพึ่งพามากขึ้น — พวกมันต้องเข้าถึงคลาสที่รับภาระหนักนี้เพื่อวัตถุประสงค์ต่างๆ ซึ่งเพิ่มการเชื่อมโยง ในทางกลับกัน การแบ่งเป็นคลาสเล็กๆ ที่มีความเชื่อมแน่นสูงจะลดจุดปฏิสัมพันธ์ระหว่างโมดูล
ในทางปฏิบัติ หมายความว่า: เมื่อคุณแยกคลาสใหม่ที่มีความเชื่อมแน่นเชิงหน้าที่ คุณจะปลดปล่อยโมดูลอื่นๆ จากความจำเป็นต้องรู้รายละเอียดการทำงานของมัน ตัวอย่างเช่น การแยก EncryptionManager เป็นคลาสแยกต่างหากที่มีความเชื่อมแน่นเชิงหน้าที่ให้อินเทอร์เฟซ encrypt/decrypt แบบง่ายแก่โมดูลอื่นๆ โดยไม่ต้องเข้าใจรายละเอียดของอัลกอริทึมการเข้ารหัส
// ความเชื่อมแน่นต่ำ — คลาสทำทุกอย่างพร้อมกัน
class UserManager {
fun fetchAndSaveUser(id: String) { }
fun parseUserJson(json: String): User { }
fun displayUserName(user: User): String { }
fun validateEmail(email: String): Boolean { }
}
// ความเชื่อมแน่นสูง — แต่ละคลาสแก้ไขงานเดียว
class UserRepository {
fun fetchUser(id: String): User { }
}
class UserJsonParser {
fun parse(json: String): User { }
}
class UserNameFormatter {
fun format(user: User): String { }
}
class EmailValidator {
fun isValid(email: String): Boolean { }
}
ตัวอย่างแสดงความแตกต่าง: UserManager มีความเชื่อมแน่นเชิงตรรกะ — เมธอดทั้งหมดเกี่ยวกับผู้ใช้ แต่แต่ละเมธอดทำงานที่แตกต่างกันโดยพื้นฐาน หลังจากการปรับโครงสร้าง แต่ละคลาสมีความเชื่อมแน่นเชิงหน้าที่ และการเชื่อมโยงลดลงเพราะโมดูลอื่นขึ้นอยู่กับคลาสที่ต้องการเท่านั้น ไม่ใช่ UserManager ทั้งหมด
LCOM (การขาดความเชื่อมแน่นของเมธอด) เป็นเมตริกที่รู้จักกันดีที่สุดสำหรับวัดความเชื่อมแน่นของคลาส LCOM นับจำนวนคู่เมธอดที่ไม่ใช้ฟิลด์ร่วมกัน ค่า 0 หมายถึงความเชื่อมแน่นในอุดมคติ (เมธอดทั้งหมดทำงานกับฟิลด์เดียวกัน) ในขณะที่ค่าสูงบ่งชี้ความเชื่อมแน่นต่ำ LCOM4 (เวอร์ชันปรับปรุง) พิจารณาการเชื่อมต่อแบบถ่ายทอดผ่านเมธอดอื่น
ในการพัฒนา Android สามารถรับเมตริกความเชื่อมแน่นได้ผ่าน Detekt ด้วยกฎ TooManyFunctions คลาสที่มีเมธอดหลายสิบเมธอดที่ใช้กลุ่มฟิลด์ต่างๆ มักจะมีความเชื่อมแน่นต่ำ ใน iOS SwiftLint มีกฎ file_length และ function_body_length — ตัวบ่งชี้ทางอ้อม: ไฟล์และเมธอดที่ยาวมักบ่งชี้ความเชื่อมแน่นต่ำ
วิธีการประเมินด้วยตนเอง: ถามคำถามว่า “คลาสนี้จะเปลี่ยนแปลงด้วยเหตุผลเดียวหรือหลายเหตุผล?” หากคุณสามารถระบุได้มากกว่าหนึ่งเหตุผลที่เป็นอิสระ — คลาสมีความเชื่อมแน่นต่ำ การทดสอบที่สอง: “คลาสนี้สามารถแบ่งเป็นสองคลาสอิสระได้หรือไม่?” ถ้าได้ — ทำเลย การตรวจสอบความเชื่อมแน่นเป็นประจำในการตรวจสอบโค้ดช่วยป้องกันคลาส God และลดหนี้ทางเทคนิค
ขั้นตอนแรก — ใช้หลักการความรับผิดชอบเดียว แต่ละคลาสควรมีความรับผิดชอบที่ชัดเจนหนึ่งอย่าง หากคลาสมีเมธอดที่ไม่เกี่ยวข้องกับงานหลักของมัน ให้แยกมันไปยังคลาสอื่น เทคนิค Extract Class หรือ Extract Delegate ใน IDE จะทำให้กระบวนการนี้เป็นอัตโนมัติ หลังจากการแยก ให้ตรวจสอบว่าคลาสเดิมมีความเฉพาะเจาะจงมากขึ้นหรือไม่
ขั้นตอนที่สอง — ใช้รูปแบบ Facade เพื่อทำให้อินเทอร์เฟซง่ายขึ้น หากคลาสให้เมธอด 20 เมธอด แต่ไคลเอ็นต์ใช้เพียง 3–4 คลาสอาจมีความเชื่อมแน่นต่ำ — มันให้ฟังก์ชันการทำงานที่หลากหลายเกินไป จัดกลุ่มเมธอดตามหัวข้อ แยกคลาสสำหรับแต่ละกลุ่ม และทำให้คลาสเดิมเป็น facade หรือลบทิ้ง
ขั้นตอนที่สาม — ให้ความสนใจกับกลุ่มฟิลด์ หากคลาสมีฟิลด์ที่ใช้โดยเมธอดเพียงบางส่วน — นั่นเป็นตัวบ่งชี้ของความเชื่อมแน่นต่ำ แบ่งคลาสตามกลุ่มฟิลด์ ตัวอย่างเช่น หากคลาสมีฟิลด์ userRepository, networkClient และ analyticsTracker แต่กลุ่มเมธอดแรกใช้เฉพาะ userRepository ในขณะที่กลุ่มที่สองใช้ networkClient — นี่คือสองคลาสที่แตกต่างกัน
ขั้นตอนที่สี่ — หลีกเลี่ยง การสร้างคลาส “utility” ที่มีเมธอด static ตามอำเภอใจ ทุกเมธอด static ที่อยู่ในคลาส Utils หรือ Helpers เป็นตัวเลือกสำหรับการแยกเป็นคลาสเฉพาะทาง FormatUtils.dateToString ควรย้ายไปที่ DateFormatter และ ValidationUtils.isValidEmail ไปที่ EmailValidator สิ่งนี้เพิ่มความเชื่อมแน่นของแต่ละคลาสและทำให้โค้ดสามารถอธิบายตัวเองได้
คำถามที่พบบ่อย
เกือบทุกครั้ง ความเชื่อมแน่นเชิงหน้าที่ทำให้โค้ดชัดเจนและคาดเดาได้ อย่างไรก็ตาม การทำให้ถึงที่สุดอาจนำไปสู่การแตกแยกมากเกินไป: การสร้างคลาสแยกสำหรับทุกการดำเนินการ ทำให้สถาปัตยกรรมซับซ้อนเกินไป ความสมดุลคือสองสามคลาสต่อฟีเจอร์ แต่ละคลาสมีความเชื่อมแน่นเชิงหน้าที่
ความเชื่อมแน่น เป็นเมตริกของความสอดคล้องภายในภายในโมดูลหรือคลาสเดียว โมดูลาริตีเป็นหลักการทางสถาปัตยกรรมที่แอปพลิเคชันถูกแบ่งออกเป็นโมดูลทางกายภาพ ความเชื่อมแน่นสูงเป็นเป้าหมายเมื่อออกแบบทั้งคลาสเดี่ยวและโมดูลทั้งหมด
Detekt สำหรับ Android และ Xcode Analyzer สำหรับ iOS เน้นคลาสที่มีจำนวนเมธอดหรือฟิลด์ที่น่าสงสัย IntelliJ IDEA และ AppCode มีการแสดงภาพการพึ่งพา — คุณสามารถเห็นกราฟการเชื่อมต่อและพบคลาสที่มีความเชื่อมแน่นต่ำ SonarQube คำนวณเมตริก LCOM โดยอัตโนมัติ
ได้ อินเทอร์เฟซที่มีเมธอด connect, disconnect และ isConnected มีความเชื่อมแน่นสูง — เมธอดทั้งหมดเกี่ยวกับการจัดการการเชื่อมต่อ อินเทอร์เฟซที่มี connect, parseData และ renderUI มีความเชื่อมแน่นต่ำ หลักการแยกอินเทอร์เฟซ (SOLID) ต้องสร้างอินเทอร์เฟซที่เน้นเฉพาะเจาะจงด้วยความเชื่อมแน่นสูง
ถาม สามคำถาม: วัตถุประสงค์ของคลาสสามารถอธิบายได้ในหนึ่งประโยคหรือไม่? เมธอดทั้งหมดสนับสนุนวัตถุประสงค์นี้หรือไม่? มีฟิลด์ในคลาสที่ไม่ได้ใช้โดยบางเมธอดหรือไม่? หากคำตอบของคำถามใดคือไม่ — ความเชื่อมแน่นต่ำ และควรแบ่งคลาส
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม