Cyklomatyczna złożoność przy wielokrotnym wywoływaniu tej samej metody

12

Dzięki pytaniu na Code Review doszedłem do małego nieporozumienia (co w zasadzie jest okazją do nauczenia się czegoś) na temat tego, czym dokładnie jest złożoność cykliczna dla poniższego kodu.

public static void main(String[] args) {
    try {
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
    }
    catch (NullPointerException e) {
    }
}

private static Random random = new Random();

public static void thro() throws NullPointerException {
    if (random.nextBoolean())
        throw new NullPointerException();
    System.out.println("No crash this time");
}

Pisząc ten kod w Eclipse i używając wtyczki Eclipse , mówi mi, że złożoność cykliczna McCabe dla głównej metody wynosi 2, a dla thrometody 2.

Jednak ktoś inny mówi mi, że złożoność wywoływania throwielokrotnego jest number of calls * method complexity, i dlatego twierdzi, że złożoność głównej metody wynosi 7 * 2 = 14.

Czy mierzymy różne rzeczy? Czy oboje możemy mieć rację? A jaka jest tutaj rzeczywista złożoność cyklomatyczna?

Simon Forsberg
źródło
5
CC funkcji wynosi dwa, ponieważ istnieją tylko dwie ścieżki. CC programu jest wyższy. Jest to kompletna próba w ciemności, ale zakładam, że oprogramowanie do analizy kodu przyjmuje każdą funkcję jako osobną czarną skrzynkę ze względu na niemożność obliczenia CC całej złożonej aplikacji za jednym razem.
Phoshi
@ Phoshi Jeśli napiszesz to jako odpowiedź i (jeśli to możliwe) podasz linki, które pokazują, że istnieje ich rozdzielenie, chętnie zaakceptuję tę odpowiedź.
Simon Forsberg
Jeśli policzysz wszystkie ścieżki spowodowane możliwymi wyjątkami w pomiarach CC, niech Bóg pomoże facetowi, który zadał pytanie o refaktoryzacji jakiegoś trywialnego kodu, aby uzyskać liczbę poniżej 10.
Mattnz

Odpowiedzi:

9

Kiedy rozumiał prawidłowo, cykliczna Złożoność z mainwynosi 8 - to liczba liniowo niezależne ścieżki przez kod. Albo dostajesz wyjątek w jednym z siedmiu wierszy, albo żaden, ale nigdy więcej niż jeden. Każdy z tych możliwych „punktów wyjątku” odpowiada dokładnie jednej innej ścieżce w kodzie.

Myślę, że kiedy McCabe wynalazł tę metrykę, nie miał języków programowania z myślą o obsłudze wyjątków.

Doktor Brown
źródło
Ale czy naprawdę ma znaczenie, która z linii generuje wyjątek?
Simon Forsberg
5
@ SimonAndréForsberg: tak, robi. Pomyśl o tym, że „thro” ma efekt uboczny, w którym zwiększa licznik globalny, gdy jest wywoływany (nie zmieniałoby to możliwych ścieżek w kodzie). Możliwe wyniki tego licznika wynoszą więc od 0 do 7, więc dowodzi to, że CC wynosi co najmniej 8.
Doc Brown
Czy powiedziałbyś, że wtyczka metryk, której używam, zgłasza niepoprawną wartość mainmetody?
Simon Forsberg
@ SimonAndréForsberg: cóż, nie znam twojej wtyczki metryk, ale 2 oczywiście nie jest 8.
Doc Brown
W moim pytaniu jest link do wtyczki metryk ...
Simon Forsberg
6

Będąc „drugim facetem”, odpowiem tutaj i sprecyzuję to, co mówię (co nie było szczególnie precyzyjne w stosunku do innych formuł).

Korzystając z powyższego przykładu kodu, obliczam złożoność cykliczną jako 8 i mam w kodzie komentarze, aby pokazać, jak to obliczam. Aby opisać ścieżki Rozważę udanej pętlę przez wszystkich tych thro()połączeń jak „” „głównej ścieżki kodu” (lub „CP = 1”):

public static void main(String[] args) {
  try {
             // This is the 'main' Code Path: CP = 1
    thro();  // this has a branch, can succeed CP=1 or throw CP=2
    thro();  // this has a branch, can succeed CP=1 or throw CP=3
    thro();  // this has a branch, can succeed CP=1 or throw CP=4
    thro();  // this has a branch, can succeed CP=1 or throw CP=5
    thro();  // this has a branch, can succeed CP=1 or throw CP=6
    thro();  // this has a branch, can succeed CP=1 or throw CP=7
    thro();  // this has a branch, can succeed CP=1 or throw CP=8
  }
  catch (NullPointerException e) {
  }
}

Tak więc liczę 8 ścieżek kodu w tej głównej metodzie, która według mnie jest złożonością cykliczną wynoszącą 8.

Mówiąc językiem Java, każdy mechanizm wyjścia z funkcji liczy się do jej złożoności, więc metoda, która ma stan powodzenia, i rzuca, na przykład, być może do 3 wyjątków, ma 4 udokumentowane ścieżki wyjścia.

Złożoność metody wywołującej taką funkcję to:

CC(method) = 1 + sum (methodCallComplexity - 1)

Sądzę, że należy wziąć pod uwagę inne kwestie: moim zdaniem catchklauzula nie przyczynia się do złożoności metody, catchjest po prostu celem throwsgałęzi, a zatem blokiem wychwytywania, który jest celem wielu throws, zlicza 1 raz dla każdego throw, i nie tylko raz na wszystko.

rolfl
źródło
Czy liczysz również możliwe gałęzie wyjątków OutOfMemory? Mam na myśli pedantycznie, że mogą powodować rozgałęzienia kodu, ale nikt ich nie liczy, ponieważ osłabiają użyteczność metryki.
Telastyn
Nie, nie jestem ... i masz rację, ale w kontekście tego argumentu liczę tylko wyjątki, które metoda ma zgłosić. Ponadto, jeśli metoda deklaruje trzy wyjątki, ale kod wywołania robi a, catch (Throwable t) {...to myślę, że nie ma znaczenia, ile wyjątków deklaruje zgłosić .
rolfl