Są tu jeszcze jacyś miłośnicy checked exceptions? Używacie?

Są tu jeszcze jacyś miłośnicy checked exceptions? Używacie?
Zawsze checked exceptions
0%
0% [0]
To zależy (czy możemy jakoś naprawić błąd)
26%
26% [6]
W libkach - tak (bo wtedy mamy ładne API), w aplikacjach - nigdy
0%
0% [0]
Nigdy, to był błąd projektowy Javy
74%
74% [17]
99xmarcin
  • Rejestracja:około 5 lat
  • Ostatnio:6 miesięcy
  • Postów:2420
1

Tak to prawda, nie wiem też jak by to miało działać np. ze zfailowaną transakcją na DB.

Zapewne dlatego to podejście pokryło się kurzem i zadne język nie planuje go wprowadzać, niemniej jako desing pattern fukcjonuje.


Holy sh*t, with every month serenityos.org gets better & better...
GO
Hmm obiekty w loggach mogły by składać callbacki, które by robily rollback gdy jakaś w przyszłości funkcja się wywali i chciała by cały ciąg wywolań cofnąć. W sumie w manualnym wykonaniu trzeba zrobić tak samo.
Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:4 dni
  • Lokalizacja:Stacktrace
  • Postów:6822
4

Sam pomysł Checked Exceptions nie jest zły. A w czasach gdy projektowano Javę, takie mechanizmy były tworzone przez programistów za pomocą checkpointów. W COBOLu był zwykły RERUN ON CHEK w FORTRANIE robiło się globalną flagę i warunek. Nie wspominam już o PL/SQL, bo tam mechanizm był mocno zależny od dostawcy, ale był. Często działało to tak, że wyjątek powodował cofnięcie do chekcpointu i wywołanie procedury „naprawczej”, która polegała na wywaleniu komunikatu z prośbą o ręczną weryfikację/naprawę danych. W Javie zrobiono to całkiem sensownie, bo wydzielono podzbiór wyjątków, które w teorii są „naprawialne”, jak brak pliku, czy połączenia z siecią, błędne dane wejściowe (ParseException), czy nieprawidłowy sposób wywołania (IllegalXXXXException). Problem polegał na tym, że ludzie zaczęli używać takich weryfikowalnych wyjątków w sposób podobny do przerwań lub jako sposób na zwrócenie wartości różnych typów.

0xmarcin napisał(a):

Tak to prawda, nie wiem też jak by to miało działać np. ze zfailowaną transakcją na DB.

Zapewne dlatego to podejście pokryło się kurzem i zadne język nie planuje go wprowadzać, niemniej jako desing pattern fukcjonuje.

A normalnie, ktoś loguje się do bazy danych i naprawia. Potem uruchamia program jeszcze raz. I kiedyś tak to działało, ale obecnie za dużo danych przeżuwamy, żeby ręcznie to ogarniać.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
GO
Są też dwa exceptiony jeden jak tworzysz system operacyjny, seqfault, divid by zero, a potem są exceptiony takie zbudowane na górze języków jak c++/java/python, działają zupełnie inaczej funkcja zapisuje kontekst i try/catch potrafi odtworzyć kontekst po dostaniu informacji o błędzie, tak się projektowało w C exceptiony z C++, które kompilator za nas robi, ale to są inne niż procesora exceptiony, które są z wektora przerwań uruchamiane do obsłużenia.
99xmarcin
Nie mylmy 2 różnych rzeczy: sprzętowe przerwanie ktore ma w nosie czy twój język obsługuje przerwania czy nie - uderza zawsze; unix'owe sygnały które też walą po aplikacji a jak ta nie obsłuży to killed i w końcu mechanizm w języku programowania ktory polega na odwijaniu stosu. W c było setjmp i longjmp do takiego skakania i odwijania stosu więc można powiedzieć że to był pierwowzor. Wydaje mi się że pierwsze wersje C++ wyjątkow nie posiadały w ogóle...
GO
@0xmarcin: jesteś drugą osobą na forum dopiero, która mówi o tym setjmp i longjmp, mało osób zna te funkcji z C, ale to zwykły thread hijacking, zapamiętujesz rejestry i udaje się, że przeprowadzone zmiany nie zmieniły zbytnio całego ramu i przywracamy wszystko i kontynuujemy pracę. Może być tak, że dana aplikacja zmieniła coś w pamięci i potem po exceptionie jak wróci do pracy to będzie exploit gotowy :>
Koziołek
@.GodOfCode., @0xmarcin: ale ja tu mówię o wysokopoziomowych mechanizmach, które niektórzy programiści chcieli używać tak, jak niskopoziomowych. W cpp wyjątki były jakoś w okolicach wersji 2.x wprowadzone.
99xmarcin
  • Rejestracja:około 5 lat
  • Ostatnio:6 miesięcy
  • Postów:2420
