Polimorficzne wyjątki - pomysł i wykonanie

Polimorficzne wyjątki - pomysł i wykonanie
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
1

Pracowałem nad pewnym projektem w pracy (nie powiem nazwy firmy), i trafiłem na przypadek w którym co jakiś czas do naszej implementacji dochodzą nowe wyjątki. Na początku był jeden, potem dochodziły nowe, co jakiś czas, teraz z tego kawałka implementacji lecą 4 wyjątki. Są to różne case'y, ale domenowo zbliżone do siebie. Starałem się rozplanować architekturę, czy na pewno nie przesadzamy z tymi wyjątkami, próbowałem podejść do tego od strony Dependency Inversion, jak również Principle of Least Astonishment, Liskov Substitution, bo czułem że to że mamy 4 wyjątki tak podobne do siebie to coś podejrzanego; po kilku godzinach rozmyślań doszedłem do wniosku, że tak, te 4 różne wyjątki muszą być rozróżnione, dlatego że chcemy je obsługiwać w różnych sposób, ale też to że dodajemy co jakiś czas musi być ogarnięte w jakiś sposób, bo jeśli zachowamy liniowy trend to za 2 lata będzie ich 12 :D

Więc, wpadłem na kontrowersyjny pomysł. Wprowadziłem takie rozwiązanie w piątek, wyjaśniłem to mojemu zespołowi; na razie wprowadzamy to w jednym miejscu, zobaczymy jak pójdzie.

Pomysł to abstrakcyjny, polimorficzny wyjątek. Pomysł za nim jest mniej więcej taki sam jak przy Open/Closed principle, żebyśmy mogli dodawać nowe wyjątki bez potrzeby edycji miejsc w których są catche.

Wygląda to mniej więcej tak

Kopiuj
abstract class DomainException extends RuntimeException {
  public abstract void handle(Visitor visitor);
}

class DomainCaseFirst extends DomainException {
  public void handle(Visitor visitor) {
    visitor.handleFirst();
  }
}

class DomainCaseSecond extends DomainException {
  public void handle(Visitor visitor) {
    visitor.handleFirst();
  }
}

użycie implementacji wygląda tak

Kopiuj
try {
  domainLogic.act();
} catch (DomainException exception) {
  exception.handle(this.visitor);
}

Z logiki biznesowej będziemy rzucać odpowiednie implementacje wyjątków. Zaleta tego rozwiązania jest taka, że można dodawać nowe implementacje do domeny biznesowej oraz nowe case'y handlowania, bez potrzeby edycji już istniejących użyć. Jeśli będziemy chcieli handlować wyjątki dokładniej, to możęmy zrobić catch'a na specyficzny case

Kopiuj
try {
  domainLogic.act();
} catch (DomainCaseSecond exception) {
  // special handling
} catch (DomainException exception) {
  // general handling
  exception.handle(this.visitor);
}

Nie jestem pewien czy ten pomysł wypali - próbuję go pierwszy raz. Nie jestem pewien też czy ktoś na to już kiedyś wpadł (pewnie tak), ale nie znalazłem takich rzeczy - jeśli ktoś zna takie użycie już to proszę o linka. Nie wydaje mi się że pomysł z tym żeby używać wyjątków do kontroli przepływu był czymś dziwnym; polimorficzne wyjątki już prędzej. Na pewno umieszczenie logiki w wyjątkach to byłby bardzo dziwny pomysł, ale call do wizytora - chyba jest git? Nie jestem na 100% pewien tego podejścia.

Jeśli ktoś miałby pomysł na inne rozwiązanie tego problemu, to też chętnie usłyszę pomysły.

Może napiszę post za 6-12 miesięćy z update'em jak rozwiązanie się u nas sprawdza.

edytowany 1x, ostatnio: Riddle
S9
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
  • Postów:1092
8

No ja tam wolę Try/Option/Either ale jak ktoś lubi goto obsługę przez wyjątki no to spoko.


SL
  • Rejestracja:około 7 lat
  • Ostatnio:26 minut
  • Postów:876
4

Za bardzo nie rozumiem tego postu, przecież w taki sposób wyglądają hierarchie wątków w praktycznie każdym języku https://rollbar.com/blog/java-exceptions-hierarchy-explained/ . Co do alternatyw to możesz zawrzeć błąd w typie jako jakiś Either, co jest używane w językach funkcyjnych

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
slsy napisał(a):

Za bardzo nie rozumiem tego postu, przecież w taki sposób wyglądają hierarchie wątków w praktycznie każdym języku https://rollbar.com/blog/java-exceptions-hierarchy-explained/ . Co do alternatyw to możesz zawrzeć błąd w typie jako jakiś Either, co jest używane w językach funkcyjnych

A w którym z nich w wyjątkach są calle do wizytorów?

scibi_92 napisał(a):

No ja tam wolę Try/Option/Either ale jak ktoś lubi goto obsługę przez wyjątki no to spoko.

A z kolei jak Optionalami mogę ohandlować różne case'y biznesowe, tak żeby followały Open/Close?

edytowany 1x, ostatnio: cerrato
SL
  • Rejestracja:około 7 lat
  • Ostatnio:26 minut
  • Postów:876
4

@TomRiddle: Nie widzę tego jak Open-Closed ma się do wyjątków, bo wyjątki to trochę wytrych w OOP. Całe OOP i wzorce opierają się jak abstrahować zachowania pod postacią interfejsów. Z drugiej strony w przypadku exception handlingu interesuje nas po prostu typ wyjątku, wyjątki same w sobie nie mają logiki.

Co do pomysłu to wydaje mi się, że bez gotowego kodu ciężko ogarnąć czy ma to sens

Riddle
Widzę że nie specjalnie zrozumiałeś istotę pojego pierwszego wpisu.
SL
Rozumiem ideę, po prostu nie potrafię wyobrazić sobie problemu, gdzie ma to sens. Jaki sens ma wymuszanie łapania wszystkich wyjątków z danej hierarchii? Jak ktoś chce dodać obsługę wyjątku Y to ją doda, jak nie chce to obsłuży bardziej generyczny X. Jeśli jednak jest to konieczne to wyjątki to raczej słaby pomysł: bardziej szedłbym w te Eithery
Riddle
@slsy: Noi dokładnie takie podejście mamy teraz i nie spełnia Open/Close, dlatego staram się znaleźć alternatywę.
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 10 godzin
  • Lokalizacja:Wrocław
9

Logika w wyjątkach i jeszcze wizytor do tego. Może to architektoniczny sen, a może to Java.

edytowany 1x, ostatnio: somekind
Zobacz pozostałe 22 komentarze
UglyMan
trójkącik z @Miang przez całą wieczność. To nawet tego @cerrato zniosę.
Miang
@UglyMan: ale nie wiadomo kto będzie po środku ;)
UglyMan
@Miang: pewnie decyduje kolejność zejścia albo kto będzie miał lepsze zasługi
PerlMonk
Ja mogę wam dolewać smoły do kotła :]
nalik
A potem powstają tematy o przeinżenierowanym kodzie.
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
3

