current_user w modelu

Overengineering i przedwczesna optymalizacja to terminy odnoszące się do trywialnych aplikacji. Przy odpowiednio skomplikowanych aplikacjach będzie to „proper engineering” i „niezbędna optymalizacja”. Problem jest tutaj głównie taki, że Ty zakładasz trywialność tej aplikacji i możliwe rozwiązanie jednym prostym callbackiem. Tuptus jednak wskazuje kolejne bardziej skomplikowane założenia, które raczej przekreślają sensowność takiego podejścia.

@sharik niestety nie, wystarczy odrobina overengineering i przedwczesnej optymalizacji i każda aplikacja przestaje być trywialna.

Za każdym razem jak jest prowadzona tego typu dyskusja wychodzi na to że ze mnie jakiś hipis jest i wzorce projektowe są be OOP jest jeszcze gorsze, a DHH to bóg mój jedyny.
A zupełnie nie prawda. Znam wzorce projektowe (i antywzorce) świetnie, Cztery smoczki połknięte zostały już dawno. Problemem jest to że Panowie (Panie zresztą też), którzy udowadniają że jakiś wzorzec i rzucają słowa typu “decoupling”, “Separation of responsibilities”, “dependency injection”, uzywają ich jakby to był cel, “osiągnąłem najlepszą separację odpowiedzialności, klękajcie narody!”, a to jest niestety tak jakby zawodowy himalaista powiedział “Zebrałem absolutnie wszystko co mi kiedykolwiek potrzebne będzie na wyprawę na Everest”, czy to znaczy że on wszedł na tą górę? czy łatwo mu będzie wejść na tą górę z 300kg sprzętu? Prawdopodobnie nie, ale zawsze może zatrudnić sobie tragarzy, i do tego niestety prowadzi zwykle overengineering. Tam gdzie wcześniej wystarczał jeden programista, nagle trzeba zespołu.
Celem jest stworzenie aplikacji, jasnej i przejrzystej, podatnej na zmiany, im więcej kodu tym trudniej wprowadzać zmiany.

Żeby nie było niejasności - moje rozwiązanie (wylistowane na początku wątku) jest obiektywnie lepsze :smiley: Z jednego prostego powodu - ma co najmniej 2 razy mniej linijek kodu, i ma tą właściwośc że nie trzeba zupełnie myśleć nad nim. Przedstawiłem też rozwiązanie dla rake’a więc to też nie jest problem żaden.

Pracowałem nad aplikacjami, które pisali ludzie o Twoim podejściu i wiem jak wyglądają po roku. Po tych doświadczeniach moja stawka wynosi 125% normalnej w takich przypadkach.

Zaproponowales osobie poczatkujacej uzywanie Thread.current i potem chcesz wcisnac ze jednak znasz wzorce i antywzorce ?

Niestety nie jest ale skoro nie odniosles sie do reszty wymagan uwazam dyskusje za zakonczona

Pracowałem nad aplikacjami, które pisali ludzie o Twoim podejściu i wiem jak wyglądają po roku. Po tych doświadczeniach moja stawka wynosi 125% normalnej w takich przypadkach.[/quote]
+1. W przypadku takich genialnych rozwiązań jak zaproponowane wyżej 125% to za mało, naprawdę szkoda zdrowia.

Przedstawione tutaj myślenie jest bardzo krótkowzroczne. Nie zawsze najmniej linijek kodu to jest najlepiej. Polecam pracę w aplikacji która ma więcej niż 6 modeli (może być nawet 20-30) i dużo callbacków, dziesiątki metod w modelach - istny koszmar. Po takim doświadczeniu człowiek wie czego nie robić.
A jeżeli zakładasz pisząc, że aplikacja skończy się na 6 modelach i będzie trywialna, do skończenia w 2 tygodnie to olej wszystko i zrób po swojemu. Koniec końców tak nie będzie nadawała się do większego rozwoju i będzie tylko do przepisania, bo będziesz jedyną osobą, która wie co się dzieje.
Callbacki ActiveRecordowe to najgorsze zło i ścierwo w Railsach.

