Mùi mã là một triệu chứng chỉ ra rằng có một vấn đề trong thiết kế sẽ có khả năng làm tăng số lượng lỗi: đây không phải là trường hợp đối với các vùng, nhưng các vùng có thể góp phần tạo ra mùi mã, như các phương thức dài.
Từ:
Một mô hình chống (hoặc phản mẫu) là một mô hình được sử dụng trong các hoạt động xã hội hoặc kinh doanh hoặc công nghệ phần mềm có thể được sử dụng phổ biến nhưng không hiệu quả và / hoặc phản tác dụng trong thực tế
khu vực là chống mẫu. Họ yêu cầu nhiều công việc hơn mà không làm tăng chất lượng hoặc khả năng đọc mã, điều này không làm giảm số lượng lỗi và điều này chỉ có thể làm cho mã phức tạp hơn để tái cấu trúc.
Không sử dụng các vùng bên trong các phương thức; thay thế
Phương pháp phải ngắn gọn . Nếu chỉ có mười dòng trong một phương thức, có lẽ bạn sẽ không sử dụng các vùng để ẩn năm trong số chúng khi làm việc trên năm dòng khác.
Ngoài ra, mỗi phương pháp phải làm một và một điều duy nhất . Các khu vực, mặt khác, được dự định để phân tách những thứ khác nhau . Nếu phương thức của bạn thực hiện A, thì B, sẽ hợp lý khi tạo hai vùng, nhưng đây là một cách tiếp cận sai; thay vào đó, bạn nên cấu trúc lại phương thức thành hai phương thức riêng biệt.
Sử dụng các vùng trong trường hợp này cũng có thể làm cho việc tái cấu trúc trở nên khó khăn hơn. Hãy tưởng tượng bạn có:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
if (!verification)
{
throw new DataCorruptedException();
}
Do(data);
DoSomethingElse(data);
#endregion
#region Audit
var auditEngine = InitializeAuditEngine();
auditEngine.Submit(data);
#endregion
}
Thu gọn khu vực đầu tiên để tập trung vào khu vực thứ hai không chỉ có rủi ro: chúng ta có thể dễ dàng quên đi ngoại lệ ngăn chặn dòng chảy (có thể có một điều khoản bảo vệ với một return
thay thế, thậm chí còn khó phát hiện hơn), nhưng cũng có vấn đề nếu mã được tái cấu trúc theo cách này:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
var info = DoSomethingElse(data);
if (verification)
{
Do(data);
}
#endregion
#region Audit
var auditEngine = InitializeAuditEngine(info);
auditEngine.Submit(
verification ? new AcceptedDataAudit(data) : new CorruptedDataAudit(data));
#endregion
}
Bây giờ, các khu vực không có ý nghĩa và bạn không thể đọc và hiểu mã trong khu vực thứ hai mà không cần nhìn vào mã trong khu vực đầu tiên.
Một trường hợp khác đôi khi tôi thấy là trường hợp này:
public void DoSomething(string a, int b)
{
#region Validation of arguments
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
#endregion
#region Do real work
...
#endregion
}
Thật hấp dẫn khi sử dụng các vùng khi xác thực đối số bắt đầu trải rộng hàng chục LỘC, nhưng có một cách tốt hơn để giải quyết vấn đề này: đó là cách được sử dụng bởi mã nguồn .NET Framework:
public void DoSomething(string a, int b)
{
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
InternalDoSomething(a, b);
}
private void InternalDoSomething(string a, int b)
{
...
}
Không sử dụng các phương thức bên ngoài để nhóm
Một số người sử dụng chúng để nhóm các trường, thuộc tính, v.v ... Cách tiếp cận này sai: nếu mã của bạn tuân thủ StyleCop, thì các trường, thuộc tính, phương thức riêng, hàm tạo, v.v ... đã được nhóm lại với nhau và dễ tìm. Nếu không, đã đến lúc bắt đầu suy nghĩ về việc áp dụng các quy tắc đảm bảo tính đồng nhất trên cơ sở mã của bạn.
Những người khác sử dụng các khu vực để ẩn rất nhiều thực thể tương tự . Ví dụ: khi bạn có một lớp với hàng trăm trường (tạo ra ít nhất 500 dòng mã nếu bạn đếm các bình luận và khoảng trắng), bạn có thể bị cám dỗ đặt các trường đó vào một vùng, thu gọn nó và quên chúng đi. Một lần nữa, bạn đang làm sai: với rất nhiều trường trong một lớp, bạn nên nghĩ tốt hơn về việc sử dụng tính kế thừa hoặc cắt đối tượng thành nhiều đối tượng.
Cuối cùng, một số người bị cám dỗ sử dụng các vùng để nhóm các thứ liên quan lại với nhau : một sự kiện với đại biểu của nó hoặc một phương thức liên quan đến IO với các phương thức khác liên quan đến IO, v.v. Trong trường hợp đầu tiên, nó trở thành một mớ hỗn độn rất khó duy trì , đọc và hiểu. Trong trường hợp thứ hai, thiết kế tốt hơn có lẽ sẽ tạo ra một vài lớp.
Có sử dụng tốt cho các khu vực?
Không. Có một cách sử dụng kế thừa: mã được tạo. Tuy nhiên, các công cụ tạo mã chỉ phải sử dụng các lớp một phần thay thế. Nếu C # có các vùng hỗ trợ, thì chủ yếu là vì sử dụng di sản này và vì hiện tại có quá nhiều người đã sử dụng các vùng trong mã của họ, nên không thể xóa chúng mà không phá vỡ các cơ sở mã hiện có.
Hãy suy nghĩ về nó như là về goto
. Thực tế là ngôn ngữ hoặc IDE hỗ trợ một tính năng không có nghĩa là nó nên được sử dụng hàng ngày. Quy tắc StyleCop SA1124 rất rõ ràng: bạn không nên sử dụng các vùng. Không bao giờ.
Ví dụ
Tôi hiện đang thực hiện đánh giá mã về mã đồng nghiệp của mình. Các cơ sở mã chứa rất nhiều vùng và thực sự là một ví dụ hoàn hảo về cả cách không sử dụng các vùng và tại sao các vùng dẫn đến mã xấu. Dưới đây là một số ví dụ:
Quái vật 4.000 LỘC:
Gần đây tôi đã đọc ở đâu đó trên Lập trình viên. Khi một tệp chứa quá nhiều using
s (sau khi thực hiện lệnh "Xóa sử dụng không sử dụng"), đó là một dấu hiệu tốt cho thấy lớp bên trong tệp này đang hoạt động quá nhiều. Điều tương tự áp dụng cho kích thước của chính tập tin.
Trong khi xem xét mã, tôi đã tìm thấy một tệp 4.000 LỘC. Dường như tác giả của mã này chỉ đơn giản là sao chép cùng một phương thức 15 dòng hàng trăm lần, thay đổi một chút tên của các biến và phương thức được gọi. Một regex đơn giản được phép cắt tập tin từ 4.000 LỘC thành 500 LỘC, chỉ bằng cách thêm một vài khái quát; Tôi khá chắc chắn rằng với một phép tái cấu trúc thông minh hơn, lớp này có thể được giảm xuống còn vài chục dòng.
Bằng cách sử dụng các vùng, tác giả khuyến khích bản thân bỏ qua thực tế là mã không thể duy trì và được viết kém, và sao chép mã nhiều thay vì cấu trúc lại mã.
Khu vực làm một khu phố, khu vực làm khu vực này
Một ví dụ tuyệt vời khác là một phương thức khởi tạo quái vật chỉ đơn giản là thực hiện nhiệm vụ 1, sau đó là nhiệm vụ 2, sau đó là nhiệm vụ 3, v.v. Có năm hoặc sáu nhiệm vụ hoàn toàn độc lập, mỗi nhiệm vụ khởi tạo một thứ gì đó trong một lớp container. Tất cả các nhiệm vụ đó được nhóm thành một phương thức và được nhóm thành các khu vực.
Điều này có một lợi thế:
- Phương pháp này khá rõ ràng để hiểu bằng cách nhìn vào tên khu vực. Điều này đang được nói, cùng một phương pháp một khi được tái cấu trúc sẽ rõ ràng như ban đầu.
Các vấn đề, mặt khác, là nhiều:
Không rõ ràng nếu có sự phụ thuộc giữa các khu vực. Hy vọng rằng, không có việc sử dụng lại các biến; nếu không, việc bảo trì có thể là một cơn ác mộng hơn nữa.
Phương pháp này gần như không thể kiểm tra. Làm thế nào bạn có thể dễ dàng biết nếu phương pháp thực hiện hai mươi điều một lần có đúng không?
Vùng trường, vùng thuộc tính, vùng xây dựng:
Mã được xem xét cũng chứa rất nhiều vùng nhóm tất cả các trường lại với nhau, tất cả các thuộc tính cùng nhau, v.v ... Điều này có một vấn đề rõ ràng: tăng trưởng mã nguồn.
Khi bạn mở một tệp và thấy một danh sách lớn các trường, bạn sẽ có xu hướng cấu trúc lại lớp trước, sau đó làm việc với mã. Với các vùng, bạn có thói quen thu gọn đồ đạc và quên nó đi.
Một vấn đề khác là nếu bạn làm điều đó ở mọi nơi, bạn sẽ thấy mình tạo ra các khu vực một khối, điều này không có ý nghĩa gì. Đây thực sự là trường hợp trong mã tôi đã xem xét, nơi có rất nhiều hàm #region Constructor
chứa một hàm tạo.
Cuối cùng, các trường, thuộc tính, hàm tạo, v.v. đã có sẵn theo thứ tự . Nếu chúng là và chúng khớp với các quy ước (hằng số bắt đầu bằng chữ in hoa, v.v.), thì rõ ràng nơi loại phần tử dừng và bắt đầu khác, vì vậy bạn không cần phải tạo vùng rõ ràng cho điều đó.