Czy na prawdę na tym forum rację bytu mają jedynie przyklepane schematy, a ekstremalne, choć przemyślane próby są automatycznie utożsamiane z "kiepskim pomysłem"?

Szkoda że nie znalazłem nikogo kto rozważyłby potencjalne wady i zalety, zamiast od razu odrzucać dany pomysł.

Tak czy tak, dziękuję za uczestnictwo! :)

edytowany 1x, ostatnio: Riddle
PerlMonk
"Czy na prawdę na tym forum rację bytu mają jedynie przyklepane schematy" - tak. A zupełnie poważnie, jest sporo osób, które robią coś ciekawego a na forum nawet nie zajrzą. Swoją opinią więc się nie podzielą.
KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:5 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
4

Zastanawiam się po co wizytator z jedną implementacją. Będziesz miał różne wizytatorzy?

UPDATE pewne przemyślenia: wyjątki rzuca się dlatego że wołany nie wie jak zareagować na błąd. Jakby wiedział to by mógł wykonać odpowiednią reakcję. Dzięki wyjątków to wołający może zareagować w taki sposób jak chce

Ty jednak masz jeden standardowy sposób reakcji na wyjątki (na wyjątek A zawsze zareagujesz w sposób A', na wyjątek B w sposób B' itd). zastanawiam się czy w takim razie naprawdę potrzebujesz wyjątków


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
edytowany 1x, ostatnio: KamilAdam
maszrum
Kiedyś zostałem zjechany za wizytator, więc ostrzegam :P
KamilAdam
Ktoś cię zjechał za użycie tego antywzorce czy za to że pisze się wizytor/odwiedzający?
KamilAdam
Ja się nie znam na antywzorcsch. Tak jest na wikipedii
maszrum
Mi to tam specjalnie nie przeszkadza. Tak tylko wolałem ostrzec ;)
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
KamilAdam napisał(a):

Zastanawiam się po co wizytator z jedną implementacją. Będziesz miał różne wizytatorzy?

Tak, aktualnie są dwie implementacje, ale spodziewam się że będzie więcej.

UPDATE pewne przemyślenia: wyjątki rzuca się dlatego że wołany nie wie jak zareagować na błąd. Jakby wiedział to by mógł wykonać odpowiednią reakcję. Dzięki wyjątków to wołający może zareagować w taki sposób jak chce

Tak, chyba że chce mieć różne sposoby handlowania go w różnych wizytorach. Jeśli wyjątek od razu byłby ohandlowany, to wołający nie mógłbym o tym zdecydować. Teraz np mogę przekazać anonimową implementację.

Ty jednak masz jeden standardowy sposób reakcji na wyjątki (na wyjątek A zawsze zareagujesz w sposób A', na wyjątek B w sposób B' itd). zastanawiam się czy w takim razie naprawdę potrzebujesz wyjątków

Tak, dlatego że ścieżka którą idzie implementacja jest zagmatwana, i czasem jest dynamiczna, więc wyjątki to idealny sposób żeby powiadomić wołającego że coś się stało.

edytowany 3x, ostatnio: Riddle
Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:około 5 godzin
  • Postów:1874
3

Jaki problem rozwiązujesz? Chcesz w różny sposób (za pomocą wyjątków) obsługiwać 4 błędy - masz 4 podklasy wyjątków. Po co tutaj visitor? Czego nie rozumiem? :)


”Engineering is easy. People are hard.” Bill Coughran
edytowany 3x, ostatnio: Charles_Ray
S9
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
  • Postów:1092
4

Czy na prawdę na tym forum rację bytu mają jedynie przyklepane schematy, a ekstremalne, choć przemyślane próby są automatycznie utożsamiane z "kiepskim pomysłem"?

Bo nie wiem czemu chcesz sterować logika aplikacji przez goto. Bo nie ma za bardzo czegoś takiego jak DomenowyException. Exception to może być w##DSD się połączenie z siecią czy z bazą danych (bo tego się nie spodziewamy).


edytowany 1x, ostatnio: scibi_92
Aventus
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
0

Tak, coś takiego się stosuje i nie jest to nic nowego (jak sam podejrzewałeś). Ja to najczęściej stosuje w ramach aplikacji webowych, gdzie jakiś middleware przechwytuje taki wyjątek i zwraca odpowiedni kod. Chociaż nie wiem po co tutaj visitor...

@scibi_92 nie zgadzam się z argumentem że to odpowiednik sterowania goto. Rzucając wyjątek przerywam cały flow logiki biznesowej, a warstwa aplikacji może zwrócić odpowiedni kod błędu do klienta. W przypadku DomainException to jest zazwyczaj w moim przypadku 422.

Zdaje sobie sprawę że są alternatywne podejścia, ale to czy coś jest właściwe zależy od języka, konkretnej aplikacji itp. Wszystko ma swoje wady i zalety. Zwracanie jakiegoś wyniku ma np. taką wadę że trzeba to "bąbelkować" przez warstwy wywołań logiki, obsługiwać w różnych miejscach itp. Fajnie to wygląda w językach funkcyjnych pozwalających "naturalnie" zaprzęgać coś takiego w łańcuch wywołań funkcji, i przerywać dalsze wywoływanie w przypadku wyniku błędu. Ale coś takiego nie zawsze jest możliwe, i rzucenie wyjątkiem pozwala na szybkie przerwanie wykonywania się logiki bez nadmiernego sprawdzania wyniku pomiędzy różnymi etapami.

Exception to może być w##DSD się połączenie z siecią czy z bazą danych (bo tego się nie spodziewamy).