0

Gdyby tylko w Java można było używać generyków w throws:

Kopiuj
// nie działa
int foo() throws Err<String> { ... }

to można było by napisać generyczną metodę toEither(this::foo) - czyli homomorfizm między throws & Either.

Teraz można zadać pytanie dla Either owców? Jakich typów na błędy używacie w swoim Either? String? Czy jakiś custom typ Error?
Bo to jest dokładnie ten sam problem który występuje przy throws...


Holy sh*t, with every month serenityos.org gets better & better...
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:2 minuty
1
0xmarcin napisał(a):

Teraz można zadać pytanie dla Either owców? Jakich typów na błędy używacie w swoim Either? String? Czy jakiś custom typ Error?
Bo to jest dokładnie ten sam problem który występuje przy throws...

Ta odpowiedz to moze byc offtop, bo pewnie chodzi Ci stricte o Jave, ale w Scali 3 sa union types wiec mozna tak:

Kopiuj
def create(input: FooInput): IO[ErrorA | ErrorB | ErrorC, Foo] = ???

// albo tak:

type FooCreationError = ErrorA | ErrorB | ErrorC
def create(input: FooInput): IO[FooCreationError, Foo] = ???

λλλ
edytowany 2x, ostatnio: stivens
Koziołek
//trollmode: on// W Javie też są, ale w wersji biednej catch (IOException | NullPointerException e){} //trollmode: off// Można tez torchę z generykami poszaleć, ale nie za dużo np. <T extends Runnable & Comparable<? super T> powinno zadziałać (pisze z pamięci)
99xmarcin
  • Rejestracja:około 5 lat
  • Ostatnio:6 miesięcy
  • Postów:2420
0

Cały czas ten sam problem że to są proste przykłady, weźmy ten:

Kopiuj

def deserializeJson[T](json: String): IO[InvalidJson | InvalidDateFormat | InvalidNumberFormat, T] = ???
def fetchString(url: URL): IO[InvalidUrl | NetworkProblem | InvalidCertificate, String] = ???

// i teraz chcemy mieć funkcje to dostaje URL a zwraca obiekt sparsowany
def fetchObject[T](url: URL): IO[ /* co tutaj ??? */, T] = {
   ???
}


Holy sh*t, with every month serenityos.org gets better & better...
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:2 minuty
2
Kopiuj
type DeserializationError = InvalidJson | InvalidDateFormat | InvalidNumberFormat
def deserializeJson[T](json: String): IO[DesarializationError, T] = ???

type FetchStringError = InvalidUrl | NetworkProblem | InvalidCertificate
def fetchString(url: URL): IO[FetchStringError, String] = ???


def fetchObject[T](url: URL): IO[DeserializationError | FetchStringError, T] = {
   ???
}

// albo idac dalej:

type FetchObjectError = DeserializationError | FetchStringError
def fetchObject[T](url: URL): IO[FetchObjectError, T] = {
   ???
}

// albo jesli jednak nie chcesz tego w ogole obslugiwac:

def fetchObject[T](url: URL): IO[Nothing, T] = {
   ???
}.orDie // robi z tego (unchecked) exception, ktore tez maja swoje miejsce w swiecie - ale tego nie ma sensu pozniej lapac, bo zamysl jest pewnie taki, zeby dalej nie obslugiwac requestu

// albo chcesz obsluzyc tylko niektore bledy:

def fetchObject[T](url: URL): IO[Nothing, T] = {
  ???
}.catchSomeOrDie { case desarializationError: DeserializationError => ??? }

λλλ
edytowany 5x, ostatnio: stivens
Zobacz pozostałe 2 komentarze
stivens
Albo inaczej... Either/Result w dyskusji pojawil sie w ten sposob, ze padla teza CE jest spoko jesli blad jest integralna czescia sygnatury funkcji i wtedy poszlo, ze ale to wtedy lepszy jest Either/Result
stivens
Zreszta to jest o tyle bardziej uniwersalne, ze jak w funkcji A chcesz, to mozesz to obsluzyc poziom/dwa/trzy wyzej; jak w funkcji B nie chcesz tego obslugiwac, to mozesz porzucic specyficzny typ i przekazywac jako ogolny typ bledu (upcast) albo stwiedzic, ze request juz nie ma sensu i robimy z tego wyjatek (.orDie)
jarekr000000
Przy tym podejściu na realnej apce # typow błędow eksploduje - nie nie eksploduje, mam całkiem duże aplikacje tak pisane w scali (duży zespół, lata pracy) i mam wrażenie, że typów błedów jest nawet mniej niż byłoby custom exceptionów w javie (prawdopodobnie nie jest mniej, ale po prostu zajmują dużo mniej linii kodu (hierarchia sealed class / case class vs ileś tam plików w javie).
99xmarcin
Ale było przecież dowodzone że checked expeptions w sygnaturze to to samo co zwracanie Either...
99xmarcin
Problem z tym podejściem jest taki że teraz zmiana implementacji np. dodanie błędu w deserializeJson np. invalidUUID spowoduje zmianę sygnatury (poprzez aliasy) fetchObject.
99xmarcin
  • Rejestracja:około 5 lat
  • Ostatnio:6 miesięcy
  • Postów:2420
0

Przetłumaczę jeszcze post @stivens na Java:

Kopiuj

abstract class DeserializationError extends Exception { }
class InvalidJson extends DeserializationError { }
class InvalidDateFormat extends DeserializationError { }
class InvalidNumberFormat extends DeserializationError { }

<T> T deserializeJson(String json): throws DesarializationError { ??? }

abstract class FetchStringError extends Exception { }
class InvalidUrl extends DeserializationError { }
class NetworkProblem extends DeserializationError { }
class InvalidCertificate extends DeserializationError { }

String fetchString(URL url) throws FetchStringError { ??? }

// Nie ma typów algebraicznych w Java ale to nic, będziemy jechać na protezie
// (nie można użyć zwykłego Pari bo musi dziedziczyć po Exception):
class AnyOfErrors<T1, T2> extends Exception { // Trzeba mieć ten typ dla 2, 3, ..., 32 krotek
  private final Object value;

}

// Znów hipotetycznie załóżmy że można użyć generyków w throws
<T> String fetchObject(URL url) throws AnyOf<DeserializationError, FetchStringError> = {
   ???
}

Holy sh*t, with every month serenityos.org gets better & better...
KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:7 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
0

Java to jest jednak poj'bana. Dodali nowy interfejs czyli kolekcje sekwencyjne a tam metoda pobierz ostatni

Kopiuj
    default E getLast() {
        if (this.isEmpty()) {
            throw new NoSuchElementException();
        } else {
            return this.get(this.size() - 1);
        }
    }

Naprawdę skoro Java ma już tego kompniętego Optionala to nie można było zrobić

Kopiuj
    default Optional<E> getLastOpt() {
        if (this.isEmpty()) {
            return Optional()
        } else {
            return Optional(this.get(this.size() - 1));
        }
    }

