Czy metody iteracyjne zmniejszają złożoność cykliczną i poprawiają wsparcie?


11

Czy metody iteracyjne, które są powszechnie spotykane w nowoczesnych językach, takich jak C #, JavaScript i (mam nadzieję) w Javie 8, zmniejszają wpływ cykliczności złożoności na zrozumiałość i obsługę kodu?

Na przykład w C # możemy mieć następujący kod:

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

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

Ma to prostą cykliczną złożoność 2.

Możemy z łatwością ponownie napisać to jako:

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

Który ma prostą cykliczną złożoność równą 0.

Czy to faktycznie powoduje, że kod jest bardziej obsługiwany? Czy istnieją jakieś rzeczywiste badania na ten temat?


2
W twoim tytule pytasz o cykliczną redukcję złożoności, ale twój tekst zakłada redukcję CC i pyta o łatwość utrzymania. Może to być cenne pytanie. Czy możesz edytować tytuł, aby był bardziej dokładny?
Kilian Foth,

@KilianFoth Czy to lepiej?
C. Ross

Metody iteracyjne łączę z iteracyjnymi metodologiami programistycznymi. Myślę, że lepszym terminem byłyby funkcje wyższego rzędu. (A tak na marginesie, metody nie mają typu return / void, podczas gdy funkcje zwracają coś).
Johannes Rudolph

@JohannesRudolph „metody nie mają typu return / void, podczas gdy funkcje coś zwracają” - w jakim języku to jest prawda? Nigdy tego nie słyszałem; w rzeczywistości, jedynym prawdziwym rozróżnieniem, jakie kiedykolwiek słyszałem, było to, że metody są powiązane z klasą, funkcje nie są.
thre

Odpowiedzi:


14

Domyślam się, że właśnie podzieliłeś / przeniosłeś złożoność. Zmniejszyło się, ponieważ nie liczysz implementacji .where()w swoim CC.

Ogólne CC naprawdę się nie zmieniło, CC własnego kodu zmniejszyło się, tylko dlatego, że zostało teraz przeniesione do kodu frameworka.

Powiedziałbym, że jest łatwiejsza w utrzymaniu. Jeśli jest to cecha języka, użyj go. To nie jest „och, rozumiem, to sprytna sztuczka” , której używasz, tylko prosta wbudowana funkcja podobna do redukcji.


11

Wszystko, co robisz, to podkreślenie błędu w cykliczności złożoności jako metryki, złożoność kodu naprawdę się nie zmieniła. Masz wyraźną gałąź w pierwszym przykładzie i musisz zrozumieć, że istnieje domniemana gałąź w drugim przykładzie. Drugi jest jaśniejszy i łatwiejszy do zrozumienia, pod warunkiem, że rozumiesz składnię, a ponieważ używa ona mniej podstawowej składni, co może być problemem.


1
Można argumentować, że cykliczność złożoności własnego kodu jest tutaj zmniejszona, ponieważ whereprawdopodobnie pochodzi z frameworka. Myślę, że złożoność cykliczna jako metryka jest przydatna nawet tutaj, ponieważ dzielenie funkcji na kilka nie zmniejsza ogólnej złożoności systemu (w rzeczywistości często ją zwiększa, choćby nieznacznie), ale pomaga określić, kiedy element systemu staje się zbyt skomplikowany i musi zostać zepsuty.
zagrali

6

Aby obiektywnie odpowiedzieć na to pytanie, potrzebowalibyśmy pewnego rodzaju mierników do utrzymania. Sama złożoność cykliczna nie jest miarą łatwości konserwacji, ale jest składnikiem niektórych wskaźników, które mają mierzyć łatwość konserwacji. Na przykład formuła indeksu konserwacji jest następująca:

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

gdzie Gjest cykliczna złożoność danego kodu. Dlatego zmniejszenie cyklicznej złożoności fragmentu kodu z definicji poprawia Indeks Konserwowalności kodu i inne mierniki, które podobnie wykorzystują złożoność cykliczną .

Trudno powiedzieć, czy proponowana przez Ciebie zmiana sprawia, że ​​program wydaje się łatwiejszy do utrzymania dla programistów; to prawdopodobnie zależy od tego, jak dobrze znają (w twoim przypadku) wheremetodę.


Ostrzeżenie: magiczne liczby! (>_<)
Izkata,

