Động lực cho Clean Code

Mar 13 2023
Bất kể quy mô của dự án phần mềm là gì, chúng ta cần là một phần của bất kỳ nhóm nào, dù lớn hay nhỏ. Do đó, điều rất quan trọng là: mã do chúng tôi viết phải được các nhà phát triển khác đọc và phát triển được.

Bất kể quy mô của dự án phần mềm là gì, chúng ta cần là một phần của bất kỳ nhóm nào, dù lớn hay nhỏ. Do đó, điều rất quan trọng là: mã do chúng tôi viết phải được các nhà phát triển khác đọc và phát triển được.

Nghệ thuật lập trình của Geek & Poke được cấp phép theo CC BY 3.0

“Ông lớn” Dave Thomas , (người sáng lập OTI, cha đỡ đầu của chiến lược Eclipse) định nghĩa mã sạch bằng các câu sau:

Mã sạch có thể được đọc và được cải tiến bởi nhà phát triển không phải là tác giả ban đầu của nó . Nó có các bài kiểm tra đơn vị và chấp nhận. Nó có tên có ý nghĩa. Nó cung cấp một cách chứ không phải nhiều cách để làm một việc. Nó có các phụ thuộc tối thiểu , được xác định rõ ràng và cung cấp một API rõ ràng và tối thiểu.

Mặt khác, ngay cả khi chúng tôi sẽ duy trì dự án một mình, chúng tôi có thể cần thực hiện các phát triển bổ sung cho bất kỳ mô-đun nào của dự án trong nhiều tháng hoặc nhiều năm sau đó. Điều cần thiết là có thể hiểu, trong nháy mắt, mã được viết từ nhiều năm trước đáp ứng nhiệm vụ gì, chúng phục vụ cho mô-đun nào của dự án, để bảo vệ sức khỏe tinh thần của nhà phát triển và để đảm bảo rằng các mô-đun khác không bị ảnh hưởng bởi sự phát triển mới.

Bất kỳ kẻ ngốc nào cũng có thể viết mã mà máy tính có thể hiểu được. lập trình viên giỏi viết mã mà con người có thể hiểu được. Martin Fowler

Tại sao mã sạch là cần thiết?

Không khó để dự đoán điều gì sẽ xảy ra khi phát triển với mã không sạch. Tôi muốn nói về một số vấn đề mà chúng ta sẽ phải đối mặt.

