0

Bí quyết viết code .NET sạch và hiệu quả hơn

Trong bài đăng này, tôi muốn chia sẻ với bạn một số mẹo đơn giản và dễ thực hiện mà tôi áp dụng hàng ngày để viết code tốt hơn nhiều. Hãy cùng khám phá xem chúng là gì nhé!

Mẹo 1: Đặt tên

Khi đặt tên cho các biến, phương thức và lớp, hãy đảm bảo bạn đặt tên rõ ràng và có ý nghĩa. Quy ước đặt tên tốt sẽ tăng khả năng đọc mã và giúp người khác (và bản thân bạn trong tương lai) hiểu được ý định của mã mà không cần ngữ cảnh bổ sung.

Hãy cùng xem xét một số ví dụ về cách đặt tên sai gây nhầm lẫn cho người đọc mã:

public class AccountService
{
    private string _str;
    private DateTime _dateTime;
    private decimal _iNumber;

    public bool HasNoTime()
    {
        return _dateTime < DateTime.Now;
    }

    public bool RemoveAmount(decimal amount)
    {
        _iNumber -= amount;
        return _iNumber >= 0;
    }
}

Bạn có thực sự hiểu mục đích của lớp này, các phương thức và các trường private của nó không? Đây là một lớp hoạt động với BankAccount, kiểm tra xem tài khoản đã hết hạn chưa và thực hiện việc rút tiền.

Nếu bạn viết code như vậy, bạn sẽ hiểu ý định của nó. Nhưng nếu người khác đọc nó thì sao? Hoặc bạn sẽ đọc lại code này vài tuần sau thì sao?

Hãy viết lại code này để làm cho nó rõ ràng hơn:

public class BankAccount
{
    private string _ownerName;
    private DateTime _expirationDate;
    private decimal _balance;

    public bool IsExpired()
    {
        return _expirationDate < DateTime.Now;
    }

    public bool Wihdraw(decimal amount)
    {
        if (_balance >= amount)
        {
            _balance -= amount;
            return true;
        }

        return false;
    }
}

Bây giờ rõ ràng là chúng ta đang làm việc với BankAccount, tên chủ sở hữu, ngày hết hạn và số dư của nó.

Các phương pháp hay nhất:

  • Lớp: Sử dụng danh từ mô tả mục đích của đối tượng (ví dụ: UserRepository, EmailService).
  • Phương thức: Sử dụng động từ hoặc cụm động từ (ví dụ: GetUserById, SendEmail).
  • Biến: Chọn tên mô tả phản ánh vai trò của chúng (ví dụ: totalPrice, isActive).

Mẹo 2: Loại bỏ những comment thừa trong code

Thông thường, việc đặt tên kém dẫn đến việc phải comment trong code để giải thích ý định. Đây là một thói quen xấu. Những comment như vậy làm lộn xộn code, làm cho nó kém dễ đọc hơn và có thể trở nên lỗi thời. Code của bạn là nguồn sự thật. Comment nên giải thích TẠI SAO, chứ không phải CÁI GÌ. Comment trong code không nên lặp lại những gì code đã thể hiện. Vì vậy, thay vì viết như thế này:

// Calculate the price for the product
var price = product.Price - Math.Max(product.Price * product.Discount / 100, MaxDiscount);

Tốt hơn là thay thế comment bằng một phương thức thể hiện rõ ràng ý định của code:

var price = GetDiscountedPrice(product);

private decimal GetDiscountedPrice(Product product)
{
    var discountValue = Math.Max(product.Price * product.Discount / 100, MaxDiscount);
    return product.Price - discountValue;
}

Các phương pháp hay nhất:

  • Loại bỏ các comment mô tả code đang làm gì.
  • Loại bỏ các comment lịch sử hoặc code đã được comment - GIT ghi nhớ mọi thứ.
  • Sử dụng comment để giải thích logic phức tạp hoặc lý do đằng sau các quyết định.
  • Sử dụng comment để viết tóm tắt code cho các hợp đồng công khai (lớp, phương thức, mô hình).
  • Đảm bảo comment được cập nhật cùng với các thay đổi của code.

Mẹo 3: Định dạng code với thụt lề và khoảng trắng

