Naming

Avoid using bad names (không dùng tên xấu)

Bad

int d;

Good

int daySinceModification;

Avoid Misleading Names (dùng tên đúng với mục đích sử dụng)

Bad

var dataFromDb = db.GetFromService().ToList();

Good

var listOfEmployee = _employeeService.GetEmployees().ToList();

Avoid Hungarian notation (tránh đặt tên biến có tiền tố chỉ loại dữ liệu của biến)

Bad

int iCounter;
string strFullName;
DateTime dModifiedDate;

public bool IsShopOpen(string pDay, int pAmount)
{
    // some logic
}

Good

int counter;
string fullName;
DateTime modifiedDate;

public bool IsShopOpen(string day, int amount)
{
    // some logic
}

Use consistent capitalization (tính nhất quán trong việc đặt tên hoa thường)

Bad

const int DAYS_IN_WEEK = 7;
const int daysInMonth = 30;

var songs = new List<string> { 'Back In Black', 'Stairway to Heaven', 'Hey Jude' };
var Artists = new List<string> { 'ACDC', 'Led Zeppelin', 'The Beatles' };

bool EraseDatabase() {}
bool Restore_database() {}

class animal {}
class Alpaca {}

Good

const int DaysInWeek = 7;
const int DaysInMonth = 30;

var songs = new List<string> { 'Back In Black', 'Stairway to Heaven', 'Hey Jude' };
var artists = new List<string> { 'ACDC', 'Led Zeppelin', 'The Beatles' };

bool EraseDatabase() {}
bool RestoreDatabase() {}

class Animal {}
class Alpaca {}

Use pronounceable names (tên hàm biến dễ đọc)

Bad

public class Employee
{
    public Datetime sWorkDate { get; set; } // what the heck is this
    public Datetime modTime { get; set; } // same here
}

Good

public class Employee
{
    public Datetime StartWorkingDate { get; set; }
    public Datetime ModificationTime { get; set; }
}

Use Camelcase notation (sử dụng camelcase cho biến và parameter)

Bad

var employeephone;

public double CalculateSalary(int workingdays, int workinghours)
{
    // some logic
}

Good

var employeePhone;

public double CalculateSalary(int workingDays, int workingHours)
{
    // some logic
}

Use domain name (đặt tên đúng với quan điểm chung, tránh mất công giải thích)

Good

public class SingleObject
{
    // create an object of SingleObject
    private static SingleObject _instance = new SingleObject();

    // make the constructor private so that this class cannot be instantiated
    private SingleObject() {}

    // get the only object available
    public static SingleObject GetInstance()
    {
        return _instance;
    }

    public string ShowMessage()
    {
        return "Hello World!";
    }
}

public static void main(String[] args)
{
    // illegal construct
    // var object = new SingleObject();

    // Get the only object available
    var singletonObject = SingleObject.GetInstance();

    // show the message
    singletonObject.ShowMessage();
}

Variables

Avoid nesting too deeply and return early (tránh sử dụng nhiều if else gây khó đọc, viết tường minh tốt hơn)

Bad

public bool IsShopOpen(string day)
{
    if (!string.IsNullOrEmpty(day))
    {
        day = day.ToLower();
        if (day == "friday")
        {
            return true;
        }
        else if (day == "saturday")
        {
            return true;
        }
        else if (day == "sunday")
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }

}

Good

public bool IsShopOpen(string day)
{
    if (string.IsNullOrEmpty(day))
    {
        return false;
    }

    var openingDays = new[] { "friday", "saturday", "sunday" };
    return openingDays.Any(d => d == day.ToLower());
}

Bad

public long Fibonacci(int n)
{
    if (n < 50)
    {
        if (n != 0)
        {
            if (n != 1)
            {
                return Fibonacci(n - 1) + Fibonacci(n - 2);
            }
            else
            {
                return 1;
            }
        }
        else
        {
            return 0;
        }
    }
    else
    {
        throw new System.Exception("Not supported");
    }
}

Good