Jest tylko jeden mały problem ze wskaźnikiem utrzymania. Trzy jego składniki to Tom Halsteada, Cyklomatyczna złożoność i Linie kodu. Wykazano, że zarówno objętość Halsteada, jak i złożoność cyklomatyczna są bardzo silnie skorelowane z liniami kodu. Oznacza to, że można wyprowadzić alternatywne przybliżone równanie dla wskaźnika konserwacji, który zależał TYLKO od linii kodu, który byłby prawie tak dokładny jak oryginał, przy znacznie mniejszej trudności obliczeniowej.
John R. Strohm,

@ JohnR.Strohm Myślę, że uzyskałbyś podobny wynik, nawet gdybyś użył LOC jako miernika łatwości konserwacji. Zmiana PO zmniejsza LOC do 1 z 4, a inne zmiany, które zmniejszają złożoność cyklomatyczną, również prawdopodobnie zmniejszają LOC. Tak czy inaczej, tak naprawdę sprowadza się do tego, jak definiujesz i mierzysz łatwość konserwacji.
Caleb

1
@Caleb: Zgoda. Chodzi mi o to, że ludzie zbyt często sięgają po te naprawdę skomplikowane metryki i kombinacje metryk, nie zdając sobie sprawy, że prawie wszystkie z nich są silnie skorelowane z rzeczywistym kodem ze zwykłymi starymi liniami kodu (LOC) , a zatem nie mają więcej wartości predykcyjnych ani opisowych niż LOC.
John R. Strohm,

3

Ponieważ wykazano, że złożoność cykliczna (CC) jest bardzo silnie skorelowana z rozmiarem kodu, „do tego stopnia, że ​​można powiedzieć, że CC nie ma absolutnie żadnej własnej mocy wyjaśniającej”. tak naprawdę pytasz, czy „metody iteracyjne, które są powszechnie spotykane w nowoczesnych językach, takich jak C #, JavaScript i (mam nadzieję) w Javie 8, zmniejszają wpływ rozmiaru kodu na zrozumiałość i obsługę kodu”.

W tym momencie można mieć nadzieję, że odpowiedź będzie oczywista. Od dziesięcioleci wiadomo, że krótszy kod jest ogólnie łatwiejszy do zrozumienia, utrzymania i wsparcia.


2

Jeśli mówisz o surowej statystyce złożoności cyklomatycznej, na pewno. Właśnie nuknąłeś go od 2 do 0. Jeśli idziesz liczbami, czysty zysk przez całą drogę.

Jednak z praktycznego punktu widzenia (czytaj: człowiek) argumentowałbym, że tak naprawdę zwiększyłeś złożoność o 2. Jednym z nich jest fakt, że teraz inny programista musi przynieść lub zdobyć wiedzę na temat płynnej składni LINQ, aby to zrozumieć kod.

Kolejny punkt zwiększonej trudności wynika ze zrozumienia wyrażeń lambda; chociaż w tym przypadku Lambda jest dość prosta , należy wprowadzić pewne zmiany paradygmatu, aby w pełni je docenić.

Ten przypadek użycia a.where(x => matches(x, arg))nie jest straszny, i szczerze mówiąc, jest to świetny sposób, aby jakiś współpracownik po raz pierwszy zobaczył i pracował z wyrażeniami LINQ i Lambda (tak naprawdę przedstawiłem samouczek na temat LINQ / Lambdas niektórym byłym współpracownikom używającym tego i inne zestawy kodu, z wielkim skutkiem.) Jednak ich użycie wymaga pewnej wiedzy.

Zalecam ostrożność, ponieważ widziałem przypadki, w których refaktor LINQ jest znacznie gorszy do odczytania niż to, czym foreachstaje się ta pętla.


1

Subiektywnie zależy to od odbiorców programistów, jeśli rozumieją wyrażenia lambda;

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

jest szybszy do zrozumienia i może nieco łatwiejszy. Byłbym bardziej zaniepokojony używaniem s i match (). Ani też nie jest samoopisowe, coś w rodzaju;

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

Lub

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

daje deweloperowi bardziej znaczące informacje i jest łatwiejszy do analizy, bez konieczności zagłębiania się w funkcję match () w celu ustalenia, które dopasowanie jest wykonywane.

Utrzymywalność to nie tylko umiejętność zrozumienia kodu, ale przede wszystkim szybkość i dokładność, z jaką można go zrozumieć.


2
Cześć Ace. Jest to przykład celowo pozbawiony treści semantycznej dla podkreślenia struktury.
C. Ross
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.