Định dạng code đúng cách giúp tăng cường khả năng đọc. Thụt lề và khoảng trắng nhất quán giúp dễ dàng theo dõi cấu trúc của code hơn. Đoạn code này thực sự rất khó đọc:

public class Calculator{
public int Add(int a,int b){return a+b;}
}

Code được định dạng tốt sẽ dễ đọc hơn nhiều:

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

Các phương pháp hay nhất:

  • Sử dụng thụt lề nhất quán (ví dụ: tab hoặc 4 khoảng trắng).
  • Đặt dấu ngoặc nhọn trên các dòng mới một cách nhất quán.
  • Thêm khoảng trắng xung quanh các toán tử và sau dấu phẩy.
  • Sử dụng các dòng trống để phân tách các khối logic của code.
  • Sử dụng các dòng trống để phân tách các trường public khỏi các trường private trong các lớp của bạn.

Mẹo 4: Giảm thiểu việc lồng ghép

Code lồng ghép sâu rất khó đọc và bảo trì:

if (user is not null)
{
    if (user.IsActive)
    {
        if (user.HasPermission)
        {
            foreach (var preference in user.Preferences)
            {
                // Perform action
            }
        }
    }
}

Khuyến nghị là cố gắng không sử dụng quá 2 mức lồng ghép. Việc giảm thiểu lồng ghép giúp cải thiện khả năng đọc code:

if (user is null || !user.IsActive || !user.HasPermission)
{
    return;
}

foreach (var preference in user.Preferences)
{
    // Perform action
}

Các phương pháp hay nhất:

  • Sử dụng mệnh đề bảo vệ để xử lý các trường hợp đặc biệt ngay từ đầu.
  • Tránh lồng ghép bằng cách trả về sớm.
  • Cân nhắc việc tái cấu trúc logic lồng ghép phức tạp thành các phương thức riêng biệt.

Mẹo 5: Trả về sớm

Khi các điều kiện không được đáp ứng, hãy trả về sớm từ phương thức và ngăn chặn việc thực thi code không cần thiết. Như chúng ta đã thấy trong mẹo trước, việc trả về sớm từ phương thức làm giảm việc lồng ghép và kết quả là nó cải thiện khả năng đọc code.

Thay vì đoạn code này:

if (user is not null)
{
    if (user.IsActive)
    {
        if (user.HasPermission)
        {
            foreach (var preference in user.Preferences)
            {
                // Perform action
            }
        }
    }
}

Bạn có thể sử dụng nguyên tắc trả về sớm:

if (user is null)
{
    return;
}

if (!user.IsActive)
{
    return;
}

if (!user.HasPermission)
{
    return;
}

foreach (var preference in user.Preferences)
{
    // Perform action
}

Điều này cho phép đọc code từ trên xuống dưới từng dòng mà không cần phải cuộn lên xuống để xem toàn bộ ngữ cảnh.

Bạn cũng có thể hợp nhất một số câu lệnh if nếu chúng thuộc về nhau:

if (user is null || !user.IsActive || !user.HasPermission)
{
    return;
}

Bạn có thể cải thiện khả năng đọc của đoạn code này hơn nữa bằng cách trích xuất một câu lệnh if phức tạp thành một phương thức private riêng biệt:

if (!IsUserAllowedToUpdatePreferences(user))
{
    return;
}

foreach (var preference in user.Preferences)
{
    // Perform action
}

private bool IsUserAllowedToUpdatePreferences(User user)
{
    return user is not null && user.IsActive && user.HasPermission;
}

Các phương pháp hay nhất:

  • Kiểm tra các điều kiện không hợp lệ ở đầu phương thức.
  • Sử dụng các câu lệnh return, continue hoặc break để thoát sớm.
  • Cải thiện luồng code và khả năng đọc.

Mẹo 6: Loại bỏ từ khóa Else

Từ khóa Else trong hầu hết các trường hợp làm giảm khả năng đọc code. Hãy cùng xem xét ví dụ code sau:

var shipmentAlreadyExists = await dbContext.Shipments
    .AnyAsync(x => x.OrderId == request.OrderId, cancellationToken);

if (shipmentAlreadyExists)
{
    logger.LogInformation("Shipment for order '{OrderId}' is already created", request.OrderId);
}
else
{
    var shipment = request.MapToShipment(shipmentNumber);
    await dbContext.AddAsync(shipment, cancellationToken);
    await dbContext.SaveChangesAsync(cancellationToken);
}