Exception to może być rowniez np. brakująca wartość w warstwie domeny, która powinna być wyłapana wcześniej przez aplikację klienta (np. walidacja DTO w web API).


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 2x, ostatnio: Aventus
Miang
zgadzam się goto może mieć jakiś sens, sterowanie wyjątkami nie ma
Aventus
@Miang: Tu nie ma sterowania bo wyjątek kończy się kodem błędu zwróconym do klienta. Ludzie chyba często nadinterpretują "sterowanie". Sterowanie jest wtedy kiedy na podstawie jakiegoś błędu wykonam jakąś inną logikę biznesową. W przypadku który ją opisuję nie ma o czymś takim mowy.
Miang
@Aventus: no ale Ty opisujesz przypadek inny niż ten z początku wątku, tam właśnie chodzi o sterowanie :(
Aventus
@Miang: autor nie wspomniał wprost wykonywania innej logiki biznesowej na podstawie wyjątku, a tylko jego przechwytywanie. Jeśli coś przeoczyłem i taka jest propozycja to faktycznie nie jest to dobry pomysł. Taki odwrócony IF obsługiwany nie wiadomo gdzie.
S9
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
  • Postów:1092
2

Exception to może być rowniez np. brakująca wartość w warstwie domeny, która powinna być wyłapana wcześniej przez aplikację klienta (np. walidacja DTO w web API).

No dobra, tu masz rację. Jeśli zakładamy że pesel nie powinien przejśc niepoprawny do domeny, to wtedy rzeczywiście wtedy może to być jakiś wyjątek. Czyli w sumie to i tak jest sytuacja teoretycznie wyjątkowa :D


edytowany 1x, ostatnio: scibi_92
Aventus
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
0

Małe sprostowanie na podstawie tego co odpisali @scibi_92 oraz @Miang. Oczywiście jestem zdania że wyjątki są od sytuacji wyjątkowych, i nie powinny być używane do sterowania wykonywaniem się logiki biznesowej. Jeśli taki jest Twój zamysł @TomRiddle to popieram przedmówców że to nie jest najlepszy pomysł.

Z drugiej strony jeśli to pomoże rozwiązać jakiś konkretny problem i przyniesie więcej pożytku niż szkody, to kim ja jestem żeby oceniać coś zastosowane na potrzeby Waszego konkretnego przypadku? Bezrefleksyjne trzymanie się zasad bo "tak jest i się z tym nie dyskutuje" zabija innowacje. Do wszystkiego trzeba podchodzić z głową, a jeśli coś nie wypali to wyciągnąć z tego wnioski.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
Zobacz pozostały 1 komentarz
S9
@Charles_Ray: Nie zauważyłam żeby ktoś pisał że wyjątki są złe, ale może nie bez powodu noszę okulary
S9
@Charles_Ray: nie zauważyłem odpowiedzi. Jedyne co widzę to krytyka używania wyjatków sytuacjach nie wyjątkowych. Nawet w Haskellu są wyjątki. Robisz więc po prostu chochoła, przykra sprawa.
WeiXiao
@scibi_92: Jedyne co widzę to krytyka używania wątków w sytuacjach nie wyjątkowych. ;) 😂😂
S9
Freudowska pomyłka, oglądałem prezentacje o Project Loom i byłem zachwycony teraz widzę wszędzie wątki ;]
MA
@scibi_92: z produkcyjnych rozwiązań to stacklessPython jest używany w Eve Online - co chyba ma podobne koncepcje co Project Loom: https://stackless.readthedocs.io/en/2.7-slp/library/stackless/tasklets.html mikrowątków lekkich i przerobieniem wszystkiego na wątki
WeiXiao
  • Rejestracja:około 9 lat
  • Ostatnio:około 12 godzin
  • Postów:5109
1

Tak, aktualnie są dwie implementacje, ale spodziewam się że będzie więcej.

bez potrzeby edycji miejsc w których są catche.

brzmi to sensownie, aczkolwiek ja osobiście nie lubię w odwiedzaczu tego że wymaga mnóstwa kodu i pewnie bym olał compile-time safety* i poszedł w kierunku:

ale nie mam pojęcia jakby to się sprawdziło w praktyce

Kopiuj
public interface IExceptionHandler
{
	void Handle(BaseEx b);
}

public class Handler1 : IExceptionHandler
{
	public void Handle(BaseEx ex)
	{
		if (ex is AEx a)
		{
			Internal_A_Handler(a);
		}
		else if (ex is BEx b)
		{
			Internal_B_Handler(b);
		}
		else
		{
			throw new NotSupportedException("lol");
		}
	}

	private void Internal_B_Handler(BEx b)
	{
		// blabla
	}

	private void Internal_A_Handler(AEx a)
	{
		// blabla
	}
}

* - chociaż może dałoby się nowym C# expr switchem poczarować

Kopiuj
public enum Test
{
    A,
    B
}

static void Main(string[] args)
{
    var a = Test.A;
    var q = a switch
    {
        Test.A => throw new NotImplementedException()
    };
}

aby rzucił

screenshot-20211017193028.png

a tak btw

odwiedzacz, double dispatch

edytowany 5x, ostatnio: WeiXiao
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 10 godzin
  • Lokalizacja:Wrocław
3
TomRiddle napisał(a):

Czy na prawdę na tym forum rację bytu mają jedynie przyklepane schematy, a ekstremalne, choć przemyślane próby są automatycznie utożsamiane z "kiepskim pomysłem"?

Szkoda że nie znalazłem nikogo kto rozważyłby potencjalne wady i zalety, zamiast od razu odrzucać dany pomysł.

Bo tak jakby podałeś za mało informacji odnośnie tego, po co Ci to wszystko.

TomRiddle napisał(a):

Pracowałem nad pewnym projektem w pracy (nie powiem nazwy firmy), i trafiłem na przypadek w którym co jakiś czas do naszej implementacji dochodzą nowe wyjątki. Na początku był jeden, potem dochodziły nowe, co jakiś czas, teraz z tego kawałka implementacji lecą 4 wyjątki. Są to różne case'y, ale domenowo zbliżone do siebie. Starałem się rozplanować architekturę, czy na pewno nie przesadzamy z tymi wyjątkami, próbowałem podejść do tego od strony Dependency Inversion, jak również Principle of Least Astonishment, Liskov Substitution, bo czułem że to że mamy 4 wyjątki tak podobne do siebie to coś podejrzanego; po kilku godzinach rozmyślań doszedłem do wniosku, że tak, te 4 różne wyjątki muszą być rozróżnione, dlatego że chcemy je obsługiwać w różnych sposób, ale też to że dodajemy co jakiś czas musi być ogarnięte w jakiś sposób, bo jeśli zachowamy liniowy trend to za 2 lata będzie ich 12 :D

Skoro już tak dobrymi praktykami rzucamy, to przyrost o 8 typów w 2 lata to nie jest taki duży problem, żeby teraz porzucić YAGNI.

żebyśmy mogli dodawać nowe wyjątki bez potrzeby edycji miejsc w których są catche.

Ja tu chyba widzę źródło problemu - czemu jeden wyjątek może być obsługiwany w więcej niż jednym catchu?

Z logiki biznesowej będziemy rzucać odpowiednie implementacje wyjątków. Zaleta tego rozwiązania jest taka, że można dodawać nowe implementacje do domeny biznesowej oraz nowe case'y handlowania, bez potrzeby edycji już istniejących użyć.

A tu drugie źródło - gdyby nie rzucać hierarchii wyjątków z logiki biznesowej, to nie trzeba byłoby ich jakoś wymyślnie obsługiwać. Wyjątek z logiki biznesowej powinien kończyć przetwarzanie i trafiać do logów, co więcej można z tym zrobić?

Nie wydaje mi się że pomysł z tym żeby używać wyjątków do kontroli przepływu był czymś dziwnym

Nie jest dziwny, za to jest równie powszechny jak okropny.

A co do samego designu - z klasy X przekazujesz sterowanie do wyjątku, który następnie przekazuje go do zależności klasy X. Jaka jest zaleta z tego kołowrotka uzasadniająca jego istnienie i utrzymywanie?

TomRiddle napisał(a):

Tak, dlatego że ścieżka którą idzie implementacja jest zagmatwana, i czasem jest dynamiczna, więc wyjątki to idealny sposób żeby powiadomić wołającego że coś się stało.