A jeśli chodzi o serwis zaproponowany przez lewego to można sobie zdefiniować go jako bardziej ogólny i po nim dziedziczyć. Jak się napisze bazę raz i dobrze, to nie będzie potrzeby tam często zaglądać, a interfejs podziedziczonych serwisów będzie trywialny w użyciu.

Osoby, które wypowiadają się o refaktoryzacji chyba nigdy nie musiały takowej dokonywać w dużej aplikacji. Mniej kodu to żaden wyznacznik.

He-he.

gogiel: mógłbyś rozwinąć wątek “callbacki to najgorsze ścierwo” ? W jakich sytuacjach polecasz się ich wystrzegać i jakie problemy mogą sprawić ? Wolę nauczyć się na cudzych błędach niż samemu sie na to natknąć żeby wyciągnąć nauczke;]

Moj faworyt to laczenie sie z zewnetrznym serwisem np facebook w callbacku modelu :slight_smile:

Pisałem strasznie zjadliwą odpowiedź ale widzę że nie ma to sensu więc: Thread local variable nie jest antywzorcem, i należało by go tutaj zastosować.

Ciekawy post w nawiązaniu do tematu: http://www.joelonsoftware.com/items/2009/09/23.html

Tak z ciekawości, trafiałeś na projekty, które były przekombinowane w sposób, o którym pisze Świstak? Ja raczej nie, ale być może to wynika z tego, że takie podejście jednak nie jest popularne w świecie railsów

Z mojego punktu widzenia wygląda to tak, że tak naprawdę na “staczanie się” projektu nie wpływa bezpośrednio brak stosowania wielu zasad OOP, o których była tutaj mowa, ale raczej nieodpowiednie podejście do zmian w projekcie. Projekt, w którym nie tworzysz Services, ani ActiveRecordFactory, będzie prawdopodobnie mniej podatny na zmiany (w takim sensie, że prawdopodobnie trzeba będzie zmienić więcej kodu przy wprowadzaniu zmian), ale nie znaczy to wcale, że musi stać się czymś bardzo ciężkim do modyfikacji. Problem jest taki, że przy takiej aplikacji trzeba się dobrze zastanowić czy jak robisz jakąś zmianę, to wystarczy, że “dodam tutaj tylko tą linijkę, chociaż wiem, że to zło”, czy może lepiej trochę pozmieniać kod, żeby lepiej to wyglądało.

Problem jest oczywiście głeboki, bo trzymanie takiej dyscypliny też nie jest łatwe, więc siłą rzeczy więcej jest projektów, które kończą kiepsko jest napisanych bez użycia dobrych praktyk OOP. Dodatkowo, często jest niestety tak, że team, który “wybiera” mniej skomplikowaną drogą, jakkolwiek by tego nie nazywać, YAGNI, KISS, Less is more itp, często łączy to z całkowitym brakiem procesu.

Jak o tym myślę, to jestem chyba zwolennikiem umiarkowania - nie można 100% czasu rozwijać projektu dodając nieprzemyślane dobrze linijki kodu, ale z drugiej strony zbyt duża ilość abstrakcji też może ugryźć. Jak patrzę na projekty, przy których pracowałem i upadły, albo kończyły w złym stanie, to częściej zdarzały się problemy biznesowe, tzn. kończymy projekt, bo skończyła się inwestycja, albo bo nie ma to sensu biznesowego, niż dlatego, że kod był brzydki. I zanim ktoś wyskoczy z tym, że dlatego upadły, bo kod był ciężki do zmiany, to powiem, że nie do końca - raczej nie były to fazy, w których kod stawał się ciężki do utrzymania.

