Nie wiem, jak postępować zgodnie z tą metodą, aby zmniejszyć złożoność cyklomatyczną. Sonar donosi 13, podczas gdy spodziewane jest 10. Jestem pewien, że nie ma nic złego w pozostawieniu tej metody, ponieważ stanowi ona jedynie wyzwanie dla mnie, jak postępować zgodnie z zasadą Sonaru. Jakiekolwiek propozycje będą mile widziane.
public static long parseTimeValue(String sValue) {
if (sValue == null) {
return 0;
}
try {
long millis;
if (sValue.endsWith("S")) {
millis = new ExtractSecond(sValue).invoke();
} else if (sValue.endsWith("ms")) {
millis = new ExtractMillisecond(sValue).invoke();
} else if (sValue.endsWith("s")) {
millis = new ExtractInSecond(sValue).invoke();
} else if (sValue.endsWith("m")) {
millis = new ExtractInMinute(sValue).invoke();
} else if (sValue.endsWith("H") || sValue.endsWith("h")) {
millis = new ExtractHour(sValue).invoke();
} else if (sValue.endsWith("d")) {
millis = new ExtractDay(sValue).invoke();
} else if (sValue.endsWith("w")) {
millis = new ExtractWeek(sValue).invoke();
} else {
millis = Long.parseLong(sValue);
}
return millis;
} catch (NumberFormatException e) {
LOGGER.warn("Number format exception", e);
}
return 0;
}
Wszystkie metody ExtractXXX są zdefiniowane jako static
klasy wewnętrzne. Na przykład jak poniżej -
private static class ExtractHour {
private String sValue;
public ExtractHour(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
return millis;
}
}
AKTUALIZACJA 1
Zamierzam zadowolić się mieszanką sugestii, aby zadowolić faceta Sonara. Zdecydowanie miejsce na ulepszenia i uproszczenia.
Guawa Function
to po prostu niechciana ceremonia. Chciał zaktualizować pytanie o bieżący stan. Nic tu nie jest ostateczne. Wlej swoje myśli, proszę ...
public class DurationParse {
private static final Logger LOGGER = LoggerFactory.getLogger(DurationParse.class);
private static final Map<String, Function<String, Long>> MULTIPLIERS;
private static final Pattern STRING_REGEX = Pattern.compile("^(\\d+)\\s*(\\w+)");
static {
MULTIPLIERS = new HashMap<>(7);
MULTIPLIERS.put("S", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractSecond(input).invoke();
}
});
MULTIPLIERS.put("s", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractInSecond(input).invoke();
}
});
MULTIPLIERS.put("ms", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractMillisecond(input).invoke();
}
});
MULTIPLIERS.put("m", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractInMinute(input).invoke();
}
});
MULTIPLIERS.put("H", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractHour(input).invoke();
}
});
MULTIPLIERS.put("d", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractDay(input).invoke();
}
});
MULTIPLIERS.put("w", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractWeek(input).invoke();
}
});
}
public static long parseTimeValue(String sValue) {
if (isNullOrEmpty(sValue)) {
return 0;
}
Matcher matcher = STRING_REGEX.matcher(sValue.trim());
if (!matcher.matches()) {
LOGGER.warn(String.format("%s is invalid duration, assuming 0ms", sValue));
return 0;
}
if (MULTIPLIERS.get(matcher.group(2)) == null) {
LOGGER.warn(String.format("%s is invalid configuration, assuming 0ms", sValue));
return 0;
}
return MULTIPLIERS.get(matcher.group(2)).apply(matcher.group(1));
}
private static class ExtractSecond {
private String sValue;
public ExtractSecond(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = Long.parseLong(sValue);
return millis;
}
}
private static class ExtractMillisecond {
private String sValue;
public ExtractMillisecond(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue));
return millis;
}
}
private static class ExtractInSecond {
private String sValue;
public ExtractInSecond(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 1000);
return millis;
}
}
private static class ExtractInMinute {
private String sValue;
public ExtractInMinute(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 60 * 1000);
return millis;
}
}
private static class ExtractHour {
private String sValue;
public ExtractHour(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 60 * 60 * 1000);
return millis;
}
}
private static class ExtractDay {
private String sValue;
public ExtractDay(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 24 * 60 * 60 * 1000);
return millis;
}
}
private static class ExtractWeek {
private String sValue;
public ExtractWeek(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 7 * 24 * 60 * 60 * 1000);
return millis;
}
}
}
AKTUALIZACJA 2
Chociaż dodałem moją aktualizację, jest to tylko tyle warte czasu. Zamierzam przejść dalej, ponieważ Sonar nie narzeka. Nie przejmuj się zbytnio i akceptuję odpowiedź Mattnz, ponieważ jest to dobra droga i nie chcę dawać złego przykładu dla tych, którzy wpadają na to pytanie. Konkluzja - Nie przesadzaj z inżynierem ze względu na Sonara (lub Half Baked Project Managera) narzeka na CC. Po prostu zrób to, co jest warte ani grosza za projekt. Dziękuje za wszystko.
private static Dictionary<string,Func<string,long>> _mappingStringToParser;
resztę zostawię dla ciebie (lub kogoś, kto ma teraz więcej wolnego czasu niż ja). Jeśli znasz już parsery monadyczne, możesz znaleźć bardziej przejrzysty interfejs API, ale nie pójdę tam teraz.ExtractBlah
definiowane są klasy? czy są to jakieś biblioteki czy homebrew?Odpowiedzi:
Odpowiedź inżynierii oprogramowania:
To tylko jeden z wielu przypadków, w których zwykłe liczenie fasoli, które można łatwo policzyć, spowoduje, że zrobisz coś złego. To nie jest złożona funkcja, nie zmieniaj jej. Cyklomatyczna złożoność jest jedynie przewodnikiem po złożoności i używasz jej słabo, jeśli zmienisz tę funkcję na jej podstawie. Jest prosty, czytelny, łatwy w utrzymaniu (na razie), jeśli w przyszłości powiększy się, CC gwałtownie wzrośnie wykładniczo i zyska uwagę, której potrzebuje, kiedy tego potrzebuje, a nie wcześniej.
Minion pracujący dla dużej międzynarodowej korporacji Odpowiedź:
Organizacje są pełne przepłaconych, nieproduktywnych zespołów liczników fasoli. Utrzymywanie liczników fasoli w stanie szczęśliwym jest łatwiejsze i na pewno mądrzejsze niż robienie właściwych rzeczy. Musisz zmienić rutynę, aby obniżyć CC do 10, ale bądź szczery, dlaczego to robisz - aby nie dopuścić do tego, aby ziarna fasoli się odwróciły. Jak sugerowano w komentarzach - pomocne mogą być „parady monadyczne”
źródło
Dzięki @JimmyHoffa, @MichaelT i @ GlenH7 za pomoc!
Pyton
Po pierwsze, naprawdę powinieneś akceptować tylko znane przedrostki, czyli „H” lub „h”. Jeśli masz do zaakceptowania zarówno, należy wykonać kilka operacji, aby je dostosować, aby zaoszczędzić miejsce w swojej mapie.
W Pythonie możesz stworzyć słownik.
Następnie chcemy, aby metoda wykorzystała to:
Powinien mieć większą złożoność cykliczną.
Jawa
Potrzebujemy tylko 1 (jednego) każdego mnożnika. Umieśćmy je na mapie, jak sugerują inne odpowiedzi.
Następnie możemy po prostu użyć mapy, aby pobrać odpowiedni konwerter
źródło
Ponieważ i tak jesteś
return millis
na końcu tego okropnego ifelseifelse , pierwszą rzeczą, która przychodzi na myśl, jest natychmiastowe zwrócenie wartości z wnętrza if-bloków. Podejście to jest zgodne z podejściem wymienionym w katalogu wzorców refaktoryzacji jako Zamień zagnieżdżone warunkowe na klauzule zabezpieczające .Pomoże ci to pozbyć się innych, spłaszczy kod i sprawi, że Sonar będzie szczęśliwy:
Kolejną rzeczą wartą rozważenia jest porzucenie bloku try-catch. Spowoduje to również zmniejszenie złożoności cyklicznej, ale głównym powodem, dla którego zalecam, jest to, że w tym bloku nie ma sposobu, aby kod wywołujący mógł odróżnić poprawnie przeanalizowane 0 od wyjątku formatu liczb.
Jeśli nie masz 200% pewności, że zwracanie 0 za błędy analizy jest tym, czego potrzebuje kod dzwoniącego, lepiej propaguj ten wyjątek i pozwól, aby kod dzwoniącego zdecydował, jak sobie z tym poradzić. Zazwyczaj wygodniej jest zdecydować u dzwoniącego, czy przerwać wykonywanie, czy ponowić próbę uzyskania danych wejściowych, czy też powrócić do wartości domyślnej, takiej jak 0 lub -1 lub cokolwiek innego.
Twój fragment kodu na przykład ExtractHour sprawia, że czuję, że funkcjonalność ExtractXXX została zaprojektowana w sposób daleki od optymalnego. Założę się, każdy z pozostałych klas bezmyślnie powtarza to samo
parseDouble
isubstring
, i mnożąc rzeczy jak 60 i 1000 w kółko i kółko.Wynika to z tego, że nie zauważyłeś istoty tego, co należy zrobić w zależności od
sValue
- a mianowicie określa, ile znaków należy wyciąć z końca łańcucha i jaka byłaby wartość mnożnika. Jeśli projektujesz swój „rdzeń” wokół tych podstawowych funkcji, wyglądałby on następująco:Następnie potrzebujesz kodu, który konfiguruje powyższe obiekty dla określonych warunków, jeśli jest spełniony, lub w jakiś sposób „omija” inaczej. Można to zrobić w następujący sposób:
W oparciu o powyższe elementy składowe kod Twojej metody może wyglądać następująco:
Widzisz, nie ma już złożoności, żadnych nawiasów klamrowych w metodzie (ani wielu zwrotów, jak w mojej oryginalnej sugestii brutalnej siły na spłaszczonym kodzie). Wystarczy sekwencyjnie sprawdzić dane wejściowe i odpowiednio dostosować przetwarzanie.
źródło
Jeśli naprawdę chcesz to zmienić, możesz zrobić coś takiego:
Chodzi o to, że masz mapę kluczy (co zawsze używasz w „kończy się”), które mapują do określonych obiektów, które wykonują pożądane przetwarzanie.
Jest tu trochę szorstko, ale mam nadzieję, że jest wystarczająco jasny. Nie podałem szczegółów,
extractKeyFromSValue()
ponieważ po prostu nie wiem wystarczająco dużo o tych ciągach, aby zrobić to poprawnie. Wygląda na to, że są to ostatnie 1 lub 2 znaki nienumeryczne (regex prawdopodobnie mógłby go łatwo wyodrębnić, może.*([a-zA-Z]{1,2})$
zadziałałby), ale nie jestem w 100% pewien ...Oryginalna odpowiedź:
Mógłbyś się zmienić
do
To może trochę cię uratować, ale szczerze mówiąc, nie martwiłbym się tym zbytnio. Zgadzam się z tobą, że nie sądzę, aby pozostawienie tej metody było w jakikolwiek sposób szkodliwe. Zamiast próbować „przestrzegać zasad Sonaru”, staraj się „trzymać się wytycznych Sonaru tak dalece, jak to możliwe”.
Możesz doprowadzać się do szaleństwa, próbując przestrzegać każdej reguły, którą będą w sobie te narzędzia analityczne, ale musisz także zdecydować, czy reguły mają sens dla twojego projektu, a także w szczególnych przypadkach, w których czas poświęcony na refaktoryzację może nie być tego wart .
źródło
Możesz rozważyć użycie wyliczenia do przechowywania wszystkich dostępnych obserwacji i predykatów w celu dopasowania wartości. Jak wspomniano wcześniej, twoja funkcja jest wystarczająco czytelna, aby pozostawić ją bez zmian. Te dane są po to, by ci pomóc, a nie na odwrót.
źródło
Związane z Twoim komentarzem do:
Inną opcją do rozważenia jest zmiana standardów kodowania zespołu w takich sytuacjach. Być może możesz dodać głosowanie zespołowe, aby zapewnić miarę rządzenia i uniknąć sytuacji skrótowych.
Ale zmiana standardów zespołu w odpowiedzi na sytuacje, które nie mają sensu, jest oznaką dobrego zespołu z właściwym podejściem do standardów. Standardy mają pomóc zespołowi, a nie przeszkadzać w pisaniu kodu.
źródło
Szczerze mówiąc, wszystkie powyższe odpowiedzi techniczne wydają się okropnie skomplikowane dla danego zadania. Jak już napisano, sam kod jest czysty i dobry, dlatego wybrałbym najmniejszą możliwą zmianę, aby zaspokoić licznik złożoności. Co powiesz na następujący refaktor:
Jeśli liczę poprawnie, wyodrębniona funkcja powinna mieć złożoność 9, co nadal spełnia wymagania. Jest to w zasadzie ten sam kod, co wcześniej , co jest dobrą rzeczą, ponieważ kod był dobry na początek.
Ponadto czytelnicy Clean Code mogą cieszyć się z faktu, że metoda najwyższego poziomu jest teraz prosta i krótka, podczas gdy wyodrębniona zajmuje się szczegółami.
źródło