Do tego, to chyba eventy służą, ewentualnie wzorzec obserwator.

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
6

Bez konkretnego przykładu trochę trudno ogarnąć jaki problem chcesz konkretnie rozwiązać, ale mam wrażenie że próbujesz zbudować Event Bus w oparciu o wyjątki. Bo skoro masz jakieś skomplikowane handlery to sugeruje że to nie są żadne wyjątki u ciebie, tylko eventy domenowe.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
2

Jeżeli wyjątki są domenowo zbliżone do siebie, to nie powinny być 1 bardziej generycznym wyjątkiem? O ile w ogole jest sens rzucać jakimiś wyjątkami z domeny na zewnętrz, jak napisał @somekind .
Zasadnicze pytanie, na które nie odpowiedziałeś, to jak są obsługiwane owe wyjątki i jaką logiką sterują. To rzeczywiście brzmi - tak jak wspomniał @Shalom - jakbyście zrobili z wyjątków zdarzenia domenowe. I teraz z jednego problemu, który został niepotrzebnie wprowadzony, robicie drugi, generyczne handlery i hierarchia wyjątków domenowych plus jakieś visitory. I cyk, kolejny stopień komplikacji architektury osiągnięty.

edytowany 2x, ostatnio: nalik
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
somekind napisał(a):
TomRiddle napisał(a):

Pracowałem nad pewnym projektem w pracy (nie powiem nazwy firmy), i trafiłem na przypadek w którym co jakiś czas do naszej implementacji dochodzą nowe wyjątki. Na początku był jeden, potem dochodziły nowe, co jakiś czas, teraz z tego kawałka implementacji lecą 4 wyjątki. Są to różne case'y, ale domenowo zbliżone do siebie. Starałem się rozplanować architekturę, czy na pewno nie przesadzamy z tymi wyjątkami, próbowałem podejść do tego od strony Dependency Inversion, jak również Principle of Least Astonishment, Liskov Substitution, bo czułem że to że mamy 4 wyjątki tak podobne do siebie to coś podejrzanego; po kilku godzinach rozmyślań doszedłem do wniosku, że tak, te 4 różne wyjątki muszą być rozróżnione, dlatego że chcemy je obsługiwać w różnych sposób, ale też to że dodajemy co jakiś czas musi być ogarnięte w jakiś sposób, bo jeśli zachowamy liniowy trend to za 2 lata będzie ich 12 :D

Skoro już tak dobrymi praktykami rzucamy, to przyrost o 8 typów w 2 lata to nie jest taki duży problem, żeby teraz porzucić YAGNI.

No, dla mnie już te 4 to za dużo, początkowo miał być jeden. Także IMO to nie YAGNI.

żebyśmy mogli dodawać nowe wyjątki bez potrzeby edycji miejsc w których są catche.

Ja tu chyba widzę źródło problemu - czemu jeden wyjątek może być obsługiwany w więcej niż jednym catchu?

Bo ta sama logika biznesowa jest wołana z trzech miejsc, jako user, z panelu administratora zupełnie inną drogą jako admin, oraz z crona jako user cron. Każdy z nich musi obsłużyć te 4 wyjątki w inny sposób, spodziewam się że być może, będzie więcej miejsc w przyszłości które będą ją wołały, ale tutaj to już raczej YAGNI.

Z logiki biznesowej będziemy rzucać odpowiednie implementacje wyjątków. Zaleta tego rozwiązania jest taka, że można dodawać nowe implementacje do domeny biznesowej oraz nowe case'y handlowania, bez potrzeby edycji już istniejących użyć.

A tu drugie źródło - gdyby nie rzucać hierarchii wyjątków z logiki biznesowej, to nie trzeba byłoby ich jakoś wymyślnie obsługiwać. Wyjątek z logiki biznesowej powinien kończyć przetwarzanie i trafiać do logów, co więcej można z tym zrobić?

No ale ja chcę tylko obsłużyć 4 sytuacje wyjątkowe. Dla usera chciałbym zwrócić odpowiedni response że się nie udało; dla admina chciałbym zwrócić cały stack track z debugiem oraz innymi rzeczami, a w cronie chciałbym żeby na wyjątek zrobił reschedule'a aktualnego joba. I jeśli ktoś mi chce tutaj mówić, że to nie tak powinno się dziać, to od razu odpowiadam - nie Twój interes co moja aplikacja robi; ja szukam tylko odpowiedniej architektury pod to.

Nie jest dziwny, za to jest równie powszechny jak okropny.

A co do samego designu - z klasy X przekazujesz sterowanie do wyjątku, który następnie przekazuje go do zależności klasy X. Jaka jest zaleta z tego kołowrotka uzasadniająca jego istnienie i utrzymywanie?

No, tak działa wizytor, prawda?

A to czy wizytor to tutaj dobre to podejście to nie wiem, nie byłem pewien na 100%, dlatego wjechałem na forum. Możliwe że jest jakiś inny wzorzec projektowy który lepiej do tego pasuje. Bo 4 catch'e to IMO zły pomysł.

TomRiddle napisał(a):

Tak, dlatego że ścieżka którą idzie implementacja jest zagmatwana, i czasem jest dynamiczna, więc wyjątki to idealny sposób żeby powiadomić wołającego że coś się stało.

Do tego, to chyba eventy służą, ewentualnie wzorzec obserwator.

No ale takie wenty i obserwator nie zatrzymają dalszego działania programu, prawda? A jak się pojawi błąd (wyjątek), to ja chcę przerwać wykonywanie i poinformować tego kto ją zawołał.

PS: no chyba że eventami da się jakoś przerwać wywoływanie?

Shalom napisał(a):

Bez konkretnego przykładu trochę trudno ogarnąć jaki problem chcesz konkretnie rozwiązać, ale mam wrażenie że próbujesz zbudować Event Bus w oparciu o wyjątki.

Ta sama odpowiedź - czy takim eventbusem da się przerwać wywołanie zadań logiki biznesowej? Nie chciałbym obsłużyć błędu, żeby potem wołana logika jeszcze coś tam robiła dodatkowego. Wyjątki zatrzymują wywołanie dopóki nie zostaną złapane, dlatego je wybrałem.

Bo skoro masz jakieś skomplikowane handlery to sugeruje że to nie są żadne wyjątki u ciebie, tylko eventy domenowe.

Hmm, no być może.

To jest np to że user chce wywołać jakąś akcję, ale nie ma na tyle kredytów na swoim koncie, powiedzmy. Albo, jest limit na jakąś akcję do powiedzmy 3ech dziennie, a user chce zrobić coś 4ty raz tego dnia. Ewentualnie to może być coś w stylu, "wyślij wiadomość do mojego polecającego", ale akurat ten user nie ma polecającego.

Ja taką akcję teraz stopuję wyjątkiem NotEnoughCreditsException, ActionLimitReachedException oraz RootReferalException. Być może to jest to o czym mówisz, że to jest "event domenowy", tylko jak takim sposobem zatrzymać flow? Tak jak wyjątki zatrzymują unless caught?