Ciekawe jest również to, że ludzie nagle zmieniają zdanie na temat podejścia jak muszą wydawać własne pieniądze, chociażby Obie Fernandez. Hashrocket znane było z ewangelizacji dobrych wzorców, dobrych praktyk, test first i co tam tylko sobie wyobrazicie. W momencie kiedy Obie zaczął własny startup, to nagle okazało się, że może nie warto pakować nie wiadomo jak dużej ilości czasu w rozwój, skoro nawet nie wiadomo czy to chwyci - klienci nie pochwalą Twojego kodu, tylko produkt, którego używają.

Callbacki AR powodują straszną nieczytelność kodu (nie widać kompletnie flow). Najczęściej problemy występują, gdy:
jest ich dużo w jednym modelu/model jest bardzo duży (ciężko się połapać)
callbacki przychodzą z mixinów (jeszcze ciężej się połapać)
ciężko wyłączyć konkretny callback bez mieszania ifów i kombinowania (a często się tak zdarza, że jednak w jakiejś konkretnej sytuacji chcemy coś wyłączyć)
budujemy za jednym zamachem model z jego relacjami - naprawdę grubo się trzeba czasami zastanowiać co i w jakiej kolejności się wykona
bardzo często są stosowane w zły sposób - w callbackach dodawana jest logia biznesowa, nie da się tego przetestować bez tworzenia modelu w bazie danych

Callbacki to główne źródło WTF typu “a czemu po wywolaniu #save nagle dane pole zmienia mi się”.
A już karygodne jest mieszanie większej logiki do callbacków, jak na przykład przytoczone wyżej łączenie się z zewnętrznym serwisem (np. “po updacie usera ściągnij z facebooka jego najnowszy avatar”).

EDIT:
Jestem ciekaw jak rozwiązujecie taki prosty problem: stworzenie w aplikacji możliwości stworzenia za jednym zamachem użytkownika, ściągnięcie jego danych z FB oraz powiązanie z rolami na bazie jakiejś tam logiki.
Wg. większości Railsowców najsensowniej jest dodać metodę do klasy User (no bo w końcu kiedyś tam uczyli, że skin controllers, fat models). Często połączymy to z jakimś callbackiem. Po pół roku nie ma szans wyciągnięcia tej metody bez całodniowej analizy co robi cała klasa User.
Moim zdaniem dużo łatwiej jest zrobić nową klasę, np UserCreator, która robi jedną rzecz, ma fajnie wyseparowane metody prywatne i wygląda ładnie, jest testowalna i podatna na zmiany bez rozpieprzania wszystkiego wokół.

Tak, w szczególności zaszła mi za skórę jedna, w której występowało zarówno morze callbacków (jako, że modele i bez tego miały po 500-1000 linii kodu, to były one wydzielone do różnych modułów i includowane, co czyniło całą sytuację jeszcze mniej czytelną), jak i stosowanie User.current (User.current czy Thread.current[:user] to ten sam problem zmiennej globalnej). Trafiłem tam po całkowitej wymianie 5-osobowego zespołu, czyli nikt nie miał pojęcia co i jak działa, więc jeszcze po 4-5 miesiącach znajdowaliśmy bugi spowodowane bezpośrednio takimi praktykami.

Oczywiście, jeśli projekt biznesowo się kręci, to znajdzie się kasa, żeby ładować dodatkowe zasoby w walkę z brzydkim kodem.

Świetny przykład. On nawet kiedyś w sądzie chyba zeznawał jako biegły, że developer nie piszący testów łamie kontrakt, bo nie wykonuje uczciwej pracy, czy coś takiego.

Bardzo slaby trolling. Ziemia jest plaska bo mowie to ja

+1

Dla mnie takim przykladem jest aplikacja opisana w http://objectsonrails.com/

W zasadzie jak sie nad tym zastanowic to testy nam powiedza jaki dokladnie kod powinnismy napisac

Widzialem projekt ktory przepisywany byl trzy razy przez brak odpowiedniej architektury.

Kiepsko to pytanie zadałem, chodziło mi o to czy kiedykolwiek natrafiłeś na projekt, w którym problemem była zbyt wiele warstwa abstrakcji, i ogólnie “over engineering” :slight_smile:

Callbacki AR powodują straszną nieczytelność kodu (nie widać kompletnie flow). Najczęściej problemy występują, gdy:
jest ich dużo w jednym modelu/model jest bardzo duży (ciężko się połapać)
callbacki przychodzą z mixinów (jeszcze ciężej się połapać)
ciężko wyłączyć konkretny callback bez mieszania ifów i kombinowania (a często się tak zdarza, że jednak w jakiejś konkretnej sytuacji chcemy coś wyłączyć)
budujemy za jednym zamachem model z jego relacjami - naprawdę grubo się trzeba czasami zastanowiać co i w jakiej kolejności się wykona
bardzo często są stosowane w zły sposób - w callbackach dodawana jest logia biznesowa, nie da się tego przetestować bez tworzenia modelu w bazie danych

Callbacki to główne źródło WTF typu “a czemu po wywolaniu #save nagle dane pole zmienia mi się”.
A już karygodne jest mieszanie większej logiki do callbacków, jak na przykład przytoczone wyżej łączenie się z zewnętrznym serwisem (np. “po updacie usera ściągnij z facebooka jego najnowszy avatar”).

EDIT:
Jestem ciekaw jak rozwiązujecie taki prosty problem: stworzenie w aplikacji możliwości stworzenia za jednym zamachem użytkownika, ściągnięcie jego danych z FB oraz powiązanie z rolami na bazie jakiejś tam logiki.
Wg. większości Railsowców najsensowniej jest dodać metodę do klasy User (no bo w końcu kiedyś tam uczyli, że skin controllers, fat models). Często połączymy to z jakimś callbackiem. Po pół roku nie ma szans wyciągnięcia tej metody bez całodniowej analizy co robi cała klasa User.
Moim zdaniem dużo łatwiej jest zrobić nową klasę, np UserCreator, która robi jedną rzecz, ma fajnie wyseparowane metody prywatne i wygląda ładnie, jest testowalna i podatna na zmiany bez rozpieprzania wszystkiego wokół.[/quote]
Dzięki bardzo ;] Miło jest między flejmami poczytać trochę przydatnych podpowiedzi w dziale ‘pomoc’ ;]

@gogiel w twoim przykladzie zgadzam si w 100% that’s the way to do it (aha aha).
W przypadku logowania callbacki wydaja sie wprost stworzone do tego :slight_smile:

@drogus dzieki ci wielkie i masz u mnie piwo przy najblizszej okazji za glos rozsadku w dyskusji.
A drugie piwo za genialny link. Wiele osob nie powie glosno tego co ja bo sie beda bac wlasnie takiej reakcji jak gogel czy szarik (“ludzie tacy jak ty!” i “125%”), ja sie nie boje powiedziec - OOP to nie zloto, Services sa przydatne ale przy wiekszych projektach. factory to zlo.

Tutaj zgodzę się, że można ich do tego użyć. Jednak trzeba moim zdaniem bardzo uważać, gdy callbacki dotykają AR, albo tak jak w tym przypadku tworzą/updatują jakieś rekordy i nie przesadzać.
I warto się zastanowić, czy faktycznie interesuje nas logowanie faktu “zupdatowano obiekt User”, czy może jednak bardziej “moderator X zmienił danego użytkownika Jan Kowalki na jego prośbę nr #5”, lub coś podobnego, bardziej domenowego. Do callbacków ciężko te informacje dostarczyć. Może się okazać, że w kolejnej fazie będziemy chcieli coś takiego zrobić i pozostanie przy callbackach spowoduje niezły burdel (typu: a dodam sobie jakiś attr_accessor, gdzie wpakuje informacje dla callbacka).

EDIT:

Jak długo trwały te projekty? Czy deweloperzy się zmieniali, jak duży był team?

Powiem tak, pracuję już w tym bajzlu (RoR) 8 lat chyba (zaczynałem od wersji 0.13b), jako że pracuję głownie na kontraktach przewinąłem się przez mnóstwo projektów, co więcej byłem wołany zwykle dlatego że projekt “nie wyrabiał” i trzeba było dodatkowych rąk do pracy. Po za tym mam jeszcze parę lat expa w projektach w Javie, PHP, Perlu, Pascalu/Delphi.

