Trzy lekcje Java, których nauczyłem się na własnej skórze
Źródło: Medium Nauka języka Java jest trudna. Uczyłem się na swoich błędach. Teraz i Ty możesz uczyć się na moich błędach i gorzkich doświadczeniach, których wcale nie musisz mieć.1. Lambdy mogą powodować problemy.
Lambdy często przekraczają 4 linie kodu i są większe niż oczekiwano. To obciąża pamięć roboczą. Czy musisz zmienić zmienną z lambda? Nie możesz tego zrobić. Dlaczego? Jeśli lambda może uzyskać dostęp do zmiennych witryny wywołania, mogą pojawić się problemy z wątkami. Dlatego nie można zmienić zmiennych z lambda. Ale szczęśliwa ścieżka w lambdzie działa dobrze. W przypadku awarii środowiska wykonawczego otrzymasz następującą odpowiedź:at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
Trudno jest śledzić ślad stosu lambda. Nazwy są mylące i trudne do wyśledzenia i debugowania. Więcej lambd = więcej śladów stosu. Jaki jest najlepszy sposób debugowania lambd? Użyj wyników pośrednich.
map(elem -> {
int result = elem.getResult();
return result;
});
Innym dobrym sposobem jest użycie zaawansowanych technik debugowania IntelliJ. Użyj klawisza TAB, aby wybrać kod, który chcesz debugować i połączyć go z wynikami pośrednimi. „Kiedy zatrzymamy się na linii zawierającej lambdę i naciśniemy klawisz F7 (wejście), wówczas IntelliJ podświetli fragment, który wymaga debugowania. Możemy przełączyć blok na debugowanie za pomocą klawisza Tab, a gdy już podejmiemy taką decyzję, ponownie naciśnij klawisz F7. Jak uzyskać dostęp do zmiennych witryny wywołań z lambda? Możesz uzyskać dostęp tylko do zmiennych końcowych lub faktycznie końcowych. Musisz utworzyć opakowanie wokół zmiennych lokalizacji połączenia. Albo używając AtomicType , albo używając własnego typu. Możesz zmienić utworzone opakowanie zmiennej za pomocą lambda. Jak rozwiązać problemy ze śledzeniem stosu? Użyj nazwanych funkcji. W ten sposób możesz szybko znaleźć odpowiedzialny kod, sprawdzić logikę i rozwiązać problemy. Użyj nazwanych funkcji, aby usunąć tajemniczy ślad stosu. Czy powtarza się ta sama lambda? Umieść go w nazwanej funkcji. Będziesz miał jeden punkt odniesienia. Każda lambda otrzymuje wygenerowaną funkcję, co utrudnia jej śledzenie.
lambda$yourNamedFunction
lambda$0
Funkcje nazwane rozwiązują inny problem. Duże lambdy. Funkcje nazwane dzielą duże lambdy, tworzą mniejsze fragmenty kodu i tworzą funkcje podłączalne.
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)
2. Problemy z listami
Musisz pracować z listami ( Listy ). Potrzebujesz HashMap dla danych. Do ról będziesz potrzebować TreeMap . I tak dalej. Nie ma sposobu, aby uniknąć pracy z kolekcjami. Jak zrobić listę? Jakiej listy potrzebujesz? Czy powinno być niezmienne czy zmienne? Wszystkie te odpowiedzi wpływają na przyszłość Twojego kodu. Wybierz odpowiednią listę z wyprzedzeniem, aby później nie żałować. Arrays::asList tworzy listę „od końca do końca”. Czego nie możesz zrobić z tą listą? Nie możesz zmienić jego rozmiaru. On jest niezmienny. Co możesz tutaj zrobić? Określ elementy, sortowanie lub inne operacje, które nie wpływają na rozmiar. Używaj Arrays::asList ostrożnie, ponieważ jego rozmiar jest niezmienny, ale jego zawartość nie. new ArrayList() tworzy nową listę „modyfikowalną”. Jakie operacje obsługuje utworzona lista? To wszystko i jest to powód, aby zachować ostrożność. Twórz modyfikowalne listy z niezmiennych, używając new ArrayList() . List::of tworzy „niezmienną” kolekcję. Jego rozmiar i zawartość pozostają niezmienione pod pewnymi warunkami. Jeśli zawartość to prymitywne dane, takie jak int , lista jest niezmienna. Spójrz na następujący przykład.@Test
public void testListOfBuilders() {
System.out.println("### TESTING listOF with mutable content ###");
StringBuilder one = new StringBuilder();
one.append("a");
StringBuilder two = new StringBuilder();
two.append("a");
List<StringBuilder> asList = List.of(one, two);
asList.get(0).append("123");
System.out.println(asList.get(0).toString());
}
### TESTOWAListalistOF ze zmienną zawartością ### a123
Musisz utworzyć niezmienne obiekty i wstawić je do List::of . Jednak List::of nie zapewnia żadnej gwarancji niezmienności. List::of zapewnia niezmienność, niezawodność i czytelność. Wiedz, kiedy używać struktur mutowalnych, a kiedy niezmiennych. Lista argumentów, które nie powinny się zmieniać, powinna znajdować się na liście niezmiennej. Lista zmienna może być listą zmienną. Dowiedz się, jakiej kolekcji potrzebujesz, aby utworzyć niezawodny kod.
3. Adnotacje spowalniają
Czy korzystasz z adnotacji? Czy je rozumiesz? Czy wiesz, co oni robią? Jeśli myślisz, że adnotacja Logged jest odpowiednia dla każdej metody, to się mylisz. Użyłem Loged do rejestrowania argumentów metod. Ku mojemu zaskoczeniu, to nie zadziałało.@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
Co jest nie tak z tym kodem? Jest tu dużo podsumowań konfiguracji. Spotkasz się z tym wiele razy. Konfiguracja jest zmieszana ze zwykłym kodem. Samo w sobie nie jest złe, ale przyciąga wzrok. Adnotacje są potrzebne, aby zredukować kod szablonowy. Nie musisz pisać logiki rejestrowania dla każdego punktu końcowego. Nie ma potrzeby konfigurowania transakcji, użyj @Transactional . Adnotacje redukują wzorzec poprzez wyodrębnienie kodu. Nie ma tutaj wyraźnego zwycięzcy, ponieważ obaj biorą udział w grze. Nadal używam XML i adnotacji. Kiedy znajdziesz powtarzający się wzór, najlepiej przenieść logikę do adnotacji. Na przykład rejestrowanie jest dobrą opcją dodawania adnotacji. Morał: nie nadużywaj adnotacji i nie zapomnij o XML.
Bonus: Możesz mieć problemy z opcją Opcjonalną
Użyjesz orElse z Option . Niepożądane zachowanie występuje, gdy nie zostanie przekazana stała orElse . Warto o tym wiedzieć, aby uniknąć problemów w przyszłości. Spójrzmy na kilka przykładów. Kiedy getValue(x) zwraca wartość, wykonywana jest funkcja getValue(y) . Metoda w orElse jest wykonywana, jeśli getValue(x) zwróci niepustą wartość opcjonalną .getValue(x).orElse(getValue(y)
.orElseThrow(() -> new NotFoundException("value not present")));
public Optional<Value> getValue(Source s)
{
System.out.println("Source: " + s.getName());
// returns value from s source
}
// when getValue(x) is present system will output
Source: x
Source: y
Użyj orElseGet . Nie wykona kodu dla niepustych Opcjonalnych .
getValue(x).orElseGet(() -> getValue(y)
.orElseThrow(() -> new NotFoundException("value not present")));
public Optional<Value> getValue(Source s)
{
System.out.println("Source: " + s.getName());
// returns value from s source
}
// when getValue(x) is present system will output
Source: x
Wniosek
Nauka Javy jest trudna. Javy nie nauczysz się w 24 godziny. Doskonalij swoje umiejętności. Poświęć czas, ucz się i doskonal się w swojej pracy.Jak używać zasad SOLID w kodzie
Źródło: Cleanthecode Pisanie niezawodnego kodu wymaga zasad SOLID. W pewnym momencie każdy z nas musiał nauczyć się programować. I bądźmy szczerzy. Byliśmy GŁUPI. A nasz kod był taki sam. Dzięki Bogu mamy SOLIDNE.SOLIDNE zasady
Jak więc napisać kod SOLID? To właściwie proste. Wystarczy przestrzegać pięciu zasad:- Zasada pojedynczej odpowiedzialności
- Zasada otwarte-zamknięte
- Zasada zastępstwa Liskowa
- Zasada separacji interfejsów
- Zasada inwersji zależności
Zasada pojedynczej odpowiedzialności
W swojej książce Robert C. Martin opisuje tę zasadę w następujący sposób: „Klasa powinna mieć tylko jeden powód do zmiany”. Przyjrzyjmy się razem dwóm przykładom.1. Czego nie robić
Mamy klasę o nazwie User , która pozwala użytkownikowi wykonywać następujące czynności:- Zarejestruj konto
- Zaloguj sie
- Otrzymuj powiadomienie przy pierwszym logowaniu
2. Co robić
Wyobraź sobie klasę, która powinna wyświetlać powiadomienie nowemu użytkownikowi, FirstUseNotification . Będzie składać się z trzech funkcji:- Sprawdź, czy powiadomienie nie zostało już wyświetlone
- Pokaż powiadomienie
- Oznacz powiadomienie jako już pokazane
Zasada otwarte-zamknięte
Zasada open-closed została ukuta przez Bertranda Meyera: „Obiekty oprogramowania (klasy, moduły, funkcje itp.) powinny być otwarte na rozszerzenie, ale zamknięte na modyfikację”. Zasada ta jest w rzeczywistości bardzo prosta. Musisz napisać swój kod, aby można było dodawać do niego nowe funkcje bez zmiany kodu źródłowego. Pomaga to zapobiec sytuacji, w której konieczna będzie zmiana klas zależnych od zmodyfikowanej klasy. Zasada ta jest jednak znacznie trudniejsza do wdrożenia. Meyer zasugerował użycie dziedziczenia. Ale to prowadzi do silnego połączenia. Omówimy to w Zasadach separacji interfejsów i Zasadach inwersji zależności. Dlatego Martin zaproponował lepsze podejście: użyj polimorfizmu. Zamiast konwencjonalnego dziedziczenia w tym podejściu wykorzystuje się abstrakcyjne klasy bazowe. W ten sposób specyfikacje dziedziczenia można ponownie wykorzystać, gdy implementacja nie jest wymagana. Interfejs można zapisać raz, a następnie zamknąć w celu wprowadzenia zmian. Nowe funkcje muszą następnie implementować ten interfejs i go rozszerzać.Zasada zastępstwa Liskowa
Zasada ta została wymyślona przez Barbarę Liskov, zdobywczynię nagrody Turinga za jej wkład w języki programowania i metodologię oprogramowania. W swoim artykule zdefiniowała swoją zasadę w następujący sposób: „Obiekty w programie powinny być zastępowalne przez instancje ich podtypów, bez wpływu na prawidłowe wykonanie programu”. Przyjrzyjmy się tej zasadzie jako programista. Wyobraź sobie, że mamy kwadrat. Może to być prostokąt, co brzmi logicznie, ponieważ kwadrat jest specjalnym kształtem prostokąta. Tutaj na ratunek przychodzi zasada zastąpienia Liskowa. Gdziekolwiek spodziewasz się zobaczyć w kodzie prostokąt, możliwe jest również pojawienie się kwadratu. Teraz wyobraź sobie, że Twój prostokąt ma metody SetWidth i SetHeight . Oznacza to, że kwadrat również potrzebuje tych metod. Niestety, nie ma to żadnego sensu. Oznacza to, że naruszona jest tutaj zasada zastępowania Liskowa.Zasada separacji interfejsów
Podobnie jak wszystkie zasady, zasada separacji interfejsów jest znacznie prostsza, niż się wydaje: „Wiele interfejsów specyficznych dla klienta jest lepszych niż jeden interfejs ogólnego przeznaczenia”. Podobnie jak w przypadku zasady pojedynczej odpowiedzialności, celem jest ograniczenie skutków ubocznych i liczby wymaganych zmian. Oczywiście nikt nie pisze takiego kodu celowo. Ale łatwo go spotkać. Pamiętasz kwadrat z poprzedniej zasady? Teraz wyobraź sobie, że decydujemy się na realizację naszego planu: z prostokąta produkujemy kwadrat. Teraz zmuszamy kwadrat do wdrożenia setWidth i setHeight , które prawdopodobnie nic nie dają. Gdyby to zrobili, prawdopodobnie coś byśmy zepsuli, ponieważ szerokość i wysokość nie byłyby takie, jakich się spodziewaliśmy. Na szczęście dla nas oznacza to, że nie naruszamy już zasady podstawienia Liskowa, ponieważ teraz pozwalamy na użycie kwadratu wszędzie tam, gdzie używamy prostokąta. Stwarza to jednak nowy problem: naruszamy teraz zasadę separacji interfejsów. Zmuszamy klasę pochodną do zaimplementowania funkcjonalności, której nie chce używać.Zasada inwersji zależności
Ostatnia zasada jest prosta: moduły wysokiego poziomu powinny nadawać się do ponownego użycia i zmiany w modułach niskiego poziomu nie powinny mieć na nie wpływu.- O. Moduły wysokiego poziomu nie powinny zależeć od modułów niskiego poziomu. Obydwa muszą zależeć od abstrakcji (takich jak interfejsy).
- B. Abstrakcje nie powinny zależeć od szczegółów. Szczegóły (konkretne realizacje) muszą zależeć od abstrakcji.
- Moduł wysokiego poziomu, w zależności od abstrakcji
- Moduł niskiego poziomu w zależności od tej samej abstrakcji
GO TO FULL VERSION