C# Best Practices
Định nghĩa (Definition)
Thực hành tốt nhất (Best Practices) C# là các hướng dẫn đã được chứng minh để viết mã dễ đọc, dễ bảo trì, hiệu suất cao và đáng tin cậy. Chúng bao gồm quy ước đặt tên, cấu trúc mã, xử lý ngoại lệ, thao tác chuỗi, tối ưu hiệu suất và các mẫu cụ thể cho ASP.NET Core.
Quy ước Đặt tên (Naming Conventions)
Đặt tên nhất quán làm cho mã tự tài liệu hóa và dễ điều hướng hơn cho các nhóm.
| Thành phần (Element) | Quy ước (Convention) | Ví dụ |
|---|---|---|
| Lớp (Class), struct, record | PascalCase | OrderService, CustomerRecord |
| Giao diện (Interface) | PascalCase với tiền tố I | ILogger, IRepository<T> |
| Phương thức (Method) | PascalCase | CalculateTotal(), GetById() |
| Thuộc tính (Property) | PascalCase | FirstName, OrderDate |
| Trường hằng số (Constant Field) | PascalCase | MaxRetryCount, DefaultTimeout |
| Trường private | camelCase với tiền tố _ | _logger, _connectionString |
| Tham số phương thức (Method Parameter) | camelCase | orderId, customerName |
| Biến cục bộ (Local Variable) | camelCase | totalCount, isValid |
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private const int MaxRetryCount = 3;
public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public decimal CalculateTotal(Order order)
{
var subtotal = order.Items.Sum(i => i.Price * i.Quantity);
return subtotal;
}
}
Một định danh được đặt tên tốt loại bỏ nhu cầu bình luận. Tránh các từ viết tắt khó hiểu — CalculateArea luôn tốt hơn Foo.
Cấu trúc Mã (Code Structure)
Khai báo Trường ở Trên cùng (Declare Fields at the Top)
Nhóm tất cả trường và hằng số ở đầu lớp. Điều này giúp trạng thái tổng thể của lớp hiển thị ngay lập tức:
public class Car
{
// Fields and constants at the top
private int _speed;
private readonly IEngine _engine;
private const int MaxSpeed = 250;
// Properties
public string Model { get; set; }
// Methods
public void Accelerate(int delta) { /* ... */ }
}
Trách nhiệm Đơn cho Phương thức (Single Responsibility for Methods)
Mỗi phương thức nên làm tốt một việc. Các phương thức lớn kết hợp nhiều trách nhiệm khó kiểm thử, gỡ lỗi và tái sử dụng:
// Bad — mixing concerns
public void ProcessOrder(Order order)
{
ValidateOrder(order);
CalculateTotal(order);
UpdateInventory(order);
SendConfirmation(order);
}
// Good — each method has one responsibility
public void ProcessOrder(Order order)
{
ValidateOrder(order);
var total = CalculateTotal(order);
UpdateInventory(order);
SendConfirmation(order);
}
Sử dụng Dấu ngoặc nhọn Nhất quán (Use Curly Braces Consistently)
Ngay cả cho câu lệnh if một dòng, luôn sử dụng dấu ngoặc nhọn. Nó ngăn lỗi khi thêm dòng sau này:
// Good
if (condition)
{
var userId = GetUserId();
}
// Risky — easy to break when adding more lines
if (condition)
var userId = GetUserId();
Sử dụng Bộ khởi tạo Đối tượng (Use Object Initializers)
Bộ khởi tạo đối tượng giảm lặp lại và cải thiện khả năng đọc:
// Verbose
var person = new Person();
person.FirstName = "John";
person.LastName = "Doe";
person.Age = 30;
// Concise
var person = new Person
{
FirstName = "John",
LastName = "Doe",
Age = 30
};
Xử lý Ngoại lệ (Exception Handling)
Bắt Ngoại lệ Cụ thể (Catch Specific Exceptions)
Tránh bắt kiểu Exception cơ sở. Chỉ bắt các ngoại lệ bạn có thể xử lý một cách có ý nghĩa:
// Bad — masks all errors
try
{
var result = 10 / divisor;
}
catch (Exception ex)
{
Log.Error($"Something went wrong: {ex.Message}");
}
// Good — targeted handling
try
{
var result = 10 / divisor;
}
catch (DivideByZeroException ex)
{
_logger.LogWarning(ex, "Attempted division by zero");
return 0;
}
Tránh Số và Chuỗi Ma thuật (Avoid Magic Numbers and Strings)
Thay thế giá trị được mã hóa cứng bằng hằng số có tên hoặc enum:
// Bad — what does "3" mean?
if (status == 3) { /* ... */ }
// Good — self-documenting
const int ActiveStatus = 3;
if (status == ActiveStatus) { /* ... */ }
// Better — use enums for discrete values
public enum OrderStatus
{
Pending = 0,
Processing = 1,
Shipped = 2,
Delivered = 3
}
if (order.Status == OrderStatus.Delivered) { /* ... */ }
Sử dụng Mệnh đề Bảo vệ (Use Guard Clauses)
Thất bại nhanh ở đầu phương thức để giữ cho logic chính sạch:
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
ArgumentException.ThrowIfNullOrEmpty(order.Id);
if (order.Items.Count == 0)
throw new ArgumentException("Order must contain items.");
// Main logic starts here — all preconditions validated
FulfillOrder(order);
}
An toàn Null (Null Safety)
Luôn Thực hiện Kiểm tra Null (Always Perform Null Checks)
Sử dụng mẫu is not null hoặc toán tử có điều kiện null để ngăn NullReferenceException:
// Pattern matching check
List<string>? users = GetUsers();
if (users is not null)
{
foreach (var user in users)
{
Console.WriteLine(user);
}
}
// Null-conditional operator for property chains
var city = person?.Address?.City;
Sử dụng string.IsNullOrWhiteSpace cho Xác thực Đầu vào
if (string.IsNullOrWhiteSpace(userEmail))
{
Console.WriteLine("Email address is missing.");
}
Thực hành Tốt nhất với Chuỗi (String Best Practices)
Ưu tiên Nội suy Chuỗi (Prefer String Interpolation)
Nội suy chuỗi dễ đọc hơn nối chuỗi:
// Avoid
var message = "Hello, " + firstName + " " + lastName + "!";
// Prefer
var message = $"Hello, {firstName} {lastName}!";
// With formatting
var formattedPrice = $"Price: {price:C2}";
Sử dụng string.Empty Thay vì ""
Nó rõ ràng hơn và tránh sự mơ hồ:
// Avoid
if (name == "") { /* ... */ }
// Prefer
if (name == string.Empty) { /* ... */ }
So sánh Chuỗi Không phân biệt Chữ hoa/thường (Case-Insensitive String Comparison)
Luôn chuẩn hóa chữ hoa/thường trước khi so sánh với đầu vào người dùng:
if (string.Equals(input, "yes", StringComparison.OrdinalIgnoreCase))
{
// ...
}
Bộ sưu tập (Collections)
Sử dụng Any() Thay vì Count > 0
Any() ngắn mạch và tránh liệt kê toàn bộ bộ sưu tập:
// Bad — enumerates the entire collection
if (tasks.Count > 0) { /* ... */ }
// Good — stops at the first element
if (tasks.Any()) { /* ... */ }
// With predicate
if (tasks.Any(t => t.IsCompleted)) { /* ... */ }
Trả về Bộ sưu tập Lớn theo Trang (Return Large Collections Across Pages)
Không bao giờ tải toàn bộ tập dữ liệu lớn cùng lúc. Sử dụng phân trang để tránh OutOfMemoryException và thời gian phản hồi chậm:
[HttpGet]
public async Task<ActionResult<PagedResult<Order>>> GetOrders(
int page = 1, int pageSize = 20)
{
var orders = await _dbContext.Orders
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return Ok(new PagedResult<Order>(orders, totalCount));
}
Hiệu suất (Performance)
Sử dụng && và || (Toán tử Ngắn mạch - Short-Circuit Operators)
Những toán tử này ngừng đánh giá ngay khi kết quả được xác định:
// Good — second condition is skipped if first is false
if (input.Length > 0 && input.StartsWith("prefix")) { /* ... */ }
if (role == "admin" || role == "supervisor") { /* ... */ }
Sử dụng var Có phân biệt (Use var Judiciously)
Sử dụng var khi kiểu rõ ràng từ vế phải của phép gán. Tránh khi kiểu không rõ ràng:
// Good — type is obvious
var name = "John Doe";
var orders = new List<Order>();
// Avoid — what does GetSomething return?
var x = GetSomething();
// Better — explicit type when unclear
Discount discount = CalculateDiscount(order);
Sử dụng using cho Tài nguyên Có thể Giải phóng (Use using for Disposable Resources)
Đảm bảo dọn dẹp đúng cách các tài nguyên như luồng tệp và kết nối cơ sở dữ liệu:
// Modern using declaration (C# 8+)
using var stream = new FileStream("data.txt", FileMode.Open);
// Automatically disposed when leaving scope
// Traditional using block
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
// Automatically disposed when block exits
}