Mam fragment kodu, w którym iteruję mapę, dopóki jakiś warunek nie jest spełniony, a następnie użyję tego warunku, aby zrobić więcej rzeczy.
Przykład:
Map<BigInteger, List<String>> map = handler.getMap();
if(map != null && !map.isEmpty())
{
for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())
{
fillUpList();
if(list.size() > limit)
{
limitFlag = true;
break;
}
}
}
else
{
logger.info("\n>>>>> \n\t 6.1 NO entries to iterate over (for given FC and target) \n");
}
if(!limitFlag) // Continue only if limitFlag is not set
{
// Do something
}
Czuję, że ustawiam flagę, a następnie używanie tego do robienia więcej rzeczy to zapach kodu.
Czy mam rację? Jak mogę to usunąć?
design-patterns
object-oriented
object-oriented-design
clean-code
Siddharth Trikha
źródło
źródło
entry
nigdzie nie jest używany w pętli funkcyjnej i możemy tylko zgadywać, colist
jest. MafillUpList
się wypełnićlist
? Dlaczego nie ma tego jako parametru?Odpowiedzi:
Nie ma nic złego w używaniu wartości boolowskiej zgodnie z jej przeznaczeniem: zapisaniem binarnego rozróżnienia.
Gdyby kazano mi zrefaktoryzować ten kod, prawdopodobnie umieściłbym pętlę w swojej własnej metodzie, aby przypisanie +
break
zmieniło się wreturn
; wtedy nie potrzebujesz nawet zmiennej, możesz po prostu powiedziećźródło
fillUpList()
jest to jakiś kod (którego OP decyduje się nie udostępniać), który faktycznie wykorzystuje wartośćentry
z iteracji; bez tego założenia wyglądałoby na to, że ciało pętli nie korzysta z niczego z iteracji pętli.To niekoniecznie jest złe, a czasem jest to najlepsze rozwiązanie. Ale ustawienie takich flag w zagnieżdżonych blokach może utrudnić śledzenie kodu.
Problem polega na tym, że masz bloki do rozgraniczenia zakresów, ale potem masz flagi, które komunikują się między zakresami, przerywając logiczną izolację bloków. Na przykład,
limitFlag
fałsz będzie, jeślimap
jestnull
, więc kod „zrób coś” zostanie wykonany, jeślimap
jestnull
. To może być to, co zamierzasz, ale może to być błąd, którego łatwo przeoczyć, ponieważ warunki dla tej flagi są zdefiniowane gdzie indziej, w zagnieżdżonym zakresie. Jeśli potrafisz zachować informacje i logikę w jak najściślejszym zakresie, powinieneś spróbować to zrobić.źródło
if(map==null || map.isEmpty()) { logger.info(); return;}
To zadziała jednak tylko wtedy, gdy kod, który widzimy, jest pełną treścią funkcji, a// Do something
część nie jest wymagana w przypadku mapy jest zerowy lub pusty.Odradzałbym rozumowanie na temat „zapachów kodu”. To tylko najbardziej leniwy możliwy sposób zracjonalizowania własnych uprzedzeń. Z czasem rozwiniesz wiele uprzedzeń i wiele z nich będzie rozsądnych, ale wiele z nich będzie głupich.
Zamiast tego powinieneś mieć praktyczne (tj. Nie dogmatyczne) powody preferowania jednej rzeczy nad drugą i unikaj myślenia, że powinieneś mieć taką samą odpowiedź na wszystkie podobne pytania.
„Zapachy kodowe” są, gdy nie myślisz. Jeśli naprawdę chcesz pomyśleć o kodzie, zrób to dobrze!
W takim przypadku decyzja może naprawdę pójść w obie strony, w zależności od otaczającego kodu. To naprawdę zależy od tego, co według ciebie jest najwyraźniejszym sposobem myślenia o tym, co robi kod. („czysty” kod to kod, który wyraźnie komunikuje, co robi innym programistom i ułatwia im sprawdzenie, czy jest poprawny)
Wiele razy ludzie piszą metody podzielone na fazy, w których kod najpierw określa, co powinien wiedzieć o danych, a następnie działa na nich. Jeśli zarówno część „określająca”, jak i część „działaj na nią” są nieco skomplikowane, może to mieć sens, a często „to, co musi wiedzieć” może być przenoszone między fazami w flagach boolowskich. Naprawdę wolałbym, żebyś nadał flagę lepszą nazwę. Coś w rodzaju „largeEntryExists” sprawiłoby, że kod byłby znacznie czystszy.
Z drugiej strony, jeśli kod „// Zrób coś” jest bardzo prosty, sensowniej jest umieścić go w
if
bloku zamiast ustawiać flagę. To przybliża efekt do przyczyny, a czytnik nie musi skanować reszty kodu, aby upewnić się, że flaga zachowuje ustawioną wartość.źródło
Tak, jest to zapach kodu (zwracaj uwagę wszystkich, którzy to robią).
Najważniejsze dla mnie jest użycie tego
break
oświadczenia. Jeśli go nie użyjesz, będziesz iterował więcej elementów niż jest to wymagane, ale użycie go daje dwa możliwe punkty wyjścia z pętli.Nie jest to poważny problem z twoim przykładem, ale możesz sobie wyobrazić, że gdy warunkowe lub warunkowe wewnątrz pętli stają się bardziej złożone lub kolejność początkowej listy staje się ważna, łatwiej jest błędowi wkraść się do kodu.
Gdy kod jest tak prosty jak twój przykład, można go zredukować do
while
pętli lub równoważnej mapy, konstruuj filtry.Gdy kod jest na tyle skomplikowany, że wymaga flag i przerw, będzie podatny na błędy.
Tak jak w przypadku wszystkich zapachów kodu: jeśli widzisz flagę, spróbuj zastąpić ją flagą
while
. Jeśli nie możesz, dodaj dodatkowe testy jednostkowe.źródło
SO as with all code smells: If you see a flag, try to replace it with a while
Czy możesz rozwinąć to na przykładzie?for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())
gofor (Iterator<Map.Entry<BigInteger, List<String>>> iterator = map.entrySet().iterator(); iterator.hasNext() && !limitFlag; Map.Entry<BigInteger, List<String>> entry = iterator.next())
. To dość rzadki wzór, że miałbym więcej problemów ze zrozumieniem go niż stosunkowo prosta przerwa.Wystarczy użyć nazwy innej niż limitFlag, która mówi, co faktycznie sprawdzasz. I dlaczego logujesz coś, gdy mapa jest nieobecna lub pusta? limtFlag będzie fałszywy, wszystko na czym ci zależy. Pętla jest w porządku, jeśli mapa jest pusta, więc nie trzeba jej sprawdzać.
źródło
Moim zdaniem ustawienie wartości logicznej w celu przekazania informacji, które już posiadałeś, jest złą praktyką. Jeśli nie ma łatwej alternatywy, oznacza to prawdopodobnie większy problem, na przykład słabą enkapsulację.
Powinieneś przenieść logikę pętli for do metody fillUpList, aby złamała się, jeśli limit zostanie osiągnięty. Następnie sprawdź bezpośrednio rozmiar listy.
Jeśli to zepsuje Twój kod, dlaczego?
źródło
Po pierwsze ogólny przypadek: użycie flagi do sprawdzenia, czy jakiś element kolekcji spełnia określony warunek, nie jest rzadkością. Ale wzór, który najczęściej widziałem w celu rozwiązania tego problemu, to przesunięcie czeku w dodatkową metodę i bezpośredni powrót z niego (jak opisał Kilian Foth w swojej odpowiedzi ):
Od wersji Java 8 istnieje bardziej zwięzły sposób, używając
Stream.anyMatch(…)
:W twoim przypadku prawdopodobnie wyglądałoby to tak (zakładając
list == entry.getValue()
w twoim pytaniu):Problemem w twoim konkretnym przykładzie jest dodatkowe połączenie z
fillUpList()
. Odpowiedź zależy w dużej mierze od tego, co ma zrobić ta metoda.Uwaga dodatkowa: wezwanie do
fillUpList()
nie ma większego sensu, ponieważ nie zależy od elementu, który aktualnie iterujesz. Wydaje mi się, że jest to konsekwencja zmniejszenia kodu w celu dopasowania go do formatu pytania. Ale właśnie to prowadzi do sztucznego przykładu, który jest trudny do interpretacji, a zatem trudny do uzasadnienia. Dlatego tak ważne jest podanie minimalnego, kompletnego i weryfikowalnego przykładu .Zakładam więc, że rzeczywisty kod przekazuje prąd
entry
do metody.Ale jest więcej pytań, które należy zadać:
BigInteger
kluczy? Jeśli nie są puste, dlaczego musisz wypełnić listy? Jeśli na liście znajdują się już elementy, czy nie jest to aktualizacja lub inne obliczenie w tym przypadku?To tylko niektóre pytania, które przyszły mi do głowy, gdy próbowałem zrozumieć fragment kodu. Tak więc, moim zdaniem, jest to prawdziwy zapach kodu : Twój kod nie komunikuje jasno intencji.
Może to oznaczać albo („wszystko albo nic”, a osiągnięcie limitu oznacza błąd):
Lub może to oznaczać („aktualizacja do pierwszego problemu”):
Lub to („zaktualizuj wszystkie listy, ale zachowaj oryginalną listę, jeśli stanie się zbyt duża”):
Lub nawet następujące (używając
computeFoos(…)
pierwszego przykładu, ale bez wyjątków):Lub może to oznaczać coś zupełnie innego… ;-)
źródło