edytowany 2x, ostatnio: Riddle
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
4

To jest np to że user chce wywołać jakąś akcję, ale nie ma na tyle kredytów na swoim koncie, powiedzmy. Albo, jest limit na jakąś akcję do powiedzmy 3ech dziennie, a user chce zrobić coś 4ty raz tego dnia. Ewentualnie to może być coś w stylu, "wyślij wiadomość do mojego polecającego", ale akurat ten user nie ma polecającego.

Serio to dla ciebie są sytuacje wyjątkowe? o_O Bo jak dla mnie to są zupełnie normalne przypadki użycia systemu. Ja bym coś takiego implementował normalnie przez Either (ewentualnie od biedy rzucenie wyjątku jeśli nie mamy fallbacka tylko chcemy wylecieć do samej góry)+generowanie eventu jeśli chcesz zeby jakieś inne elementy systemu jakoś to zdarzenie obsłużyły. W przypadku z wyjątkiem nie ma żadnego handlingu oprócz top-level catch który zamieni wyjątek na odpowiedni response code, a to że ta sytuacja generuje dodatkowo event który jakoś jest obsługiwany w ogóle nie ma zadnego związku z rzuconym wyjątkiem.
Dzięki temu np. obłsuga tego eventu w ogóle może być ogarnięta też przez zupełnie inne serwisy w naszym systemie, bo taki event może lecieć do jakiejs Kafki.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
Shalom napisał(a):

To jest np to że user chce wywołać jakąś akcję, ale nie ma na tyle kredytów na swoim koncie, powiedzmy. Albo, jest limit na jakąś akcję do powiedzmy 3ech dziennie, a user chce zrobić coś 4ty raz tego dnia. Ewentualnie to może być coś w stylu, "wyślij wiadomość do mojego polecającego", ale akurat ten user nie ma polecającego.

Serio to dla ciebie są sytuacje wyjątkowe? o_O

Noooo, tak.

Bo jak dla mnie to są zupełnie normalne przypadki użycia systemu.

No zależy chyba jak na to patrzysz, prawda?

Z punktu widzenia aplikacji - tak, normalne. Z punktu widzenia domeny biznesowej, nielegalna akcja IMO. Takie jest moje zdanie, nie chcę wchodzić w dyskusje na ten temat. Jakby, naturalne wydaje mi się że jak user chce coś kupić, ale nie ma kasy, to powinien dostać wyjątek (IMO!).

Ja bym coś takiego implementował normalnie przez Either (ewentualnie od biedy rzucenie wyjątku jeśli nie mamy fallbacka tylko chcemy wylecieć do samej góry)+generowanie eventu jeśli chcesz zeby jakieś inne elementy systemu jakoś to zdarzenie obsłużyły. W przypadku z wyjątkiem nie ma żadnego handlingu oprócz top-level catch który zamieni wyjątek na odpowiedni response code, a to że ta sytuacja generuje dodatkowo event który jakoś jest obsługiwany w ogóle nie ma zadnego związku z rzuconym wyjątkiem.

No dobrze, ale czy w taki sposób można zatrzymać flow programu? Jak mówiłem, jak logika biznesowa robi trzy rzeczy, A, B, C, i z B poleci wyjątek, to nie chcę żeby C się zrobiło. Z tymi ewentami tak nie ma, rozumiem? Że wykonają się tak czy tak wszystkie, i musiałbym dodać jakiś inny mechanizm który by nie dopuścił żeby C się wykonało?

Dzięki temu np. obłsuga tego eventu w ogóle może być ogarnięta też przez zupełnie inne serwisy w naszym systemie, bo taki event może lecieć do jakiejs Kafki.

No, nie, kafki w ogóle w to nie chcę mieszać, i nie chcę żeby inne serwisy handlowały ten wyjątek, tylko caller.

edytowany 1x, ostatnio: Riddle
S9
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
  • Postów:1092
4

Być może to jest to o czym mówisz, że to jest "event domenowy", tylko jak takim sposobem zatrzymać flow? Tak jak wyjątki zatrzymują unless caught?

Zdradzę sekret: słowo kluczowe return nie musi być na samym końcu metody.


Zobacz pozostałe 3 komentarze
Riddle
@scibi_92: I if lepszy jest na pewno od wyjątku Nie masz pojęcia o czym mówisz. Powodzenia z utrzymaniem, refaktorem i otestowaniem tego na dłuższą metę.
nalik
Sterowanie logiką przez wyjątek wydaje mi się gorsze w utrzymaniu długoterminowo. Można zastanowić się czy zamiast zagnieżdżać wywołania, które mogą się nie powieść, nie można skleić je w jakiś "workflow"/"pipeline", spłaszczając ilość warstw pośrednich.
WeiXiao
Zdradzę sekret: słowo kluczowe return nie musi być na samym końcu metody. ehh :D :D
S9
Przepraszam, ale o co chodzi?
WeiXiao
zbyt wiele razy słyszałem że 1 return is good practice :D
Aventus
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
0

Ja zagram tutaj adwokata diabła, bo- raz jeszcze podkreślając- jeśli rzucimy wyjątkiem którego już nie obsługuje logika biznesowa, a po prostu jak w opisanym przeze mnie wcześniej przypadku obsłuży go warstwa aplikacji i odpowiednio zwróci komunikat o błędzie do klienta (web UI czy cokolwiek innego), to takie podejście jest jak najbardziej normalne. Owszem, można zastosować either itp, jak i całą IFologię z tym związaną, ale to zależy od konkretnego projektu. Czasem to właśnie takie bawienie się w opcjonale będzie przerostem formy nad treścią, czasem nie ale wprowadzenie tego retrospekcyjnie nie da wymiernych korzyści itp. To kwestia konkretnego przypadku więc bezrefleksyjne rzucanie się bo "olaboga, dla czego tak?" jest trochę bez sensu. Co za tym idzie odpowiadając na Twój komentarz @scibi_92

if lepszy jest na pewno od wyjątku

Nie, nie lepszy, ani nie gorszy. To naprawdę zależy.

Jeśli natomiast chodzi o Twój wymóg @TomRiddle aby komunikować pewne wydarzenia innym modułom w systemie to faktycznie do tego najlepiej nadadzą się eventy. Najlepiej poczytaj sobie o architecture event-driven. Jest to coś co można dosyć łatwo wprowadzić nawet w procesie, przy wykorzystaniu wzorca mediator. Nie musi to od razy być coś asynchronicznego z kolejkowaniem jeśli nie ma takiej potrzeby.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 3x, ostatnio: Aventus
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
3

@TomRiddle:

Z punktu widzenia aplikacji - tak, normalne. Z punktu widzenia domeny biznesowej, nielegalna akcja IMO.

Nie bardzo cię rozumiem. Nielegalność akcji nie ma nic wspólnego z wyjątkowością. To jest normalne działanie systemu - nie masz kasy na koncie, nie możesz wypłacić. To jest zwykła domenowa walidacja akcji, nie ma w niej nic wyjątkowego.

