بسیاری از افرادی که تازه وارد دنیای برنامهنویسی شدهاند، تفاوت مابین معماری نرمافزار و طراحی نرمافزار را نمیدانند و در برخی مواقع ممکن است دولوپرهای حرفهای نیز در تشخیص این دو دچار اشتباه شوند که در همین راستا در این مقاله قصد داریم تا به بررسی این موضوع بپردازیم که چرا برای همهٔ دولوپرها آگاهی از مفهوم طراحی نرمافزار ارجحتر از آشنایی با معماری نرمافزار است.
منظور از معماری نرمافزار چیست؟
به طور خلاصه، معماری نرمافزار (Software Architecture) یک سولوشن ساختاریافته است که انتظارات فنی و تجاری از نرمافزار مد نظر را برآورده میسازد و به منظور حل مشکلات رایج در توسعۀ نرمافزار به کار گرفته میشود به طوری که اپلیکیشن مد نظر ویژگیهایی از جمله انعطافپذیری، مقیاسپذیری، قابلیت استفادۀ مجدد و امنیت را برآورده سازد.
همچنین نیاز به توضیح نیست که اپلیکیشنهای مختلف معماری مختص به خود را دارند و وب اپلیکیشنها نیز از این قاعده مستثنی نیستند که در همین راستا در مقالهای مجزا به بررسی ساختار معماری یک وب اپلیکیشن پرداختهایم که برای کسب اطلاعات بیشتر توصیه میکنیم به مقالۀ Web Architecture: آشنایی با مفاهیم پایهای مرتبط با معماری وب اپلیکیشنها مراجعه کنید.
همانطور که پیش از این توضیح دادیم، نوع معماری یک نرمافزار مسائلی همچون نیازمندیها و انتظارات آن را در سطوح عملیاتی و فنی توصیف میکند و از همین روی برای ماندن در عرصۀ رقابت در بازارهایی که به سرعت در حال تغییر هستند، باید بتوان در مواقع لزوم مدل کسبوکار خود را به سرعت تغییر داد. به عبارت دیگر، اگر سرویسی داریم که با ریکوئستهایی مواجه میشود که نیاز به ریسپانس فوری دارند، برخورداری از نرمافزاری با ویژگیهایی همچون قابلیت استفادۀ مجدد، معماری ماژولار و قابلیت نگاهداری منجر بدین خواهند شد تا سرویسی درخور در اختیار کاربران قرار دهیم.
در واقع، در طراحی معماری نرمافزار ویژگیهایی همچون پرفورمنس، تحمل خطا، مقیاسپذیری و ضریب اطمینان جزو موارد کلیدی در فرآیند توسعهٔ نرمافزار به شمار میروند (تحمل خطا یا به اصطلاح Fault Tolerance به شرایطی گفته میشود که علیرغم وجود باگ در سیستم، نرمافزار کماکان به کار خود ادامه خواهد داد.) علاوه بر موارد فوقالذکر، یک نرمافزار خوب باید یکسری ویژگیهای دیگر نیز داشته باشد که توصیه میکنیم برای کسب اطلاعات بیشتر به صفحۀ ویکیپدیای ویژگیهای یک نرمافزار خوب مراجعه نمایید.
منظور از طراحی نرمافزار چیست؟
معماری نرمافزار مسئول اسکلتبندی و زیرساخت کلی یک نرمافزار میباشد اما این در حالی است که طراحی نرمافزار (Software Design) مسئول طراحی در سطح کُد است بدین صورت که مشخص میشود وظایف هر یک از ماژولها، اِسکوپ کلاسها و اهداف هر یک از فانکشنها چیست و ارتباط آنها با یکدیگر چگونه باید باشد.
وقتی پای طراحی نرمافزار به میان میآید، دولوپرها باید با مفهومی تحت عنوان دیزاین پترن نیز آشنایی داشته باشند که برای پیادهسازی موفق یک دیزاین پترن باید آگاهی کاملی از اصول SOLID داشته و بدانند که چگونه یک دیزاین پترن مشکلات رایج در توسعۀ نرمافزار را حل میکند.
SOLID سرواژۀ عبارات Interface Segregation ،Liskov Substitution ،Open Closed ،Single Responsibility و Dependency Inversion Principles است که در ادامه به بررسی مفهوم هر یک از آنها میپردازیم (جهت آشنایی بیشتر با مفاهیم SOLID توصیه میکنیم به آموزش آشنایی با قوانین پنجگانۀ SOLID مراجعه نمایید.)
- Single Responsibility: این اصل بدان معنا است که هر کلاس باید یک هدف واحد و تنها یک مسئولیت داشته باشد و تنها به یک دلیل تغییر یابد که آن هم تغییر در کاری است که انجام میدهد.
- Open Closed: در این اصل گفته شده که دولوپرها باید امکان توسعه و افزودن قابلیتهای جدید به هر یک از کلاسها را داشته باشند اما این توسعه به نحوی انجام شود که تغییری در کدهای پیشین فانکشن مد نظر اِعمال نشود. به عبارت بهتر، دولوپرها صرفاً قادر بر افزودن قابلیتهای جدید به یک کلاس هستند اما فانکشنهای موجود در کلاس نباید به نحوی ویرایش شوند که کلاس مذکور تغییر یابد.
- Liskov Substitution: این اصل به دولوپرها کمک میکند تا از مفهوم وراثت در کدنویسی استفاده کنند به طوری که منطق برنامه در هیچ نقطهای دچار مشکل نشود. برای مثال، اگر یک کلاس فرزند تحت عنوان ChildClass از کلاس والد ParentClass ارثبری میکند، کلاس فرزند باید عملکرد کلی کلاس والد خود را به نحوی تکرار کند که رفتار کلاس والد را تغییر ندهد که در چنین شرایطی میتوان به راحتی از آبجکت ساخته شده از روی ChildClass به جای آبجکت ParentClass استفاده کرد بدون آن که عملکرد کلی نرمافزار تحتالشعاع قرار گیرد.
- Interface Segregation: بر اساس این قانون، از آنجایی که یک کلاس میتواند چندین اینترفیس را به اصطلاح Implement کند، پس باید دولوپرها کُدشان را به نحوی بنویسند که اینترفیسها تکمنظوره باشند تا یک کلاس مجبور به پیادهسازی فانکشنی نباشد که در راستای اهدافش نیست که در چنین شرایطی بهتر است تا از چندین اینترفیس تخصصی به جای یک اینترفیس کلی استفاده کرد.
- Dependency Inversion: اگر تاکنون شیوۀ Test Driven Development یا به اختصار TDD را برای توسعۀ نرمافزار دنبال کرده باشید، میدانید که نحوۀ نوشتن کُد به صورت اصطلاحاً Decoupled (جدا از هم) جهت تست و ماژولار بودن اپلیکیشن مهم است.
جمعبندی
در یک کلام، دیزاین پترنها مجموعهای از راهکارها و الگوها در برنامهنویسی هستند که توسط دولوپرهای باتجربه در زمینهٔ شییٔگرایی پیادهسازی شدهاند و توسعهدهندگان میتوانند با بهکارگیری آنها دست به طراحی اپلیکیشنهایی انعطافپذیر، تغییرپذیر با قابلیت نگاهداری بالا بزنند.
همچنین به خاطر داشته باشیم که مابین یک معمار نرمافزار با توسعهدهندهٔ نرمافزار تفاوت زیادی وجود دارد به طوری که میتوان گفت معمولاً معمارها مدیران فنی باتجربهای هستند که دانش و تجربهٔ خوبی در زمینهٔ سولوشنهای مختلف پیادهسازی یک اپلیکیشن (معماری نرمافزار) دارند بدین گونه که میتوانند سایر اعضای تیم را به منظور انتخاب ابزار مناسب راهنمایی کنند اما در مقابل یک توسعهٔدهندهٔ نرمافزار یا دولوپر کسی است که باید در مورد دیزاین پترنها (طراحی نرمافزار) اطلاعاتی عمیق داشته باشد اما در عین حال نیمنگاهی هم به مفاهیم معماری نرمافزار داشته باشد که در همین راستا توصیه میکنیم به مقالات برنامهنویس، مهندس نرمافزار یا معمار نرمافزار؟ و آیا میدانستید که مهندسین نرمافزار و برنامهنویسان چه تفاوتهایی با یکدیگر دارند؟ برای کسب اطلاعات بیشتر در ارتباط با تفاوت نقشها در تیمهای نرمافزاری مراجعه نمایید.