قبل از هر چیزی، بیایید تکلیفمان را با کلمهی «اصول» در عبارت «اصول SOLID» روشن کنیم.
قاعده، قانون و وحیمنزل نیستند. رابرت.سی.مارتین در مقالهای میگوید جملهی «An apple a day, keeps the doctor away» یک اصل و توصیهی خوبه! اما حقیقتِ محض یا قانون نیست.
اصول، به مفاهیم یک اسم اختصاص میدهد تا ما بتوانیم با استفاده از آن اسم، راجع به آن مفهوم حرف بزنیم و استدلال کنیم. با این تعریف، اصول مثل یک مرهم و یا آرامبخش هستند - در مورد کدی که به آن احساس بدی دارید، شاید بتوانید اصلی را پیدا کنید که به شما توصیهای برای اینکه حس بهتری پیدا کنید بدهد.
چنین اصولی، اکتشافی هستند - توصیههایی متداول برای حل یکسری مشکلات مشترک!
اما مثل هرچیزِ اکتشافی دیگری، ماهیت تجربی دارند. در خیلی از موارد استفاده میشوند و جواب میدهند. اما هیچ مدرکی نیست که ثابت کند همیشه جواب میدهند یا باید همیشه از آنها پیروی کنیم.
تقریبا هر تصمیم و انتخابی در مسیر توسعه نرمافزار یک Trade-off است - نه یک انتخاب مطلقا خوب یا مطلقا بد. Trade-off کردن یعنی پذیرفتن معایب و هزینههای یک انتخاب در برابر مزیتهایی که در یک Context مشخص برای ما دارد و یکی از مهمترین تصمیمات ما به عنوان توسعهدهندهی نرمافزار بالانس کردن این Trade-offها و پذیرفتن هزینهایست که آن تصمیم در قبال چیزی که به دست میآوریم برای ما در پی دارد.
مجموعهای از اصول طراحی، در توسعه یک نرمافزارِ شیگراست. فلسفهی طراحی شیگرا نه ابزارهاییست که در اختیار ما قرار میدهد و نه کمکی که در مدل کردن مفاهیم واقعی به ما میکند، بلکه به دلیل کمک در مدیریت کردن وابستگیهاست و با این رویکرد، اصول SOLID تضمین میکنند که وابستگی در سیستم ما درست مدیریت شود. این اصول در کنار هم، فرآیند توسعه و نگهداری نرمافزار را تسهیل میکنند.در نگاه اول این 5 اصل مستقل از هم به نظر میرسند اما نقض کردن هر کدام از آنها با احتمال بالایی منجر به نقض شدن 4 اصل دیگر میشود. آنها میتوانند از ایجاد Code Smellها جلوگیری کنند ،انجام Refactoring را راحتتر کنند و حتی میتوانند یک فلسفهی اصلی برای روش توسعهی چابک و توسعهی یک نرمافزار Adaptive باشند.
SOLID مخفف پنج اصل طراحی زیر است که در این نوشتاره از اصل اول صحبت میشود و در آینده به باقی اصول میپردازیم :
هر ماژول نرمافزاری باید، فقط و فقط یک دلیل برای تغییر کردن داشته باشد.
معنی «یک دلیل برای تغییر کردن» چیست؟
مثلا Bug-Fixing یا Refactoring هم یک دلیل برای تغییر کردنِ یک ماژول هستند؟
توضیح کلمهی «مسئولیت»، به واضح شدن پاسخ این سوال کمک میکند. قاعدتاً مسئولیت رفع خطا و Refactoring با توسعهدهندهست، نه کد و برنامه!
سوال درستتر این است که کد ما مسئول چه عملکردی در برنامهاست؟
برای پاسخ دادن به این سوال، مثال زیر را از مقالهای که توسط آقای رابرت سی.مارتین منتشر شده است، بررسی میکنیم:
در یک شرکت نرمافزاری، افرادی همچون مدیر ارشد عملیات(COO)، مدیر ارشد مالی (CFO) و مدیر ارشد فنی (CTO) فعالیت دارند. کد زیر را در نظر بگیرید:
فرض کنید هر کدام از این مدیران ارشد، در برابر یکی از عملکردهای بالا مسئولاند و در صورت ایجاد یک مشکل فاجعهبار در نحوهی عمکلرد آنها، شخص مسئول اخراج میشود.
محاسبهی حقوق، یک مفهوم «مالی»ست و اگر قرار بر تغییر نحوهی محاسبهی حقوق باشد، این درخواستِ تغییر، از طرف CFO شرکت صورت میگیرد و ایشان مسئولیت انجام درست این عملکرد را به عهده دارد.
تصمیمگیری در مورد چگونگیِ ذخیرهی موجودیت «کارمند» در دیتابیس و اعمال تغییر در بیزینس فعلی، به عهدهی CTO شرکت است و مسئولیت هرگونه اشتباه در نحوهی ذخیرهسازی گریبانگیر CTO میشود.
و این COO شرکت است که در مورد آیتمهایی که باید در گزارش ساعات کاری کارمند وجود داشته باشد و نحوهی گزارشگیری اعمال نظر میکند و در صورت بروز خطا مسئولیت پاسخگویی به عهدهی COOست.
از اینجا به تعریف اصل Single Responsibility میرسیم.
شکستن سیستم به ماژولها و طراحی بر اساس Flowchart تقریبا همیشه کار اشتباهیست. به جای آن، بهتر است با لیستی از تصمیمات سخت طراحی یا تصمیمات طراحیای که احتمال تغییر آنها بیشتر است شروع کنیم. در این صورت هر ماژول به گونهای طراحی میشود که آن تصمیم طراحی را از بقیه جدا نگه میدارد.
منشا این تغییرات «آدمها» هستند. آدمها هستند که درخواست تغییرات را میدهند. وقتی یک ماژول نرمافزاری را توسعه میدهیم،میخواهیم مطمئن بشویم که تغییرات فقط میتوانند «یک منبع اعلام تغییرات» داشته باشند. منظور از این یک منبع، برای مثال یک واحد خاص در سازمانست که عملکرد مشخصی دارد. میخواهیم که مبنای طراحی ما به صورتی باشد که هر ماژول، مسئول رفع نیاز فقط یک عملکرد از کسبوکار باشد.
چرا؟ چون ما نمیخواهیم CFO شرکت به دلیل تغییری که CTO روی نحوهی محاسبهی حقوق داده است اخراج بشود!
برای رعایت SRP در مثال بالا، طراحی سیستم باید به این شکل تغییر کند:
هیچ چیزی ترسناکتر از این نیست که مشتری، یک نقص عملکرد در برنامه مشاهده کند که هیچجوره با چیزی که درخواست تغییر آن را داده بود، ربط ندارد و این دلیلیست که ما باید بهخاطرش مفاهیم High-Cohesion، Separation Of Concerns و Low Coupling رو در تعریف ماژولها رعایت کنیم تا هر چه بهتر، مرز بین چیزهایی که به «دلایل متفاوتی» تغییر میکنند را از یکدیگر تفکیک کنیم.
اگر که با ایجاد تغییری، دچار تغییر عملکرد در همان ماژول (Rigidity Design Smell) و یا ماژولهای دیگر (Fragility Design Smell) بشویم و یا مجبور باشیم برای رفع خطا یا هرگونه تغییری چندین و چند کلاس را تغییرات کوچک و بزرگ بدهیم (Shotgun Surgery Code Smell) در تقسیم مسئولیت اشتباه و یک مسئولیت را بین کلاسهای مختلفی پخش کردهایم و SRP را رعایت نکردهایم.