Trong bài viết này, chúng ta sẽ tìm hiểu một số mẹo và thủ thuật C# hữu ích về cách cải thiện chất lượng và hiệu suất code của chúng ta. Sau đó, chúng ta sẽ thấy một số lưu ý khi nói đến việc xử lý ngoại lệ mà chúng ta nên biết.
Null-Check
Trong các dự án, ta thực hiện kiểm tra null khá thường xuyên, và cách phổ biến nhất mà chúng ta thường làm là:
var product = GetProduct();
if (product == null)
{
// ...
}
Cách tổ chức code này có vẻ rất bình thường nhưng bạn có biết vấn đề với cách tiếp cận này là gì không? Toán tử == có thể bị ghi đè và không có đảm bảo chặt chẽ rằng việc so sánh một đối tượng với null sẽ tạo ra kết quả mà chúng ta mong đợi. Thay vào đó, ta nên dùng toán tử mới được giới thiệu trong C# 7, đó là toán tử is.
Đây là cách chúng ta có thể thực hiện kiểm tra null với toán tử is như sau:
var product = GetProduct();
if (product is null)
{
// ...
}
Toán tử is sẽ luôn đánh giá true trước tiên xem đối tượng được chỉ định có phải là không null. Nó cũng là một cách viết kiểm tra null rõ ràng hơn vì nó đọc giống như một câu.
Bắt đầu với C#9, bạn có thể sử dụng phủ định để thực hiện kiểm tra null như sau:
var product = GetProduct();
if (product is not null)
{
// ...
}
Reduce Nesting
Nesting là khi code nằm giữa hai hoặc nhiều hơn hai dấu ngoặc nhọn. Một ví dụ đơn giản về nesting là phần thân của một phương thức, trường hợp này ta sẽ gặp ở bất kỳ ngôn ngữ nào, và ta thấy rằng quá nhiều dấu ngoặc lồng ghép nhau thì code sẽ rất rối.
Tại sao khi code quá nhiều nesting sẽ không tốt? Thông thường, một hoặc hai cấp độ nesting sẽ không có vấn đề gì. Tuy nhiên, chúng ta càng có nhiều cấp độ lồng vào nhau, thì việc đọc code càng trở nên khó khăn hơn và các lỗi sẽ trở nên khó kiểm soát hơn.
Nhưng chúng ta có thể cải thiện một cách dễ dàng. Hôm nay mình sẽ hướng dẫn đến bạn một số tips C# để giảm nesting trong bộ code của bạn.
Chúng ta hãy xem một ví dụ trong đó chúng ta có một câu lệnh if-else. Bên trong câu lệnh if, chúng ta trả về một số giá trị:
Product PurchaseProduct(int id)
{
var product = GetProduct(id);
if (product.Quantity > 0)
{
product.Quantity--;
return product;
}
else
{
SendOutOfStockNotification(product);
return null;
}
}
Product PurchaseProduct(int id)
{
var product = GetProduct(id);
if (product.Quantity > 0)
{
product.Quantity--;
return product;
}
SendOutOfStockNotification(product);
return null;
}
bool IsProductInStock(int id)
{
var product = GetProduct(id);
if (product is not null)
{
if (product.Quantity > 0)
{
return true;
}
}
return false;
}
bool IsProductInStock(int id)
{
var product = GetProduct(id);
if (product is null)
{
return false;
}
if (product.Quantity <= 0)
{
return false;
}
return true;
}
Chúng ta có thể tối ưu hóa điều này hơn nữa bằng cách kết hợp hai câu lệnh if thành một câu lệnh duy nhất với code như sau:
bool IsProductInStock(int id)
{
var product = GetProduct(id);
if (product is null || product.Quantity <= 0)
{
return false;
}
return true;
}
Using Declarations
Các bạn có thể thấy, ta quá quen khi sử dụng quá nhiều nesting khi sử dụng câu lệnh using, ví dụ như:
using (var streamReader = new StreamReader("..."))
{
string content = streamReader.ReadToEnd();
}
using var streamReader = new StreamReader("...");
string content = streamReader.ReadToEnd();
Nhưng khi sử dụng, bạn nên lưu ý rằng câu lệnh using chỉ sử dụng ở block-level scope.
Logical Expression
C# 9 đã giới thiệu các logical patterns mới mà chúng ta có thể sử dụng để cải thiện các biểu thức logic trở nên dễ hiểu dễ đọc hơn và bớt rối hơn.Ví dụ, chúng ta sẽ viết một hàm để kiểm tra xem một ký tự được chỉ định có phải là một chữ cái hay không:
bool IsLetter(char ch) => (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
Với các logical pattern mới như and và or, chúng ta có thể viết lại hàm trước đó như sau:
bool IsLetter(char ch) => ch is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
Loại bỏ if-else khi set giá trị boolean
Chúng ta thường gặp một tình huống trong code khi cần trả về một giá trị bool từ một hàm, ví dụ như sau:
bool IsInStock(Product product)
{
if (product.Quantity > 0)
{
return true;
}
else
{
return false;
}
}
bool IsInStock(Product product)
{
return product.Quantity > 0;
}
bool IsInStock(Product product) => product.Quantity > 0;
Switch Statements
Câu lệnh switch có thể rất hữu ích khi chúng ta muốn đánh giá một số đối tượng và dựa trên các giá trị có thể trả về một kết quả khác.
Ví dụ, ta hãy viết một câu lệnh chuyển đổi để kiểm tra xem ngày hiện tại có phải là ngày cuối tuần hay không:
switch (DateTime.Now.DayOfWeek)
{
case DayOfWeek.Monday:
return "Not Weekend";
case DayOfWeek.Tuesday:
return "Not Weekend";
case DayOfWeek.Wednesday:
return "Not Weekend";
case DayOfWeek.Thursday:
return "Not Weekend";
case DayOfWeek.Friday:
return "Not Weekend";
case DayOfWeek.Saturday:
return "Weekend";
case DayOfWeek.Sunday:
return "Weekend";
default:
throw new ArgumentOutOfRangeException();
}
switch (DateTime.Now.DayOfWeek)
{
case DayOfWeek.Monday:
case DayOfWeek.Tuesday:
case DayOfWeek.Wednesday:
case DayOfWeek.Thursday:
case DayOfWeek.Friday:
return "Not Weekend";
case DayOfWeek.Saturday:
case DayOfWeek.Sunday:
return "Weekend";
default:
throw new ArgumentOutOfRangeException();
}
DateTime.Now.DayOfWeek switch
{
DayOfWeek.Monday => "Not Weekend",
DayOfWeek.Tuesday => "Not Weekend",
DayOfWeek.Wednesday => "Not Weekend",
DayOfWeek.Thursday => "Not Weekend",
DayOfWeek.Friday => "Not Weekend",
DayOfWeek.Saturday => "Weekend",
DayOfWeek.Sunday => "Weekend",
_ => throw new ArgumentOutOfRangeException()
}
DateTime.Now.DayOfWeek switch
{
DayOfWeek.Monday or DayOfWeek.Tuesday or DayOfWeek.Wednesday or DayOfWeek.Thursday or DayOfWeek.Friday => "Not Weekend",
DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend",
_ => throw new ArgumentOutOfRangeException()
}
DateTime.Now.DayOfWeek switch { not (DayOfWeek.Saturday or DayOfWeek.Sunday) => "Not Weekend", DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend", _ => throw new ArgumentOutOfRangeException() }
Filter Exceptions
Giả sử bạn có gặp tình huống khi bạn cần phải xử lý một ngoại lệ. Chúng ta thường gặp một trường hợp như thế này, trong đó chúng ta phải thực hiện các logic xử lý ngoại lệ khác nhau dựa trên một số điều kiện.
Giả sử chúng ta muốn xử lý HttpRequestException theo một cách khi đó StatusCode là 400(Bad Request) hay khi gặp StatusCode là 404 (Not Found). Cách tiếp cận đơn giản sẽ là bắt ngoại lệ và sau đó viết một câu lệnh if để kiểm tra một điều kiện:
try
{
await GetBlogsFromApi();
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.BadRequest)
{
HandleBadRequest(e);
}
else if (e.StatusCode == HttpStatusCode.NotFound)
{
HandleNotFound(e);
}
}
try
{
await GetBlogsFromApi();
}
catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.BadRequest)
{
HandleBadRequest(e);
}
catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.NotFound)
{
HandleNotFound(e);
}
Return Empty Collections
Thông thường, chúng ta có các phương thức trả về một collection. Tuy nhiên, chúng ta nên return lại những gì trong trường hợp các điều kiện tiên quyết không được đáp ứng? Thường thì chúng ta trả về các giá trị null, chẳng hạn như:
IEnumerable<Product> GetProductsByCategory(string category)
{
if (string.IsNullOrWhiteSpace(category))
{
return null;
}
var products = _dbContext.Products.Where(p => p.Category == category).ToList();
return products;
}
IEnumerable<Product> GetProductsByCategory(string category)
{
if (string.IsNullOrWhiteSpace(category))
{
return Enumerable.Empty<Product>();
}
var products = _dbContext.Products.Where(p => p.Category == category).ToList();
return products;
}
Lời kết
Và đây, chúng chính là những mẹo lập trình C# giúp năng cao chất lượng và hiệu suất của bạn. Trong thực tế, còn rất nhiều cách để tối ưu, và mình sẽ tổng hợp và giới thiệu các bạn trong các phần tiếp theo.
Như các bạn có thể thấy, các phiên bản C# về sau này, mỗi bạn cập nhật luôn tạo ra những cách cải thiện và tối ưu hiệu năng đáng kể. Vì thế trong những bài tới mình sẽ giới thiệu các bạn phiên bản C# mới nhất với nhứng hiệu quả vượt trội.
Tham khảo: https://code-maze.com/c-tips-to-improve-code-quality-and-performance
Mong bài viết hữu ích, chúc các bạn thành công!Hieu Ho.