Nếu mã không sạch sẽ;

  • Tạo nợ kỹ thuật (Nếu bạn muốn xem: “https://en.wikipedia.org/wiki/Technical_debt”). Chúng ta có thể nói về việc quản lý các khoản nợ kỹ thuật dưới một tiêu đề khác, nhưng có thể có nhiều lý do dẫn đến nợ kỹ thuật. Bộ phận đánh lửa đầu tiên của bấc này cũng không phải là mã sạch.
  • Chúng tôi gặp phải các khối mã trở nên không thể duy trì được, theo thời gian, dự án cũng có thể trở nên không thể duy trì được.
  • Sẽ tốn kém cho sự phát triển. Chi phí phát triển sẽ cao hơn mức cần thiết. Nếu nhóm nhà phát triển không thể thấy trước chi phí, thì đó là dấu hiệu rõ ràng rằng chúng tôi có một dự án không được phát triển rõ ràng.
  • Sự nhầm lẫn lớn dần. Với mỗi tính năng được thêm vào, mỗi khối mã được phát triển, sự nhầm lẫn sẽ tăng lên. Điều này có nghĩa là dự án chết theo thời gian. Bạn có thể đã nghe rất nhiều: “Hãy viết lại điều này.”

Trước hết, chúng tôi cần nhận thức của tất cả các thành viên trong nhóm và tổ chức sở hữu dự án. Vì đạt được clean code sẽ tốn thời gian. (tiền có giá tự nhiên, thời gian = tiền)

Chúng ta cần có động lực để ghép nối lập trình và tái cấu trúc với các thành viên khác trong nhóm.

Chúng tôi cần trao đổi thêm với các nhóm kỹ thuật và thương mại để đảm bảo rằng chúng tôi có thể giải thích và thảo luận về mô-đun hoặc khối mã một cách đơn giản.

Viết mã sạch không phải là một kỹ năng có thể đạt được trong một sớm một chiều. Đó là một thói quen cần được phát triển bằng cách ghi nhớ những nguyên tắc này và áp dụng chúng bất cứ khi nào bạn viết mã. Yiğit Kemal Erinç

Khung mã sạch

Đơn giản, tập trung, đầy đủ, chính xác.

Để chúng tôi chấp nhận rằng các nguyên tắc mã sạch được tuân theo;

  • Mã phải đơn giản và dễ hiểu.
  • Các chức năng, lớp và biến nên được tập trung . Họ nên chịu trách nhiệm duy nhất và không có tác dụng phụ.
  • Nó phải có khả năng đáp ứng mọi nhu cầu , nên bao gồm các trường hợp có thể xảy ra, nên cho phép phát triển và ngăn chặn các tình huống nên tránh.
  • Chúng ta phải đảm bảo nó hoạt động chính xác . Điều này chỉ có thể với các bài kiểm tra đơn vị và chấp nhận. Kiểm tra tích hợp được yêu cầu để kiểm tra tích hợp.

Tên của một biến, hàm hoặc lớp phải trả lời tất cả các câu hỏi lớn. Nó sẽ cho bạn biết tại sao nó tồn tại, nó làm gì và nó được sử dụng như thế nào. Nếu một tên yêu cầu một bình luận, thì tên đó không tiết lộ ý định của nó. Robert C. Martin

public List<string> GetTags(int[] a)
{
  var list = new List<string>();
  
  for (var b = 0; b < theList.Count; b++)
  {
    if (theList[b].CityCodes.Intersect(a).Any())
    {
      list.Add(theList[b].TagName);
     }
   }

   return list;
}

Nếu không có nhận xét hoặc tìm hiểu sâu về mã, tôi không thể hiểu phương pháp và tham số này phục vụ mục đích gì. Tại thời điểm này, nếu tôi chỉnh sửa cách đặt tên với điều kiện nó hợp lý trong ngữ cảnh mà tôi đang phát triển, tôi sẽ tiếp cận mã sạch hơn. Hãy làm nó.

public List<string> GetTagNamesThatMatchedCityCodes(int[] cityCodes)
{
  var tagNamesThatMatchedCityCodes = new List<string>();
  foreach (var tag in locationTags)
  {
    if (tag.CityCodes.Intersect(cityCodes).Any())
    {
      tagNamesThatMatchedCityCodes.Add(tag.TagName);
     }
   }

    return tagNamesThatMatchedCityCodes;
 }

Nếu chúng tôi muốn tối ưu hóa hiệu suất, đặc biệt là cho các tập dữ liệu lớn, chúng tôi cũng có thể ưu tiên sử dụng "lợi nhuận thu được", nhưng điều này có thể làm giảm khả năng đọc mã. Trong những tình huống khó khăn như vậy, chúng tôi có thể cần phải quyết định xem khả năng đọc hoặc hiệu suất của chương trình có quan trọng đối với chúng tôi hay không. Ví dụ: nếu tôi đang phát triển thuật toán nén, tôi muốn nó hoạt động hiệu quả. Nhưng nếu tôi đang phát triển một chương trình dành cho doanh nghiệp và chương trình đó sẽ không tạo ra quá nhiều tải cho hệ thống, tôi có thể chấp nhận giảm một số hiệu năng để có mã có thể đọc và bảo trì được.

public IEnumerable<string> GetTagNamesThatMatchedCityCodes(int[] cityCodes)
{
  foreach (var tag in tags)
  {
    if (tag.CityCodes.Intersect(cityCodes).Any())
    {
      yield return tag.TagName;
     }
   }
}

Nhà phát triển, nhà phân tích kinh doanh, chủ sở hữu sản phẩm, v.v. Tên phải rõ ràng và dễ hiểu đối với mọi thành viên trong nhóm. Eric Evans định nghĩa khái niệm “ ngôn ngữ phổ biến ” trong cuốn sách “Thiết kế theo hướng miền: Giải quyết sự phức tạp trong trái tim của phần mềm”, đây là một cuốn sách khác mà tôi khuyên bạn nên đọc nếu có cơ hội.

Ngôn ngữ phổ biến là một trong những yếu tố chính để xử lý sự phức tạp. Nhận thức này cũng sẽ ngăn ngừa tai nạn lao động.

Chức năng ngắn và tập trung

Chúng ta nên phát triển các chức năng càng ngắn gọn và tập trung càng tốt. Họ chỉ nên có một nhiệm vụ (trách nhiệm duy nhất) và họ nên thực hiện nhiệm vụ này với khả năng tốt nhất của mình. Nhận thức này cũng sẽ ngăn chặn sự trùng lặp mã.

Các phương thức có nhiều dòng mã làm tăng độ phức tạp. Đặc biệt nếu chúng ta có câu lệnh if hoặc khối mã có vòng lặp, chúng ta có thể rút ngắn khối mã bằng cách sử dụng các phương thức mới để giữ cho mã dễ hiểu hơn. Đây là một chủ đề quan trọng trong các buổi đánh giá mã để tránh tăng độ phức tạp.

private void CreatePluralDependentMessageParts(int orderCount) 
{
  if (orderCount == 0) {
    FormatMessageForCustomerWithNoOrder();
   } else if (orderCount == 1) {
     FormatMessageForCustomerWithSingleOrder();
   } else {
     FormatMessageForCustomerWithManyOrders(orderCount);
   }
 }

Các đối tượng truyền dữ liệu là một cách rõ ràng để truyền dữ liệu giữa các lớp hoặc chức năng của ứng dụng.

Tôi có thể nói về một tình huống có thể sẽ xảy ra với bạn nếu bạn không sử dụng các đối tượng truyền dữ liệu. Như trong ví dụ dưới đây, cùng một loại tham số phương thức quay trở lại, nhà phát triển cố gắng đạt được thời hạn có thể nhầm lẫn giữa chuỗi email và chuỗi tham số appKey. Sẽ không lâu trước khi bạn nhận ra rằng dịch vụ thông báo qua thư của bạn đã bị lỗi.

public Customer(string name, string surname, string email, 
string appKey, int age, bool isPremium)
{
      Name = name;
      Surname = surname;
      Email = email;
      AppKey = appKey;
      Age = age;
      IsPremium = isPremium;
}

public Customer(CustomerDto customerDto)
 {
      Name = customerDto.Name;
      Surname = customerDto.Surname;
      Email = customerDto.Email;
      AppKey = customerDto.AppKey;
      Age = customerDto.Age;
      IsPremium = customerDto.IsPremium;
 }

Chúng ta nên tránh viết bình luận. Thoạt nhìn, chúng ta có thể hiểu khối mã phục vụ cho mục đích gì. Đặt tên đúng và được tập trung là rất quan trọng cho sự rõ ràng.

Nếu mã không rõ ràng, chúng ta nên tái cấu trúc mã hơn là bình luận. Tuy nhiên, có thể đưa ra nhận xét đối với những tình huống cần giải thích.

// Check to see if the order is eligible for shipping
if (order.Payment.IsCompleted && order.Delivery.Slot.IsAvailable)

if (order.IsEligibleForShipping())

Tránh sử dụng số ma thuật trong mã của bạn. Sử dụng các hằng số có thể tìm kiếm giúp bạn tiếp cận mã sạch hơn. Ngoài ra, khi bạn muốn thực hiện các thay đổi trong logic nghiệp vụ của mình, bạn sẽ có thể chỉnh sửa dễ dàng mà không cần phải quét toàn bộ mã.

if (password.Length > 10)
{
   throw new ValidationException($"Must be at least 10 characters");
}
if (password.Length > MAX_PASSWORD_LENGTH)
{
    throw new ValidationException($"Must be at least {MAX_PASSWORD_LENGTH} characters");
}

Đôi khi chúng ta có thể thấy rằng các khối mã được viết để xử lý kết quả xác thực hoặc xử lý lỗi tăng quá nhiều và khiến mã khó đọc. Cần có một cách tiếp cận tiêu chuẩn để xử lý lỗi và xác thực trong toàn bộ ứng dụng. Và chúng tôi phải đảm bảo rằng chúng tôi không làm ô nhiễm cơ sở mã. Để đạt được điều này, chúng tôi có thể muốn đưa ra một ngoại lệ hơn là mang mã lỗi hoặc cờ. Chúng tôi có thể phát triển phần mềm trung gian xử lý lỗi để ghi lại các ngoại lệ và định dạng phản hồi của chúng tôi.

public bool IsValidPassword(string password)
 {
   if (string.IsNullOrEmpty(password))
     throw new ValidationException("Cannot be null or empty");
   if (password.Any(char.IsLetterOrDigit))
     throw new ValidationException("Must contain at least one special character");
   if (password.Length > MAX_PASSWORD_LENGTH)
     throw new ValidationException($"Must be at least {MAX_PASSWORD_LENGTH} characters");

    return true;
  }

Vì sức khỏe tinh thần của bản thân và nhóm của mình, chúng ta nên cố gắng làm cho mã của mình tốt hơn bằng cách tránh những phát triển bất thường. Mã bẩn sẽ gây lãng phí nhiều thời gian hơn trong tương lai và tệ hơn là các vấn đề rất khó giải quyết. Khi bạn tuân thủ các nguyên tắc mã sạch, bạn sẽ nhận ra rằng bạn phát triển mã có chất lượng tốt hơn và kết quả đầu ra của sự phát triển của bạn sẽ mang lại cho bạn nhiều hạnh phúc hơn.

Cảm ơn vì đã dành thời gian cho tôi.

Okan Yüksel, Kỹ sư phần mềm

Người giới thiệu

  • Robert C. Martin, 'Mã sạch: Sổ tay thủ công phần mềm linh hoạt'
  • Akın Kaldıroğlu, Mã sạch
  • Yiğit Kemal Erinç,https://www.freecodecamp.org/news/clean-coding-for-beginners/
  • Quốc vương đầy nắng,https://sunnysultan1640.medium.com/what-is-bad-code-how-to-write-clean-code-a9b7b539ad8