return shipmentAlreadyExists ? Results.Conflict(...) : Results.Ok(response);

Thông thường, khi đọc code trong câu lệnh else, bạn cần phải cuộn lên để xem câu lệnh if tương ứng. Đây là một ví dụ nhỏ, nhưng hãy tưởng tượng một cơ sở code lớn hơn nhiều. Sau khi bạn thêm một lệnh return sớm, khối else trở nên không cần thiết:

var shipmentAlreadyExists = await dbContext.Shipments
    .AnyAsync(x => x.OrderId == request.OrderId, cancellationToken);

if (shipmentAlreadyExists)
{
    logger.LogInformation("Shipment for order '{OrderId}' is already created", request.OrderId);
    return Results.Conflict(...);
}

var shipment = request.MapToShipment(shipmentNumber);
await dbContext.AddAsync(shipment, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);

return Results.Ok(response);

Đoạn code này dễ đọc hơn, vì bạn đọc nó từng dòng mà không cần phải cuộn lên xuống.

Các phương pháp hay nhất:

  • Đơn giản hóa code bằng cách loại bỏ các khối else không cần thiết.
  • Cải thiện luồng tuyến tính của code.

Mẹo 7: Tránh phủ định kép

Khi viết các câu lệnh if, hãy tránh sử dụng phủ định kép vì chúng thực sự gây nhầm lẫn. Ví dụ:

if (!user.IsNotActive)
{
    // User is active
}

Điều này thực sự gây nhầm lẫn. Và cách viết này dễ đọc hơn nhiều:

if (user.IsActive)
{
    // User is active
}

if (!user.IsActive)
{
    // User is NOT active
}

Đặt tên cho tất cả các biến, thuộc tính và phương thức kiểu boolean của bạn từ phía tích cực, trả lời điều gì đang xảy ra hoặc điều gì đã xảy ra thay vì điều gì không xảy ra.

Ví dụ, đặt tên không tốt:

  • user.IsNotActive
  • user.HasNoDept
  • creditCard.IsNotExpired

Đây là cách đặt tên tốt hơn:

  • user.IsActive
  • user.HasDept
  • creditCard.IsExpired

Mẹo 8: Tránh số ma thuật và chuỗi ma thuật

Số ma thuật và chuỗi ma thuật là các giá trị được mã hóa cứng mà không có lời giải thích. Hãy thay thế chúng bằng các hằng số hoặc enum được đặt tên.

var discountValue = Math.Max(product.Discount, 100);

if (order.Status == 3)
{
    // Process order
}

if (user.MembershipLevel == "Silver")
{
    // Process user order
}
const MaxDiscount = 100;
const SilverMembershipLevel = "Silver";

public enum OrderStatus
{
    Pending = 1,
    Processing = 2,
    Completed = 3
}

Với các hằng số và enum, code trở nên dễ đọc và dễ bảo trì hơn:

var discountValue = Math.Max(product.Discount, MaxDiscount);

if (order.Status is OrderStatus.Completed)
{
    // Process order
}

if (user.MembershipLevel == SilverMembershipLevel)
{
    // Process user order
}

Bất cứ khi nào bạn cần tăng số lượng chiết khấu tối đa, đổi tên cấp độ thành viên hoặc thêm trạng thái đơn hàng mới - bạn có thể thực hiện việc đó ở một nơi duy nhất.

Mẹo 9: Kiểm soát số lượng tham số của phương thức

Các phương thức có nhiều tham số rất khó đọc và sử dụng. Giới hạn số lượng tham số, lý tưởng nhất là ba hoặc ít hơn.

public void CreateUser(string firstName, string lastName, int age, string email, string phoneNumber)
{
    // Create user
}

Tốt hơn nhiều nếu nhóm các tham số phương thức liên quan vào một lớp riêng biệt:

public class CreateUserRequest
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }
}

public void CreateUser(CreateUserRequest request)
{
    // Create user
}

Sẽ dễ dàng hơn nhiều để thêm các thuộc tính mới vào lớp mà không cần thay đổi chữ ký của phương thức.

Mẹo 10: Áp dụng Nguyên tắc Đơn nhiệm (Single Responsibility Principle)