Widać iż podniecanie się w Javie wyjątkami trwa w najlepsze i lepiej nie będzie :(


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
jarekr000000
W ogóle javowe kolekcje to patologia, właśnie przez starą składnię (sprzed generyków) i zaszłości. Zaprawdę nie wiem jak ktoś tego może używać w jakimś poważnym projekcie https://www.youtube.com/watch?v=w6hhjg_gt_M
99xmarcin
  • Rejestracja:około 5 lat
  • Ostatnio:6 miesięcy
  • Postów:2420
0

@KamilAdam obawiam się że to jest po prostu praca z legacy code. Inne metody api na kolekcji rzucały NoSuchElement a więc i nowa metoda musi też go rzucać.
Ujowo ale jednakowo jak mawiają w wojsku.

Natomiast to prawda że mogli by dodać też analogi które zwracają Optional. Sam o tym pisałem żeby unikać wyjątków kilka postów wcześniej...


Holy sh*t, with every month serenityos.org gets better & better...
YA
  • Rejestracja:prawie 4 lata
  • Ostatnio:3 dni
  • Postów:252
0

Tego tematu nigdy nie zrozumiałem.

Zazwyczaj głosi się, że wyjątki, gdy aplikacja nie musi się crashować, wyjątki, które są obsługiwalne, to nigdy nie powinny być wyjątki, tylko Either.

Dlaczego to ma być Either, a nie wyjątki? Ano dlatego (jak rozumiem piewców Eithera), że Either jest kontrolowany przez system typów, a wyjątki nie. Jak jest Either, to klient API wie, że tu może być błąd, wręcz jest zmuszony ten błąd obsłużyć. A jak są wyjątki, no to błędy nagminnie nie są obsługiwane, bo bardzo łatwo zapomnieć, albo w ogóle nigdy się nie zorientować, że gdzieś tam dużo niżej coś może rzucić jakimś wyjątkiem. Dawniej byłem cięty na takie stanowisko, ale doświadczenie nauczyło mnie, ze ma ono dużo sensu.

No to mamy checked exceptions. One robią DOKŁADNIE to samo, co Either - możliwość wystąpienia błędu włączają do sygnatury metody, tak że nie da się tego przeoczyć.

Teraz z kolei krzyk, że checked exceptions to zuo.

Czy mógłby mi ktoś wytłumaczyć, w czym jest lepszy Either od checked exceptions?

Druga sprawa:

KamilAdam napisał(a):

Java to jest jednak poj'bana. Dodali nowy interfejs czyli kolekcje sekwencyjne a tam metoda pobierz ostatni

Kopiuj
    default E getLast() {
        if (this.isEmpty()) {
            throw new NoSuchElementException();
        } else {
            return this.get(this.size() - 1);
        }
    }

Naprawdę skoro Java ma już tego kompniętego Optionala to nie można było zrobić

Kopiuj
    default Optional<E> getLastOpt() {
        if (this.isEmpty()) {
            return Optional()
        } else {
            return Optional(this.get(this.size() - 1));
        }
    }

Widać iż podniecanie się w Javie wyjątkami trwa w najlepsze i lepiej nie będzie :(

A co miałby tu dać Optional??

To jest klasyczna sytuacja, gdy mamy błąd wynikający z winy programisty (bug, 'boneheaded exception') i aplikacja musi iść w dół. To jest błąd tej samej kategorii, jak próba pobrania elementu z nieistniejącego indeksu tablicy itp. Bez typów zależnych takich błędów się nie wyeliminuje.

Po co zmuszać programistę do obsługi takich błędów? Żeby za każdym razem, gdy programista jest PEWNY, że tablica jest niepusta i próbuje pobrać ostatni element musiał pisac ifa, że jeśli Optional nie ma wartości, to rzuć wyjątek "ProgrammerIsAnIdiotError"? Bo tego NIE DA SIĘ obsłużyć sensownie inaczej, jak tylko kładąc aplikację.

KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:7 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
1
YetAnohterone napisał(a):

A co miałby tu dać Optional??

To iż sam mogę zdecydować jak to potem obsłużyć. Akurat w moim wypadku chcę wpisać tę wartość do arkusza więc muszę zamienić na stringa więc miałbym kod

Kopiuj
list.getLastOpt().map(e -> e.tpString).getOrElse("");

To jest klasyczna sytuacja, gdy mamy błąd wynikający z winy programisty (bug, 'boneheaded exception') i aplikacja musi iść w dół. To jest błąd tej samej kategorii, jak próba pobrania elementu z nieistniejącego indeksu tablicy itp. Bez typów zależnych takich błędów się nie wyeliminuje.

Tu też może być zwracany Optional

Po co zmuszać programistę do obsługi takich błędów? Żeby za każdym razem, gdy programista jest PEWNY, że tablica jest niepusta i próbuje pobrać ostatni element musiał pisac ifa, że jeśli Optional nie ma wartości, to rzuć wyjątek "ProgrammerIsAnIdiotError"? Bo tego NIE DA SIĘ obsłużyć sensownie inaczej, jak tylko kładąc aplikację.

Jak faktycznie chcesz exception to nie musisz go rzucać. Czasem starczy

Kopiuj
list.getLastOpt().get();

Poza tym nie wykluczam iż getLast() nie może istnieć równolegle do getLastOpt(). Jak akurat potrzebowałem getLastOpt() i teraz musiałem ją sobie sam napisać


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
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:39 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4709
2
YetAnohterone napisał(a):

Zazwyczaj głosi się, że wyjątki, gdy aplikacja nie musi się crashować, wyjątki, które są obsługiwalne, to nigdy nie powinny być wyjątki, tylko Either.

Dlaczego to ma być Either, a nie wyjątki? Ano dlatego (jak rozumiem piewców Eithera), że Either jest kontrolowany przez system typów, a wyjątki nie. Jak jest Either, to klient API wie, że tu może być błąd, wręcz jest zmuszony ten błąd obsłużyć. A jak są wyjątki, no to błędy nagminnie nie są obsługiwane, bo bardzo łatwo zapomnieć, albo w ogóle nigdy się nie zorientować, że gdzieś tam dużo niżej coś może rzucić jakimś wyjątkiem. Dawniej byłem cięty na takie stanowisko, ale doświadczenie nauczyło mnie, ze ma ono dużo sensu.
Czy mógłby mi ktoś wytłumaczyć, w czym jest lepszy Either od checked exceptions?

Faktycznie checked exception jest częścią systemu typów w javie i zmusza do obsługi (tak jak Either).
Tylko, że jest to bardzo koślawy modyfikator typu, bo dostępny tylko dla funkcji (jako część typu funkcji).

W takim kodzie:

Kopiuj
var x = mojaFunkcja() 

innaFunkcja(x) //i co teraz?

jeśli jest oparty o checked exception to wyjątku nie przekażesz już jako rezultat do innaFunkcja.


jeden i pół terabajta powinno wystarczyć każdemu
99xmarcin
Nie przesadzajmy, oba rozwiązania są równoważne - zawsze można złapać wyjątek w try-catch i przekazać...
jarekr000000
Nie pisze, że wyjątkami nie da się zrobić "projektu", jest to po prostu czasami niewygodne.
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około 8 godzin
2

nawiązując do:

0xmarcin napisał(a):

Gdyby tylko w Java można było używać generyków w throws:

Kopiuj
// nie działa
int foo() throws Err<String> { ... }

to można było by napisać generyczną metodę toEither(this::foo) - czyli homomorfizm między throws & Either.

Teraz można zadać pytanie dla Either owców? Jakich typów na błędy używacie w swoim Either? String? Czy jakiś custom typ Error?
Bo to jest dokładnie ten sam problem który występuje przy throws...

oraz:

stivens napisał(a):
0xmarcin napisał(a):

Teraz można zadać pytanie dla Either owców? Jakich typów na błędy używacie w swoim Either? String? Czy jakiś custom typ Error?
Bo to jest dokładnie ten sam problem który występuje przy throws...

Ta odpowiedz to moze byc offtop, bo pewnie chodzi Ci stricte o Jave, ale w Scali 3 sa union types wiec mozna tak:

Kopiuj
def create(input: FooInput): IO[ErrorA | ErrorB | ErrorC, Foo] = ???

// albo tak:

type FooCreationError = ErrorA | ErrorB | ErrorC
def create(input: FooInput): IO[FooCreationError, Foo] = ???

to dla scali 3 oderski i świta pracują nad caprese, czyli typowanie i śledzenie zasobów i efektów bez monad transformerów czy innych złożonych typów:
około 36 minuty są obrazki porównujące caprese do rusta i innych języków:

ogólnie to nazywa się direct style. virtual thready z javy 21+ to też direct style, ale nie jest uogólnione do śledzenia zasobów.

problem z checked exceptions (takimi jakie są w javie!, a nie np. w podanym wyżej caprese) jest to, że nie daje się tego abstrahować / komponować / etc. jeśli metoda przyjmuje tylko funkcje, która nie rzucają sprawdzanych wyjątków, a to co chcemy podać takie rzuca, to musimy przerobić checked exception na unchecked exception lub w ogóle zjeść exceptiona całkiem. weźmy pod uwagę taki hipotetyczny przykład. chcemy przemapować strumień stringów. javowy stream.map przyjmuje tylko funkcje, która nie rzucają checked exceptions, więc jeśli chcemy taką tam użyć, to musimy dodatkowo przepakować exception w taki unchecked albo zjeść ten exception albo zrobić coś innego, żeby już się w sygnaturze typu nie pojawiał.

problem z wyjątkami w ogóle jest taki, że np. nie są referentially transparent. załóżmy, że jakaś metoda coś tam liczy i zwraca, ale przy okazji może rzucić wyjątek. teraz miejsce wywołania takiej metody ma znaczenie. jeśli wywołamy ją w bloku try-catch tam gdzie trzeba i zrobimy co trzeba to jest ok. jednak jeśli przeniesiemy wywołanie gdzieś, gdzie nie powinno być, np. poza try-catch, do callbacku, wyniesiemy poza warunkowe bloki kodu (a więc np. coś co wykonywało się czasem, będzie wykonywać się zawsze) to się okaże, że try-catch też trzeba przerobić, żeby nadal działał dobrze, ale kompilator nam tego nie podpowie, bo mamy przecież unchecked exception - coś co kompilator olewa.

eithery i inne monadki są ogólnie bezpieczne jeśli chodzi o refaktoring. można przenosić kod z miejsca na miejsce i nic nie powinno wybuchać (no chyba, że ktoś ma jakieś lewe monadki, albo używa monady typu identity która nic nie opakowuje). jeśli typy się nie zgadzają to kompilator protestuje. jako, że eithery i inne monadki to generyczne opakowania, można je używać bezproblemowo w generycznych metodach.

mimo wszystko wyjątków i tak używam w pewnych przypadkach. główne zastosowanie to testy, bo testy piszę ofensywnie. inne zastosowanie to kod, który ma prosty przepływ sterowania, tzn. jest prosty zarówno faktycznie (nie ma żadnej asynchroniczności, leniwej ewaluacji i innych fajnych fikołków) jak i intuicyjnie (tzn. patrząc na nazwę i zastosowanie kodu domyślam się, że nie powinno być tam takich fajnych fikołków jak wymieniłem wcześniej, które sprawiają, że przepływ sterowania nie jest liniowy, a więc i zachowywałyby się nieobliczalnie w obliczu wyjątków).

w javie dawno nie kodowałem, ale z tego co pamiętam, to te checked exceptions były zwykle upierdliwą sztuką dla sztuki. nie zdążyłem poznać wyraźnych zalet tego rozwiązania :P . natomiast eithery w scali często są spoko. ładnie można sobie zamodelować błędy (prostą hierarchię typów), ładnie widać skąd te błędy przychodzą i kiedy się propagują (nawet jeśli są wpakowane np. w future'y), itp, itd.

koniec końców, dużo zależy od stylu programowania i problemu, który okodowujemy. ja tam nie mam podejścia skrajnego, tzn. nie obstaję twardo przy monadach w każdej sytuacji, ale z drugiej strony mocno je preferuję, więc programując w scali prawie zawsze są monadki, a wyjątki w kodzie produkcyjnym są bardzo rzadko.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.