تو این مقاله میخوایم به معنی کانسنس و انواع اون در نرم افزار و کاربرد عملی اون برای ریفکتور کردن و طراحی بهتر نرم افزار ها میپردازیم. ( این مقاله از روی کتاب Fundamentals of Software Architecture نوشته شده است)

تا حالا براتون پیش اومده یه تغییر خیلی کوچیک توی یک بخش از کد بدید و ناگهان ببینید چند تا ماژول یا سرویس دیگه بدون هیچ دلیل واضحی از کار افتادن؟ این همون کابوسیه که مهندسهای نرمافزار بهش میگن «دردسر وابستگی» یا Coupling. اما توی دنیای معماری نرمافزار، یک کلمه دقیقتر و جذابتر برای توصیف این دردسر وجود داره: Connascence (کانسنس).
اگه به عنوان مدیر فنی درگیر بدهیهای فنی پروژهاید، یا به عنوان یک مهندس نرمافزار میخواید سیستمی طراحی کنید که فردا روزی توسعهاش تبدیل به یک کار فرسایشی نشه، درک مفهوم کانسنس براتون از نون شب واجبتره.
این مفهوم رو اولین بار آقای میلی پیج-جونز (Meilir Page-Jones) مطرح کرد. تعریفش خیلی ساده، اما به شدت عمیقه:
“دو تا کامپوننت زمانی با هم کانسنس دارن که اگه یکیشون رو تغییر دادی، مجبور بشی اون یکی رو هم تغییر بدی تا سیستم همچنان درست کار کنه.”
هرچی کانسنس تو سیستم شما بیشتر باشه، یعنی سیستم شما شکنندهتره. پیج-جونز کانسنسها رو به دو دسته بزرگ تقسیم میکنه: استاتیک و داینامیک. بیاید خیلی شفاف و مستقیم بررسیشون کنیم.
این نوع وابستگیها تو همون سطح سورسکد اتفاق میافتن و معمولا با یه نگاه به کد (یا با کمک IDEها) میشه پیداشون کرد. ۵ نوع اصلی داره:
کانسنس نام (Name): سادهترین نوعشه. چند تا کامپوننت باید روی اسم یک متد یا کلاس توافق داشته باشن. اگه اسمش رو تو یکی عوض کنی، تو بقیه هم باید عوض بشه.
کانسنس نوع داده (Type): کامپوننتها باید روی تایپ یک متغیر توافق کنن. اگه یه متغیر از نوع String رو بکنی Int، هرجا که داره ازش استفاده میشه هم باید آپدیت بشه.
کانسنس معنا یا قرارداد (Meaning/Convention): این همون تلهی “اعداد جادویی” (Magic Numbers) در کده. مثلاً تو دیتابیس هاردکد کردید که وضعیت 1 یعنی کاربر فعال. حالا همه کامپوننتها باید این معنا رو بدونن و روش توافق داشته باشن!
کانسنس موقعیت (Position): وابستگی به ترتیب و جایگاه. مثلاً یک فانکشن دارید که ۳ تا پارامتر میگیره. ترتیب پاس دادن این پارامترها به فانکشن مهمه و اگه عوض بشه، همهچیز به هم میریزه.
کانسنس الگوریتم (Algorithm): وقتی دو بخش از سیستم باید حتماً از یک الگوریتم مشترک استفاده کنن. مثال بارزش الگوریتمهای هشینگ پسورد بین کلاینت و سروره. اگر سرور الگوریتمش رو عوض کنه، کلاینت هم حتماً باید خودش رو تغییر بده.
اینجاست که کار سخت میشه! این وابستگیها تو زمان اجرا (Runtime) خودشون رو نشون میدن و پیدا کردنشون موقع کد زدن اصلاً راحت نیست:
کانسنس اجرا (Execution): وقتی ترتیب اجرای کارها مهمه. مثلاً برای ارسال ایمیل، اول باید کانکشن ساخته بشه، بعد بدنه ایمیل ست بشه و در نهایت متد send() فراخوانی بشه. اگه این ترتیب به هم بخوره، سیستم ارور میده.
کانسنس زمانبندی (Timing): وقتی صحت اجرای سیستم به زمان وابسته است. مشکل معروف Race Condition دقیقاً همینجاست؛ وقتی دو تا ترد (Thread) همزمان میخوان یک دیتا رو تغییر بدن و زمانبندیِ اجرا، نتیجه نهایی رو عوض میکنه.
کانسنس مقادیر (Values): وقتی چند تا مقدار مختلف توی سیستم باید حتماً با هم تغییر کنن. بهترین مثالش تراکنشهای توزیعشده (Distributed Transactions) هستن؛ یا همه تغییرات باید با هم اعمال بشن، یا هیچکدوم!
کانسنس هویت (Identity): وقتی چند تا کامپوننت مختلف دارن دقیقاً به یک «موجودیت» (Instance) مشترک تو مموری اشاره میکنن و وابستگی شدیدی به اون دارن.
تا اینجا فهمیدیم که کانسنس چطور میتونه یک سیستم نرمافزاری رو به شدت شکننده کنه. اما نکته مهم اینجاست که کانسنس فقط یک «مفهوم توصیفی» نیست؛ یک ابزار تحلیلی و کاربردی برای تصمیمگیریهای معماری هم هست.
برای اینکه بتونیم کانسنس رو درست تحلیل و مدیریت کنیم، سه ویژگی کلیدی وجود داره که باید همیشه زیر نظر بگیریمشون:
قدرت کانسنس مشخص میکنه که تغییر دادن یا ریفکتور کردن یک وابستگی چقدر سخت و پرریسکه. بعضی از کانسنسها ذاتاً ضعیفتر و مدیریتپذیرترن، و بعضیها هم به شدت قوی، خطرناک و پرهزینه.
هرچی یک کانسنس قویتر باشه:
تغییر دادنش سختتره
اثرش روی کل سیستم بیشتره
احتمال شکستن بخشهای دیگر سیستم بالاتره
یک اصل مهم اینه که تا جای ممکن سیستم رو باید به سمت کانسنسهای ضعیفتر ببریم.