public long Fibonacci(int n)
{
    if (n == 0)
    {
        return 0;
    }

    if (n == 1)
    {
        return 1;
    }

    if (n > 50)
    {
        throw new System.Exception("Not supported");
    }

    return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Avoid mental mapping (tên biến tường minh)

Bad

var l = new[] { "Austin", "New York", "San Francisco" };

for (var i = 0; i < l.Count(); i++)
{
    var li = l[i];
    DoStuff();
    DoSomeOtherStuff();

    // ...
    // ...
    // ...
    // Wait, what is `li` for again?
    Dispatch(li);
}

Good

var locations = new[] { "Austin", "New York", "San Francisco" };

foreach (var location in locations)
{
    DoStuff();
    DoSomeOtherStuff();

    // ...
    // ...
    // ...
    Dispatch(location);
}

Avoid magic string (tránh sử dụng chuỗi ký tự fix cứng rất dễ gây bug)

Bad

if (userRole == "Admin")
{
    // logic in here
}

Good

const string ADMIN_ROLE = "Admin"
if (userRole == ADMIN_ROLE)
{
    // logic in here
}

Don’t add unneeded context (không thêm ngữ cảnh không cần thiết vào tên biến, class)

Bad

public class Car
{
    public string CarMake { get; set; }
    public string CarModel { get; set; }
    public string CarColor { get; set; }

    //...
}

Good

public class Car
{
    public string Make { get; set; }
    public string Model { get; set; }
    public string Color { get; set; }

    //...
}

Use meaningful and pronounceable variable names (tên biến có ý nghĩa và dễ phát âm)

Bad

var ymdstr = DateTime.UtcNow.ToString("MMMM dd, yyyy");

Good

var currentDate = DateTime.UtcNow.ToString("MMMM dd, yyyy");

Use the same vocabulary for the same type of variable (đồng nhất 1 loại từ vựng)

ví dụ đang dùng là customerName thì nên tiếp tục sử dụng “customerName” thay vì “clientName” cho cả ứng dụng

Bad

GetUserInfo();
GetUserData();
GetUserRecord();
GetUserProfile();

Good

GetUser();

Use searchable names (sử dụng tên có thể tìm kiếm)

Bad

// What the heck is data for?
var data = new { Name = "John", Age = 42 };

var stream1 = new MemoryStream();
var ser1 = new DataContractJsonSerializer(typeof(object));
ser1.WriteObject(stream1, data);

stream1.Position = 0;
var sr1 = new StreamReader(stream1);
Console.Write("JSON form of Data object: ");
Console.WriteLine(sr1.ReadToEnd());

Good

var person = new Person
{
    Name = "John",
    Age = 42
};

var stream2 = new MemoryStream();
var ser2 = new DataContractJsonSerializer(typeof(Person));
ser2.WriteObject(stream2, data);

stream2.Position = 0;
var sr2 = new StreamReader(stream2);
Console.Write("JSON form of Data object: ");
Console.WriteLine(sr2.ReadToEnd());

Use explanatory variables(sử dụng các biến có tên mô tả mục đích và ý nghĩa của chúng, thay vì sử dụng các giá trị cứng hard-coded values trực tiếp trong code)

Bad

const string Address = "One Infinite Loop, Cupertino 95014";
var cityZipCodeRegex = @"/^[^,\]+[,\\s]+(.+?)\s*(\d{5})?$/";
var matches = Regex.Matches(Address, cityZipCodeRegex);
if (matches[0].Success == true && matches[1].Success == true)
{
    SaveCityZipCode(matches[0].Value, matches[1].Value);
}

Good

Giảm sự phụ thuộc vào biểu thức chính quy bằng cách đặt tên cho subpatterns.

const string Address = "One Infinite Loop, Cupertino 95014";
var cityZipCodeWithGroupRegex = @"/^[^,\]+[,\\s]+(?<city>.+?)\s*(?<zipCode>\d{5})?$/";
var matchesWithGroup = Regex.Match(Address, cityZipCodeWithGroupRegex);
var cityGroup = matchesWithGroup.Groups["city"];
var zipCodeGroup = matchesWithGroup.Groups["zipCode"];
if(cityGroup.Success == true && zipCodeGroup.Success == true)
{
    SaveCityZipCode(cityGroup.Value, zipCodeGroup.Value);
}

Use default arguments instead of short circuiting or conditionals (sử dụng đối số mặc định trong khai báo hàm thay vì sử dụng các điều kiện rẽ nhánh hoặc các biểu thức ngắn gọn (short-circuiting) để xử lý các trường hợp đặc biệt)

Not good:

public void CreateMicrobrewery(string name = null)
{
    var breweryName = !string.IsNullOrEmpty(name) ? name : "Hipster Brew Co.";
    // ...
}

Good

public void CreateMicrobrewery(string breweryName = "Hipster Brew Co.")
{
    // ...
}

Functions

Avoid side effects (1 hàm chỉ nên nhận vào một giá trị và trả về một giá trị khác, không nên thực hiện các thao tác bên ngoài phạm vi hàm đó như thay đổi biến toàn cục, ghi file,.. mà không có sự chú ý hoặc kiểm soát cẩn thận)

Bad

// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
var name = "Ryan McDermott";

public void SplitAndEnrichFullName()
{
    var temp = name.Split(" ");
    name = $"His first name is {temp[0]}, and his last name is {temp[1]}"; // side effect
}

SplitAndEnrichFullName();

Console.WriteLine(name); // His first name is Ryan, and his last name is McDermott

Good

public string SplitAndEnrichFullName(string name)
{
    var temp = name.Split(" ");
    return $"His first name is {temp[0]}, and his last name is {temp[1]}";
}

var name = "Ryan McDermott";
var fullName = SplitAndEnrichFullName(name);

Console.WriteLine(name); // Ryan McDermott
Console.WriteLine(fullName); // His first name is Ryan, and his last name is McDermott

Avoid negative conditionals (tránh sử dụng các biểu thức điều kiện mà có kết quả là phủ định negatives hoặc phức tạp.)

Bad

public bool IsDOMNodeNotPresent(string node)
{
    // ...
}

if (!IsDOMNodeNotPresent(node))
{
    // ...
}

Good

public bool IsDOMNodePresent(string node)
{
    // ...
}

if (IsDOMNodePresent(node))
{
    // ...
}

Avoid conditionals (tránh sử dụng điều kiện if, else, switch trong code, và thay thế chúng bằng sự sử dụng đa hình polymorphism trong nhiều trường hợp.)

Bad

class Airplane
{
    // ...

    public double GetCruisingAltitude()
    {
        switch (_type)
        {
            case '777':
                return GetMaxAltitude() - GetPassengerCount();
            case 'Air Force One':
                return GetMaxAltitude();
            case 'Cessna':
                return GetMaxAltitude() - GetFuelExpenditure();
        }
    }
}

Good

interface IAirplane
{
    // ...

    double GetCruisingAltitude();
}

class Boeing777 : IAirplane
{
    // ...

    public double GetCruisingAltitude()
    {
        return GetMaxAltitude() - GetPassengerCount();
    }
}

class AirForceOne : IAirplane
{
    // ...

    public double GetCruisingAltitude()
    {
        return GetMaxAltitude();
    }
}

class Cessna : IAirplane
{
    // ...

    public double GetCruisingAltitude()
    {
        return GetMaxAltitude() - GetFuelExpenditure();
    }
}

Avoid type-checking (tránh kiểm tra type data của đối tượng, thay thế nó bằng sự sử dụng đa hình polymorphism để linh hoạt và dễ bảo trì hơn)

Bad

public Path TravelToTexas(object vehicle)
{
    if (vehicle.GetType() == typeof(Bicycle))
    {
        (vehicle as Bicycle).PeddleTo(new Location("texas"));
    }
    else if (vehicle.GetType() == typeof(Car))
    {
        (vehicle as Car).DriveTo(new Location("texas"));
    }
}

Good

public Path TravelToTexas(Traveler vehicle)
{
    vehicle.TravelTo(new Location("texas"));
}

or

// pattern matching
public Path TravelToTexas(object vehicle)
{
    if (vehicle is Bicycle bicycle)
    {
        bicycle.PeddleTo(new Location("texas"));
    }
    else if (vehicle is Car car)
    {
        car.DriveTo(new Location("texas"));
    }
}

Avoid flags in method parameters (Tránh sử dụng cờ flags trong các tham số của method nhằm giảm thiểu việc một phương thức có nhiều hơn một nhiệm vụ)

Bad

public void CreateFile(string name, bool temp = false)
{
    if (temp)
    {
        Touch("./temp/" + name);
    }
    else
    {
        Touch(name);
    }
}

Good

public void CreateFile(string name)
{
    Touch(name);
}

public void CreateTempFile(string name)
{
    Touch("./temp/"  + name);
}

Don’t write to global functions (không ghi dữ liệu vào các hàm global dùng chung, dễ gây bug)

Bad

public Dictionary<string, string> Config()
{
    return new Dictionary<string,string>(){
        ["foo"] = "bar"
    };
}

Good

class Configuration
{
    private Dictionary<string, string> _configuration;

    public Configuration(Dictionary<string, string> configuration)
    {
        _configuration = configuration;
    }

    public string[] Get(string key)
    {
        return _configuration.ContainsKey(key) ? _configuration[key] : null;
    }
}
var configuration = new Configuration(new Dictionary<string, string>() {
    ["foo"] = "bar"
});

Don’t use a Singleton pattern (không sử dụng singleton pattern, thay vào đó dùng interface dependency injection)

Bad

class DBConnection
{
    private static DBConnection _instance;

    private DBConnection()
    {
        // ...
    }

    public static GetInstance()
    {
        if (_instance == null)
        {
            _instance = new DBConnection();
        }

        return _instance;
    }

    // ...
}

var singleton = DBConnection.GetInstance();

Good

class DBConnection
{
    public DBConnection(IOptions<DbConnectionOption> options)
    {
        // ...
    }

    // ...
}
var options = <resolve from IOC>;
var connection = new DBConnection(options);

Function arguments (method ít hơn 2 tham số là tốt nhất)

Bad

public void CreateMenu(string title, string body, string buttonText, bool cancellable)
{
    // ...
}

Good

public class MenuConfig
{
    public string Title { get; set; }
    public string Body { get; set; }
    public string ButtonText { get; set; }
    public bool Cancellable { get; set; }
}

var config = new MenuConfig
{
    Title = "Foo",
    Body = "Bar",
    ButtonText = "Baz",
    Cancellable = true
};

public void CreateMenu(MenuConfig config)
{
    // ...
}

Functions should do one thing (method chỉ nên làm 1 việc duy nhất)

Bad

public void SendEmailToListOfClients(string[] clients)
{
    foreach (var client in clients)
    {
        var clientRecord = db.Find(client);
        if (clientRecord.IsActive())
        {
            Email(client);
        }
    }
}

Good

public void SendEmailToListOfClients(string[] clients)
{
    var activeClients = GetActiveClients(clients);
    // Do some logic
}

public List<Client> GetActiveClients(string[] clients)
{
    return db.Find(clients).Where(s => s.Status == "Active");
}

Function names should say what they do (tên method nói rõ chức năng của chúng)

Bad

public class Email
{
    //...

    public void Handle()
    {
        SendMail(this._to, this._subject, this._body);
    }
}

var message = new Email(...);
// What is this? A handle for the message? Are we writing to a file now?
message.Handle();

Good

public class Email
{
    //...

    public void Send()
    {
        SendMail(this._to, this._subject, this._body);
    }
}

var message = new Email(...);
// Clear and obvious
message.Send();

Functions should only be one level of abstraction (1 hàm chỉ 1 cấp độ trừu tượng, chia nhỏ method ra)

Bad

public string ParseBetterJSAlternative(string code)
{
    var regexes = [
        // ...
    ];

    var statements = explode(" ", code);
    var tokens = new string[] {};
    foreach (var regex in regexes)
    {
        foreach (var statement in statements)
        {
            // ...
        }
    }

    var ast = new string[] {};
    foreach (var token in tokens)
    {
        // lex...
    }

    foreach (var node in ast)
    {
        // parse...
    }
}

Bad too:

We have carried out some of the functionality, but the ParseBetterJSAlternative() function is still very complex and not testable.

public string Tokenize(string code)
{
    var regexes = new string[]
    {
        // ...
    };

    var statements = explode(" ", code);
    var tokens = new string[] {};
    foreach (var regex in regexes)
    {
        foreach (var statement in statements)
        {
            tokens[] = /* ... */;
        }
    }

    return tokens;
}

public string Lexer(string[] tokens)
{
    var ast = new string[] {};
    foreach (var token in tokens)
    {
        ast[] = /* ... */;
    }

    return ast;
}

public string ParseBetterJSAlternative(string code)
{
    var tokens = Tokenize(code);
    var ast = Lexer(tokens);
    foreach (var node in ast)
    {
        // parse...
    }
}

Good

The best solution is move out the dependencies of ParseBetterJSAlternative() function.

class Tokenizer
{
    public string Tokenize(string code)
    {
        var regexes = new string[] {
            // ...
        };

        var statements = explode(" ", code);
        var tokens = new string[] {};
        foreach (var regex in regexes)
        {
            foreach (var statement in statements)
            {
                tokens[] = /* ... */;
            }
        }

        return tokens;
    }
}

class Lexer
{
    public string Lexify(string[] tokens)
    {
        var ast = new[] {};
        foreach (var token in tokens)
        {
            ast[] = /* ... */;
        }

        return ast;
    }
}

class BetterJSAlternative
{
    private string _tokenizer;
    private string _lexer;

    public BetterJSAlternative(Tokenizer tokenizer, Lexer lexer)
    {
        _tokenizer = tokenizer;
        _lexer = lexer;
    }

    public string Parse(string code)
    {
        var tokens = _tokenizer.Tokenize(code);
        var ast = _lexer.Lexify(tokens);
        foreach (var node in ast)
        {
            // parse...
        }
    }
}

Function callers and callees should be close (viết code theo thứ tự từ trên xuống khi gọi hàm)

Bad

class PerformanceReview
{
    private readonly Employee _employee;

    public PerformanceReview(Employee employee)
    {
        _employee = employee;
    }

    private IEnumerable<PeersData> LookupPeers()
    {
        return db.lookup(_employee, 'peers');
    }

    private ManagerData LookupManager()
    {
        return db.lookup(_employee, 'manager');
    }

    private IEnumerable<PeerReviews> GetPeerReviews()
    {
        var peers = LookupPeers();
        // ...
    }

    public PerfReviewData PerfReview()
    {
        GetPeerReviews();
        GetManagerReview();
        GetSelfReview();
    }

    public ManagerData GetManagerReview()
    {
        var manager = LookupManager();
    }

    public EmployeeData GetSelfReview()
    {
        // ...
    }
}

var  review = new PerformanceReview(employee);
review.PerfReview();

Good

class PerformanceReview
{
    private readonly Employee _employee;

    public PerformanceReview(Employee employee)
    {
        _employee = employee;
    }

    public PerfReviewData PerfReview()
    {
        GetPeerReviews();
        GetManagerReview();
        GetSelfReview();
    }

    private IEnumerable<PeerReviews> GetPeerReviews()
    {
        var peers = LookupPeers();
        // ...
    }

    private IEnumerable<PeersData> LookupPeers()
    {
        return db.lookup(_employee, 'peers');
    }

    private ManagerData GetManagerReview()
    {
        var manager = LookupManager();
        return manager;
    }

    private ManagerData LookupManager()
    {
        return db.lookup(_employee, 'manager');
    }

    private EmployeeData GetSelfReview()
    {
        // ...
    }
}

var review = new PerformanceReview(employee);
review.PerfReview();

Encapsulate conditionals (đóng gói điền kiện lại dễ maintain và sử dụng lại code)

Bad

if (article.state == "published")
{
    // ...
}

Good

if (article.IsPublished())
{
    // ...
}

Remove dead code (xóa code thừa)

Bad

public void OldRequestModule(string url)
{
    // ...
}

public void NewRequestModule(string url)
{
    // ...
}

var request = NewRequestModule(requestUrl);
InventoryTracker("apples", request, "www.inventory-awesome.io");

Good

public void RequestModule(string url)
{
    // ...
}

var request = RequestModule(requestUrl);
InventoryTracker("apples", request, "www.inventory-awesome.io");

Objects and Data Structures

Use getters and setters (sử dụng public, protected, private để điều khiển properties modification của object)

  • Khi muốn làm nhiều việc hơn ngoài việc lấy thuộc tính đối tượng, bạn không cần phải tra cứu và thay đổi mọi trình truy cập trong cơ sở mã của mình.
  • Làm cho việc thêm xác thực trở nên đơn giản khi thực hiện set.
  • Đóng gói biểu diễn bên trong.
  • Dễ dàng thêm tính năng ghi nhật ký và xử lý lỗi khi nhận và cài đặt.
  • Kế thừa lớp này, bạn có thể ghi đè chức năng mặc định.
  • Bạn có thể lười tải các thuộc tính của đối tượng, giả sử lấy nó từ máy chủ.

Ngoài ra, đây là một phần của Open/Closed principle SOLID

Bad

class BankAccount
{
    public double Balance = 1000;
}

var bankAccount = new BankAccount();

// Fake buy shoes...
bankAccount.Balance -= 100;

Good

class BankAccount
{
    private double _balance = 0.0D;

    pubic double Balance {
        get {
            return _balance;
        }
    }

    public BankAccount(balance = 1000)
    {
       _balance = balance;
    }

    public void WithdrawBalance(int amount)
    {
        if (amount > _balance)
        {
            throw new Exception('Amount greater than available balance.');
        }

        _balance -= amount;
    }

    public void DepositBalance(int amount)
    {
        _balance += amount;
    }
}

var bankAccount = new BankAccount();

// Buy shoes...
bankAccount.WithdrawBalance(price);

// Get balance
balance = bankAccount.Balance;

Make objects have private/protected members (ẩn các thành phần bên trong chỉ hiển thị ra bên ngoài bằng interfaces rõ ràng)

Bad

class Employee
{
    public string Name { get; set; }

    public Employee(string name)
    {
        Name = name;
    }
}

var employee = new Employee("John Doe");
Console.WriteLine(employee.Name); // Employee name: John Doe

Good

class Employee
{
    public string Name { get; }

    public Employee(string name)
    {
        Name = name;
    }
}

var employee = new Employee("John Doe");
Console.WriteLine(employee.Name); // Employee name: John Doe

Use method chaining (sử dụng this để viết function giúp ngắn gọn, dễ đọc và dễ bảo trì hơn)

Good

public static class ListExtensions
{
    public static List<T> FluentAdd<T>(this List<T> list, T item)
    {
        list.Add(item);
        return list;
    }

    public static List<T> FluentClear<T>(this List<T> list)
    {
        list.Clear();
        return list;
    }

    public static List<T> FluentForEach<T>(this List<T> list, Action<T> action)
    {
        list.ForEach(action);
        return list;
    }

    public static List<T> FluentInsert<T>(this List<T> list, int index, T item)
    {
        list.Insert(index, item);
        return list;
    }

    public static List<T> FluentRemoveAt<T>(this List<T> list, int index)
    {
        list.RemoveAt(index);
        return list;
    }

    public static List<T> FluentReverse<T>(this List<T> list)
    {
        list.Reverse();
        return list;
    }
}

internal static void ListFluentExtensions()
{
    var list = new List<int>() { 1, 2, 3, 4, 5 }
        .FluentAdd(1)
        .FluentInsert(0, 0)
        .FluentRemoveAt(1)
        .FluentReverse()
        .FluentForEach(value => value.WriteLine())
        .FluentClear();
}

Prefer composition over inheritance

As stated famously in Design Patterns by the Gang of Four, you should prefer composition over inheritance where you can. There are lots of good reasons to use inheritance and lots of good reasons to use composition.

The main point for this maxim is that if your mind instinctively goes for inheritance, try to think if composition could model your problem better. In some cases it can.

You might be wondering then, “when should I use inheritance?” It depends on your problem at hand, but this is a decent list of when inheritance makes more sense than composition:

  1. Your inheritance represents an “is-a” relationship and not a “has-a” relationship (Human->Animal vs. User->UserDetails).
  2. You can reuse code from the base classes (Humans can move like all animals).
  3. You want to make global changes to derived classes by changing a base class (Change the caloric expenditure of all animals when they move).

Bad

class Employee
{
    private string Name { get; set; }
    private string Email { get; set; }

    public Employee(string name, string email)
    {
        Name = name;
        Email = email;
    }

    // ...
}

// Bad because Employees "have" tax data.
// EmployeeTaxData is not a type of Employee

class EmployeeTaxData : Employee
{
    private string Name { get; }
    private string Email { get; }

    public EmployeeTaxData(string name, string email, string ssn, string salary)
    {
         // ...
    }

    // ...
}

Good

class EmployeeTaxData
{
    public string Ssn { get; }
    public string Salary { get; }

    public EmployeeTaxData(string ssn, string salary)
    {
        Ssn = ssn;
        Salary = salary;
    }

    // ...
}

class Employee
{
    public string Name { get; }
    public string Email { get; }
    public EmployeeTaxData TaxData { get; }

    public Employee(string name, string email)
    {
        Name = name;
        Email = email;
    }

    public void SetTax(string ssn, double salary)
    {
        TaxData = new EmployeeTaxData(ssn, salary);
    }

    // ...
}

Testing (sử dụng AAA pattern)

Bad


public class MakeDotNetGreatAgainTests
{
    [Fact]
    public void HandleDateBoundaries()
    {
        var date = new MyDateTime("1/1/2015");
        date.AddDays(30);
        Assert.Equal("1/31/2015", date);

        date = new MyDateTime("2/1/2016");
        date.AddDays(28);
        Assert.Equal("02/29/2016", date);

        date = new MyDateTime("2/1/2015");
        date.AddDays(28);
        Assert.Equal("03/01/2015", date);
    }
}

Good


public class MakeDotNetGreatAgainTests
{
    [Fact]
    public void Handle30DayMonths()
    {
        // Arrange
        var date = new MyDateTime("1/1/2015");

        // Act
        date.AddDays(30);

        // Assert
        Assert.Equal("1/31/2015", date);
    }

    [Fact]
    public void HandleLeapYear()
    {
        // Arrange
        var date = new MyDateTime("2/1/2016");

        // Act
        date.AddDays(28);

        // Assert
        Assert.Equal("02/29/2016", date);
    }

    [Fact]
    public void HandleNonLeapYear()
    {
        // Arrange
        var date = new MyDateTime("2/1/2015");

        // Act
        date.AddDays(28);

        // Assert
        Assert.Equal("03/01/2015", date);
    }
}

Concurrency

Use Async/Await

Name Description Exceptions
Avoid async void Prefer async Task methods over async void methods Event handlers
Async all the way Don’t mix blocking and async code Console main method (C# <= 7.0)
Configure context Use ConfigureAwait(false) when you can Methods that require con­text

The Async Way of Doing Things

To Do This … Instead of This … Use This
Retrieve the result of a background task Task.Wait or Task.Result await
Wait for any task to complete Task.WaitAny await Task.WhenAny
Retrieve the results of multiple tasks Task.WaitAll await Task.WhenAll
Wait a period of time Thread.Sleep await Task.Delay

Best practice

The async/await is the best for IO bound tasks (networking communication, database communication, http request, etc.) but it is not good to apply on computational bound tasks (traverse on the huge list, render a hugge image, etc.). Because it will release the holding thread to the thread pool and CPU/cores available will not involve to process those tasks. Therefore, we should avoid using Async/Await for computional bound tasks.

For dealing with computational bound tasks, prefer to use Task.Factory.CreateNew with TaskCreationOptions is LongRunning. It will start a new background thread to process a heavy computational bound task without release it back to the thread pool until the task being completed.

Know Your Tools

There’s a lot to learn about async and await, and it’s natural to get a little disoriented. Here’s a quick reference of solutions to common problems.

Solutions to Common Async Problems

Problem Solution
Create a task to execute code Task.Run or TaskFactory.StartNew (not the Task constructor or Task.Start)
Create a task wrapper for an operation or event TaskFactory.FromAsync or TaskCompletionSource<T>
Support cancellation CancellationTokenSource and CancellationToken
Report progress IProgress<T> and Progress<T>
Handle streams of data TPL Dataflow or Reactive Extensions
Synchronize access to a shared resource SemaphoreSlim
Asynchronously initialize a resource AsyncLazy<T>
Async-ready producer/consumer structures TPL Dataflow or AsyncCollection<T>

Read the Task-based Asynchronous Pattern (TAP) document. It is extremely well-written, and includes guidance on API design and the proper use of async/await (including cancellation and progress reporting).

There are many new await-friendly techniques that should be used instead of the old blocking techniques. If you have any of these Old examples in your new async code, you’re Doing It Wrong(TM):

Old New Description
task.Wait await task Wait/await for a task to complete
task.Result await task Get the result of a completed task
Task.WaitAny await Task.WhenAny Wait/await for one of a collection of tasks to complete
Task.WaitAll await Task.WhenAll Wait/await for every one of a collection of tasks to complete
Thread.Sleep await Task.Delay Wait/await for a period of time
Task constructor Task.Run or TaskFactory.StartNew Create a code-based task

Error Handling

Basic concept of error handling Thrown errors are a good thing! They mean the runtime has successfully identified when something in your program has gone wrong and it's letting you know by stopping function execution on the current stack, killing the process (in .NET/.NET Core), and notifying you in the console with a stack trace.
Don't use 'throw ex' in catch block If you need to re-throw an exception after catching it, use just 'throw' By using this, you will save the stack trace. But in the bad option below, you will lost the stack trace. _Bad_ ``` try { // Do something.. } catch (Exception ex) { // Any action something like roll-back or logging etc. throw ex; } ``` _Good_ ``` try { // Do something.. } catch (Exception ex) { // Any action something like roll-back or logging etc. throw; } ``` **[⬆ back to top](#table-of-contents)**
Don't ignore caught errors Doing nothing with a caught error doesn't give you the ability to ever fix or react to said error. Throwing the error isn't much better as often times it can get lost in a sea of things printed to the console. If you wrap any bit of code in a `try/catch` it means you think an error may occur there and therefore you should have a plan, or create a code path, for when it occurs. _Bad_ ``` try { FunctionThatMightThrow(); } catch (Exception ex) { // silent exception } ``` _Good_ ``` try { FunctionThatMightThrow(); } catch (Exception error) { NotifyUserOfError(error); // Another option ReportErrorToService(error); } ``` **[⬆ back to top](#table-of-contents)**
Use multiple catch block instead of if conditions. If you need to take action according to type of the exception, you better use multiple catch block for exception handling. _Bad_ ``` try { // Do something.. } catch (Exception ex) { if (ex is TaskCanceledException) { // Take action for TaskCanceledException } else if (ex is TaskSchedulerException) { // Take action for TaskSchedulerException } } ``` _Good_ ``` try { // Do something.. } catch (TaskCanceledException ex) { // Take action for TaskCanceledException } catch (TaskSchedulerException ex) { // Take action for TaskSchedulerException } ``` **[⬆ back to top](#table-of-contents)**
Keep exception stack trace when rethrowing exceptions C# allows the exception to be rethrown in a catch block using the `throw` keyword. It is a bad practice to throw a caught exception using `throw e;`. This statement resets the stack trace. Instead use `throw;`. This will keep the stack trace and provide a deeper insight about the exception. Another option is to use a custom exception. Simply instantiate a new exception and set its inner exception property to the caught exception with throw `new CustomException("some info", e);`. Adding information to an exception is a good practice as it will help with debugging. However, if the objective is to log an exception then use `throw;` to pass the buck to the caller. _Bad_ ``` try { FunctionThatMightThrow(); } catch (Exception ex) { logger.LogInfo(ex); throw ex; } ``` _Good_ ``` try { FunctionThatMightThrow(); } catch (Exception error) { logger.LogInfo(error); throw; } ``` _Good_ ``` try { FunctionThatMightThrow(); } catch (Exception error) { logger.LogInfo(error); throw new CustomException(error); } ``` **[⬆ back to top](#table-of-contents)**

Formatting

Uses .editorconfig file _Bad_ Has many code formatting styles in the project. For example, indent style is `space` and `tab` mixed in the project. _Good_ Define and maintain consistent code style in your codebase with the use of an `.editorconfig` file ``` root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true # C# files [*.cs] indent_size = 4 # New line preferences csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_within_query_expression_clauses = true # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_switch_labels = true csharp_indent_labels = one_less_than_current # avoid this. unless absolutely necessary dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion # only use var when it's obvious what the variable type is # csharp_style_var_for_built_in_types = false:none # csharp_style_var_when_type_is_apparent = false:none # csharp_style_var_elsewhere = false:suggestion # use language keywords instead of BCL types dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion # name all constant fields using PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case # static fields should have s_ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_style.static_prefix_style.required_prefix = s_ dotnet_naming_style.static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_style.camel_case_underscore_style.required_prefix = _ dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case # Code style defaults dotnet_sort_system_directives_first = true csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false # Expression-level preferences dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion # Expression-bodied members csharp_style_expression_bodied_methods = false:none csharp_style_expression_bodied_constructors = false:none csharp_style_expression_bodied_operators = false:none csharp_style_expression_bodied_properties = true:none csharp_style_expression_bodied_indexers = true:none csharp_style_expression_bodied_accessors = true:none # Pattern matching csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion # Null checking preferences csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = do_not_ignore csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false [*.{asm,inc}] indent_size = 8 # Xml project files [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] indent_size = 2 # Xml config files [*.{props,targets,config,nuspec}] indent_size = 2 [CMakeLists.txt] indent_size = 2 [*.cmd] indent_size = 2 ``` **[⬆ back to top](#table-of-contents)**

Comments

Avoid positional markers They usually just add noise. Let the functions and variable names along with the proper indentation and formatting give the visual structure to your code. _Bad_ ``` //////////////////////////////////////////////////////////////////////////////// // Scope Model Instantiation //////////////////////////////////////////////////////////////////////////////// var model = new[] { menu: 'foo', nav: 'bar' }; //////////////////////////////////////////////////////////////////////////////// // Action setup //////////////////////////////////////////////////////////////////////////////// void Actions() { // ... }; ``` _Bad_ ``` #region Scope Model Instantiation var model = { menu: 'foo', nav: 'bar' }; #endregion #region Action setup void Actions() { // ... }; #endregion ``` _Good_ ``` var model = new[] { menu: 'foo', nav: 'bar' }; void Actions() { // ... }; ``` **[⬆ back to top](#table-of-contents)**
Don't leave commented out code in your codebase Version control exists for a reason. Leave old code in your history. _Bad_ ``` doStuff(); // doOtherStuff(); // doSomeMoreStuff(); // doSoMuchStuff(); ``` _Good_ ``` doStuff(); ``` **[⬆ back to top](#table-of-contents)**
Don't have journal comments Remember, use version control! There's no need for dead code, commented code, and especially journal comments. Use `git log` to get history! _Bad_ ``` /** * 2018-12-20: Removed monads, didn't understand them (RM) * 2017-10-01: Improved using special monads (JP) * 2016-02-03: Removed type-checking (LI) * 2015-03-14: Added combine with type-checking (JR) */ public int Combine(int a,int b) { return a + b; } ``` _Good_ ``` public int Combine(int a,int b) { return a + b; } ``` **[⬆ back to top](#table-of-contents)**
Only comment things that have business logic complexity Comments are an apology, not a requirement. Good code _mostly_ documents itself. _Bad_ ``` public int HashIt(string data) { // The hash var hash = 0; // Length of string var length = data.length; // Loop through every character in data for (var i = 0; i < length; i++) { // Get character code. const char = data.charCodeAt(i); // Make the hash hash = ((hash << 5) - hash) + char; // Convert to 32-bit integer hash &= hash; } } ``` **Better but still Bad:** ``` public int HashIt(string data) { var hash = 0; var length = data.length; for (var i = 0; i < length; i++) { const char = data.charCodeAt(i); hash = ((hash << 5) - hash) + char; // Convert to 32-bit integer hash &= hash; } } ``` If a comment explains WHAT the code is doing, it is probably a useless comment and can be implemented with a well named variable or function. The comment in the previous code could be replaced with a function named `ConvertTo32bitInt` so this comment is still useless. However it would be hard to express by code WHY the developer chose djb2 hash algorithm instead of sha-1 or another hash function. In that case a comment is acceptable. _Good_ ``` public int Hash(string data) { var hash = 0; var length = data.length; for (var i = 0; i < length; i++) { var character = data[i]; // use of djb2 hash algorithm as it has a good compromise // between speed and low collision with a very simple implementation hash = ((hash << 5) - hash) + character; hash = ConvertTo32BitInt(hash); } return hash; } private int ConvertTo32BitInt(int value) { return value & value; } ``` **[⬆ back to top](#table-of-contents)**

Tools

  • codemaid - open source Visual Studio extension to cleanup and simplify our C#, C++, F#, VB, PHP, PowerShell, JSON, XAML, XML, ASP, HTML, CSS, LESS, SCSS, JavaScript and TypeScript coding
  • Sharpen - Visual Studio extension that intelligently introduces new C# features into your existing code base

Cheatsheets