Một lớp hoặc phương thức chỉ nên có một, và chỉ một, lý do để thay đổi. Nguyên tắc này giúp đơn giản hóa việc bảo trì và tăng cường độ rõ ràng của code. Thay vì một lớp và phương thức khổng lồ:

public class ReportService
{
    public void GenerateReport()
    {
        // Fetch data
        // Analyze data
        // Generate report
        // Send report via email
    }
}

Hãy chia nhỏ nó thành nhiều phương thức hoặc lớp:

public class DataFetcher {  }
public class DataAnalyzer {  }
public class ReportGenerator {  }
public class EmailService {  }

Hãy cùng xem xét một ví dụ mà một phương thức đang thực hiện nhiều việc:

public Task Handle(User user)
{
    await SaveUserAndSendEmailAsync(user);
}

private Task SaveUserAndSendEmailAsync(User user)
{
}

private Task ValidateAndUpateUserAsync(User user)
{
}

Ví dụ: ở đây SaveUserAndSendEmailAsync đang thực hiện 2 việc: lưu người dùng vào cơ sở dữ liệu và gửi email sau đó. Tôi cũng thích chia nhỏ các phương thức như vậy thành các phương thức đơn nhiệm riêng biệt:

public Task<Result> Handle(User user)
{
    if (!IsValid(user))
    {
        return Result.BadRequest(...);
    }

    await SaveAsync(user);

    await SendEmailAsync(user);
}

private Task SaveAsync(User user)
{
}

private Task SendEmailAsync(User user)
{
}

private Task<bool> IsValid(User user)
{
}

Bằng cách này, code dễ bảo trì và dễ đọc hơn nhiều.

Mẹo 11: Sử dụng dấu ngoặc nhọn đúng cách

Luôn sử dụng dấu ngoặc nhọn {} với các câu lệnh điều kiện, ngay cả khi chúng chỉ có một dòng. Điều này ngăn ngừa lỗi khi thêm các dòng mới và làm cho code của bạn dễ đọc và dễ dự đoán hơn.

if (isValid)
    ProcessData();

foreach (var preference in user.Preferences)
    AddToReport(preference);
SaveReport();

Đoạn code này khó đọc, phải không? Thêm dấu ngoặc nhọn và xuống dòng, và nó sẽ dễ đọc hơn nhiều:

if (isValid)
{
    ProcessData();
}

foreach (var preference in user.Preferences)
{
    AddToReport(preference);
}

SaveReport();

Các phương pháp hay nhất:

  • Việc sử dụng dấu ngoặc nhọn nhất quán giúp cải thiện khả năng đọc.
  • Giảm nguy cơ lỗi do thực thi code ngoài ý muốn.

Mẹo 12: Không trả về Null cho các tập hợp (Collections)

Việc trả về các tập hợp null có thể dẫn đến NullReferenceException hoặc quá nhiều lần kiểm tra if.

public List<Item>? GetOrderItems()
{
    if (noItemsFound)
    {
        return null;
    }

    return items;
}

public void ProcessOrder(...)
{
    var items = GetOrderItems();

    // NullReferenceException is thrown here
    var count = items.Count;

    foreach (var item in items)
    {
        // Process items
    }
}

public void ProcessOrder(...)
{
    var items = GetOrderItems();

    if (items is null)
    {
        return;
    }

    var count = items.Count;

    foreach (var item in items)
    {
        // Process items
    }
}

Thay vào đó, hãy trả về một tập hợp rỗng.

public List<Item> GetOrderItems()
{
    if (noItemsFound)
    {
        return new List<Item>();
    }

    return items;
}

public void ProcessOrder(...)
{
    var items = GetOrderItems();

    // No need for any additional checks
    var count = items.Count;

    foreach (var item in items)
    {
        // Process items
    }
}

Bằng cách trả về một tập hợp rỗng, bạn làm cho code của mình an toàn hơn và dễ đọc hơn.

Trong C# 12, bạn có thể đơn giản hóa việc trả về một tập hợp rỗng thành các biểu thức tập hợp bằng cách sử dụng cú pháp []:

public List<Item> GetOrderItems()
{
    if (noItemsFound)
    {
        return [];
    }

    return items;
}

Kết luận

Đây là những mẹo nhỏ có thể giúp code của bạn tốt hơn nhiều. Cảm ơn các bạn đã theo dõi!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí