Hiểu về độ phức tạp của chu kỳ


11

Gần đây tôi đã bắt gặp Cyclomatic Complexity và tôi muốn cố gắng hiểu nó tốt hơn.

Một số ví dụ mã hóa thực tế của các yếu tố khác nhau đi vào tính toán độ phức tạp là gì? Cụ thể, đối với phương trình Wikipedia của M = E − N + 2P, tôi muốn hiểu rõ hơn về mỗi thuật ngữ sau đây có nghĩa là gì:

  • E = số cạnh của đồ thị
  • N = số lượng nút của đồ thị
  • P = số lượng thành phần được kết nối

Tôi nghi ngờ rằng E hoặc N có thể là số điểm quyết định (nếu, khác nếu, for, foreach, v.v.) trong một khối mã, nhưng tôi không chắc chắn đó là điểm nào hoặc cái gì khác biểu thị. Tôi cũng đoán rằng P đề cập đến các lệnh gọi hàm và khởi tạo lớp, nhưng không có định nghĩa rõ ràng nào mà tôi có thể thấy. Nếu ai đó có thể làm sáng tỏ hơn một chút với một số ví dụ mã rõ ràng của từng ví dụ, điều đó sẽ giúp ích.

Theo dõi, Độ phức tạp Cyclomatic có tương quan trực tiếp với số lượng thử nghiệm đơn vị cần thiết cho phạm vi bảo hiểm đường dẫn 100% không? Ví dụ, một phương thức có độ phức tạp 4 chỉ ra rằng 4 bài kiểm tra đơn vị là cần thiết để bao quát phương pháp đó?

Cuối cùng, các biểu thức chính quy có ảnh hưởng đến Độ phức tạp Cyclomatic không, và nếu có thì bằng cách nào?


Tôi thấy rằng bạn có thể lấy bài báo gốc của McCabe từ Wikipedia và Google Books sẽ mang lại cuốn sách mà McCabe đã sử dụng cho bài báo gốc của mình. Thật thú vị, sau đó bạn sẽ thấy rằng McCabe đã sử dụng sai định lý ban đầu (và cũng giải thích một cách khó hiểu khi anh ta nên bắt đầu với một biểu đồ vô hướng và không cần phải kết nối mạnh mẽ ở vị trí đầu tiên) nhưng dù sao các con số cũng xuất hiện chính xác ( công thức đúng sẽ là M = E + 1-N + P, nhưng vì P luôn là 1, nên nó phù hợp ...) Ý nghĩ xảy ra là "Xử lý ngoại lệ" hiện đại ném một cờ lê vào các tác phẩm của số liệu đó.
David Tonhofer

... Và những gì về các cuộc gọi đệ quy (có thể thông qua một chuỗi các chức năng). Có một cầu chì các đồ thị chức năng? Làm thế nào về các toán tử boolean ngắn mạch như "&&". Toán tử được bảo vệ như "ref? .X" mang lại null nếu ref là null? Ồ, đó chỉ là một số liệu khác. Nhưng có một số công việc cho một dự án đại học nhỏ ở đây.
David Tonhofer

Câu trả lời:


8

Về công thức: các nút đại diện cho các trạng thái, các cạnh thể hiện các thay đổi trạng thái. Trong mọi chương trình, các câu lệnh mang lại những thay đổi trong trạng thái chương trình. Mỗi câu lệnh liên tiếp được biểu thị bằng một cạnh và trạng thái của chương trình sau (hoặc trước ...) việc thực hiện câu lệnh là nút.

Nếu bạn có một câu lệnh phân nhánh ( ifví dụ) - thì bạn có hai nút xuất hiện, bởi vì trạng thái có thể thay đổi theo hai cách.

