Các phương pháp lặp có làm giảm độ phức tạp chu kỳ và cải thiện khả năng hỗ trợ không?


11

Các phương thức lặp như thường thấy trong các ngôn ngữ hiện đại như C #, JavaScript và (hy vọng) trong Java 8 có làm giảm tác động của độ phức tạp theo chu kỳ đối với tính dễ hiểu và khả năng hỗ trợ của mã không?

Ví dụ: trong C # chúng ta có thể có đoạn mã sau:

List<String> filteredList = new List<String>();

foreach (String s in originalList){
   if (matches(s)){
      filteredList.add(s);
   }
}

Điều này có độ phức tạp chu kỳ đơn giản là 2.

Chúng ta có thể dễ dàng viết lại điều này như sau:

List<String> filteredList = originalList.where(s => matches(s));

Trong đó có độ phức tạp chu kỳ đơn giản là 0.

Điều này thực sự dẫn đến mã hỗ trợ nhiều hơn? Có bất kỳ nghiên cứu thực tế về chủ đề này?


2
Tiêu đề của bạn hỏi về việc giảm độ phức tạp theo chu kỳ, nhưng văn bản của bạn giả định giảm CC và hỏi về khả năng duy trì. Điều này có khả năng là một câu hỏi có giá trị, bạn có thể chỉnh sửa tiêu đề để chính xác hơn?
Kilian Foth

@KilianFoth Điều đó có tốt hơn không?
C. Ross

Tôi muốn liên kết các phương pháp lặp với các phép đo phát triển lặp lại. Tôi nghĩ rằng một thuật ngữ tốt hơn sẽ là chức năng bậc cao. (Và như một bên, các phương thức không có kiểu trả về / void trong khi các hàm trả về một cái gì đó).
Julian Rudolph

Các phương thức @JohannesRudolph "không có kiểu trả về / void trong khi các hàm trả về một cái gì đó" - ngôn ngữ này có đúng không? Tôi chưa bao giờ nghe điều này; trong thực tế, sự khác biệt thực sự duy nhất tôi từng nghe là các phương thức được liên kết với một lớp, các hàm thì không.
Threed

Câu trả lời:


14

Tôi đoán là bạn chỉ cần chia / di chuyển sự phức tạp. Nó giảm vì bạn không tính việc thực hiện .where()trong CC của mình.

CC tổng thể chưa thực sự di chuyển, CC của mã của bạn đã giảm, chỉ vì giờ nó đã được chuyển sang mã của khung.

Tôi muốn nói rằng nó dễ bảo trì hơn. Khi đó là một tính năng của ngôn ngữ, sử dụng nó. Đó không phải là "ồ, tôi hiểu rồi, đó là một mẹo thông minh" mà bạn đang sử dụng, chỉ là một chức năng giảm nội tuyến đơn giản.


11

Tất cả những gì bạn đang làm là làm nổi bật một lỗ hổng về độ phức tạp theo chu kỳ như một số liệu, độ phức tạp của mã thực sự không thay đổi. Bạn có một nhánh rõ ràng trong ví dụ đầu tiên và cần hiểu có một nhánh ngụ ý trong lần thứ hai. Thứ hai là rõ ràng và dễ hiểu hơn với điều kiện bạn hiểu cú pháp và vì nó sử dụng cú pháp ít cơ bản hơn có thể là một vấn đề.


1
Người ta có thể lập luận rằng độ phức tạp theo chu kỳ của mã riêng của một người đã giảm ở đây, vì whereở đây có khả năng từ một khung. Tôi nghĩ rằng độ phức tạp theo chu kỳ như một số liệu rất hữu ích ngay cả ở đây bởi vì, trong khi việc chia một hàm thành một số không làm giảm độ phức tạp chung của hệ thống (thực sự, nó thường làm tăng nó, nếu chỉ một chút), nó giúp bạn xác định khi nào phần của hệ thống trở nên quá phức tạp và cần phải được chia nhỏ.
Threed

6

Để trả lời câu hỏi một cách khách quan, chúng tôi cần một số loại số liệu cho khả năng duy trì. Độ phức tạp theo chu kỳ tự nó không phải là thước đo khả năng bảo trì, nhưng nó một thành phần của một số số liệu có ý định đo lường khả năng bảo trì. Ví dụ: công thức cho Chỉ số duy trì là:

MI = 171 - 5.2 * ln(V) - 0.23 * (G) - 16.2 * ln(LOC)

nơi Glà sự phức tạp cyclomatic của mã trong câu hỏi. Do đó, việc giảm độ phức tạp theo chu kỳ của một đoạn mã theo định nghĩa sẽ cải thiện Chỉ số duy trì của mã và các số liệu khác sử dụng độ phức tạp theo chu kỳ tương tự .

Thật khó để nói liệu loại thay đổi mà bạn đề xuất có làm cho chương trình dường như dễ duy trì hơn đối với các lập trình viên hay không; điều đó có thể phụ thuộc vào mức độ quen thuộc của chúng với (trong trường hợp của bạn) wherephương thức.


Cảnh báo: Số ma thuật! (>_<)
Izkata

Chỉ có một vấn đề nhỏ với Chỉ số duy trì. Ba thành phần của nó là Halstead Volume, Cyclomatic Complexity và Lines of Code. Cả Halstead Volume và Cyclomatic Complexity đã được chứng minh là có mối tương quan rất chặt chẽ với Lines of Code. Điều này có nghĩa là một phương trình gần đúng thay thế cho Chỉ số duy trì phụ thuộc CHỈ trên các dòng mã có thể có nguồn gốc gần như chính xác như ban đầu, với độ khó tính toán ít hơn đáng kể.
John R. Strohm

@ JohnR.Strohm Tôi nghĩ rằng bạn sẽ nhận được một kết quả tương tự ngay cả khi bạn chỉ sử dụng LỘC làm chỉ số duy trì. Thay đổi của OP làm giảm LỘC xuống còn 1 từ 4 và các thay đổi khác làm giảm độ phức tạp theo chu kỳ cũng có khả năng giảm LỘC tương tự. Dù bằng cách nào, nó thực sự đi vào cách bạn xác định và đo lường khả năng bảo trì.
Caleb

1
@Caleb: Đồng ý. Điểm tôi cố gắng đưa ra là mọi người thường xuyên sử dụng các số liệu và kết hợp số liệu thực sự phức tạp này, không nhận ra rằng tất cả chúng đều được chứng minh là có mối tương quan chặt chẽ với mã thực với Dòng mã cũ (LỘC) đơn giản và do đó không có giá trị mô tả hoặc mô tả nhiều hơn LỘC.
John R. Strohm

3

Bởi vì nó đã được chứng minh rằng độ phức tạp chu kỳ (CC) có mối tương quan rất mạnh với kích thước mã, "đến mức CC có thể nói là hoàn toàn không có sức mạnh giải thích của riêng nó." điều bạn thực sự hỏi là liệu "các phương thức lặp như thường thấy trong các ngôn ngữ hiện đại như C #, JavaScript và (hy vọng) trong Java 8 có làm giảm tác động của kích thước mã đến tính dễ hiểu và khả năng hỗ trợ của mã hay không."

Tại thời điểm đó, người ta sẽ hy vọng rằng câu trả lời sẽ rõ ràng. Trong nhiều thập kỷ, người ta đã biết rằng mã ngắn hơn thường dễ hiểu, dễ bảo trì và hỗ trợ hơn.


2

Nếu bạn đang nói về chỉ số thô của Độ phức tạp Cyclomatic, chắc chắn. Bạn chỉ cần thu được từ 2 đến 0. Nếu bạn đi bằng số, hoàn toàn đạt được.

Từ quan điểm thực tế (đọc: con người), tôi cho rằng bạn thực sự đã tăng độ phức tạp lên 2. Một điểm xuất phát từ thực tế là bây giờ một số lập trình viên khác phải mang hoặc có kiến ​​thức về LINQ cú pháp trôi chảy để hiểu điều này mã.

Một điểm khó khăn gia tăng khác đến từ việc hiểu Lambda Expressions; mặc dù Lambda khá đơn giản trong trường hợp này , có một số thay đổi mô hình phải được thực hiện để đánh giá đầy đủ chúng.

Trường hợp này, sử dụng a.where(x => matches(x, arg))không phải là khủng khiếp, và thật lòng là một cách tuyệt vời để khiến một số đồng nghiệp nhìn thấy và làm việc với các biểu thức LINQ và Lambda lần đầu tiên (tôi thực sự đã trình bày một hướng dẫn về LINQ / Lambdas cho một số đồng nghiệp cũ sử dụng điều này và các bộ mã khác, có hiệu quả rất lớn.) Tuy nhiên, việc sử dụng chúng không đòi hỏi một số kiến ​​thức.

Tôi khuyên bạn nên thận trọng, bởi vì tôi đã thấy các trường hợp trong đó bộ tái cấu trúc LINQ thực sự tệ hơn đáng kể để đọc so với những gì foreachvòng lặp đó trở thành.


1

Theo chủ quan, nó phụ thuộc vào đối tượng nhà phát triển, nếu họ hiểu biểu thức lambda, thì;

List<String> filteredList = originalList.where(s => matches(s));

là nhanh hơn để hiểu, và có lẽ dễ dàng hơn một chút. Tôi sẽ quan tâm hơn về việc sử dụng s và match (). Không phải là tự mô tả, một cái gì đó như;

List<String> filteredList = 
    originalList.where(stringToBeTested => matchesNameTest(stringToBeTested));

Hoặc là

List<String> filteredList = 
        originalList.where(originalListString => matchesNameTest(originalListString));

cung cấp cho nhà phát triển thông tin có ý nghĩa hơn và dễ phân tích hơn, mà không cần phải đi sâu vào hàm Match () để xác định xem trận đấu nào đang được thực hiện.

Khả năng bảo trì không chỉ là về khả năng hiểu mã, mà chủ yếu là về tốc độ và độ chính xác mà người ta có thể hiểu được mã.


2
Xin chào Ace. Đó là một ví dụ, cố ý không có nội dung ngữ nghĩa để làm nổi bật cấu trúc.
C. Ross
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.