1️⃣ اصل اول SOLID: تک مسئولیتی (Single Responsibility Principle)
تا حالا یه کلاسی نوشتید که اولش کوچیک بوده، ولی کم کم اونقدر بزرگ و پیچیده شده که دیگه دست زدن بهش ترسناک بوده؟ این مشکل معمولاً از زیر پا گذاشتن اولین و مهمترین اصل SOLID یعنی اصل تک مسئولیتی (SRP) میاد.
این اصل چی میگه؟ 🎯
به زبان ساده:
✨️یک کلاس باید فقط و فقط یک دلیل برای تغییر داشته باشه.
یعنی هر کلاس باید یه وظیفه مشخص و واحد داشته باشه و فقط همون رو به بهترین شکل انجام بده.
مثال از دنیای واقعی: یه چاقوی سوئیسی رو تصور کنید که هم چاقوئه، هم پیچگوشتی، هم در باز کن. شاید به نظر کارآمد بیاد، ولی هیچکدوم از کارها رو به خوبی یه ابزار تخصصی انجام نمیده. تو کدنویسی هم همینه.
مثال عملی: کد "بدبو" 👎 در برابر کد "تمیز" 👍
فرض کنید یه کلاسی برای ثبتنام کاربر داریم.
مثال بد (نقض اصل تک مسئولیتی):
public class UserService
{
public void RegisterUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
// مسئولیت اول: لاگ کردن خطا در فایل
File.WriteAllText("errors.log", "Username is empty");
}
// مسئولیت دوم: ثبت کاربر در دیتابیس
// ... code to save user to database ...
// مسئولیت سوم: ارسال ایمیل خوشآمدگویی
// ... code to send a welcome email ...
}
}
مشکل چیه؟ این کلاس الان سه تا دلیل برای تغییر داره: ۱. اگه منطق ثبتنام عوض بشه. ۲. اگه نحوه لاگ کردن عوض بشه. ۳. اگه متن ایمیل عوض بشه. این یعنی یه کلاس شلوغ و شکننده!
مثال خوب (رعایت اصل تک مسئولیتی):
حالا میایم و هر مسئولیت رو به یه کلاس جدا و متخصص میسپاریم.
// ۱. کلاسی فقط برای لاگ کردن
public class FileLogger
{
public void LogError(string message) { /* ... */ }
}
// ۲. کلاسی فقط برای ارسال ایمیل
public class EmailService
{
public void SendWelcomeEmail(string username) { /* ... */ }
}
// ۳. کلاس اصلی که حالا فقط روی منطق کاربر تمرکز داره
public class UserService
{
private readonly FileLogger _logger;
private readonly EmailService _emailService;
public UserService(FileLogger logger, EmailService emailService)
{
_logger = logger;
_emailService = emailService;
}
public void RegisterUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username))
{
_logger.LogError("Username is empty");
return;
}
// ... code to save user to database ...
_emailService.SendWelcomeEmail(username);
}
}
حالا هر بخش، کار تخصصی خودش رو انجام میده. کد تمیز، قابل تست و قابل نگهداریه!
🔖 هشتگها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #SRP
2️⃣ اصل دوم SOLID: باز/بسته (Open/Closed Principle)
تا حالا شده یه تیکه کد بنویسید که خوب کار میکنه، ولی هر بار که یه قابلیت جدید میخواید، مجبور میشید برگردید و همون کد قدیمی و حساس رو دستکاری کنید؟ این کار ریسک ایجاد باگهای جدید رو خیلی بالا میبره.
اصل باز/بسته (OCP) برای حل همین مشکل اومده.
این اصل چی میگه؟ 🎯
به زبان ساده:
کلاسها و ماژولهای شما باید برای "توسعه" (Extension) باز، ولی برای "تغییر" (Modification) بسته باشند.
یعنی شما باید بتونید رفتار جدیدی به سیستم اضافه کنید، بدون اینکه نیاز به تغییر کدهای موجودی که قبلاً تست شده و کار میکنه، داشته باشید.
مثال عملی: کد "شکننده" 👎 در برابر کد "انعطافپذیر" 👍
فرض کنید یه کلاسی داریم که مساحت اشکال مختلف رو محاسبه میکنه.
مثال بد (نقض اصل باز/بسته):
این کد برای تغییر "بسته" نیست. اگه بخوایم یه شکل جدید مثل "مثلث" رو اضافه کنیم، مجبوریم این کلاس رو باز کنیم و یه if جدید بهش اضافه کنیم. این یعنی دستکاری کد قدیمی و ریسک ایجاد باگ.
public class AreaCalculator
{
public double CalculateTotalArea(object[] shapes)
{
double totalArea = 0;
foreach (var shape in shapes)
{
if (shape is Rectangle r)
{
totalArea += r.Width * r.Height;
}
if (shape is Circle c)
{
totalArea += Math.PI * c.Radius * c.Radius;
}
// برای اضافه کردن مثلث، باید این کلاس رو دستکاری کنیم!
}
return totalArea;
}
}
مثال خوب (رعایت اصل باز/بسته):
حالا با استفاده از یک انتزاع (Abstraction) مثل interface، کد رو جوری بازنویسی میکنیم که برای توسعه "باز" باشه.
قدم اول: ساختن یک قرارداد (Interface)
public interface IShape
{
double CalculateArea();
}
قدم دوم: پیادهسازی قرارداد برای هر شکل
public class Rectangle : IShape
{
public double Width { get; set; }
public double Height { get; set; }
public double CalculateArea() => Width * Height;
}
public class Circle : IShape
{
public double Radius { get; set; }
public double CalculateArea() => Math.PI * Radius * Radius;
}
قدم سوم: کلاس محاسبهگر انعطافپذیر
حالا کلاس AreaCalculator ما دیگه به نوع مشخصی وابسته نیست، فقط با قرارداد IShape کار میکنه.
public class AreaCalculator
{
public double CalculateTotalArea(IShape[] shapes)
{
double totalArea = 0;
foreach (var shape in shapes)
{
totalArea += shape.CalculateArea();
}
return totalArea;
}
}
جادو اینجا اتفاق میفته: حالا اگه بخوایم شکل جدید "مثلث" رو اضافه کنیم، هیچ نیازی به تغییر کلاس AreaCalculator نداریم! فقط یه کلاس جدید میسازیم:
public class Triangle : IShape
{
public double Base { get; set; }
public double Height { get; set; }
public double CalculateArea() => 0.5 * Base * Height;
}
کد ما برای توسعه (اضافه کردن کلاس جدید) باز بود، ولی برای تغییر (دستکاری AreaCalculator) بسته بود!
🔖 هشتگها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #OCP
3️⃣ اصل سوم SOLID: جایگزینی لیسکوف (Liskov Substitution Principle)
تا حالا شده یه کلاس فرزند بسازید که از یه کلاس پدر ارثبری میکنه، ولی وقتی ازش به جای پدر استفاده میکنید، یهو همه چی به هم میریزه و برنامه رفتار غیرمنتظرهای نشون میده؟
اصل جایگزینی لیسکوف (LSP) دقیقاً برای جلوگیری از همین فاجعه طراحی شده.
این اصل چی میگه؟ 🎯
به زبان ساده:
شما باید همیشه بتونید یک نمونه از کلاس فرزند (Subclass) رو به جای یک نمونه از کلاس پدر (Superclass) استفاده کنید، بدون اینکه برنامه به مشکل بخوره یا رفتارش عوض بشه.یعنی کلاس فرزند نباید "قراردادها" و انتظاراتی که از کلاس پدر میره رو زیر پا بذاره. اگه کلاس پدر قول داده یه کاری رو انجام بده، کلاس فرزند هم باید همون قول رو بده، نه اینکه بزنه زیرش!
مثال عملی: کابوس معروف مربع و مستطیل!
این یه مثال کلاسیکه که نقض LSP رو به بهترین شکل نشون میده.
مثال بد (نقض اصل جایگزینی لیسکوف):
در نگاه اول، به نظر منطقی میاد که Square (مربع) از Rectangle (مستطیل) ارثبری کنه، چون مربع یه نوع خاص از مستطیله. ولی این کار، قرارداد رو میشکنه!
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
}
public class Square : Rectangle
{
// برای اینکه مربع باقی بمونه، وقتی عرض رو تغییر میدیم،
// باید ارتفاع رو هم تغییر بدیم (و برعکس).
public override int Width
{
set { base.Width = base.Height = value; }
}
public override int Height
{
set { base.Width = base.Height = value; }
}
}
مشکل کجاست؟
حالا یه متدی رو تصور کنید که یه Rectangle میگیره و انتظار داره با تغییر عرض، ارتفاع ثابت بمونه. اگه ما بهش یه Square پاس بدیم، کل منطقش به هم میریزه!
public void SomeMethod(Rectangle r)
{
// این متد انتظار داره با تغییر عرض، ارتفاع ثابت بمونه
r.Width = 10;
r.Height = 5;
// اگه r یک مستطیل واقعی باشه، مساحت 50 میشه
// ولی اگه یه مربع بهش پاس داده باشیم، مساحت 25 میشه! (چون ارتفاع هم 5 شده)
// این یعنی رفتار برنامه غیرمنتظره شده!
Console.WriteLine(r.Width * r.Height);
}
اینجا کلاس فرزند (Square) نتونست بدون دردسر جای پدرش (Rectangle) رو بگیره.
مثال خوب (رعایت اصل جایگزینی لیسکوف):
راه حل اینه که به جای وراثت مستقیم، از یه انتزاع مشترک مثل interface استفاده کنیم.
public interface IShape
{
double CalculateArea();
}
public class Rectangle : IShape
{
public int Width { get; set; }
public int Height { get; set; }
public double CalculateArea() => Width * Height;
}
public class Square : IShape
{
public int Side { get; set; }
public double CalculateArea() => Side * Side;
}
حالا دیگه هیچکدوم قرارداد اون یکی رو نمیشکنه و هر کدوم زندگی خودشون رو دارن.
🔖 هشتگها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #LSP
4️⃣ اصل چهارم SOLID: تفکیک اینترفیسها (Interface Segregation Principle)
تا حالا شده یه اینترفیس (Interface) رو پیادهسازی کنید و ببینید مجبورید کلی متد رو به صورت خالی یا با پرتاب Exception پیادهسازی کنید، چون اصلاً به کار کلاس شما نمیان؟
این مشکل معمولاً از طراحی اینترفیسهای بزرگ و "چاق" (Fat Interfaces) به وجود میاد. اصل تفکیک اینترفیسها (ISP) برای حل همین مشکل طراحی شده.
این اصل چی میگه؟ 🎯
به زبان ساده:
کلاسها نباید مجبور بشن اینترفیسهایی رو پیادهسازی کنن که به متدهای اون نیازی ندارن.
به عبارت دیگه، به جای ساختن یک اینترفیس بزرگ و همهکاره، بهتره چندین اینترفیس کوچک، تخصصی و مجزا بسازیم.
مثال عملی: کابوس پرینترهای همهکاره!
فرض کنید یه اینترفیس برای کار با دستگاههای اداری طراحی میکنیم.
مثال بد (نقض اصل تفکیک اینترفیسها):
اینجا یه اینترفیس "چاق" داریم. اگه یه پرینتر ساده و ارزون داشته باشیم که فقط قابلیت پرینت داره، باز هم مجبوره متدهای Scan و Fax رو پیادهسازی کنه، که این کار منطقی نیست و کد رو کثیف میکنه.
// ❌ اینترفیس "چاق" و بد
public interface IMultiFunctionDevice
{
void Print(string content);
void Scan(string content);
void Fax(string content);
}
public class CheapPrinter : IMultiFunctionDevice
{
public void Print(string content)
{
// OK
}
public void Scan(string content)
{
// این پرینتر اسکنر نداره! مجبوریم خطا برگردونیم
throw new NotImplementedException();
}
public void Fax(string content)
{
// این پرینتر فکس هم نداره!
throw new NotImplementedException();
}
}
مثال خوب (رعایت اصل تفکیک اینترفیسها):
حالا میایم و اون اینترفیس بزرگ رو به چند اینترفیس کوچکتر و تخصصیتر میشکنیم.
// ✅ اینترفیسهای کوچک و تخصصی
public interface IPrinter
{
void Print(string content);
}
public interface IScanner
{
void Scan(string content);
}
حالا هر کلاسی، فقط اینترفیسی رو پیادهسازی میکنه که واقعاً بهش نیاز داره.
// این پرینتر ساده، فقط قرارداد پرینت رو امضا میکنه
public class CheapPrinter : IPrinter
{
public void Print(string content)
{
// OK
}
}
// این دستگاه همهکاره، هر دو قرارداد رو امضا میکنه
public class AllInOnePrinter : IPrinter, IScanner
{
public void Print(string content)
{
// OK
}
public void Scan(string content)
{
// OK
}
}
کد ما حالا خیلی تمیزتر، انعطافپذیرتر و قابل فهمتره!
🔖 هشتگها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #ISP
5️⃣ اصل پنجم SOLID: وارونگی وابستگی (Dependency Inversion Principle)
تا حالا شده یه کلاس بنویسید که داخلش یه کلاس دیگه رو با new میسازید و بعداً برای تغییر اون کلاس داخلی، مجبور میشید کلاس اصلی رو هم دستکاری کنید؟ این یعنی کدهای شما به هم سفت و سخت (Tightly Coupled) وصل شدن.
اصل وارونگی وابستگی (DIP) برای حل همین مشکل و ایجاد کدهای انعطافپذیر طراحی شده.
این اصل چی میگه؟ 🎯
این اصل دو بخش مهم داره:
ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند. هر دو باید به انتزاع (Abstraction) وابسته باشند.به زبان ساده: کلاسهای اصلی و سطح بالای شما (مثلاً بیزنس لاجیک) نباید به جزئیات پیادهسازی (مثلاً نحوه کار با دیتابیس یا ارسال ایمیل) وابسته باشن. در عوض، هر دو باید به یک قرارداد مشترک (Interface) وابسته باشن.
انتزاعها نباید به جزئیات وابسته باشند. این جزئیات هستن که باید به انتزاعها وابسته باشند.
مثال عملی: سیستم اطلاعرسانی
فرض کنید یه سیستمی برای اطلاعرسانی به کاربر داریم.
مثال بد (نقض اصل وارونگی وابستگی):
اینجا کلاس سطح بالای Notification مستقیماً به کلاس سطح پایین EmailSender وابسته است. اگه فردا بخوایم به جای ایمیل، با SMS اطلاعرسانی کنیم، مجبوریم کلاس Notification رو تغییر بدیم. این یعنی وابستگی سفت و سخت.
// ❌ این کلاس سطح پایین است
public class EmailSender
{
public void Send() => Console.WriteLine("Email sent!");
}
// ❌ این کلاس سطح بالاست و مستقیماً به کلاس پایینی وابسته است
public class Notification
{
private readonly EmailSender _emailSender = new EmailSender();
public void SendNotification()
{
_emailSender.Send();
}
}
مثال خوب (رعایت اصل وارونگی وابستگی):
حالا با استفاده از یک Interface، این وابستگی رو "وارونه" میکنیم.
قدم اول: ساختن قرارداد (Interface)
این قرارداد در لایه سطح بالا تعریف میشه.
public interface IMessageSender
{
void SendMessage();
}
قدم دوم: کلاسهای سطح پایین از قرارداد پیروی میکنند
public class EmailSender : IMessageSender
{
public void SendMessage() => Console.WriteLine("Email sent!");
}
public class SmsSender : IMessageSender
{
public void SendMessage() => Console.WriteLine("SMS sent!");
}
قدم سوم: کلاس سطح بالا به قرارداد وابسته است، نه به جزئیات
public class Notification
{
private readonly IMessageSender _sender;
// وابستگی از طریق Constructor تزریق میشود (Dependency Injection)
public Notification(IMessageSender sender)
{
_sender = sender;
}
public void SendNotification()
{
_sender.SendMessage();
}
}
جادو اینجا اتفاق میفته: حالا کلاس Notification دیگه کاری نداره که پیام چطوری ارسال میشه (با ایمیل یا SMS). اون فقط "قرارداد" رو میشناسه. ما میتونیم موقع ساختن آبجکت Notification، هر نوع IMessageSender که دوست داریم رو بهش پاس بدیم، بدون اینکه یک کلمه از کدش رو تغییر بدیم!
🤔 حرف حساب و تجربه شما
این DIP قلب معماریهای تمیز و مدرنه و اساس کار تزریق وابستگی (Dependency Injection) هست. رعایت این اصل، کد شما رو فوقالعاده انعطافپذیر، قابل تست و توسعهپذیر میکنه.
شما چقدر به این اصل در پروژههاتون اهمیت میدید؟ بهترین مثالی که از کاربرد این اصل تو ذهنتون دارید چیه؟
💬 بحث و گفتگوی بیشتر در گروه کامیونیتی:
[C# Geeks Community]
🔖 هشتگها:
#CSharp #Programming #Developer #SOLID #SoftwareArchitecture #CleanCode #DIP
💡کدام اصل SOLID در کلاس UserManager به وضوح نقض شده است؟
public class UserManager
{
public void RegisterUser(string email, string password)
{
// ۱. فرمت ایمیل را اعتبارسنجی میکند
// ۲. چک میکند کاربر در دیتابیس وجود دارد یا نه
// ۳. کاربر جدید را در دیتابیس ذخیره میکند
// ۴. یک ایمیل خوشآمدگویی برای کاربر ارسال میکند
}
}
🛡 دستور using در #C: بهترین دوست IDisposable و راهی برای جلوگیری از نشت منابعوقتی با فایلها، دیتابیس، ارتباطات شبکه یا هر منبع خارجی دیگهای کار میکنید، یه خطر بزرگ وجود داره: یادتون بره منابعی که باز کردید رو ببندید! این کار باعث "نشت منابع" (Resource Leaks) و مشکلات جدی در پرفورمنس و پایداری برنامه میشه.
سیشارپ یه راه حل خیلی شیک، امن و حرفهای برای این مشکل داره: دستور using.
1️⃣ مشکل کجاست؟ IDisposable و مدیریت منابع
بعضی از آبجکتها در داتنت، منابعی رو مدیریت میکنن که Garbage Collector به صورت خودکار نمیتونه اونها رو آزاد کنه (مثل دستگیره فایل در سیستمعامل). این کلاسها، اینترفیس IDisposable رو پیادهسازی میکنن که فقط یک متد به اسم Dispose() داره.
وظیفه ما به عنوان برنامهنویس اینه که همیشه و تحت هر شرایطی، بعد از تموم شدن کارمون با این آبجکتها، متد Dispose() اونها رو صدا بزنیم.
2️⃣ راه حل قدیمی (و زشت): try...finally 👎
راه سنتی برای اینکه مطمئن بشیم Dispose() همیشه صدا زده میشه (حتی اگه وسط کار یه Exception رخ بده)، استفاده از بلوک try...finally هست:
StreamReader reader = null;
try
{
reader = new StreamReader("myFile.txt");
// ... کار کردن با فایل ...
}
finally
{
if (reader != null)
{
reader.Dispose(); // تضمین میکنه که فایل همیشه بسته میشه
}
}
این کد کار میکنه، ولی طولانی و زشته.
3️⃣ راه حل مدرن و شیک: دستور using ✨
دستور using یه "شکر سینتکسی" (Syntactic Sugar) برای همون بلوک try...finally هست.
به محض اینکه اجرای کد از بلوک using خارج بشه (چه به صورت عادی و چه به خاطر یه Exception)، کامپایلر به صورت خودکار متد Dispose() رو روی اون آبجکت صدا میزنه. اینجوری کد شما خیلی تمیزتر، کوتاهتر و امنتر میشه.
// این کد دقیقاً معادل کد try...finally بالاست
using (StreamReader reader = new StreamReader("myFile.txt"))
{
// ... کار کردن با فایل ...
}
// reader.Dispose() اینجا به صورت خودکار در هر حالتی صدا زده میشه
4️⃣ فرم جدیدتر (از C# 8): using declarations 🚀
از C# 8 به بعد، کار حتی از این هم سادهتر شده. اگه متغیر using رو به این شکل تعریف کنید، دیگه نیازی به آکولاد {} ندارید و اون متغیر در انتهای اسکوپ فعلی (معمولاً در انتهای متد) به صورت خودکار Dispose میشه.
void ReadFile()
{
using var reader = new StreamReader("myFile.txt");
using var writer = new StreamWriter("log.txt");
// ... کار کردن با فایلها ...
} // reader.Dispose() و writer.Dispose() اینجا به صورت خودکار صدا زده میشن
🤔 حرف حساب و قانون طلایی
قانون طلایی: هر کلاسی که IDisposable رو پیادهسازی کرده، باید داخل یه بلوک using (یا با سینتکس جدیدش) ازش استفاده بشه.
شما بیشتر از کدوم فرمت using استفاده میکنید؟ کلاسیک با براکت یا فرمت جدید بدون براکت؟
🔖 هشتگها:
#CSharp #Programming #Developer #DotNet #BestPractices #CleanCode #IDisposable
😵💫 برنامهنویس NET.؟
میخوای از باگهای دردناک، امنیت داغون و کد اسپاگتی در امان بمونی؟
این 10 قاتل خاموش رو دستکم نگیر!
(حتی تو پروژههای بهظاهر «Enterprise» هم میبینمشون…)
⸻
1️⃣ بدون اعتبارسنجی ورودی؟ خداحافظ با ثبات 😬
دادهی خراب بیاد تو، همهچی میریزه به هم.
پاک کردن دادهی خراب ۱۰ برابر سختتر از جلوگیری از ورودشه!
🧱 همیشه ورودی رو چک کن → Validate before trust
⸻
2️⃣ مقادیر هاردکد شده 🎯
عاشق سرتاسری Replace زدن تو پروژهای؟
از «عدد جادویی» استفاده کن 😎
نه؟ پس برو سراغ:
🔸 فایل پیکربندی
🔸 ثابتها
🔸 دیتابیس
⸻
3️⃣ وابستگی سفت و سخت بین کلاسها 🔗
وقتی یه کلاس به کلاس دیگه دوخته شده باشه، انعطاف میره هوا.
🙅♂️ با abstraction (مثل Interface) کدت رو قابل تغییر نگه دار.
⸻
4️⃣ تست واحد؟ وقت ندارم؟ 😐
اگه الان وقت نداری تست بنویسی،
فردا کلی وقت داری باگهات رو دیباگ کنی! 🔥
⸻
5️⃣ مدیریت نکردن Exception ها 💣
اگه استکتریس کامل رو فرستادی تو پاسخ HTTP، یه کادو به هکر دادی!
📛 ارورها رو لاگ کن.
✅ پیام امن بده به کاربر.
⸻
6️⃣ کد غیرقابل خوندن 🤯
واسه انسان بنویس نه ماشین!
اگه باید یه کد رو ۳ بار بخونی تا بفهمی چی میگه، خیلی پیچیدهست.
🧼 تمیز بنویس تا خودت ۳ ماه دیگه هم بفهمیش!
⸻
7️⃣ طراحی ضعیف دیتابیس 🐢
طرح دیتای اشتباه یعنی:
🔹 کندی
🔹 غیرقابل توسعه بودن
🔹 زجر در آینده
بلندمدت فکر کن موقع مدلسازی!
⸻
8️⃣ امنیت؟ بذار بعداً! ❌
👾 اینجوری هک میشی رفیق!
✅ ورودیها رو بررسی کن
✅ کمترین سطح دسترسی
✅ هرچی میاد رو پاکسازی کن (Sanitize)
امنیت چیز اختیاری نیست… هیچوقت!
⸻
9️⃣ بدون لاگ و مانیتورینگ 🚫📊
برنامه رو منتشر کردی؟ دمت گرم!
ولی حالا از کجا میفهمی داره درست کار میکنه؟
🕵️♂️ بدون لاگ = پرواز کور
⸻
🔟 اختراع دوباره چرخ؟ 😅
مگه قراره ORM اختراع کنی؟
تا وقتی یه ابزار تستشده و امن هست،
✅ استفادهش کن
🧠 وقتتو صرف چیزای ارزشمندتر کن!
⸻
🎯 لازم نیست کدت بینقص باشه…
ولی اگه اینا رو نادیده بگیری،
کد Legacy درست کردی، نه پروژه حرفهای!
⸻
میخوای از باگهای دردناک، امنیت داغون و کد اسپاگتی در امان بمونی؟
این 10 قاتل خاموش رو دستکم نگیر!
(حتی تو پروژههای بهظاهر «Enterprise» هم میبینمشون…)
⸻
1️⃣ بدون اعتبارسنجی ورودی؟ خداحافظ با ثبات 😬
دادهی خراب بیاد تو، همهچی میریزه به هم.
پاک کردن دادهی خراب ۱۰ برابر سختتر از جلوگیری از ورودشه!
🧱 همیشه ورودی رو چک کن → Validate before trust
⸻
2️⃣ مقادیر هاردکد شده 🎯
عاشق سرتاسری Replace زدن تو پروژهای؟
از «عدد جادویی» استفاده کن 😎
نه؟ پس برو سراغ:
🔸 فایل پیکربندی
🔸 ثابتها
🔸 دیتابیس
⸻
3️⃣ وابستگی سفت و سخت بین کلاسها 🔗
وقتی یه کلاس به کلاس دیگه دوخته شده باشه، انعطاف میره هوا.
🙅♂️ با abstraction (مثل Interface) کدت رو قابل تغییر نگه دار.
⸻
4️⃣ تست واحد؟ وقت ندارم؟ 😐
اگه الان وقت نداری تست بنویسی،
فردا کلی وقت داری باگهات رو دیباگ کنی! 🔥
⸻
5️⃣ مدیریت نکردن Exception ها 💣
اگه استکتریس کامل رو فرستادی تو پاسخ HTTP، یه کادو به هکر دادی!
📛 ارورها رو لاگ کن.
✅ پیام امن بده به کاربر.
⸻
6️⃣ کد غیرقابل خوندن 🤯
واسه انسان بنویس نه ماشین!
اگه باید یه کد رو ۳ بار بخونی تا بفهمی چی میگه، خیلی پیچیدهست.
🧼 تمیز بنویس تا خودت ۳ ماه دیگه هم بفهمیش!
⸻
7️⃣ طراحی ضعیف دیتابیس 🐢
طرح دیتای اشتباه یعنی:
🔹 کندی
🔹 غیرقابل توسعه بودن
🔹 زجر در آینده
بلندمدت فکر کن موقع مدلسازی!
⸻
8️⃣ امنیت؟ بذار بعداً! ❌
👾 اینجوری هک میشی رفیق!
✅ ورودیها رو بررسی کن
✅ کمترین سطح دسترسی
✅ هرچی میاد رو پاکسازی کن (Sanitize)
امنیت چیز اختیاری نیست… هیچوقت!
⸻
9️⃣ بدون لاگ و مانیتورینگ 🚫📊
برنامه رو منتشر کردی؟ دمت گرم!
ولی حالا از کجا میفهمی داره درست کار میکنه؟
🕵️♂️ بدون لاگ = پرواز کور
⸻
🔟 اختراع دوباره چرخ؟ 😅
مگه قراره ORM اختراع کنی؟
تا وقتی یه ابزار تستشده و امن هست،
✅ استفادهش کن
🧠 وقتتو صرف چیزای ارزشمندتر کن!
⸻
🎯 لازم نیست کدت بینقص باشه…
ولی اگه اینا رو نادیده بگیری،
کد Legacy درست کردی، نه پروژه حرفهای!
⸻
🔖 هشتگها:
#DotNet #CSharp #CodingTips #BestPractices #Programming #SoftwareEngineering #DevLife
💾 حافظه کلاسها در #C: راهنمای کامل Fields, readonly و const
تا حالا شده از خودتون بپرسید فرق بین یه فیلد readonly و یه const چیه؟ یا اینکه متغیرهایی که داخل یه کلاس تعریف میکنیم (فیلدها) چطور و کِی مقداردهی میشن؟
امروز میخوایم بریم تو دل کلاسها و با اعضای دادهای اونها و قوانین حاکم بر اونها آشنا بشیم.
1️⃣ فیلدها (Fields):
حافظه داخلی یک آبجکت فیلدها، متغیرهایی هستن که داخل یه کلاس یا struct تعریف میشن و "وضعیت" یا "داده"های اون آبجکت رو در طول عمرش نگه میدارن. هر نمونه (instance) از کلاس، یک کپی مستقل از فیلدهای خودش رو داره.
💡نکته حرفهای (قرارداد نامگذاری):مقداردهی اولیه فیلدها:
یه قرارداد رایج و خوب، استفاده از آندرلاین (_) برای فیلدهای private هست تا به راحتی از متغیرهای محلی و پارامترها تشخیص داده بشن (مثلاً name_).
مقداردهی اولیه به فیلدها اختیاریه. اگه مقداری بهشون ندید، مقدار پیشفرض نوعشون رو میگیرن. نکته مهم اینه که این مقداردهی قبل از اجرای اولین خط کد در سازنده (constructor) انجام میشه.
2️⃣ کلید readonly: فیلدهای فقط-خواندنی 🔒
اگه میخواید یه فیلد فقط یک بار مقداردهی بشه و بعد از ساخته شدن آبجکت دیگه هرگز تغییر نکنه، از readonly استفاده کنید. این برای مقادیری که در زمان ساخت آبجکت مشخص میشن ولی بعدش باید ثابت بمونن، عالیه.
قانون مهم: فیلدهای readonly فقط در دو جا میتونن مقدار بگیرن: یا موقع تعریف اولیه، یا داخل سازنده (constructor) اون کلاس.
public class UserProfile
{
// مقداردهی موقع تعریف
public readonly Guid UserId = Guid.NewGuid();
// تعریف بدون مقدار اولیه
public readonly DateTime RegistrationDate;
public UserProfile()
{
// مقداردهی در سازنده
RegistrationDate = DateTime.UtcNow;
}
public void UpdateProfile()
{
// UserId = Guid.NewGuid(); // ❌ خطای زمان کامپایل!
}
}
3️⃣ دوئل بزرگ: const در برابر static readonly ⚔️
این یکی از کلاسیکترین سوالات مصاحبه و یکی از مهمترین نکات در طراحی حرفهایه. هر دو برای مقادیر ثابت به کار میرن، ولی تفاوتهای حیاتی دارن.
🔵 const (ثابت زمان کامپایل):
• مقدارش باید در زمان کامپایل مشخص و ثابت باشه
(const double Pi = 3.14;).
• کامپایلر مقدارش رو مستقیماً در کد جایگزین میکنه (مثل ماکرو).
• فقط برای انواع داده ساده (عددی، رشته، بولین و...) قابل استفادهست.
🔴 static readonly (ثابت زمان اجرا):
• مقدارش در زمان اجرا (معمولاً موقع استارت برنامه) مشخص میشه.
• یه فیلد واقعیه و در حافظه نگهداری میشه، مقدارش جایگزین نمیشه.
• برای هر نوع دادهای، حتی آبجکتهای پیچیده، قابل استفادهست.
public static readonly DateTime StartupTime = DateTime.Now;
😈تله خطرناک public const:
اگه شما یه public const رو در یک کتابخانه (Assembly A) تعریف کنید و در یک پروژه دیگه (Assembly B) ازش استفاده کنید، مقدار اون ثابت موقع کامپایل در Assembly B "پخته" یا "bake in" میشه. حالا اگه شما مقدار const رو در Assembly A عوض کنید، تا وقتی که Assembly B رو دوباره کامپایل نکنید، همچنان از همون مقدار قدیمی استفاده خواهد کرد! این میتونه منجر به باگهای فاجعهبار بشه. static readonly این مشکل رو نداره و همیشه آخرین مقدار رو میخونه، بنابراین انتخاب امنتریه.
🤔 حرف حساب و تجربه شما
انتخاب درست بین const و static readonly یکی از نشانههای یه توسعهدهنده باتجربهست که به نگهداری بلندمدت کد فکر میکنه.
شما بیشتر از const استفاده میکنید یا static readonly؟ آیا تا حالا به مشکل اسمبلی با const برخورد کرده بودید؟
🔖 هشتگها:
#CSharp #Programming #Developer #DotNet #OOP #CleanCode #BestPractices
🔬 کالبدشکافی متدها در #C: امضا (Signature) و Overloading
هر متدی در #C یک "اثر انگشت" یا امضای (Signature) منحصر به فرد داره. این امضا هویت متد رو مشخص میکنه و به کامپایلر اجازه میده بین چندین متد با اسم یکسان، تفاوت قائل بشه.
امروز میخوایم این مفهوم حیاتی و قوانین Overloading رو کالبدشکافی کنیم.
1️⃣ امضای متد (Method Signature) چیست؟ 🆔
امضای یک متد از دو بخش اصلی تشکیل شده:
• اسم متد
• نوع و ترتیب پارامترها
نکات کلیدی و تلهها: ⚠️
موارد زیر جزو امضای متد حساب نمیشن:
❌ نوع خروجی (Return Type)
❌ کلمه کلیدی params
❌ اسم پارامترها (فقط نوع و ترتیبشون مهمه)
اما کلمات کلیدی ref و out جزو امضا حساب میشن!
2️⃣ اورلودینگ (Overloading): یک اسم، چند کاربرد 🎭
اورلودینگ یعنی شما میتونید چند متد با اسم یکسان در یک کلاس داشته باشید، به شرطی که امضاشون متفاوت باشه (یعنی تعداد، نوع یا ترتیب پارامترهاشون فرق کنه).
مثال Overloadهای معتبر: ✅
// اینها همگی Overloadهای معتبری از Foo هستن
void Foo(int x) { }
void Foo(double x) { } // نوع پارامتر فرق داره
void Foo(int x, float y) { } // تعداد پارامترها فرق داره
void Foo(float x, int y) { } // ترتیب انواع پارامتر فرق داره
void Foo(ref int x) { } // وجود ref امضا رو تغییر میده
مثال Overloadهای نامعتبر: ❌
// ❌ خطای زمان کامپایل!
// چون فقط نوع خروجی فرق میکنه که جزو امضا نیست.
void Foo(int x) { }
float Foo(int x) { }
// ❌ خطای زمان کامپایل!
// چون params جزو امضا نیست.
void Goo(int[] x) { }
void Goo(params int[] x) { }
// ❌ خطای زمان کامپایل!
// ref و out نمیتونن تنها تفاوت امضا باشن.
void Another(ref int x) { }
void Another(out int x) { }
🤔 حرف حساب و تجربه شما
درک دقیق قوانین امضا و اورلودینگ، به شما کمک میکنه APIهای تمیز، خوانا و قابل فهمی طراحی کنید.
آیا تا حالا با خطای کامپایل به خاطر امضای تکراری متدها برخورد کردید؟ یا از Overloading برای ساختن متدهای انعطافپذیرتر در کدهاتون استفاده میکنید؟
🔖 هشتگها:
#CSharp #Programming #Developer #DotNet #OOP #Methods #BestPractices
🐳 داکر (Docker) چیست و چرا باید به عنوان یک توسعهدهنده #C بهش اهمیت بدیم؟تا حالا شده بگید "ولی رو سیستم من کار میکرد!"؟ 😑 یا برای راهاندازی پروژه روی یه سیستم جدید، ساعتها درگیر نصب داتنت، دیتابیس و هزارتا چیز دیگه بشید؟
داکر (Docker) اومده که این کابوسها رو برای همیشه تموم کنه.
داکر به زبان ساده: کانتینرهای باربری! 🚢
فکر کنید برنامهی شما یه سری کالای باارزشه. داکر این کالاها رو به همراه تمام وسایل مورد نیازشون (کد، داتنت رانتایم، کتابخانهها، تنظیمات و...) داخل یه کانتینر استاندارد بستهبندی میکنه.
حالا این کانتینر روی هر کشتیای (هر سیستمی که داکر روش نصبه، از ویندوز و مک گرفته تا لینوکس) بدون هیچ مشکلی بارگیری و اجرا میشه! این یعنی پایان تمام مشکلات وابستگی و تفاوت محیطها.
🤔 خب، این به چه درد ما توسعهدهندههای #C میخوره؟
1️⃣ پایان مشکل "رو سیستم من کار میکرد":
اگه تو کانتینر کار کنه، همه جا کار میکنه. تمام!
2️⃣ محیطهای یکسان (Development / Production Parity):
محیطی که شما روی لپتاپتون کد میزنید، با محیط تست و سرور پروداکشن شما دقیقاً یکی میشه.
3️⃣ دیپلوی (Deploy) ساده و سریع:
دیگه نیازی به نصب چیزی روی سرور نیست. فقط کانتینر رو روی سرور میبرید و با یک دستور اجراش میکنید.
4️⃣ معماری میکروسرویس:
داکر ستون فقرات معماریهای مدرن میکروسرویسه و به شما اجازه میده هر سرویس رو در یک کانتینر ایزوله اجرا کنید.
5️⃣ توسعه چندسکویی:
به راحتی اپلیکیشن ASP.NET Core خودتون رو روی یک کانتینر لینوکس دولوپ و اجرا کنید،حتی اگه سیستمعامل خودتون ویندوزه.
یادگیری داکر دیگه یه آپشن نیست، یه مهارت ضروری برای هر توسعهدهنده مدرنه، به خصوص برای ماهایی که تو دنیای NET. و وب کار میکنیم.
شما از داکر تو پروژههاتون استفاده میکنید؟ بزرگترین مزیتی که ازش دیدید چی بوده؟
🔖 هشتگها:
#Docker #DevOps #CSharp #DotNet #Backend #SoftwareArchitecture #Containerization
میتونید مشکل این کد رو پیدا کنید؟ 🧐
پیدا کردنش یه چیزه. چطور حلش میکنید؟
😈اینجا نقطهایه که همه چیز خراب میشه:
شما یک کاربر رو در دیتابیس ذخیره میکنید.
بعد یه ایمیل خوشآمدگویی میفرستید.
و بعد یه پیام رو برای یه سرویس دیگه منتشر میکنید.
اینها سه تا عملیات مجزا هستن و هر کدومشون ممکنه شکست بخوره.
حالا سیستم شما در یک وضعیت عجیب و ناسازگار (inconsistent) قرار میگیره:
• کاربر ذخیره شده، ولی ایمیل هرگز ارسال نشده.
• یا پیام در صف قرار نگرفته.
• یا عملیات رو دوباره تکرار کردید و همه چیز دو بار انجام شده!
وضعیت به سرعت بهمریخته و پیچیده میشه.
راه حل: الگوی Outbox 📬
الگوی Outbox به شما کمک میکنه از این مشکل جلوگیری کنید.
این الگو اینجوری کار میکنه:
1️⃣ یک تراکنش (transaction) دیتابیس رو شروع کنید.
2️⃣ کاربر رو ذخیره کنید.
3️⃣ قصدِ ارسال ایمیل و انتشار پیام رو در یک جدول به نام Outbox (صندوق خروجی) درج کنید.
4️⃣ تراکنش رو Commit کنید.
بعداً، یک سرویس پسزمینه (background service) این رکوردهای Outbox رو برمیداره و اونها رو به صورت قابل اطمینان و با مکانیزم تلاش مجدد (retry) پردازش میکنه.
✨️ نتیجه نهایی:
حالا، تمام این مراحل با هم موفق میشن، یا هیچکدومشون انجام نمیشن.
عملیات نوشتن در دیتابیس شما اتمیک (atomic) هست. دلیلی نداره که جریانهای کاری شما هم اتمیک نباشن.
🛠 متدهای مدرن در #C
Expression-bodied و Local Methods
سیشارپ مدرن، پر از قابلیتهای شیک و کاربردیه که به ما کمک میکنه کدهای کوتاهتر، خواناتر و منظمتری بنویسیم. امروز با دو تا از این تکنیکهای خفن برای کار با متدها آشنا میشیم.
1️⃣ متدهای Expression-bodied (=>): خداحافظی با return و {}
اگه متد شما فقط از یک عبارت (Expression) تشکیل شده، دیگه نیازی به نوشتن بلوک {} و کلمه کلیدی return ندارید! میتونید با استفاده از "فت ارو" (=>)، اون رو در یک خط بنویسید.
این کار، کد شما رو فوقالعاده تمیز و مینیمال میکنه.
// روش قدیمی
int Double(int x)
{
return x * 2;
}
// روش مدرن و شیک با Expression-bodied
int DoubleModern(int x) => x * 2;
// این قابلیت برای متدهای void هم کار میکنه
void SayHello() => Console.WriteLine("Hello!");
2️⃣ متدهای محلی (Local Methods): متد در دل متد!
گاهی وقتا یه منطق کمکی دارید که فقط و فقط داخل یک متد دیگه استفاده میشه. به جای اینکه اون رو به عنوان یه متد private تو کل کلاس تعریف کنید و کلاس رو شلوغ کنید، میتونید اون رو به عنوان یه متد محلی، دقیقاً داخل همون متدی که بهش نیاز دارید، تعریف کنید.
مزایای این کار:
• کپسولهسازی عالی: اون متد کمکی، فقط همونجا قابل دسترسه و هیچ جای دیگهای از کلاس رو آلوده نمیکنه.
• دسترسی به متغیرهای محلی: متد محلی میتونه به متغیرهای محلی و پارامترهای متد بیرونی دسترسی داشته باشه.
void WriteCubes()
{
Console.WriteLine(Cube(3));
Console.WriteLine(Cube(4));
// این متد، فقط داخل WriteCubes قابل دسترسه
int Cube(int value) => value * value * value;
}
💡نکته حرفهای (static local methods):
از C# 8 به بعد، اگه متد محلی شما نیازی به دسترسی به متغیرهای متد بیرونی نداره، میتونید اون رو با کلمه کلیدی static تعریف کنید. این کار به کامپایلر کمک میکنه بهینهسازیهای بهتری انجام بده و جلوی دسترسیهای ناخواسته رو هم میگیره.
🤔 حرف حساب و تجربه شما
این تکنیکهای مدرن، ابزارهای شما برای نوشتن کدهایی هستن که هم کار میکنن و هم خوندنشون لذتبخشه.
شما از کدوم یکی از این قابلیتها بیشتر استفاده میکنید؟ آیا متدهای Expression-bodied جزو استایل کدنویسی شما هستن؟
🔖 هشتگها:
#CSharp #Programming #Developer #DotNet #CleanCode #ModernCSharp #BestPractices
🏛️ مونولیت ماژولار چیست؟ معماری هوشمندانهای که قبل از میکروسرویس باید بشناسیدهمیشه بحث بوده: سادگی و یکپارچگی مونولیت یا انعطافپذیری و قدرت میکروسرویس؟ اما اگه یه راه سومی وجود داشته باشه که بهترین مزایای هر دو رو داشته باشه چی؟
با معماری مونولیت ماژولار (Modular Monolith) آشنا بشید؛ رویکردی که سادگی مونولیت رو با نظم و مرزبندی میکروسرویس ترکیب میکنه.
1️⃣ مونولیت ماژولار به زبان ساده چیه؟ 🧱
تصور کنید به جای ساختن یه ساختمون غولپیکر یکتکه (مونولیت سنتی)، یا چندین ساختمون کاملاً جدا از هم (میکروسرویس)، شما یک مجتمع آپارتمانی بزرگ میسازید.
کل مجتمع یک واحد یکپارچه است، ولی داخلش به واحدهای مستقل و ایزوله با دیوارهای محکم تقسیم شده. این واحدها همون ماژولها هستن (مثلاً ماژول پرداخت، ماژول کاربران). هر ماژول کارکردهای مرتبط به خودش رو گروهبندی میکنه و مرزهای مشخصی داره.
💡نکته کلیدی: این واحدها (ماژولها) اتصال سستی (loosely coupled) با هم دارن و فقط از طریق یک API عمومی و مشخص با هم صحبت میکنن، نه اینکه در جزئیات پیادهسازی هم دخالت کنن.
انعطافپذیری در عمل: فرض کنید در فصل تعطیلات، ماژول رزرو شما نیاز به منابع بیشتری داره. در این معماری، شما میتونید موقتاً همون ماژول رو به صورت مستقل دیپلوی کنید و بعداً دوباره به مونولیت اصلی برش گردونید!
2️⃣ چرا "Monolith First"؟ (حتی به گفته بزرگان!) 💡
با اینکه میکروسرویسها خیلی محبوبن، پیچیدگیهای سیستمهای توزیعشده (Distributed Systems) رو به همراه دارن. برای همین، خیلی از متخصصان، از جمله مارتین فاولر، میگن:
"شما نباید یک پروژه جدید را با میکروسرویسها شروع کنید، حتی اگر مطمئن باشید که اپلیکیشن شما به اندازهای بزرگ خواهد شد که این کار ارزشش را داشته باشد."حتی گوگل هم در مقالات تحقیقاتی جدیدش، به ترند مونولیت ماژولار پیوسته. دلیل این امر، چالشهای ذاتی میکروسرویسهاست:
1️⃣ پرفورمنس: سربار شبکه و سریالسازی دادهها، عملکرد رو کاهش میده.
2️⃣ صحت: تضمین صحت یک سیستم توزیعشده بسیار دشواره.
3️⃣ مدیریت: شما باید چندین اپلیکیشن مختلف با چرخههای انتشار متفاوت رو مدیریت کنید.
4️⃣ بعدی APIهای منجمد: تغییر APIها بدون شکستن سرویسهای دیگه، سخت میشه.
5️⃣ سرعت توسعه: یک تغییر کوچک در یک سرویس، ممکنه نیازمند تغییر و هماهنگی در چندین سرویس دیگه باشه.
حالا که فهمیدیم مونولیت ماژولار چیه و چرا یک انتخاب هوشمندانهست، در قسمت بعدی این سری به صورت عمیق به مزایای مشخص اون میپردازیم و اون رو مستقیماً با میکروسرویسها مقایسه میکنیم.
🔖 هشتگها:
#SoftwareArchitecture #CSharp #DotNet #Monolith #Microservices #Developer #CleanCode