بهترین شیوههای توسعه برنامه، Clean Architecture (معماری تمیز) و Clean Code (کد تمیز)
منبع: Coding Clean, Reliable, and Safe REST APIs with ASP.NET Core 8 — Anthony Giretti
اعتبار ترجمه: ترجمه با کمک هوش مصنوعی
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
فصل ۳: مقدمهای بر Best Practiceهای توسعهٔ برنامه
اکنون به قلب موضوع رسیدهایم. پیش از توسعهٔ API با ASP.NET Core 8، مبانی عمومی توسعهٔ Software را مرور میکنیم؛ اصولی که برای API، Mobile Application، Desktop Application و دیگر انواع Software یکساناند. در حالت ایدهآل، هر Application باید این Fundamentals را رعایت کند: تسلط بر مبانی Programming، سازماندهی Code در Clean Architecture (معماری تمیز)، نوشتن Code خوانا، Maintainable و آسان برای Debug، و امن نگه داشتن Application با اصول Open Worldwide Application Security Project (OWASP).
موضوعات این فصل عبارتاند از:
- رسیدن به طرز فکر درست
- مبانی Clean Architecture
- مبانی Clean Code
- اصول OWASP
doi.org/10.1007/978-1-4842-9979-1_3
رسیدن به طرز فکر درست
Information Technology (IT)، و بهویژه Software Development، حوزهای دشوار است و به مجموعهای از ویژگیها نیاز دارد.
درک پایهای از کار
حداقل لازم، درک Algorithmها و تسلط بر یک Programming Language مانند C# است. در این کتاب فرض میکنم Fundamentals زبان را از قبل میدانید.
مهارت حل مسئله
پس از یادگیری Algorithm و زبان، باید بتوانید مسئله را حل کنید؛ یعنی منطق کامل یک مسئله را به Programming Language منتقل کنید. همچنین باید با رفتارهای غیرمنتظره، یعنی Bugها، مواجه شوید. در بخشهای بعد تکنیکهایی برای Debugging را خواهیم دید.
درک Programming Paradigmها
Paradigmهای گوناگونی مانند Procedural Programming و Object-Oriented Programming وجود دارند. در این کتاب عمدتاً از OOP استفاده میکنم، هرچند گاهی Functionهای ساده به سبک Procedural نیز بهکار میروند.
تفکر منطقی و ساختاری
این سختترین بخش و موضوعی است که در سراسر کتاب روی آن تأکید میکنم. دانستن مبانی IT، حل مسئله و شناخت Paradigmها شما را Programmer میکند؛ اما تفکر منطقی و ساختاری شما را Programmer ممتاز میسازد. کارکردن Program بهتنهایی کافی نیست. Software باکیفیت باید دو ویژگی اساسی داشته باشد:
- فهم و نگهداری آسان: Code ناخوانا و غیرقابل نگهداری باعث خطاهای فراوان میشود. Naming متغیرها و Classها، Documentation، Cyclomatic Complexity، امنیت Code و موارد مشابه در اینجا اهمیت دارند؛ این همان Clean Code است.
- سازماندهی صحیح: Program باید به Layerهایی تقسیم شود که هرکدام Classهایی با Responsibility مشخص داشته باشند و تا حد امکان مستقل و Reusable باشند؛ این همان Clean Architecture است.
مبانی Clean Architecture (معماری تمیز)
Clean Architecture یعنی نحوهٔ سازماندهی Code و تعریف رابطهٔ بخشهای مختلف آن. برای پیادهسازی آن «حقیقت مطلق» واحدی وجود ندارد. اینجا دیدگاهی را معرفی میکنم که طی سالها تجربه انتخاب کردهام. در صنعت نامهایی مانند Hexagonal Architecture، Onion Architecture، Domain-Driven Design (DDD) و «Clean Architecture» رایجاند. همهٔ آنها دربارهٔ سازماندهی Layerهای Application—یعنی Projectها در .NET—صحبت میکنند.
من وارد شرح جداگانهٔ همهٔ این Architectureها نمیشوم. منظور من از «Clean Architecture» در این فصل دقیقاً نسخهٔ شخصی خودم برای APIهایی با Complexity پایین تا متوسط است. در Architectureهای پیچیده جزئیات بیشتری لازم است، اما این مدل برای بخش عمدهٔ Projectهای وبی که یک API Developer در کار حرفهای با آنها روبهرو میشود کافی است.
اصل بنیادین، استقلال یا Weak Coupling است: استقلال از Technology، Data Sourceهای خارجی و Layerهای دیگر. بهطور مشخص:
- استقلال از User Interface: UI مانند API یا Desktop Application باید از Core Application شامل Business Logic و Data Access جدا باشد. در این کتاب با Abstraction نشان میدهم چگونه وابستگی مستقیم را کاهش دهیم.
- استقلال از Library و Frameworkهای Third-Party: Application نباید به Library خاصی Strongly Coupled باشد، زیرا Maintenance را محدود میکند. بعداً نحوهٔ Abstract کردن آنها را خواهیم دید.
- استقلال از Data Access خارجی: باید بتوان Database Type یا حتی شیوهٔ Data Access، مانند XML File روی Network، را با کمترین تغییر عوض کرد. این نیز به Abstraction وابسته است.
- قابل آزمون بهصورت مستقل: Testing باید جدا از Layerها و Technologyهای دیگر انجام شود. Unit Testهایی که در پایان کتاب معرفی میشوند نیز بر Abstraction تکیه دارند.
در مدل من Application حداقل چهار نوع Layer دارد:
- Domain Layer: Domain Objectها، Repository Interfaceها، Service Interfaceها، Contractها و Abstractionها را نگه میدارد. به هیچ Layer دیگری وابسته نیست.
- Presentation Layer: در این کتاب Web Layer مبتنی بر ASP.NET Core است که API را روی HTTP ارائه میکند. Configuration این Layer باید بداند هر Abstraction با کدام Concrete Class پیادهسازی میشود، همانطور که در
Program.cs دیدیم. - Business Logic یا Application Layer: Business Ruleها را پیادهسازی و عملیات چندمرحلهای Componentهایی مانند Data Access، Logging و Caching را Orchestrate میکند.
- Business Layer فقط باید Domain Contract و Abstraction را بشناسد و نباید به Infrastructure Technology وابسته باشد. تنها استثنا میتواند Layer عمومی مثل Tools باشد که Classهای C# قابل استفادهٔ مجدد و مستقل از Technology را ارائه میکند.
- یک یا چند Infrastructure Layer: هرکدام Technology خاصی را پیادهسازی میکنند و Contractهای Domain را Implement میکنند. بهتر است برای هر Technology Layer جداگانهای داشته باشیم؛ مثلاً SQL Data Access و HTTP Data Access مستقل باشند.
- Tools Layer اختیاری: برای Generic Code مستقل از Business Logic و Technology؛ برای مثال تبدیل Byte Array به Stream یا Class قابل استفادهٔ مجدد برای Regular Expression.
شکل ۳-۱. دیدگاه نویسنده دربارهٔ Clean Architectureنمودار فقط Ideology را نشان میدهد. در سراسر کتاب بهصورت عملی خواهیم دید هر Functionality در کدام Layer قرار میگیرد. مهمترین نکته همان طرز فکر Decoupling است.
Design Patternها
هدف کتاب آموزش Design Patternها نیست، اما Patternها روی Software Architecture اثر دارند. Design Pattern چیدمانی پذیرفتهشده از Moduleها برای حل مسئلهای مشخص در Software Design است. Gang of Four (GoF) مجموعهای از ۲۴ Pattern کلاسیک را معرفی کردهاند. لازم نیست همه را حفظ کنید. فقط چند مورد لازم را در کتاب خواهیم دید؛ یکی از آنها Singleton است که در فصل ۲ با آن آشنا شدید و اجازه میدهد فقط یک Instance از Class در عمر Application وجود داشته باشد.
www.gofpatterns.com/
مبانی Clean Code (کد تمیز)
سازماندهی مناسب Layerها کافی نیست؛ Code داخل آنها نیز باید تمیز باشد.
اصول عمومی Coding
- Code باید ساده باشد: هرچه سادهتر باشد، Readability و Maintainability بهتر است. این اصل در IT با KISS یا Keep It Simple, Stupid شناخته میشود. از پیشبینی نیازهای نامحتمل بپرهیزید؛ اصل YAGNI یا You Ain’t Gonna Need It میگوید برای چیزی که احتمالاً لازم نمیشود Complexity بیدلیل ایجاد نکنید.
- Code باید Single Responsibility داشته باشد: هر Instruction، Function یا قطعه Code باید یک هدف مشخص را حل کند. این اصل بخشی از SOLID است.
- Code نباید تکرار شود: Copy/Paste را کنار بگذارید و Reusability را ترجیح دهید تا دو قطعه Code یکسان که یک مسئله را حل میکنند در مسیرهای مختلف تکامل نیابند. این اصل DRY یا Don’t Repeat Yourself است؛ البته فقط زمانی که واقعاً Code یکسان مسئلهٔ یکسانی را حل میکند.
- Code باید از Concerns دیگر جدا باشد: Separation of Concerns (SoC) با Single Responsibility یکسان نیست. Single Responsibility دربارهٔ وظیفهٔ یک قطعه Code است؛ SoC دربارهٔ تفکیک بخشهای مستقل یک جریان است.
- برای مثال در سفارش Pizza، انتخاب Pizza، پرداخت و Delivery سه Concern جدا هستند. Function مربوط به ساخت Pizza یک Responsibility دارد؛ Payment و Delivery نیز Code مستقل خود را دارند.
- Code باید Testable باشد: اگر اصول قبلی رعایت شوند، Unit Testing سادهتر میشود و برای Maintainability بلندمدت حیاتی است. در فصل پایانی به آن بازمیگردیم.
اصول SOLID
SOLID مخفف پنج اصل مهم در Object-Oriented Programming است:
- Single Responsibility: هر Class یا Component یک دلیل اصلی برای تغییر داشته باشد.
- Open-Closed: بهتر است رفتار با Extension گسترش یابد، نه با تغییر مداوم Base Class. در APIها همیشه قابل اعمال نیست.
- Liskov Substitution: Child Class باید بتواند جای Parent Class قرار گیرد بدون آنکه رفتار System را بیثبات کند. Inheritance زیاد میتواند Architecture را شکننده کند.
- Interface Segregation: Interfaceهای بزرگ را به Contractهای کوچک و هدفمند تقسیم کنید؛ مانند Interface جدا برای ساخت Pizza، Payment و Delivery.
- Dependency Inversion: هرچه به Layer سطحبالا، مانند UI، نزدیکتر میشوید، بیشتر به Abstraction وابسته باشید نه Classهای سطح پایین. Dependency Injection پیادهسازی عملی این طرز فکر است و در سراسر کتاب استفاده خواهد شد.
برای مطالعهٔ بیشتر SOLID، منبع اشارهشده در کتاب: www.c-sharpcorner.com/UploadFile/damubetha/solid-principles-in-C-Sharp/.
Coding Style
Clean Code فقط ساختار منطقی نیست؛ باید خواندن آن نیز آسان باشد. نام فایلها، Classها، Interfaceها، Variableها و Directoryها باید صریح و معنیدار باشند. برای مثال Directoryای با نام Download میتواند Subfolder مربوط به Helperها، Classهایی مانند AmazonS3PathBuilder.cs و AzureFileStoragePathBuilder.cs و Serviceای مانند DownloadService.cs داشته باشد. حتی بدون خواندن Code، نامها بخشی از Intent را منتقل میکنند.
شکل ۳-۲. Directory مربوط به Download و Classهای وابستهدو PathBuilder مسیر Fileها را برای Amazon S3 و Azure File Storage میسازند؛ هر دو Provider سرویسهای Hosting و Cloud هستند. سپس محتوای DownloadService.cs بررسی میشود.
شکل ۳-۳. محتوای فایل DownloadService.csنام DownloadService خودگو است. Interface آن یعنی IDownloadService فقط Contractای به نام GetFileAsync دارد که Intent آن از نامش روشن است. Parameter ورودی و مقدار برگشتی نیز صریحاند؛ مقدار برگشتی Tupleای شامل وضعیت دانلود File، پیام وضعیت و Objectای حاوی دادهٔ File است.
شکل ۳-۴. پیادهسازی متد GetFileAsync
پارامتر Function یعنی GetFileParameters تنها Parameter آن است. نگه داشتن یک Parameter Object که Propertyهای متعدد را در خود دارد Best Practice مفیدی است، زیرا با رشد Application لازم نیست Signature Function دائماً تغییر کند. Variableهایی مانند index، fileStorageProvider و donwloadFileStream نیز نامهای صریح دارند.
در Naming Convention از PascalCase، camelCase و برای Fieldهای Class از camelCase با پیشوند _ استفاده شده است. Propertyها نیز PascalCase دارند:
public int MyProperty { get; set; }
جدول ۳-۱. Naming Conventionهای استفادهشده| عنصر | Convention | نمونه |
|---|
| Namespace | PascalCase | DownloadService |
| Class | PascalCase | Demo.Business.Download |
| Method | PascalCase | GetFileAsync |
| Method parameter type | PascalCase | GetFileParameters |
| Property | PascalCase | MyProperty |
| Variable | camelCase | index |
| Parameter name | camelCase | parameters |
| Field | camelCase با _ | _dataStoreBusiness |
در مثالهای بعدی کتاب تا حد امکان همین Style حفظ میشود. اصول Clean Code در مراحل دیگری مانند Error Handling، Testing و Comment—که باید با احتیاط استفاده شود—نیز دیده میشوند و بعداً به آنها میپردازیم.
اصول OWASP
Open Worldwide Application Security Project (OWASP) سازمانی بینالمللی است که Recommendationهای امنیت Software ارائه میدهد و ابزارها، Documentation و Videoهایی برای افزایش آگاهی توسعهدهندگان و شرکتها دربارهٔ امنیت Web Application نگهداری میکند.
موضوع مهم اینجا OWASP Top 10 است که حملهها و ضعفهای بسیار رایج Web Applicationها را معرفی میکند. در فصلهای مرتبط مثالهایی برای محافظت API خواهیم دید. امنیت باید در طراحی REST API اولویت باشد و نباید برای آن مصالحه کرد.
- Authentication و Authorization ضعیف: حتی با وجود Authentication ممکن است Application در برابر Brute Force آسیبپذیر باشد؛ مهاجم صدها یا هزاران ترکیب Username/Password را امتحان میکند. Password قابل حدس خطر را بیشتر میکند. در فصل ۵ Rate Limiting را بهعنوان یکی از راهکارها میبینیم. Two-Factor Authentication نیز راهکار دیگری است ولی در این کتاب بررسی نمیشود.
- Injection: بهویژه در SQL Database و همچنین NoSQL، MongoDB یا LDAP، ورودی اعتبارسنجینشده میتواند Query اصلی را منحرف کند و داده را غیرقانونی استخراج یا تغییر دهد. SQL Injection نمونهٔ معروف است و در فصل ۶ هنگام Data Access بررسی میشود. Cross-Site Scripting (XSS) نیز با ارسال دادهٔ حاوی Executable Code، مثلاً JavaScript، میتواند محتوای ناخواسته Inject کند یا Authentication Cookie را بدزدد. در فصل ۴ هنگام Input Validation نمونه خواهیم دید.
- Broken Access Control: Authentication بهتنهایی برای محافظت از داده یا Action حساس کافی نیست. Userهای مختلف باید Privilegeهای متفاوت داشته باشند. در فصل ۱۰ Authentication و Authorization را پیادهسازی میکنیم.
- Logging و Monitoring ناکافی: Logging و Monitoring در تشخیص Brute Force یا افزایش غیرعادی Activity کمک میکنند. فصل ۸ دربارهٔ Observability، Logging و Tracing درخواستهای HTTP است.
- Insecure Data Integrity: Serialization/Deserialization ناامن ممکن است اجرای Malicious Code روی سرور را ممکن کند. در فصل ۴ Input Validation را بررسی میکنیم، اما این موضوع را عمیقتر دنبال نخواهیم کرد.
- Cryptographic Failures: رمزنگاری نکردن دادهٔ حساس Vulnerability ایجاد میکند. در این کتاب انتقال Client/Server از طریق HTTPS انجام میشود، هرچند این موضوع معادل Encryption همهٔ Data در همهٔ Contextها نیست.
- Weak Application Design: ضعف منطق کسبوکار ممکن است امکان سوءاستفاده فراهم کند؛ مثال نویسنده Promotion یا کد شارژ یکبارمصرفی است که به علت Design ضعیف چند بار قابل استفاده بوده و برای شرکت هزینه ایجاد کرده است.
- Weak Security Configuration: Accountهایی با Password بدون Expiration، Password ساده یا Credential بلااستفاده ولی فعال، خطر امنیتیاند. Accountهای فعال را مدیریت و Passwordها را منظم تغییر دهید.
- SSRF: Server-Side Request Forgery زمانی رخ میدهد که Web Application یک Resource راه دور را بدون Validation URL کاربر بازیابی کند و مهاجم Application را وادار کند Request دستکاریشدهای به مقصد نامناسب بفرستد. این موضوع بیشتر در Web Applicationهای HTML مطرح است و اینجا ادامه داده نمیشود.
- Obsolete Component: Framework و Libraryهای قدیمی که Update نمیشوند ممکن است Vulnerability شناختهشده داشته باشند. Microsoft برای .NET Updateهای امنیتی منتشر میکند؛ Application را همواره بهروز نگه دارید.
OWASP پروژهٔ OWASP Secure Headers Project (OSHP) را نیز نگهداری میکند که Response Headerهای امنیتی قابل افزودن به Application را معرفی میکند. برای ASP.NET Core یک NuGet Package نیز وجود دارد که Installation آن مستند شده است: www.nuget.org/packages/OwaspHeaders.Core#readme-body-tab.
جمعبندی
این فصل حداقل قابل قبول Clean Code و Architecture برای یک API را معرفی کرد. ابزارهایی مانند Code Formatter یا ReSharper میتوانند کیفیت Code را بهتر کنند، اما هدف این است که ابتدا طرز فکر لازم برای تمیز نگه داشتن Code را یاد بگیرید، نه اینکه همهٔ مسئولیت را به Toolها بسپارید. همچنین نیازی نیست ۲۴ Design Pattern را حفظ کنید؛ اغلب فقط برای مسئلههای مشخص لازماند و استفادهٔ بیجا Complexity ایجاد میکند. و در نهایت، دربارهٔ Security هیچ مصالحهای مجاز نیست؛ امنیت باید دغدغهٔ دائمی شما باشد.