Z tej perspektywy powiem jedno: jeszcze nigdy problemem nie było że aplikacja ma za mało warstw abstrakcji, albo nie wystarczającą liczbę fabryk, albo używała za mało wzorców projektowych. Dwa razy (jakieś 10% projektów), zdażyło mi się tylko że aplikacja miała za mało testów. Za to aż 3 razy zdażyło się że aplikacja miała za dużo testów. Jedna to był zupełnie skrajny przypadek gdzie zmiana byle pierdoły (na prawdę pierdoły, np. labela do pola do logowania) natychmiast powodowała 10-20 faili.

Wniosek jaki z tego wysnułem jest prosty: KISS i DRY. I to polecam. A jak lewy wreszcie znajdzie jakiekolwiek uzasadnienie dlaczego Thread-local storage jest antywzorcem w Ruby/Rails chętnie posłucham.

Ps. W ciągu tych paru lat przeżyłem już nie jeden “fad”, na przykład 3-4 lata temu osoba która mówiła że dekoratory to zło, jeszcze wcześniej programowanie funkcjonalne było “all the rage”, chociaż wtedy mówiło się zdaje “jazzy”, w tym roku Services i OOP rządzą, i krytykowanie ich to jakby komuś matkę zgwałcić. Minie trochę czasu, ludzie zrozumieją że ta róża ma swoje kolce, przyjdzie kolejna moda, może czas na prezentery? - Chociaż nie teraz sobie przypomniałem że tym się jarano 2 lata temu.

Nie, chociaż trafiłem na projekty ze złymi warstwami abstrakcji :wink: To znaczy było wiele warstw, ale źle zdefiniowanych, ze złym podziałem odpowiedzialności. Po refaktoringu wyszło mniej więcej tyle samo warstw, więc nie wiem czy można mówić o „overengineering” czy raczej o „bad engineering” :wink:

@sharik: Niestety Bad engineering jest formą nadrzędną overengineeringu. A złe warstwy powstają niestety zwykle w efekcie tego co ja nazywam overengineeringiem nie wiesz jeszcze czy będziesz potrzebował serwisów i fabryk, ale na wszelki wypadek wpieprzmy je do aplikacji, nawet jeżeli jedyne co będzie robił to wołał model.create w metodzie create_model.

Ja bardzo chętnie znajdę.
Thread-local storage to po prostu globalna zmienne. Nie chce mi się tłumaczyć, czemu to jest złe.
Na razie żyjemy w biednym świecie jednowątkowych aplikacji. A co się stanie, gdy będziesz chciał wywołać daną metodę na innym wątku?
Istnieje fajny gem delayed_job pozwalający wywołać daną metodę z opóźnieniem (np za 1h). Działa w ten sposób, że serializuje obiekt (czyli jego zmienne instancji). Przy takim podejściu nie ma szans na łatwe użycie tego popularnego gema w łatwy sposób. Konieczne jest wciskanie jakiejś dodatkowej metody do zapisania wszystkich zmiennych wątku i odtworzenie ich na samym początku. Dla mnie to burdel.

Co do Twojego doświadczenia to dospecyfikuj w jakich projektach pracowałeś. Czy wszystko to jakieś półroczne, czy roczne kontrakty typu zrób stronę i zapomnij, czy może zdarzyło Ci się pracować w projekcie, który miał więcej niż np: 2-3 lata. Mój aktualny projekt nad którym pracuję jest przejęty po innej firmie, powstał w 2011. Poprzedni miał 4 lata, był robiony w atmosferze “dobrych praktyk railsowych” i wiem jaki to burdel.

Zgodzę się z tym, że do prostych projektów nie ma co się zastanawiać nad architekturą za bardzo, tylko jechać ile wlezie i po 2 miesiącach zapomnieć.