No dobrze, ale czy w taki sposób można zatrzymać flow programu? Jak mówiłem, jak logika biznesowa robi trzy rzeczy, A, B, C, i z B poleci wyjątek, to nie chcę żeby C się zrobiło. Z tymi ewentami tak nie ma, rozumiem? Że wykonają się tak czy tak wszystkie, i musiałbym dodać jakiś inny mechanizm który by nie dopuścił żeby C się wykonało?

Tak jak pisałem - albo robimy tam jakieś Eithery i wtedy akcja "sama" się przewie bo jak masz jakieś validateAction().map(this::doX).map(this::doY)... to pierwszy Left sprawi że kolejne map czy flatMap się już nie wykonają, albo, tak jak pisałem, możesz rzucić sobie wyjątek jeśli bardzo chcesz, ale to tylko takie ułatwienie żeby "wyskoczyć" do samej góry wywołań i zwrócić jakiś error code.

Jeśli chcesz zakomunikować komponentom systemu, że "coś się stało" to powinieneś wygenerować sobie odpowiedni event i tyle.

No, nie, kafki w ogóle w to nie chcę mieszać, i nie chcę żeby inne serwisy handlowały ten wyjątek, tylko caller.

Jasne, teraz nie, ale sam pisałeś ze dochodzą ci kolejne "handlery". Nie zdziwi mnie jak pojawi sie wymaganie że jak user wykona nielegalną akcje, to taka informacja powinna zostać przesłana do jakiegoś serwisu security czy audit ;) Chciałem tylko pokazać że opcja z eventami nie ma takich mocnych ograniczeń jak to co teraz robisz.

Kolejny problem jaki widzę z tymi twoimi wyjątkami jest taki, ze ktoś ci moze ten wyjątek złapać, mniej lub bardziej przypadkiem, i nagle w ogóle te twoje handlery się nie odpalą. Ot głupi przykład to odpalenie czegoś w innym watku albo z jakiegoś CompletableFuture. Exlpicite emitowanie eventu jest dużo bardziej niezawodne.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 10 godzin
  • Lokalizacja:Wrocław
2
TomRiddle napisał(a):

No ale ja chcę tylko obsłużyć 4 sytuacje wyjątkowe. Dla usera chciałbym zwrócić odpowiedni response że się nie udało; dla admina chciałbym zwrócić cały stack track z debugiem oraz innymi rzeczami, a w cronie chciałbym żeby na wyjątek zrobił reschedule'a aktualnego joba. I jeśli ktoś mi chce tutaj mówić, że to nie tak powinno się dziać, to od razu odpowiadam - nie Twój interes co moja aplikacja robi; ja szukam tylko odpowiedniej architektury pod to.

Wreszcie opisałeś problem, który chcesz rozwiązać. Powinieneś w pierwszym poście. :P

Skoro masz trzy aplikacje, każda inaczej obsługuje wyjątki, to oznacza 3 catche (po jednym w każdej aplikacji). Nie trzeba żadnych wzorców.

No, tak działa wizytor, prawda?

Owszem, w ten sposób działa, i przez to jest tak bardzo nieintuicyjny. :)

To jest np to że user chce wywołać jakąś akcję, ale nie ma na tyle kredytów na swoim koncie, powiedzmy. Albo, jest limit na jakąś akcję do powiedzmy 3ech dziennie, a user chce zrobić coś 4ty raz tego dnia. Ewentualnie to może być coś w stylu, "wyślij wiadomość do mojego polecającego", ale akurat ten user nie ma polecającego.

Ja taką akcję teraz stopuję wyjątkiem NotEnoughCreditsException, ActionLimitReachedException oraz RootReferalException. Być może to jest to o czym mówisz, że to jest "event domenowy", tylko jak takim sposobem zatrzymać flow? Tak jak wyjątki zatrzymują unless caught?

W ogóle nie powinieneś zaczynać flow, skoro warunki nie są spełnione. Wtedy nie trzeba byłoby niczego przerywać wyjątkiem.

TomRiddle napisał(a):

Z punktu widzenia aplikacji - tak, normalne. Z punktu widzenia domeny biznesowej, nielegalna akcja IMO. Takie jest moje zdanie, nie chcę wchodzić w dyskusje na ten temat. Jakby, naturalne wydaje mi się że jak user chce coś kupić, ale nie ma kasy, to powinien dostać wyjątek (IMO!).

Wyjątek powinien dostać np., gdy mimo walidacji na każdym etapie będzie brakowało jakichś wymaganych danych, albo gdy utraci połączenie z bazą danych, a nie wtedy, gdy próbuje wykonać coś wbrew regułom biznesowym. To, że ludzie się mylą albo kombinują to nie jest nic wyjątkowego, to normalna i oczywista rzecz. Coś oczywistego nie może być jednocześnie wyjątkowe.

nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
2

Ja nadal nie rozumiem dlaczego nie można:

  • Mieć funkcję/komendę/akcję/whatever która łączy A,B i C i która zwróci wartość wskazująca czy operacja się powiodła, zamiast propagować wyjątek do klientów. Kij w wyjątkiem, nich sobie będzie, ale niech nie niesie logiki i będzie obsłużony w jednym miejscu. Wyjątek może nie dopuścić do wykonania C, skoro tak to macie rozwiązane, ok.
  • Informacje dla administratora załatwić poprzez konfigurację poziomu logowania.
  • Reschedule uzależnić od tego czy akcja się powiodła. Ewentualnie reschedule uzależnić od jakiegoś health checka?

Jeżeli problemem jest to, że masz 3 klientów, i każdy ma bogatą logikę obsługiwania wyjątków i sterowanie logiką na tej podstawie, to pytanie czy nie da się tego zamknąć w jednym bycie, który wyjątków na zewnątrz nie zwraca.

edytowany 5x, ostatnio: nalik
Aventus
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
2

@Shalom:

No, nie, kafki w ogóle w to nie chcę mieszać, i nie chcę żeby inne serwisy handlowały ten wyjątek, tylko caller.

Jasne, teraz nie, ale sam pisałeś ze dochodzą ci kolejne "handlery". Nie zdziwi mnie jak pojawi sie wymaganie że jak user wykona nielegalną akcje, to taka informacja powinna zostać przesłana do jakiegoś serwisu security czy audit ;) Chciałem tylko pokazać że opcja z eventami nie ma takich mocnych ograniczeń jak to co teraz robisz.

Jeszcze warto podkreślić że tak jak już wspomniałem eventy != używanie kolejek, asynchroniczność itp. Jeśli coś takiego nie wchodzi w grę to spoko- można emitować eventy i obsługiwać je w ramach tego samego procesu.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
Shalom napisał(a):
TomRiddle napisał(a):

Z punktu widzenia aplikacji - tak, normalne. Z punktu widzenia domeny biznesowej, nielegalna akcja IMO.

Nie bardzo cię rozumiem. Nielegalność akcji nie ma nic wspólnego z wyjątkowością.

No dobra, tu się zgadzam.

To jest normalne działanie systemu - nie masz kasy na koncie, nie możesz wypłacić. To jest zwykła domenowa walidacja akcji, nie ma w niej nic wyjątkowego.

Ale jednak, chciałbym też móc moim podejściem ohandlować coś co faktycznie jest wyjątkowe. Np gdyby ktoś podał zły SECRET_KEY w .env, to chciałbym żeby część userowa na to zareagowała inaczej niż część adminiowa albo cronowa. A co do postu @somekind, to to nie są 3 aplikacje. To jest jedna aplikacja, do której mogą się zalogować dwa typy userów (plus job w cronie). Panel admina i panel usera to jedna aplikacja.

TomRiddle napisał(a):

No dobrze, ale czy w taki sposób można zatrzymać flow programu? Jak mówiłem, jak logika biznesowa robi trzy rzeczy, A, B, C, i z B poleci wyjątek, to nie chcę żeby C się zrobiło. Z tymi ewentami tak nie ma, rozumiem? Że wykonają się tak czy tak wszystkie, i musiałbym dodać jakiś inny mechanizm który by nie dopuścił żeby C się wykonało?

Tak jak pisałem - albo robimy tam jakieś Eithery i wtedy akcja "sama" się przewie bo jak masz jakieś validateAction().map(this::doX).map(this::doY)... to pierwszy Left sprawi że kolejne map czy flatMap się już nie wykonają, albo, tak jak pisałem, możesz rzucić sobie wyjątek jeśli bardzo chcesz, ale to tylko takie ułatwienie żeby "wyskoczyć" do samej góry wywołań i zwrócić jakiś error code.

No dobra, tylko wtedy te Eithery muszą przejść przez wszystkie warstwy, nie miałbym prostych metod które zwracają jedną wartość, tylko taki Either :/ Nie jestem pewien czy chciałbym mieć coś takiego w swojej logice biznesowej. Im bardziej skomplikowana logika biznesowa, tym taki either będzie bardziej skomplikowany. To tak na prawdę, gdyby takie potencjalne źródło problemu mogło wyjść z bardzo niskiego poziomu z logiki biznesowej, to to by znaczyło że praktycznie każda funkcja u mnie musiałaby zwracać Either. I'm not sure :/

Jeśli chcesz zakomunikować komponentom systemu, że "coś się stało" to powinieneś wygenerować sobie odpowiedni event i tyle.

No, tylko w sumie nie chcę tego robić. Chcę zastopować akcję, którą zrobił user/admin/cron, i dać możliwość callerowi zareagować na to.

Wysyłanie eventów IMO byłoby spoko, gdyby to były jakieś peryferia aplikacji (typu właśnie z web do kafki), ale w samej logice biznesowej to mi się wydaje bad idea.

No, nie, kafki w ogóle w to nie chcę mieszać, i nie chcę żeby inne serwisy handlowały ten wyjątek, tylko caller.

Jasne, teraz nie, ale sam pisałeś ze dochodzą ci kolejne "handlery". Nie zdziwi mnie jak pojawi sie wymaganie że jak user wykona nielegalną akcje, to taka informacja powinna zostać przesłana do jakiegoś serwisu security czy audit ;) Chciałem tylko pokazać że opcja z eventami nie ma takich mocnych ograniczeń jak to co teraz robisz.

No, jeśli zajdzie taka potrzeba, to eventy pewnie będą oczywistym wyjściem. Ale na razie nie ma.

Kolejny problem jaki widzę z tymi twoimi wyjątkami jest taki, ze ktoś ci moze ten wyjątek złapać, mniej lub bardziej przypadkiem, i nagle w ogóle te twoje handlery się nie odpalą. Ot głupi przykład to odpalenie czegoś w innym watku albo z jakiegoś CompletableFuture. Exlpicite emitowanie eventu jest dużo bardziej niezawodne.

No, niby tak, ale ten sam problem jest z Eitherami, ktoś go może przechwycić i zwrócić inny either.

Poza tym, jeśli jakiś caller sobie złapie ten wyjątek i nie puści go dalej, to znaczy że obsłużył już błąd i nie powinien być propagowany dalej, więc nie wiem czy to tak znowu źle. A jeśli mówisz o tym że w mojej logice biznesowej coś go złapie, i nie puści, to to znaczy że jest w niej bug, i unity to powinny wykryć IMO.

somekind napisał(a):

Skoro masz trzy aplikacje, każda inaczej obsługuje wyjątki, to oznacza 3 catche (po jednym w każdej aplikacji). Nie trzeba żadnych wzorców.

No ale to jest jedna aplikacja.

TomRiddle napisał(a):

No, tak działa wizytor, prawda?

Owszem, w ten sposób działa, i przez to jest tak bardzo nieintuicyjny. :)

Dla mnie ma sens :D Może po prostu trzeba dobrze znać wzorce, a nie tylko o nich słyszeć kiedyś :D Joke. No offence.

To jest np to że user chce wywołać jakąś akcję, ale nie ma na tyle kredytów na swoim koncie, powiedzmy. Albo, jest limit na jakąś akcję do powiedzmy 3ech dziennie, a user chce zrobić coś 4ty raz tego dnia. Ewentualnie to może być coś w stylu, "wyślij wiadomość do mojego polecającego", ale akurat ten user nie ma polecającego.

Ja taką akcję teraz stopuję wyjątkiem NotEnoughCreditsException, ActionLimitReachedException oraz RootReferalException. Być może to jest to o czym mówisz, że to jest "event domenowy", tylko jak takim sposobem zatrzymać flow? Tak jak wyjątki zatrzymują unless caught?

W ogóle nie powinieneś zaczynać flow, skoro warunki nie są spełnione. Wtedy nie trzeba byłoby niczego przerywać wyjątkiem.

No ale to są case'y których nie da się/ciężko sprawdzić przed wywołaniem flow. To jest coś w stylu

Kopiuj
user.buyItem("Monitor");

...gdy nie wiemy ile user ma kasy i nie wiemy ile "Monitor" kosztuje, cała ta sprawdzajka dzieje się w środku logiki biznesowej. Żeby to sprawdzić zanim się ją zawoła, to trzebaby ją zduplikować w sumie.

Z punktu widzenia aplikacji - tak, normalne. Z punktu widzenia domeny biznesowej, nielegalna akcja IMO. Takie jest moje zdanie, nie chcę wchodzić w dyskusje na ten temat. Jakby, naturalne wydaje mi się że jak user chce coś kupić, ale nie ma kasy, to powinien dostać wyjątek (IMO!).

Wyjątek powinien dostać np., gdy mimo walidacji na każdym etapie będzie brakowało jakichś wymaganych danych, albo gdy utraci połączenie z bazą danych, a nie wtedy, gdy próbuje wykonać coś wbrew regułom biznesowym. To, że ludzie się mylą albo kombinują to nie jest nic wyjątkowego, to normalna i oczywista rzecz. Coś oczywistego nie może być jednocześnie wyjątkowe.

