CodeReview, dobre praktyki, Solid - rlip/java GitHub Wiki

CodeReview

  • używać String.format("Service %s in provider %s not found", service, provider) zamiast plusa
  • używać StringUtils.equals do porównywania stringów, nie będzie null pointa
  • StringUtils.getBooleanFromString() zamiast porównywania np. do string
  • Czy nie puste - StringUtils.isNotEmpty(overdraftAccountIban)
  • lepiej porównać "string".equals() niż odwrotnie bo to w cudzysłowiu nie będzie nigdy nullem
  • tworzyć statyczne, finalne i niezmienne tablice jeśli nie mają się zmieniać
  • public static final TAK_NAYWAC
  • można robić elseif żeby było mniej zagnieżdżeń, ale lepiej wyciągnąć do metody
  • nie używac pętli for(int i=0; lepiej tą co literuje po każdym elemencie albo stream
  • nie numerować zmiennych, tylko nadawać znaczące nazwy
  • if zawsze w klamerkach
  • Zamiast listOfSpouses.size() == 0 wydajniej jest użyć isEmpty
  • Zamiast toLowerCase().equals("false") mozna uzyć equalsIgnoreCase
  • Boolean.TRUE.equals(Boolean.valueOf(regulationsParam))
  • StringUtils.in(getParameterValue(parameters, parameterName), parameterValue, parameterValue2)
  • jeżeli controller zwraca List to możesz go zawołać asList(restTemplate.postForObject("/path", request,SomeOjbect[].class))
  • longToInt: Math.toIntExact
  • sprawdzanie czy nie null .filter(Objects::nonNull)
  • czas:
LocalDateTime.from(statusRequestBackwardsCompilant.getResumeDate())
.toInstant()
.atZone(ZoneId.systemDefault()));
private static final List<String> PRODUCT_GROUP_LIST_TO_EXCLUDE_NO_STANDARD_OFFER;

    static {
        PRODUCT_GROUP_LIST_TO_EXCLUDE_NO_STANDARD_OFFER = Collections.unmodifiableList(Arrays.asList(
                ProductGroup.GPR_CC.name(),
                ProductGroup.GPR_CF.name(),
                ProductGroup.GPR_PL.name(),
                ProductGroup.GPR_OD.name(),
                ProductGroup.GPR_TL.name(),
                ProductGroup.GPR_OL.name(),
                ProductGroup.GPR_IC.name()
        ));
}
--------------------------
private final List<Order> orders;

    private OrderDao() {
        this.orders = new ArrayList<>();
        try {
            readOrders();
        } catch (FileNotFoundException ex) {
            System.err.println("Please ensure the required CSV files are present: " + ex.getMessage());
            System.exit(1);
        } catch (WarehouseException ex) {
            System.err.println("Failed to initialize the warehouse: " + ex.getMessage());
            System.exit(2);
        }
    }

    public Collection<Order> getOrders() {
        return orders.stream()
            .map(Order::new)
            .collect(Collectors.toUnmodifiableList());
    }

Dobre praktyki

  • łatwość wprowadzenia zmian i bez naruszania struktury
  • luźne wiązania
  • możliwość ponownego wykorzystania
  • unikać powtórzeń kodu, długich procedur i długich pętli, a także zbyt dużych zagnieżdżeń
  • żeby nie było zbyt dużo parametrów w funkcji czy konstruktorze, można użyć buldiera
  • żeby w funkcjach był taki sam poziom abstrakcji
  • jak klasa łączy kilka słabo powiązanych zakresów odpowiedzialności - trzeba ją podzielić
  • konstruktor max 3 elementowy
  • klasy niemodyfikowalne (final, bez seterów, klasy zawierające są też niemodyfikowalne)
  • czyste funkcje (jak już coś musimy zmienić to zwracamy nowy obiekt)
  • unikamy nuli (przez zwracanie Optional)

Praktyki 1

  • Dobrze jest sprawdzić ograniczenia parametrów, dzięki czemu będzie można szybciej wykryć błąd
  • Dobre nazwy metod
  • Bez długich nazw parametrów
  • Lepiej używać interfejsów zamiast klas
  • Lepiej dwuelementowe enum niż bool
  • Lepiej nie wystawiać takiej samej metody z taką samą liczbą parametrów, a najlepiej ją inaczej nazwać
  • Nie zwracać nulli zamiast pustych tablic lub kolekcji

Praktyki 2

  • używać pętli for (jeszcze lepiej foreach) zamiast while żeby ograniczyć zasięg zmiennych, a jeszcze lepiej małych metod
  • ThreadLocalRandom zamiast Random
  • Poznanie i wykorzystywanie bibliotek
  • Używać Bigdecimal (albo kwoty np w groszach i wtedy int) zamiast float i double jeśli potrzebne są dokładne wartości np. final BigDecimal TEN_CENTS = new BigDecimal( ".10");
  • Używać typów prostych zamiast typów opakowanych wszędzie tam gdzie jest to możliwe. Typy proste są mniej skomplikowane i szybsze.
  • Unikać typu String, gdy istnieją bardziej odpowiednie typy
  • Do łączenia używać StringBuilder zamiast plusa jeśli łączymy dużo zamiennych bo ma lepszą wydajność
  • deklarować zmienne jako interfejs wtedy będą bardziej elastyczne a jak się nie da to jak najwyższa klasa, która ma wszystko co potrzebujemy (wyjątek klasy z wartościami np. String i jak są częścią modelu używającego klas)

