Cohesion (چسبندگی) — متریکی است که نشان میدهد چقدر المان های داخل یک ماژول یا کلاس به هم پیوسته هستند. به گزارش ویکیپدیا، چسبندگی بالا نشانه یک ماژول خوب طراحی شده است که تمام روشها و فیلدها بر روی یک وظیفه کار میکنند. Cohesion مستقیماً بر قابلیت حفظ کد تأثیر میگذارد و در مقابل coupling — پیوستگی بین ماژولها — قرار میگیرد.
نکات کلیدی
Cohesion (چسبندگی) — متریکی است که ارزیابی میکند چقدر روشها، فیلدها و ویژگیهای داخل یک کلاس یا ماژول از نظر منطقی به هم مرتبط هستند. یک ماژول با چسبندگی بالا یک وظیفه انجام میدهد و تنها المانهایی را شامل میشود که برای انجام آن لازم است. یک ماژول با چسبندگی پایین سعی میکند چند چیز را به طور همزمان انجام دهد — روشها از نظر معنایی ضعیفاً مرتبط هستند.
در بزرگ برنامهنویسی شیگرایی cohesion با Single Responsibility Principle (S) ارتباط نزدیکی دارد. اگر یک کلاس یک مسئولیت واضح داشته باشد، اگلب چسبندگی آن بالاست. اگر یک کلاس هم با UI، هم با منطق کسبوکار و هم با کار شبکه سروکار دارد — cohesion پایین است و چنین کلاسی را باید به چند کلاس جداگانه با مسئولیت محدودتر تقسیم کرد.
درک cohesion به برنامهنویس در تصمیمگیری درباره بازآرایی کمک میکند. وقتی میبینید در یک کلاس روشی وجود دارد که از فیلدهای کلاس استفاده نمیکند، این نشانه چسبندگی پایین است. چنین روشی یا در کلاس اضافی است یا کلاس به طور نادرست طراحی شده است. تلاش برای چسبندگی بالا کاری مداوم برای بهبود معماری در هر سطح کد است.
در مهندسی نرمافزار هفت سطح cohesion وجود دارد که از بدترین تا بهترین مرتب شدهاند. درک این مقیاس به شما امکان میدهد کیفیت ماژول را به صورت عینی ارزیابی کرده و تعیین کنید در چه جهتی در بازآرایی حرکت کنید. هر چقدر سطح بالاتر باشد، کد قابل حفظتر و قابل فهمتر خواهد بود.
تصادفی (coincidental) — بدترین سطح، زمانی که المانها در ماژول به صورت تصادفی و بدون هیچ ارتباط منطقی گروهبندی شدهاند. مثال: کلاس Utilities که شامل روشهای فرمتدهی تاریخ، ارسال ایمیل و محاسبه تخفیف است. چنین کلاسی را بدون خواندن تمام روشها نمیتوان فهمید و تغییر یک روش میتواند دیگران را فقط به دلیل اینکه در کنار هم قرار دارند تخریب کند.
چسبندگی منطقی (logical) — المانها وظایف منطقیاً مرتبط ولی از نظر ماهیت متفاوت انجام میدهند. کلاسی با روشهای parseJSON، parseXML و parseCSV از نظر منطقی به موضوع «پارسینگ» مرتبط است، اما هر روش کاری اساساً متفاوت انجام میدهد. مشکل: با افزودن فرمت جدید (YAML)، کلاس بزرگتر شده و اینترفیس آن متورم میشود.
چسبندگی زمانی (temporal) — المانها بر اساس زمان اجرا گروهبندی شدهاند. کلاس AppInitializer که پایگاه داده را تنظیم میکند، کونفیگ را بارگیری میکند، آنالیتیک را اهلیه میکند — همه اینها در زمان راهاندازی اتفاق میافتد، اما وظایف با هم مرتبط نیستند. بهتر است آنها را به Initializerهای جداگانه برای هر حوزه مسئولیت تقسیم کرد.
چسبندگی رویهای (procedural) زمانی ایجاد میشود که المانها با تسلسل اجرا متحد شوند. ماژول «پردازش سفارش» شامل روشهای validateCart، processPayment، sendConfirmation است — هر روش بطور حتمی بعد از قبلی فراخوانده میشود. این از چسبندگی تصادفی یا منطقی بهتر است، اما هنوز ایدئال نیست: هر قدم را میتوان به یک ماژول جداگانه منتقل کرد.
چسبندگی ارتباطی (communicational) — المانها با دادههای یکسان کار میکنند. کلاس UserService با روشهای getUser، updateUser، deleteUser با نهاد مشترک User متحد شده است. این به مراتب از چسبندگی رویهای بهتر است: کلاس یک حوزه موضوعی واضح دارد. اکثر کلاسهای Repository در پروژههای موبایل چسبندگی ارتباطی دارند.
چسبندگی وظیفهای (functional) — بالاترین سطح، زمانی که هر عنصر ماژول در انجام یک وظیفه شرکت میکند. کلاس PasswordValidator با تک روش validate که طول، وجود شرایط و پیچیدگی رمز عبور را بررسی میکند — نمونه چسبندگی وظیفهای است. اگر چنین کلاسی تغییر کند، فقط به دلیل تغییر قوانین اعتبارسنجی رمز عبور است.
دستیابی به چسبندگی وظیفهای هدف اصلی بازآرایی معماری است. هر کلاس باید دقیقاً یک دلیل برای تغییر داشته باشد. در توسعه موبایل، چسبندگی وظیفهای با جداسازی Use Caseهای مجزا، Viewهای سفارشی، فرمتدهندهها و اعتبارسنجها به دست میآید. هر چنین کلاسی یک بلوک ساختمانی کامل با یک حوزه مسئولیت واضح است.
Cohesion و coupling — دو جنبه یک کیفیت. هر چقدر cohesion داخل ماژول بالاتر باشد، coupling بین ماژولها پایینتر است. یک سیستم خوب طراحی شده همزمان به چسبندگی بالا در داخل و پیوستگی ضعیف در خارج گرایش دارد. این قاعده از سالهای 1970 در مهندسی نرمافزار بنیادین تلقی میشود.
نسبت cohesion-coupling را میتوان به عنوان یک تعادل در نظر گرفت. اگر برنامهنویس با ادغام چند وظیفه در یک کلاس، cohesion را قربان میدهد، ماژولهای مجاور وابستگی بیشتری پیدا میکنند — آنها باید برای اهداف مختلف به این کلاس پربار مراجعه کنند که coupling را افزایش میدهد. و برعکس، تقسیم به کلاسهای کوچک با چسبندگی بالا تعداد نقاط تعامل بین ماژولها را کاهش میدهد.
در عمل این یعنی: وقتی یک کلاس جدید با چسبندگی وظیفهای جدا میکنید، دیگر ماژولها را از لزوم دانستن جزئیات پیادهسازی آن آزاد میکنید. به عنوان مثال، با جداسازی 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 چسبندگی منطقی دارد — تمام روشها مربوط به کاربران است، اما هر کدام کاری اساساً متفاوت انجام میدهند. پس از بازآرایی، هر کلاس چسبندگی وظیفهای دارد و coupling کاهش مییابد، زیرا دیگر ماژولها فقط به کلاس مورد نیاز خود وابسته هستند، نه به کل مجموع UserManager.
LCOM (Lack of Cohesion of Methods) — معروفترین متریک برای اندازهگیری چسبندگی کلاس است. LCOM تعداد زوجروشهایی را که از فیلدهای مشترک استفاده نمیکنند شمارش میکند. مقدار 0 به معنای چسبندگی ایدئال است (تمام روشها با فیلدهای یکسان کار میکنند)، مقدار بالا به معنای چسبندگی پایین است. LCOM4 (نسخه بهبود یافته) ارتباطات انتقالی از طریق سایر روشها را نیز در نظر میگیرد.
در توسعه Android میتوان متریکهای چسبندگی را از طریق Detekt با قاعده TooManyFunctions به دست آورد. کلاسهایی با دهها روش که از گروههای فیلدی مختلف استفاده میکنند به احتمال زیاد چسبندگی پایینی دارند. در iOS، SwiftLint قواعد file_length و function_body_length را دارد — نشاندهندگان غیرمستقیم: فایلها و روشهای طولانی اغلب چسبندگی پایین را نشان میدهند.
روش دستی ارزیابی: سوال «آیا این کلاس به یک دلیل تغییر میکند یا چند دلیل؟» را بپرسید. اگر میتوانید بیش از یک دلیل مستقل نام ببرید — کلاس چسبندگی پایینی دارد. آزمایش دوم: «آیا میتوان این کلاس را به دو کلاس مستقل تقسیم کرد؟» اگر بله — این کار را انجام دهید. بررسی منظم cohesion در بازبینی کد از ایجاد کلاسهای God جلوگیری میکند و بدهی فنی را کاهش میدهد.
گام اول — Single Responsibility Principle را اعمال کنید. هر کلاس باید یک مسئولیت واضح داشته باشد. اگر در کلاس روشی وجود دارد که به وظیفه اصلی آن مربوط نیست، آن را به یک کلاس جداگانه منتقل کنید. تکنیک Extract Class یا Extract Delegate در IDE این فرآیند را خودکار میکند. پس از جداسازی بررسی کنید که آیا کلاس اصلی متمرکزتر شده است.
گام دوم — از الگوی Facade برای سادهسازی اینترفیس استفاده کنید. اگر یک کلاس 20 روش ارائه میدهد و مشتریان فقط از 3-4 تا آن استفاده میکنند، احتمالاً کلاس چسبندگی پایینی دارد — فقره بسیار کارکرد مختلف است. روشها را بر اساس موضوع گروهبندی کرده و برای هر گروه کلاسهای جداگانه ایجاد کنید، و کلاس اصلی را به یک facade تبدیل کنید یا حذف کنید.
گام سوم — به گروههای فیلدی توجه کنید. اگر کلاس فیلدهایی دارد که فقط بخشی از روشها استفاده میکنند — این نشانگر چسبندگی پایین است. کلاس را بر اساس گروههای فیلدی تقسیم کنید. به عنوان مثال، اگر کلاس شامل فیلدهای userRepository، networkClient و analyticsTracker است، اما روشهای گروه اول فقط از userRepository و گروه دوم فقط از networkClient استفاده میکنند — اینها دو کلاس متفاوت هستند.
گام چهارم — از ایجاد کلاسهای «ابزاری» با روشهای static تصادفی خودداری کنید. هر روش static در کلاس Utils یا Helpers کاندیدای جداسازی به یک کلاس تخصصی است. FormatUtils.dateToString را به DateFormatter و ValidationUtils.isValidEmail را به EmailValidator منتقل کنید. این کار cohesion هر کلاس را افزایش میدهد و کد را خودمستند میسازد.
سوالات متداول
تقریباً همیشه. چسبندگی وظیفهای کد را قابل فهم و قابل پیشبینی میکند. اما افراط در این کار میتواند منجر به تقسیم افراطی شود: زمانی که برای هر عملیات یک کلاس جداگانه ایجاد شود و معماری بیش از حد پیچیده گردد. تعادل — چند کلاس برای یک ویژگی، هر کدام با چسبندگی وظیفهای.
Cohesion — متریک توافق داخلی یک ماژول یا کلاس است. Modularity — اصل معماری که بر اساس آن اپلیکیشن به ماژولهای فیزیکی تقسیم میشود. Cohesion بالا هدفی در طراحی هم کلاسهای جداگانه و هم ماژولهای کامل است.
Detekt برای Android و Xcode Analyzer برای iOS کلاسهایی را که تعداد مشکوکی زیاد روش یا فیلد دارند برجسته میکنند. IntelliJ IDEA و AppCode نمایش وابستگیها را دارند — میتوانید گراف ارتباطات را مشاهده کرده و کلاسهای با چسبندگی پایین را کشف کنید. SonarQube متریکهای LCOM را به صورت خودکار محاسبه میکند.
بله. یک اینترفیس با روشهای connect، disconnect و isConnected چسبندگی بالایی دارد — تمام روشها مربوط به مدیریت اتصال است. یک اینترفیس با روشهای connect، parseData و renderUI چسبندگی پایینی دارد. اصل Interface Segregation (SOLID) ایجاد اینترفیسهای متخصص با چسبندگی بالا را ایجاب میکند.
سه سوال بپرسید: آیا میتوان هدف کلاس را با یک جمله توصیف کرد؟ آیا تمام روشها از این هدف پشتیبانی میکنند؟ آیا در کلاس فیلدهایی وجود دارد که توسط بخشی از روشها استفاده نمیشوند؟ اگر پاسخ به هر کدام از سوالات منفی است — cohesion پایین است و کلاس باید تقسیم شود.
نتیجهگیری
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید