فصل اول: استانداردها و اصول کدنویسی در سی شارپ 🖥️

هدف اصلی استانداردها و اصول کدنویسی در C# این است که برنامه‌نویسان در حرفه خود پیشرفت کنند و کدی بنویسند که کارایی بالاتری داشته باشد و نگهداری آن آسان‌تر باشد.

در این فصل، به بررسی نمونه‌هایی از کدهای خوب در مقایسه با کدهای بد می‌پردازیم. این موضوع ما را به سمت بحث در مورد اینکه چرا به استانداردهای کدنویسی، اصول و متدولوژی‌ها نیاز داریم، هدایت می‌کند. سپس به قراردادهای نام‌گذاری، نوشتن توضیحات (Comments) و فرمت‌بندی سورس کد (شامل کلاس‌ها، متدها و متغیرها) می‌پردازیم.

یک برنامه بزرگ ممکن است درک و نگهداری آن دشوار باشد. برای برنامه‌نویسان تازه‌کار، شناخت کد و درک عملکرد آن می‌تواند کاری دلهره‌آور باشد. همچنین کار گروهی روی چنین پروژه‌هایی سخت‌تر می‌شود و از دیدگاه تست نرم‌افزار نیز مشکلاتی ایجاد می‌شود.

به همین دلیل، بررسی خواهیم کرد که چگونه با استفاده از ماژولار بودن (Modularity) می‌توان برنامه‌ها را به ماژول‌های کوچک‌تر تقسیم کرد که همگی با هم کار کنند تا یک راه‌حل کامل، قابل تست، قابل توسعه توسط تیم‌های مختلف به‌صورت هم‌زمان و به‌راحتی خواندنی و قابل مستندسازی ارائه دهند.

در پایان فصل، با برخی از راهنماهای طراحی نرم‌افزار آشنا می‌شویم، از جمله:
KISS، YAGNI، DRY، SOLID و تیغ اوکام (Occam's Razor).


موضوعات تحت پوشش در این فصل:


اهداف آموزشی این فصل:

الزامات فنی 🔧

برای کار با کدهای این فصل، باید Visual Studio 2019 Community Edition یا نسخه‌های بالاتر را دانلود و نصب کنید. می‌توانید این IDE را از لینک زیر دریافت کنید:
https://visualstudio.microsoft.com/

کدهای این کتاب در لینک زیر قرار دارند:
https://github.com/PacktPublishing/Clean-Code-in-C-

تمام کدها در قالب یک سولوشن (Solution) قرار داده شده‌اند و هر فصل به‌صورت یک پوشه در همان سولوشن است. کدهای مربوط به هر فصل در پوشه همان فصل قرار دارند.

نکته: هنگام اجرای یک پروژه، فراموش نکنید که آن را به‌عنوان Startup Project تنظیم کنید.


کد خوب در مقابل کد بد ⚖️

هر دو نوع کد خوب و کد بد کامپایل می‌شوند؛ این اولین نکته مهم است که باید بدانید. نکته دوم این است که کد بد به دلیل خاصی بد است و به همان ترتیب کد خوب نیز به دلیل خاصی خوب است.

بیایید در جدول زیر به برخی از این دلایل نگاه کنیم:

Conventions-UsedThis-Book

این فهرست مفصل نیست؟ 🤔

بله، واقعاً فهرست کاملی است، درست است؟ در بخش‌های بعدی، به بررسی این ویژگی‌ها و تفاوت‌های بین کد خوب و کد بد و تأثیر آن‌ها بر عملکرد کد شما می‌پردازیم.


کد بد 🚨

در ادامه، نگاهی کوتاه به هر یک از روش‌های بد کدنویسی که پیش‌تر لیست کردیم می‌اندازیم و توضیح می‌دهیم که هر یک از این روش‌ها چگونه بر کیفیت کد شما تأثیر می‌گذارد.


تورفتگی (Indentation) نامناسب

تورفتگی نامناسب باعث می‌شود که کد به‌سختی خوانده شود، به‌خصوص زمانی که متدها بزرگ باشند. برای اینکه کد برای انسان‌ها خوانا باشد، نیاز به تورفتگی درست داریم.

وقتی کد تورفتگی مناسبی ندارد، تشخیص اینکه کدام بخش کد به کدام بلوک تعلق دارد بسیار دشوار می‌شود.

کدی که به‌طور نادرست تورفتگی دارد، زمان زیادی برای اصلاح می‌گیرد و باعث هدررفت وقت برنامه‌نویس می‌شود؛ در حالی که می‌توانست به‌آسانی از این مشکل جلوگیری شود.

مثال زیر را ببینید:

public void DoSomething()
{
for (var i = 0; i < 1000; i++)
{
var productCode = $"PRC000{i}";
//...implementation
}
}

کد بالا ظاهر چندان خوبی ندارد، اما هنوز خوانا است. با این حال، هر چه تعداد خطوط بیشتر شود، خواندن و درک آن سخت‌تر خواهد شد.

همچنین، وقتی تورفتگی درست نباشد، پیدا کردن آکولاد یا پرانتز بسته نشده بسیار سخت می‌شود چون به‌راحتی نمی‌توان فهمید کدام بلوک بسته نشده است.


کامنت‌هایی که بدیهیات را بیان می‌کنند

بعضی از برنامه‌نویس‌ها از کامنت‌هایی که بدیهیات را بیان می‌کنند واقعاً عصبانی می‌شوند چون آن‌ها را بیهوده یا حتی توهین‌آمیز می‌دانند.

در بحث‌های برنامه‌نویسی که حضور داشته‌ام، بارها شنیده‌ام که برنامه‌نویس‌ها می‌گویند کامنت‌گذاری را دوست ندارند و معتقدند که کد باید خودش گویا باشد.

من کاملاً این احساس را درک می‌کنم؛ اگر بتوانید کدی بنویسید که بدون کامنت مثل یک کتاب خوانا باشد، آن کد واقعاً خوب است.

به‌عنوان مثال:

public int _value; // This is used for storing integer values.

در اینجا نیازی نیست بنویسید این متغیر برای ذخیره مقادیر عدد صحیح (int) استفاده می‌شود، چون نوع داده خودش این موضوع را مشخص کرده است.

چنین کامنت‌هایی فقط وقت و انرژی را هدر می‌دهند و باعث شلوغی بی‌مورد در کد می‌شوند.

کامنت‌هایی که کد بد را توجیه می‌کنند ⚠️

ممکن است زمان محدودی برای انجام یک کار داشته باشید، اما کامنت‌هایی مانند:

// I know this code sucks but hey at least it works!

کاملاً غیرحرفه‌ای و غیرقابل قبول هستند. این نوع کامنت‌ها بی‌توجهی به حرفه‌ای بودن را نشان می‌دهند و می‌توانند همکاران برنامه‌نویس را ناراضی کنند.

اگر واقعاً مجبور هستید چیزی سریع آماده شود، بهتر است یک تیکت بازآرایی (Refactor Ticket) ایجاد کنید و آن را در قالب یک کامنت TODO اضافه کنید:

// TODO: PBI23154 Refactor Code to meet company coding practices

سپس شما یا دیگر برنامه‌نویسانی که روی Technical Debt کار می‌کنند، می‌توانید این مورد را بررسی و کد را بازآرایی کنید.

مثال دیگر:

int value = GetDataValue(); // This sometimes causes a divide by zero error. Don't know why!

این کامنت واقعاً بد است. ممنون که اطلاع دادید خطا رخ می‌دهد، اما آیا تیکت باگ ایجاد کرده‌اید؟ آیا تلاش کرده‌اید مشکل را پیدا و اصلاح کنید؟ اگر هیچ‌کس روی آن کد کار نکند، چگونه متوجه وجود کد مشکل‌دار خواهند شد؟

حداقل کاری که باید انجام دهید این است که یک کامنت TODO اضافه کنید تا در Task List ظاهر شود و توسعه‌دهندگان از آن مطلع شده و روی آن کار کنند.


خطوط کد کامنت‌شده ❌

اگر خطوطی از کد را برای آزمایش کامنت می‌کنید، مشکلی ندارد. اما اگر قرار است کد جایگزین استفاده شود، قبل از Check-in، خطوط کامنت‌شده را حذف کنید.

یک یا دو خط کامنت‌شده مشکلی ایجاد نمی‌کند، اما چندین خط کامنت‌شده حواس‌پرت‌کننده است و نگهداری کد را دشوار می‌کند و حتی می‌تواند باعث سردرگمی شود:

/* No longer used as has been replaced by DoSomethingElse().
public void DoSomething()
{
   // ...implementation...
}
*/

اگر کد جایگزین شده و دیگر نیاز نیست، آن را حذف کنید. اگر از کنترل نسخه (Version Control) استفاده می‌کنید و نیاز دارید دوباره به متد دسترسی پیدا کنید، همیشه می‌توانید تاریخچه فایل را بررسی کرده و متد را بازیابی کنید.


سازمان‌دهی نادرست Namespaceها 📂

هنگام استفاده از Namespaceها، کدهایی را که باید در جای دیگری باشند، اضافه نکنید. این کار یافتن کد درست را سخت یا غیرممکن می‌کند، به‌خصوص در کدهای بزرگ.

مثال:

namespace MyProject.TextFileMonitor
{
   public class Program { ... }
   public class DateTime { ... }
   public class FileMonitorService { ... }
   public class Cryptography { ... }
}

در مثال بالا، همه کلاس‌ها در یک Namespace قرار دارند، اما می‌توان با افزودن سه Namespace جدید کد را بهتر سازمان‌دهی کرد:

قراردادهای نام‌گذاری نامناسب 🚫

در گذشته و در دوران Visual Basic 6، از Hungarian Notation استفاده می‌کردیم. من آن را وقتی به Visual Basic 1.0 مهاجرت کردم، به‌کار بردم. اما امروزه استفاده از Hungarian Notation ضروری نیست و حتی باعث می‌شود کد زشت به نظر برسد.

به‌جای نام‌هایی مانند:

lblName, txtName, btnSave

روش مدرن استفاده از نام‌هایی مانند:

NameLabel, NameTextBox, SaveButton

است.


کلاس‌هایی که چند کار انجام می‌دهند ❌

یک کلاس باید تنها یک وظیفه داشته باشد.

کلاسی که هم به دیتابیس وصل می‌شود، داده می‌گیرد، آن را پردازش می‌کند، گزارش تولید می‌کند، داده‌ها را به گزارش اختصاص می‌دهد، گزارش را نمایش می‌دهد، ذخیره و چاپ می‌کند و خروجی می‌گیرد، بیش از حد کار انجام می‌دهد. این کلاس باید به کلاس‌های کوچکتر و منظم‌تر تقسیم شود.

کلاس‌های چندوظیفه‌ای خواندن را دشوار و دلهره‌آور می‌کنند. اگر با چنین کلاس‌هایی برخورد کردید:

  1. عملکردها را در Regions مرتب کنید.
  2. سپس کد داخل آن Regions را به کلاس‌های جدید منتقل کنید که تنها یک کار انجام دهند.

مثال یک کلاس چندوظیفه‌ای:

public class DbAndFileManager
{
    #region Database Operations
    public void OpenDatabaseConnection() { throw new NotImplementedException(); }
    public void CloseDatabaseConnection() { throw new NotImplementedException(); }
    public int ExecuteSql(string sql) { throw new NotImplementedException(); }
    public SqlDataReader SelectSql(string sql) { throw new NotImplementedException(); }
    public int UpdateSql(string sql) { throw new NotImplementedException(); }
    public int DeleteSql(string sql) { throw new NotImplementedException(); }
    public int InsertSql(string sql) { throw new NotImplementedException(); }
    #endregion

    #region File Operations
    public string ReadText(string filename) { throw new NotImplementedException(); }
    public void WriteText(string filename, string text) { throw new NotImplementedException(); }
    public byte[] ReadFile(string filename) { throw new NotImplementedException(); }
    public void WriteFile(string filename, byte[] binaryData) { throw new NotImplementedException(); }
    #endregion
}

بازآرایی کلاس‌ها 🔄

  1. کد دیتابیس را استخراج و به کلاس جداگانه‌ای به نام DatabaseManager منتقل می‌کنیم:
using System;
using System.Data.SqlClient;

namespace CH01_CodingStandardsAndPrinciples.GoodCode.Data
{
    public class DatabaseManager
    {
        #region Database Operations
        public void OpenDatabaseConnection() { throw new NotImplementedException(); }
        public void CloseDatabaseConnection() { throw new NotImplementedException(); }
        public int ExecuteSql(string sql) { throw new NotImplementedException(); }
        public SqlDataReader SelectSql(string sql) { throw new NotImplementedException(); }
        public int UpdateSql(string sql) { throw new NotImplementedException(); }
        public int DeleteSql(string sql) { throw new NotImplementedException(); }
        public int InsertSql(string sql) { throw new NotImplementedException(); }
        #endregion
    }
}
  1. کد فایل سیستم نیز به کلاس FileManager در Namespace مناسب منتقل می‌شود:
using System;

namespace CH01_CodingStandardsAndPrinciples.GoodCode.FileSystem
{
    public class FileManager
    {
        #region File Operations
        public string ReadText(string filename) { throw new NotImplementedException(); }
        public void WriteText(string filename, string text) { throw new NotImplementedException(); }
        public byte[] ReadFile(string filename) { throw new NotImplementedException(); }
        public void WriteFile(string filename, byte[] binaryData) { throw new NotImplementedException(); }
        #endregion
    }
}

با این کار، کلاس‌هایی که بیش از حد کار انجام می‌دهند شناسایی و بازآرایی شدند تا هر کلاس تنها یک وظیفه مشخص داشته باشد.


در ادامه، همین فرآیند را برای متدهایی که چند کار انجام می‌دهند نیز تکرار خواهیم کرد.

متدهایی که چند کار انجام می‌دهند ⚠️

من بارها خودم را در متدهایی با سطوح متعدد تورفتگی گم کرده‌ام، جایی که متد چندین کار مختلف را در همان تورفتگی‌ها انجام می‌دهد. ترکیب‌های ممکن واقعاً سردرگم‌کننده بودند.

می‌خواستم کد را بازآرایی کنم تا نگهداری آسان‌تر شود، اما برنامه‌نویس ارشد مانع شد. به وضوح می‌توانستم ببینم که متد می‌توانست کوچکتر شود اگر کد به متدهای مختلف منتقل می‌شد.


مثال

در این مثال، متد یک رشته (string) دریافت می‌کند، سپس آن رشته را رمزگذاری و رمزگشایی می‌کند. طول متد زیاد است تا نشان دهد چرا متدها باید کوتاه باشند:

public string security(string plainText)
{
    try
    {
        byte[] encrypted;
        using (AesManaged aes = new AesManaged())
        {
            ICryptoTransform encryptor = aes.CreateEncryptor(Key, IV);
            using (MemoryStream ms = new MemoryStream())
                using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter sw = new StreamWriter(cs))
                        sw.Write(plainText);
                    encrypted = ms.ToArray();
                }
        }
        Console.WriteLine($"Encrypted data: {System.Text.Encoding.UTF8.GetString(encrypted)}");

        using (AesManaged aesm = new AesManaged())
        {
            ICryptoTransform decryptor = aesm.CreateDecryptor(Key, IV);
            using (MemoryStream ms = new MemoryStream(encrypted))
            {
                using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader reader = new StreamReader(cs))
                        plainText = reader.ReadToEnd();
                }
            }
        }
        Console.WriteLine($"Decrypted data: {plainText}");
    }
    catch (Exception exp)
    {
        Console.WriteLine(exp.Message);
    }
    Console.ReadKey();
    return plainText;
}

همان‌طور که می‌بینید، این متد 10 خط کد دارد و خواندن آن دشوار است. همچنین بیش از یک کار انجام می‌دهد.

این کد می‌تواند به دو متد جداگانه تقسیم شود:

  1. متدی برای رمزگذاری رشته
  2. متدی برای رمزگشایی رشته

این مثال نشان می‌دهد چرا متدها نباید بیش از 10 خط باشند.


متدهای بیش از 10 خط 📝

اگر برای خواندن متد نیاز به اسکرول کردن زیاد دارید، یعنی متد خیلی طولانی است و می‌تواند باعث استرس برنامه‌نویس و سوءتفاهم شود، که در نهایت ممکن است کد یا هدف آن را خراب کند.

روش مناسب:


کد قبلی نمونه‌ای عالی است که نشان می‌دهد چرا متدها باید کوچک باشند.

OpenDatabaseConnection()
CloseDatabaseConnection()

و متدها از هدفشان منحرف نشوند.


در ادامه، به بررسی پارامترهای متدها می‌پردازیم.

متدهایی با بیش از دو پارامتر ⚠️

متدهایی که پارامترهای زیادی دارند معمولاً کمی دشوار و دست و پا گیر می‌شوند.


استفاده از Exception برای کنترل جریان برنامه 🚫

استفاده از Exceptionها برای کنترل جریان برنامه می‌تواند هدف کد را پنهان کند و به نتایج غیرمنتظره منجر شود.

اگر کد شما برای انتظار یک یا چند Exception برنامه‌ریزی شده است، یعنی طراحی شما اشتباه است.

مثالی رایج: Business Rule Exceptions (BREs)

مثال استفاده از BRE برای کنترل جریان برنامه:

public void BreFlowControlExample(BusinessRuleException bre)
{
    switch (bre.Message)
    {
        case "OutOfAcceptableRange":
            DoOutOfAcceptableRangeWork();
            break;
        default:
            DoInAcceptableRangeWork();
            break;
    }
}

کنترل جریان با منطق Boolean ✅

یک روش بهتر استفاده از Boolean برای کنترل جریان است. مثال:

public void BetterFlowControlExample(bool isInAcceptableRange)
{
    if (isInAcceptableRange)
        DoInAcceptableRangeWork();
    else
        DoOutOfAcceptableRangeWork();
}

کدی که سخت خوانده می‌شود 📜

کدهایی مانند Lasagna و Spaghetti واقعاً خواندن و دنبال کردن آن‌ها دشوار است.

Lasagna code 🥘

Spaghetti code 🍝

مثال شخصی:

روش درست:

  1. همه کارهایی که برنامه انجام می‌دهد را روی کاغذ بنویسید.
  2. ویژگی‌ها را گروه‌بندی کنید.
  3. یک لیست از نیازمندی‌ها، وظایف، تست‌ها و معیارهای پذیرش بسازید.
  4. سپس برنامه را براساس مشخصات نوشته شده پیاده‌سازی کنید.

کدی که به شدت به هم وابسته است 🔗

کدی که تنگاتنگ به هم وابسته است:

مثال tight coupling:

مثال عملی:

راه‌حل:

مثال بد از کد tightly coupled:

public class Database
{
    private SqlServerConnection _databaseConnection;
    public Database(SqlServerConnection databaseConnection)
    {
        _databaseConnection = databaseConnection;
    }
}

چسبندگی کم (Low Cohesion) 🧩


اشیایی که در حافظه باقی می‌مانند 🕸️

دلایل رایج:

  1. Static variables

    • متغیرهای استاتیک که به اشیایی ارجاع می‌دهند، توسط Garbage Collector جمع‌آوری نمی‌شوند.
    • هر چیزی که GC Root باشد، توسط جمع‌آورنده زباله علامت‌گذاری می‌شود که جمع‌آوری نشود.
  2. Anonymous methods

    • وقتی یک متد ناشناس، اعضای کلاس را Capture می‌کند، Instance کلاس زنده می‌ماند تا متد ناشناس وجود دارد.
  3. کد unmanaged (COM)

    • اگر اشیای managed و unmanaged آزاد نشوند و حافظه به طور صریح آزاد نشود، Memory Leak ایجاد می‌شود.
  4. Cache بدون محدودیت

    • کدی که Cache را بدون weak references یا حذف موارد استفاده نشده ذخیره می‌کند، نهایتاً باعث اتمام حافظه می‌شود.
  5. Threadهای بدون پایان

    • اگر ارجاعات به اشیاء در Threadی ایجاد شود که هرگز پایان نمی‌یابد، حافظه هدر می‌رود.
  6. Event subscriptions غیر ناشناس

    • وقتی Eventها هنوز به کلاس‌ها متصل هستند، اشیاء در حافظه باقی می‌مانند.
    • لغو ثبت (unsubscribe) ضروری است تا Memory Leak رخ ندهد.

استفاده از متد Finalize() ⚰️


Over-engineering 🏗️


یادگیری اصل KISS 🧩

💡 نتیجه‌گیری: همیشه لازم نیست که OOP، SOLID و DRY را به‌صورت سختگیرانه دنبال کنید. گاهی ساده بودن و استفاده از KISS بهترین راه است. در نهایت، حتی زیباترین سیستم OOP در پشت صحنه به کد رویه‌ای (Procedural) تبدیل می‌شود که کامپیوتر راحت‌تر می‌فهمد!


نبود Region در کلاس‌های بزرگ 📚

کد با هدف گمشده (Lost-intention code)

مثال کد بد:

public class MyClass
{
    public void MyMethod()
    {
        // ...implementation...
    }

    public DateTime AddDates(DateTime date1, DateTime date2)
    {
        //...implementation...
    }

    public Product GetData(int id)
    {
        //...implementation...
    }
}

مشکلات:

  1. نام کلاس چیزی نمی‌گوید و هدف کلاس مشخص نیست.

  2. متد MyMethod مشخص نیست چه کاری انجام می‌دهد.

  3. کلاس هم تاریخ‌ها را مدیریت می‌کند و هم داده‌های محصول را دریافت می‌کند – عدم رعایت اصل تک مسئولیتی (SRP).

    • AddDates باید در کلاس مدیریت تاریخ باشد.
    • GetData باید در ViewModel مربوط به محصول باشد.

افشای مستقیم اطلاعات (Directly exposing information) ⚠️

public class Product
{
    public int Id;
    public int Name;
    public int Description;
    public string ProductCode;
    public decimal Price;
    public long UnitsInStock;
}

مشکلات:

  1. تغییر نوع UnitsInStock از long به int نیازمند تغییر در همه جا است.
  2. اگر بخواهید قوانین اعتبارسنجی برای ProductCode اضافه کنید، نمی‌توانید چون مقدار مستقیماً توسط کلاس فراخوانده شده تغییر می‌کند.

کد خوب (Good Code) ✅

Indentation مناسب (Proper indentation)

مثال:

public void DoSomething()
{
    for (var i = 0; i < 1000; i++)
    {
        var productCode = $"PRC000{i}";
        //...implementation
    }
}

کامنت‌های معنادار (Meaningful Comments) 📝


کامنت‌های مستندسازی API (API Documentation Comments) 📄

مثال:

/// <summary>
/// Create a new <see cref="KustoCode"/> instance from the text and globals.
/// Does not perform semantic analysis.
/// </summary>
/// <param name="text">The code text</param>
/// <param name="globals">
///   The globals to use for parsing and semantic analysis. 
///   Defaults to <see cref="GlobalState.Default"/>
/// </param>
public static KustoCode Parse(string text, GlobalState globals = null) {
    // ...implementation...
}

سازماندهی مناسب با استفاده از Namespaceها 📂

Conventions-UsedThis-Book

قوانین نام‌گذاری خوب (Good Naming Conventions) ✨

مثال:

using System;
using System.Text.RegularExpressions;

namespace CompanyName.ProductName.RegEx
{
    /// <summary>
    /// An extension class for providing regular expression extensions methods.
    /// </summary>
    public static class RegularExpressions
    {
        private static string _preprocessed;
        public static string RegularExpression { get; set; }

        public static bool IsValidEmail(this string email)
        {
            // Email address: RFC 2822 Format.
            // Matches a normal email address. Does not check the top-level domain.
            // Requires the "case insensitive" option to be ON.
            var exp = @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)@(?:[a-z0-9](?:[a-z0-9-][a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z";
            bool isEmail = Regex.IsMatch(email, exp, RegexOptions.IgnoreCase);
            return isEmail;
        }
    }
}

کلاس‌ها و متدهای تک‌وظیفه‌ای 🎯


طول متدها و تعداد پارامترها 📏

استفاده صحیح از Exceptionها ⚠️


کد قابل خواندن 📝


کد کم‌وابسته (Loosely Coupled) 🔗

public class Database
{
    private IDatabaseConnection _databaseConnection;

    public Database(IDatabaseConnection databaseConnection)
    {
        _databaseConnection = databaseConnection;
    }
}

انسجام بالا (High Cohesion) 🎯


آزادسازی تمیز منابع (Clean Disposal) 🧹

using (var unitOfWork = new UnitOfWork())
{
    // Perform unit of work here.
}
// unitOfWork به‌صورت خودکار Dispose شده است

اجتناب از استفاده از Finalize() ☢️


سطح مناسب انتزاع (Abstraction) 🎛️


استفاده از Regions در کلاس‌های بزرگ 📂


ضرورت استانداردها، اصول و متدولوژی‌ها 🏗️

رعایت استانداردها باعث می‌شود کد یکپارچه، قابل خواندن و کمتر خطادار باشد.

اصول کدنویسی 🧩

برخی اصول رایج:


متدولوژی‌های کدنویسی 🛠️


استانداردهای کدنویسی 📏


مدولار بودن کد (Modularity) 🧱

مزیت: کد کوچک و مدولار راحت‌تر خوانده و درک می‌شود و باعث فهم بهتر و استفاده آسان‌تر توسط توسعه‌دهندگان می‌شود.


KISS – Keep It Simple, Stupid ⚡

سادگی کد به خوانایی، نگهداری آسان و کاهش فشار ذهنی توسعه‌دهندگان کمک می‌کند.

YAGNI 🛑


DRY 🔄


SOLID ⚖️

مجموعه‌ای از ۵ اصل طراحی برای ساخت نرم‌افزار قابل فهم و قابل نگهداری:

  1. Single Responsibility Principle (SRP): هر کلاس یا متد فقط یک مسئولیت داشته باشد و عناصر مرتبط با آن در کنار هم باشند.
  2. Open/Closed Principle (OCP): کلاس‌ها باید برای توسعه باز و برای تغییر بسته باشند.
  3. Liskov Substitution Principle (LSP): هر شیء پایه باید بتواند هر کلاس مشتق‌شده‌ای را بدون تغییر رفتار جایگزین کند.
  4. Interface Segregation Principle (ISP): به جای یک اینترفیس بزرگ، چند اینترفیس کوچک بسازید تا کلاس‌ها فقط متدهای مورد نیازشان را پیاده‌سازی کنند.
  5. Dependency Inversion Principle (DIP): ماژول‌های سطح بالا نباید به ماژول‌های سطح پایین وابسته باشند؛ هر دو باید به انتزاع‌ها وابسته باشند.

نکته: همیشه متغیرها را با نوع انتزاعی (Interface یا Abstract Class) تعریف کنید، نه کلاس‌های Concrete.


Occam’s Razor 🔍

خلاصه فصل ۱ 📘

در این فصل، با کد خوب و کد بد آشنا شدیم و اهمیت نوشتن کد خوب را بررسی کردیم. همچنین، به لینک Microsoft C# Coding Conventions اشاره شد تا بتوانید بهترین استانداردهای مایکروسافت را دنبال کنید.

همچنین به طور خلاصه با برخی متدولوژی‌ها و اصول برنامه‌نویسی آشنا شدید:

مزایای مدولار کردن کد با استفاده از namespace و assembly:

در فصل بعد، به Peer Code Reviews پرداخته خواهد شد که با وجود اینکه گاهی ناخوشایند است، به حفظ کیفیت کد و رعایت استانداردهای شرکت کمک می‌کند.


سوالات پیشنهادی برای مرور ✏️

  1. نتایج کد بد چیست؟
  2. نتایج کد خوب چیست؟
  3. مزایای نوشتن کد مدولار چیست؟
  4. کد DRY چیست؟
  5. چرا باید هنگام نوشتن کد KISS رعایت کرد؟
  6. مخفف SOLID چیست؟
  7. YAGNI را توضیح دهید.
  8. Occam’s Razor چیست؟

منابع پیشنهادی برای مطالعه بیشتر 📚