مثال خیلی معروفش «اعداد جادویی» در کده:
// روش ضعیف و مبهم if (status == 1) // CoM // روش بهتر و شفاف if (status == ACTIVE_USER) // CoT
اینجا از یک وابستگی مبهم به یک وابستگی شفاف منتقل شدیم.
این دقیقاً همون حرکتیه که قدرت کانسنس رو کاهش میده.
کانسنس استاتیک تقریباً همیشه بهتر از کانسنس داینامیکه، چون که کانسنس داینامیک فقط هنگام اجرا معلوم میشه؛ یعنی دیر متوجهش میشی، پیدا کردنش سخته، و دیباگش معمولاً یک کابوسه.
لوکالیتی یا فاصله کانسنس مشخص میکنه که اجزای وابسته چقدر به هم نزدیک یا دور هستن.
همون نوع کانسنس، اگر در یک محدوده نزدیک اتفاق بیفته قابل تحمله٬ اما اگر بین بخشهای دور از هم بوجود بیاد، خطرناک میشه.
مثال:
دو کلاس داخل یک ماژول که کانسنس Meaning دارن: قابل قبوله
دو سرویس مستقل با همین کانسنس : خطرناک
چرا خطرناک؟
چون هر تغییری باید در چند نقطه مختلف اعمال بشه.
هماهنگی سختتره، احتمال ناسازگاری بالاتر میره و سیستم شکنندهتر میشه.
کانسنس قوی داخل یک ماژول: قابل مدیریت
همان کانسنس قوی بین دو سیستم مستقل: نشانه یک مشکل طراحی جدی
گستردگی کانسنس مشخص میکنه که چند بخش از سیستم تحت تأثیر یک وابستگی هستن.
هرچی تعداد ماژولها یا سرویسهای درگیر بیشتر باشه، کنترل و نگهداری سختتر و ریسک سیستم بالاتر میره.
مثال:
کانسنس بین دو کلاس: قابل کنترل
کانسنس بین ۲۰ سرویس: تقریباً غیرقابل مدیریت
نکته مهم اینه که حتی یک کانسنس قوی داینامیک اگر فقط چند بخش کوچک رو درگیر کنه، ممکنه خطری نداشته باشه.
اما مشکل اینجاست که سیستمها در طول زمان رشد میکنن و چیزی که امروز کوچک و بیخطره، فردا میتونه تبدیل به یک بحران تمامعیار بشه.
برای اینکه کانسنس به جای تهدید، تبدیل به ابزار کمکی ما در طراحی بشه، این سه اصل کمکمون میکنه:
هرچه سیستم رو به بخشهای مستقلتر تقسیم کنیم، مرزها روشنتر میشن و وابستگیها کاهش پیدا میکنن.
این یعنی پیچیدگی کمتر، تغییرات ایزولهتر و سیستم قابل کنترلتر.
اگر مجبوریم بین بخشها ارتباط داشته باشیم، باید این ارتباط رو تا جای ممکن ضعیف و کنترلپذیر نگه داریم.
مثال:
به جای اشتراک مستقیم دیتا: استفاده از API
به جای وابستگیهای زمان اجرا: استفاده از Message Queue
داخل یک ماژول، کانسنس نهتنها بد نیست؛ بلکه مفید هم هست.
چون تغییرات در یک نقطه متمرکز میشن، پیچیدگی پخش نمیشه و هماهنگی سادهتره.
Connascence فقط یه واژه شیک تو معماری نرمافزار نیست؛ در واقع یه زبان دقیق برای حرف زدن درباره کیفیت وابستگیهاست. اگر coupling فقط میگه «بخشهای سیستم چقدر به هم وصلن»، connascence میگه «این اتصال دقیقاً چطور کار میکنه و تغییر دادنش چه هزینهای داره». همین تفاوت برای کسی که میخواد سیستم بسازه، ریفکتور کنه یا مقیاسش بده، خیلی مهمه.
در نهایت هم معماری خوب از این به دست نمیاد که همه وابستگیها رو حذف کنیم؛ چنین چیزی عملاً ممکن نیست. هنر کار اینه که نوع وابستگیها رو درست انتخاب کنیم. همون جایی که سیستم سادهتر، قابلفهمتر و قابل نگهداریتر میشه؛ و دقیقاً همون نقطهایه که یه مهندس نرمافزار کمکم از «کدنویس» به «معمار» نزدیک میشه.