Một cách khác để tính Số phức tạp theo chu kỳ (CCN) là tính toán có bao nhiêu "vùng" trong biểu đồ thực thi mà bạn có (trong đó "vùng độc lập" là một vòng tròn không chứa các vòng tròn khác). Trong trường hợp này, CCN sẽ là số vùng độc lập cộng với 1 (sẽ chính xác bằng số công thức trước đó mang lại cho bạn).

CCN được sử dụng để bao phủ phân nhánh hoặc bảo hiểm đường dẫn , giống nhau. CCN bằng với số lượng đường dẫn phân nhánh khác nhau về mặt lý thuyết có thể có trong một ứng dụng luồng đơn (có thể bao gồm các nhánh như " if x < 2 and x > 5 then", nhưng phải được trình biên dịch tốt bắt như một mã không thể truy cập được). Bạn phải có ít nhất số lượng các trường hợp thử nghiệm khác nhau (có thể nhiều hơn vì một số trường hợp thử nghiệm có thể lặp lại các đường dẫn được bao phủ bởi các trường hợp trước đó, nhưng không ít giả sử mỗi trường hợp bao gồm một đường dẫn). Nếu bạn không thể bao quát một đường dẫn với bất kỳ trường hợp thử nghiệm nào có thể - bạn đã tìm thấy mã không thể truy cập được (mặc dù bạn sẽ thực sự phải chứng minh cho chính mình tại sao nó không thể truy cập được, có thể một số x < 2 and x > 5ẩn nấp ở đâu đó).

Đối với các biểu thức thông thường - tất nhiên chúng ảnh hưởng, như bất kỳ đoạn mã nào khác. Tuy nhiên, CCN của cấu trúc regex có thể quá cao để bao gồm trong một thử nghiệm đơn vị và bạn có thể giả sử rằng công cụ regex đã được thử nghiệm và bỏ qua tiềm năng phân nhánh của biểu thức cho nhu cầu thử nghiệm của bạn (trừ khi bạn đang thử nghiệm động cơ regex, tất nhiên).


2
+1: Trên thực tế, bạn phải tin tưởng rằng công cụ regex đã được thử nghiệm. Nếu bạn không tin tưởng nó, có được một bạn làm niềm tin.
S.Lott

"CCN bằng với số đường dẫn thực thi khác nhau có thể có trong một ứng dụng luồng đơn" Điều này sai vì CCN chỉ dựa trên cấu trúc liên kết của mã không theo ý nghĩa của nó . Một tỷ lệ tốt của các đường dẫn này có thể không thể thực hiện được vì chúng yêu cầu trạng thái đầu vào không thể được đặt ( ví dụ một số x là 5 và cũng nhỏ hơn 2). Thành thật mà nói, tôi nghĩ rằng việc sử dụng CCN để quyết định các trường hợp thử nghiệm để chạy là sai lầm. CCN là một con số để nói với nhà phát triển "bạn có thể đã quá nhiệt tình ở đây, vui lòng xem xét tái cấu trúc". Và thậm chí sau đó, có thể có lý do chính đáng cho CCN cao.
David Tonhofer

1
@David đã thêm một câu để giải quyết điều đó. CCN là một phạm vi bảo hiểm chi nhánh và không bao giờ có lý do chính đáng cho CCN cao ở mức thấp hơn (nói chung tôi đề nghị thực thi theo từng chức năng riêng lẻ).
littleadv

Bảo hiểm chi nhánh và bảo hiểm đường dẫn không giống nhau. Bảo hiểm chi nhánh nhằm mục đích bao trùm tất cả các chi nhánh trong khi bảo hiểm đường dẫn nhằm mục đích bao trùm tất cả các tổ hợp chi nhánh.
mouviciel

13

Một số nhận xét về điều này mà tôi nhàn rỗi viết lên ...

Cụ thể, đối với phương trình Wikipedia của M = E - N + 2P

Phương trình đó rất sai .