Nazewnictwo

  • pakiety z domeną, małymi
  • klasy i interfejsy CamelCase, pierwsza duża
  • metody i pola CamelCase, pierwsza mała
  • stałe (statyczne pole final) - dużymi z podkreślnikami
  • nazwy parametrów typu: jedna litera: T dla dowolnego typu, E dla typu elementów kolekcji, K oraz V dla typów kluczy i wartości w odwzorowaniach oraz X dla wyjątków. Typem zwracanym przez funkcję jest najczęściej R. Sekwencją dowolnych typów może być T, U, V lub T1, T2, T3.

Tworzenie i usuwanie obiektów

  • decimal lepszy do liczb od float (błąd zaokrąglenia)
  • 0 i 1 to jedyne dopuszczalne literały, reszta jako stałe
  • deklaracja zmiennej blisko użycia,
  • w switch jako default błędy
  • należy używać typów prostych zamiast opakowanych typów prostych i uważać na niezamierzone operacje autoboxingu
  • nie przechowywać referencji do obiektów jeśli nie będą już używane np. w tablicy (można przypisać im wartość null)
  • nie należy korzystać z oczyszczaczy, a w przypadku Javy 8 lub wcześniejszej — z finalizatorów, poza mechanizmami „siatek bezpieczeństwa” lub zwalniania niekrytycznych zasobów systemu operacyjnego
  • klasa reprezentująca zasób wymagający zamknięcia powinna implementować interfejs AutoCloseable. Potem można robić try z zasobami:
try (InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dst)) {
      byte[] buf = new byte[BUFFER_SIZE];
      int n;
      while ((n = in.read(buf)) >= 0)
         out.write(buf, 0, n);
   }
  • jeśli coś ma być wydajne to zamiast tworzyć ciągle nowe obiekty można zapisać np. pattern do zmiennej finalnej:
public class RomanNumerals {
   private static final Pattern ROMAN = Pattern.compile(
      "^(?=.)M*(C[MD]|D?C{0,3})"
      + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
   static boolean isRomanNumeral(String s) {
      return ROMAN.matcher(s).matches();
   }
}

Optional

Jeśli tworzymy metodę, która nie zawsze będzie w stanie zwrócić wartość, i wierzymy, że dla użytkowników metody ważne będzie każdorazowe uwzględnienie takiej ewentualności, warto rozważyć zwrócenie obiektu opcjonalnego. Musimy jednak pamiętać o prawdziwych konsekwencjach wydajnościowych związanych ze zwracaniem takich obiektów — w metodach wymagających szczególnie dużej wydajności zwrócenie null lub zgłoszenie wyjątku może okazać się lepszym wyborem. Prawie nigdy nie stosuj obiektów opcjonalnych w sytuacjach innych niż zwracanie wartości.

// Wykorzystanie obiektu opcjonalnego do zgłoszenia wybranego wyjątku
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);

Solid

S - Single Responsibility Principle - Pojedyncza odpowiedzialność

Klasa (albo metoda) ma pojedynczą odpowiedzialność. Nigdy nie istnieje więcej niż 1 powód do modyfikacji Czyli jak 2 osoby chcą coś innego od obiektu to rozdzielić to na 2 klasy, albo jak jest coś stałego i coś co może się zmienić to najlepiej to rozdzielić tak, że zostaje stała klasa i np. fileSaver(Book book) używany do zapisu. Trzeba używać z głową, jak będzie za dużo klas to też będzie trudno się połapać. Zwykle wystarcza aby klasa nie łamała różnych poziomów abstrakcji.

O - Open Close Principle - otwartość na rozszerzenia, zamknięcie na modyfikacje

Przy zmianie wymagań nie powinien być zmieniany stary kod, ale dodawany nowy, który rozszerza zachowanie Można:

  • zastosować wzorzec strategia (co masz zrobić dostajesz w klasie dostarczonej np. w konstruktorze)
  • zrobić klasę abstrakcyjną

L - Liscov Substition Principle - Zasada podstawienia Liscov

Korzystanie z funkcji klasy bazowej musi być również możliwe w przypadku podstawienia klas pochodnych Klasa dziecko nie może zmieniać za dużo, np. nie może być tak, że setter szerokości ustawia też długość w kwadracie, bo jeśli będziemy chcieli gdzieś użyć kwadratu jako prostokąta to mu nie ustawimy różnej szerokości i długości. Metoda nadpisywana musi mieć taki same przyjmowane parametry, zwracane i w takich samych okolicznościach zwracać takiego samego typu wyjątki.

I - Interface Separation Pricible - Zasada segregacji interfejsów

Wiele dedykowanych interfejsów jest lepsze niż jeden ogólny. Klasa nie powinna implementować całego dużego interfejsu jeśli nie używa go w całości. Powinno się podzielić ten interfejs na małe części. Interfejs powinien odzwierciedlać potrzeby klienta.

D - Dependency Inversion Principle - Zasada odwrócenia zależności

Klasa wymaga źródła danych, nie interesuje ją szczegóły dostarczenia tych danych, bo mogą być różne: Nie może być tak, że np. jakiś reader przyjmuje obiekty o danej klasie, zamiast tego powinien przyjmować obiekty z określonym interfejsem Wysokopoziomowe moduły nie powinny zależeć od modułów niskopoziomowych. Zależności pomiędzy nimi powinny być wyrażone przez intrefejsy. Interfejs nie powinien być zależny od implementacji. To implementacja powinna być zależna od interfejsu. Naprawa poprzez wstrzykiwanie zależności

⚠️ **GitHub.com Fallback** ⚠️