No tak, i takie sytuacje też są, i takie również chcę obsłużyć - tylko że inaczej dla różnych caller'ów (inaczej dla usera, dla admina i dla crona).

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
3

to by znaczyło że praktycznie każda funkcja u mnie musiałaby zwracać Either

Często właśnie tak jest! Wszystko co może się "nie udać" zwraca Either czy inny Optional. Niczym się to specjalnie nie rózni od jakiegoś throws XYZException. Najgorsze co można zrobić to rzucanie RuntimeException z takiego miejsca, bo RuntimeException jest podobny tutaj do nulla -> nie widać go! Optinal czy Either czy nawet ten throws... sygnalizują na poziomie typesystemu że coś się może nie udać.

i dać możliwość callerowi zareagować na to.

Jak dla mnie to jest idealny przykład na użycie Eithera skoro chcesz żeby ktoś kto zawołał daną metodę mógł zareagować na jakiegoś Lefta.

No, niby tak, ale ten sam problem jest z Eitherami, ktoś go może przechwycić i zwrócić inny either.

Nie zgodzę się, bo sygnatura funkcji się nagle nie będzie pasować :) Rzucenie lub nie jakiegoś RuntimeException jest niewidzialne. Ktoś go złapie i nawet nie będzie wiedział że ty masz gdzieś wyżej jakiś handler który chciał to złapać. W przypadku Eithera od razu będzie widać że "coś" musimy zwrócić. Co więcej, wołając taką metodę od razu widzisz ze ona mogła nie zadziałałać (bo zwraca Either) i może warto coś z tym zrobić. Jak masz metodę która gdzieśtam na dole moze walnąć RuntimeException to wołając ją nie masz pojęcia że coś takiego może się stać, chyba że przekopiesz się przez cały kod. Bardzo łatwo coś takiego przeoczyć i łyknąć wyjątek jak wątek umrze.

Poza tym, jeśli jakiś caller sobie złapie ten wyjątek i nie puści go dalej, to znaczy że obsłużył już błąd i nie powinien być propagowany dalej, więc nie wiem czy to tak znowu źle.

To zależy. Jestem w stanie wyobrazić sobie catch(RuntimeException e) nad jakimś kawałkiem kodu, żeby sobie go zalogować nie wiedząc że ktoś jednak jeden z tych wyjątków chciał obsłużyć w specjalny sposób.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
Shalom napisał(a):
TomRiddle napisał(a):

to by znaczyło że praktycznie każda funkcja u mnie musiałaby zwracać Either

Często właśnie tak jest! Wszystko co może się "nie udać" zwraca Either czy inny Optional. Niczym się to specjalnie nie rózni od jakiegoś throws XYZException. Najgorsze co można zrobić to rzucanie RuntimeException z takiego miejsca, bo RuntimeException jest podobny tutaj do nulla -> nie widać go! Optinal czy Either czy nawet ten throws... sygnalizują na poziomie typesystemu że coś się może nie udać.

No właśnie zacząłem zauważać, że to co opisujesz jest bardzo podobne do checked-wyjątków.

TomRiddle napisał(a):

i dać możliwość callerowi zareagować na to.

Jak dla mnie to jest idealny przykład na użycie Eithera skoro chcesz żeby ktoś kto zawołał daną metodę mógł zareagować na jakiegoś Lefta.

No dobra, ale czy to znaczy że polimorifczny AbcResult w Either<>, byłby tożsamy z AbcException który jest checked wyjątkiem? Jak dla mnie checked exception spoko w tym case'ie, jeśli miałbym wybrać.

No, niby tak, ale ten sam problem jest z Eitherami, ktoś go może przechwycić i zwrócić inny either.

Nie zgodzę się, bo sygnatura funkcji się nagle nie będzie pasować :) Rzucenie lub nie jakiegoś RuntimeException jest niewidzialne. Ktoś go złapie i nawet nie będzie wiedział że ty masz gdzieś wyżej jakiś handler który chciał to złapać. W przypadku Eithera od razu będzie widać że "coś" musimy zwrócić. Co więcej, wołając taką metodę od razu widzisz ze ona mogła nie zadziałałać (bo zwraca Either) i może warto coś z tym zrobić. Jak masz metodę która gdzieśtam na dole moze walnąć RuntimeException to wołając ją nie masz pojęcia że coś takiego może się stać, chyba że przekopiesz się przez cały kod. Bardzo łatwo coś takiego przeoczyć i łyknąć wyjątek jak wątek umrze.

Tzn, w przypadku w którym ktoś połyka wyjątek to tak, wiadomo.

Ale mówiłeś że wyjątek ktoś może złapać i rzucić inny. Ja tylko mówię, że z Eitherami jest podobnie, ktoś może zjeść Either i zwrócić inny.

PS: Zgadzam się żę połykanie wyjątków to częstszy przypadek, ale raczej w moim projekcie nie pozwalam tak robić, więc Ctrl+F, "catch (RuntimeException) {}" returns 0 matches.

Poza tym, jeśli jakiś caller sobie złapie ten wyjątek i nie puści go dalej, to znaczy że obsłużył już błąd i nie powinien być propagowany dalej, więc nie wiem czy to tak znowu źle.

To zależy. Jestem w stanie wyobrazić sobie catch(RuntimeException e) nad jakimś kawałkiem kodu, żeby sobie go zalogować nie wiedząc że ktoś jednak jeden z tych wyjątków chciał obsłużyć w specjalny sposób.

No wiadomo.

Ale, jak już mówiłem - dla mnie takie wyciszenie wyjątku to jest bug w domenie biznesowej. Domena miała rzucić wyjątek a nie rzuca, ergo bug, ergo unit testy powinny mi to złapać. Także nie boję się specjalnie tego.

Poza tym, jeszcze jest inna sprawa. Jeśli domena biznesowa wystawia jakiś jeden interfejs (jedną fasadę lub jeden entry point), to pod spodem może mieć znacznie bardziej pokaszanioną logikę. I teraz, to że interfejs wystawia Eithera albo wyjątek, nie znaczy że implementacja musi polegać na wyjątkach/eitherze. Przecież, dopóki te same testy jednostkowe przechodzą, to raz przyklepany interfejs (wyjątki lub either) jest stały, ale implementacja, nie ważne jaka, powinna móc się zmieniać do woli. Więc trzeba by się zastanowić, które z tych podejść jest okej:

  • Either<> zarówno w interfejsie jak i w bebecach
  • Wyjątek zarówno w interfejsie jak i bebechach
  • Implementacja na wyjątkach, interfejs łapie i zwraca Either z odpowiednim Result
  • Implementacja na Either<>, ale rzucany wyjątek na samym końcu.

I od razu uprzedzam - jeśli ktoś chce automatycznie wybrać jedną z tych opcji, i odrzucić 3 pozostałe bez sensownych argumentów i przemyśleń, to może w ogóle nie pisać. Ja się spodziewam przemyślanych odpowiedzi, kompanów do debaty, rozumnych rozważań, a nie powtarzania schematów.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)