Vì một số lý do, McCabe thực sự sử dụng nó trong bài báo gốc của mình ("Biện pháp phức tạp", Giao dịch của IEEE về Kỹ thuật phần mềm, Vo .. SE-2, số 4, tháng 12 năm 1976), nhưng không biện minh cho nó và sau khi thực sự trích dẫn chính xác công thức trên trang đầu tiên, đó là

v (G) = e - v + p

(Ở đây, các yếu tố công thức đã được dán nhãn lại)

Cụ thể, McCabe tham khảo cuốn sách C.Berge, Đồ thị và Siêu dữ liệu (viết tắt dưới đây là G & HG). Trực tiếp từ cuốn sách đó :

Định nghĩa (trang 27 dưới cùng của G & HG):

Số chu kỳ v (G) của đồ thị (không bị chặn) G (có thể có một số thành phần bị ngắt kết nối) được định nghĩa là:

v (G) = e - v + p

Trong đó e = số cạnh, v = số đỉnh, p = số thành phần được kết nối

Định lý (trang 29 đầu G & HG) (không được McCabe sử dụng):

Số chu kỳ v (G) của đồ thị G bằng số chu kỳ độc lập tối đa

Một chu kỳ là một chuỗi các đỉnh bắt đầu và kết thúc tại cùng một đỉnh, với mỗi hai đỉnh liên tiếp trong dãy liền kề nhau trong biểu đồ.

Theo trực giác, một tập hợp các chu trìnhđộc lập nếu không có chu trình nào có thể được xây dựng từ các chu kỳ khác bằng cách áp dụng các bước đi.

Định lý (trang 29 giữa G & HG) (như McCabe sử dụng):

Trong đồ thị G được kết nối mạnh, số chu kỳ bằng với số lượng mạch độc lập tuyến tính tối đa.

Một mạch là một chu kỳ không có sự lặp lại của các đỉnh và cạnh được phép.

Một đồ thị có hướng được cho là được kết nối mạnh nếu mọi đỉnh có thể tiếp cận được từ mọi đỉnh khác bằng cách đi qua các cạnh theo hướng được chỉ định của chúng.

Lưu ý rằng ở đây chúng tôi đã chuyển từ các biểu đồ vô hướng sang các biểu đồ được kết nối mạnh mẽ (được định hướng ... Berge không làm cho điều này hoàn toàn rõ ràng)

McCabe hiện áp dụng định lý trên để rút ra một cách đơn giản để tính toán một số phức phức theo chu kỳ của McC McCabe (CCN):

Đưa ra một biểu đồ có hướng đại diện cho cấu trúc liên kết nhảy nhảy của một thủ tục (biểu đồ luồng lệnh), với một đỉnh được chỉ định đại diện cho điểm vào duy nhất và một đỉnh được chỉ định đại diện cho điểm thoát duy nhất (đỉnh của điểm thoát có thể cần phải được xây dựng bằng cách thêm nó trong trường hợp trả về nhiều lần), tạo một biểu đồ được kết nối mạnh bằng cách thêm cạnh có hướng từ đỉnh điểm thoát vào đỉnh điểm vào, do đó làm cho đỉnh điểm vào có thể tiếp cận được từ bất kỳ đỉnh nào khác.

Bây giờ McCabe đặt ra (khá khó hiểu tôi có thể nói) rằng số chu kỳ của biểu đồ luồng lệnh đã sửa đổi "phù hợp với khái niệm trực quan của chúng tôi về 'số lượng đường dẫn tối thiểu'", và vì vậy chúng tôi sẽ sử dụng số đó làm thước đo độ phức tạp.

Thật tuyệt, vì vậy:

Số độ phức tạp chu kỳ của biểu đồ luồng lệnh được sửa đổi có thể được xác định bằng cách đếm các mạch "nhỏ nhất" trong biểu đồ không bị chặn. Điều này không đặc biệt khó thực hiện bởi con người hoặc máy móc, nhưng áp dụng định lý trên cho chúng ta một cách dễ dàng hơn để xác định nó:

v (G) = e - v + p

nếu một người coi thường tính định hướng của các cạnh.

Trong mọi trường hợp, chúng tôi chỉ xem xét một thủ tục duy nhất, do đó chỉ có một thành phần được kết nối trong toàn bộ biểu đồ, và vì vậy:

v (G) = e - v + 1.

Trong trường hợp người ta xem xét biểu đồ ban đầu mà không có cạnh "thoát ra để vào" , người ta chỉ cần lấy:

(G) = ẽ - v + 2

như ẽ = e - 1

Hãy minh họa bằng cách sử dụng ví dụ của McCabe từ bài báo của mình:

Ví dụ của McCabe

Ở đây chúng tôi có:

  • e = 10
  • v = 6
  • p = 1 (một thành phần)
  • v (G) = 5 (chúng tôi đang đếm rõ ràng 5 chu kỳ)

Công thức cho số chu kỳ nói:

v (G) = e - v + p

mang lại 5 = 10 - 6 + 1 và đúng như vậy!

"Số phức tạp chu kỳ McCabe" như được nêu trong bài báo của ông là

5 = 9 - 6 + 2 (không có giải thích nào thêm được đưa ra trong bài báo về cách thức)

điều này xảy ra là đúng (nó mang lại v (G)) nhưng vì những lý do sai, tức là chúng ta sử dụng:

(G) = ẽ - v + 2

và do đó (G) = v (G) ... phew!

Nhưng biện pháp này có tốt không?

Trong hai từ: Không phải là rất

  • Không hoàn toàn rõ ràng làm thế nào để thiết lập "biểu đồ luồng hướng dẫn" của một thủ tục, đặc biệt là nếu xử lý ngoại lệ và đệ quy nhập vào hình ảnh. Lưu ý rằng McCabe đã áp dụng ý tưởng của mình cho mã được viết bằng FORTRAN 66 , một ngôn ngữ không có đệ quy, không có ngoại lệ và cấu trúc thực thi đơn giản.
  • Việc một thủ tục có quyết định và thủ tục có vòng lặp mang lại CCN giống nhau không phải là một dấu hiệu tốt.

nhập mô tả hình ảnh ở đây


1
@JayElston Bắt tốt. Thực sự tôi làm. Đã sửa!
David Tonhofer

1
Lớn +1 để liên kết với giấy gốc. Nhiều bài báo được viết vào khoảng thời gian đó khá dễ đọc đối với bất kỳ lập trình viên trung cấp nào và nên đọc.
Daniel T.

1

Theo dõi, Độ phức tạp Cyclomatic có tương quan trực tiếp với số lượng thử nghiệm đơn vị cần thiết cho phạm vi bảo hiểm đường dẫn 100% không?

Vâng, về cơ bản. Đó cũng là một ý tưởng tốt để sử dụng độ phức tạp chu kỳ như là một chỉ báo về thời điểm tái cấu trúc. Theo kinh nghiệm của tôi, khả năng kiểm tra và tái sử dụng tăng lên rất nhiều cho CC thấp hơn (mặc dù bạn nên thực tế - không tái cấu trúc quá mức và một số phương pháp sẽ có CC cao do bản chất của chúng - không phải lúc nào cũng có ý nghĩa để thử và ép buộc thấp hơn).

Cuối cùng, các biểu thức chính quy có ảnh hưởng đến Độ phức tạp Cyclomatic không, và nếu có thì bằng cách nào?

Có, nếu bạn muốn chính xác, mặc dù hầu hết các công cụ phân tích mã dường như không xem xét chúng theo cách đó. Biểu thức thông thường chỉ là các máy trạng thái hữu hạn, vì vậy tôi đoán CC của chúng có thể được tính từ biểu đồ FSM, nhưng nó sẽ là một con số khá lớn.


+1 - Tôi đoán rằng việc tính toán CC cho RegExes không phải là một nhiệm vụ thú vị.
VirtuosiMedia
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.