Na parking dotarliśmy o 0850 po 2 godzinach podróżny na dystansie 170km (widocznie gdzieś po drodze nastąpiło lokalne zagięcie czasoprzestrzeni). Następnie udaliśmy się do punktu rejestracji na 3. piętrze...
STOP!
Przepraszam, zapomniałem, że blog to nie zeszyt do polskiego, a post to nie sprawozdanie z wycieczki do muzeum w 4. klasie podstawówki;P
Na wstępie gratulacje i uznanie dla organizatorów za rozmach oraz podziękowania dla wolontariuszy.
Chciałbym podzielić się przemyśleniami z kilku prezentacji:
Pisz po pijaku, przeglądaj na trzeźwo
Bezkompromisowa i nonkonformistyczna forma. Wspaniała interakcja z publicznością, bez puszenia się i pokazywania jaki to ja jestem mądry. Doskonałe zakończenie dnia.
W momencie kiedy Piotr wniósł sześciopak piwa i żołądkową (bo poprzednicy rozdali wszystkie gadżety przeznaczone na nagrody dla uczestników) pomyślałem tylko: "wiedz, że coś się dzieje":)
Na prezentacji mieliśmy błyskotliwą syntezę dwóch - popularnych ostatnio w "miękkim" IT - koncepcji:
- Model mózgu składającego się z "procesorów" Rich i Linear
- Model kompetencji Braci Dreyfus
Synteza polegała na postawieniu tezy, że ekspert w danej dziedzinie (piąty poziom Dreyfus) myśli nad problemem głównie "procesorem" Rich.
Nie wiem czy istnieją badania potwierdzające tą hipotezę, ale mnie się podoba, bo wydaje się brzmieć sensownie - póki co "kupuję to".
To do czego dotarłem w literaturze tematu i co wiadomo na pewno, to fakt, że ekspert na pewno czasem (w twórczym uniesieniu) myśli mniej, jest to zjawisko tak zwanego "lśnienia" polegające na tym, że podczas małej aktywności mózgu (zużycie energii) dochodzi do wykonania dużej i przełomowej pracy intelektualnej. Zjawisko to można obserwować na rezonansach.
Widziałem duże zainteresowanie publiczności, więc polecam materiały:
- Pragmatic Thinking and Learning: Refactor Your Wetware - dowiecie się z niej na temat modelu R/L oraz kilkunastu innych arcyciekawych i przydatnych w życiu sprawach. Autor dokonał syntezy wieli zagadnień z psychologii, socjologii, kognitywistyki podając je na tacy tak abyśmy nie musieli już sami szukać. Najlepsza książka jaką czytałem w zeszłym roku. Książki pisane przez programistów zawsze są dobre...
- Developing Expertise: Herding Racehorses, Racing Sheep - prezentacja na temat Dreyfus.
Materiał z kategorii "musisz zobaczyć".
- Wspinaczka do profesjonalizmu - artykuł na temat Dreyfus, który popełniłem w zeszłym roku dla Software Developer's Journal.
Miło było usłyszeć na prezentacji kilka odniesień do tekstu:)
Wracając do prezentacji Piotra, warto zapamiętać dwie rady praktyczne - techniki na uruchamianie Rich "procesora":
- odwracanie problemu: jeżeli nie wiemy jak zoptymalizować kod, to może zastanówmy się jak go spowolnić; jeżeli nie wiemy jak zaprojektować ergonomiczne GUI, to zastanówmy się jak zaprojektować je w sposób ultra-autystyczny.
Dlaczego to działa? Odsyłam do książki "Pragmatic Thinking and Learning" a później dalej...
- pair programming: niewiele osób uświadamia sobie, co się dzieje podczas programowania w parach. Mianowicie driver programując (czyli myśląc symbolicznie) pracuje na swoim "linearnym procesorze" a pilot zwolniony z tych czynności może uruchomić "procesor rich" i dokonywać myślenia syntetycznego na zasadzie nieświadomego pattern matching.
Play!Framework - ewolucja w świecie aplikacji webowych
Można opowiedzieć o frameworku webowym w sensowny i poukładany sposób tak aby było od razu wiadomo o co chodzi i kiedy oraz do czego mogę go użyć?
Można!
Brawa dla Wojtka. Konkretnie, na temat, dobrze dobrane przykłady kodu, dobre rysunki, szeroki zakres wiedzy i wszystko w 45 min! Wyszło lepiej niż filmiki na oficjalnej stronie frameworka.
Szczerze mówiąc to zacząłem rozważać Play jako narzędzie do pewnych klas problemów. Dzięki Wojtek.
Warto zwrócić uwagę na prezentację jako wzór do naśladowania:
- kod na Youtube z komentarzem na żywo prowadzącego (na pewno się nie wywali i nie będziemy tracić czasu na słuchanie nieśmiesznych tłumaczeń, że na prezentacji nigdy nie działa)
- szeroki wachlarz tematów do omówienia do wyboru wg zainteresowania słuchaczy (z uwagi ograniczony czas prezentacji).
Jeszcze raz brawo.
Quo Vadis IT
Pana Tomasza pamiętam jeszcze z zamierzchłych czasów studenckich, gdy przyjeżdżał na nasze rodzime uczelnie i priczował na temat technologii Microsoftu. Dałem się wówczas uwieść - ale tylko na chwilę:)
Prezentacja bardzo ciekawa, z uwagi na tematykę. Spojrzenie na branżę IT z wysokości 10km, co pozwala zauważyć powtarzające się w czasie patterny.
Widać było też na jakim poziomie i jak wnikliwie MS analizuje potrzeby oraz nawyki Userów (prywatnie i w pracy) oraz jak dobrze rozumie przemiany społeczne i mentalne zachodzące w czasie.
Miazga. My robaczki siedzimy sobie i męczymy się z kodzikiem, podczas gdy wysoko ponad naszymi głowami ktoś obserwuje i planuje rzeczywistość na kilkanaście lat do przodu.
Re-fuck-toryzacja czyli sprowadzanie sp****go kodu na właściwe tory
Paweł - dzięki za odniesienie do posta na temat radzenia sobie ze złożonością.
Muszę jednak zwrócić uwagę, że to co wyszło podczas refaktoryzacji to nie była Strategia a raczej zmodyfikowany Chain of Responsibility. Zmodyfikowany w taki sposób, że każde ogniowo łańcucha odpowiadało na pytanie "czy umiesz zająć się problemem", a jeżeli tak to "zajmij się". Czasem stosuję takie konstrukcję - łańcuch zarządzany przez "managera" - zębatkę.
Taka "zębatka" może być sama w sobie implementacją interfejsu strategii. Czyli mamy złożenie 2 patternów: strategia na wyższym poziomie (wariacje rozwiązania dużego problemu), a jedną z implementacji strategii może być taka zębatka zarządzająca łańcuchem małych ogniw, które coś tam sobie liczą (mikro problemiki wchodzące w skład jednego z wariantu rozwiązań problemów wyższego rzędu).
Teledysk na koniec - rotfl. Dobre zdjęcia, ładny dom:)
Co do mojej prezentacji, to jak zwykle: trema, przez którą wychodzi inaczej niż się planowało. Gadzi mózg podpowiada, że jeżeli stoisz sam bez broni i bez schronienia przed liczną grupą jakiś osobników to najpewniej za chwilę zginiesz. Ehh instynkt.
//=======================================
Przy okazji podzielę się jednak ciekawą obserwacją odnośnie psycho-akustyki (którą ostatnimi czasy nieco się interesuję). Z uwagi na wielkość sali, konstrukcję ścian, siłę oraz ustawienia nagłośnienia (i kilkanaście innych niesprzyjających uwarunkowań pomieszczenia) pojawił się efekt echa. Nie pogłosu, który jest do zniesienia a echa, które jest dla mówiącej osoby zabójcze.
Jako, że jestem tak zwanym słuchowcem (nie jest to takie proste, ale przyjmijmy na potrzeby posta, że jest coś takiego jak słuchowcy) to taki efekt nie jest tylko drażniący. U mnie niemal uniemożliwia mówienie. Przez chwilę myślałem, że się rozpłaczę ucieknę ze sceny hehehe.
Nie wiem czy ktoś włączył tłumienie fal obitych, ale nie widziałem nikogo majstrującego przy wzmacniaczach. Jednak po kilku minutach efekt echa zniknął. Ehh mózg ma niesamowitą zdolność do kompensowanie bodźców. A to co słyszymy jest tylko tym co nam się wydaje:)
Inżynieria oprogramowania w ujęciu systemowym.
Zintegrowane podejście do metodyk,
technologii (głównie Java EE), architektury i rozwoju ścieżki kariery programisty.
niedziela, 12 czerwca 2011
środa, 8 czerwca 2011
Degustacja Confitury
Confitura odbędzie się 11 czerwca ale już dziś możecie sprawdzić czy warto przyjść na jedną z prezentacji.
Abstrakt: Domain Driven Design - Wszystko ma swoje miejsce i wszystko jest na miejscu
//==============================
W branży można spotkać się z dwoma podejściami do prezentacji:
- niespodzianka
- możliwość obejrzenia wcześniej
Ja preferuję drugie podejście z tego samego względu, dla którego sprawdzam dema gier, czytam wyrywki tekstu z książek w księgarni oraz sprawdzam recenzje filmów - po prostu oszczędność czasu:)
wtorek, 7 czerwca 2011
Zawiązywanie sznurowadeł - robisz to źle
...sprawdź i przekonaj się, video trwa tylko 3 minuty:)
Zweryfikowałem wczoraj na butach biegowych z wyślizganymi od użytkowania sznurowadłami. Działa!
TED jak zwykle "uczy i bawi".
//==============================
Swoją drogą ciekawe ile jeszcze takich oczywistych oczywistości robimy nie do końca dobrze...
"live long and prosper":)
poniedziałek, 6 czerwca 2011
Rekrutacja
Temat zadań rekrutacyjnych dla developerów przewija się w branży co jakiś czas. Ostatnio znowu odżył ze względu na odbicie do tak zwanego kryzysu.
Post jest głosem w dyskusji, jaka toczy się na grupie Warszawa JUG, którą to czytam namiętnie ze względu na spektrum tematów oraz aktywność członków.
Dyskusja zaczęła od pytania o słuszność lub nie wymagania wiedzy na temat sortowania kolekcji obiektów w Javie. Chodzi o użycie biblioteki standardowej/trzeciej a nie o implementację od zera sortowania bąbelkowego (bo i o takich kuriozach się słyszy).
Wydaje mi się, że dyskutowanie nad treścią zadań bez określenia kontekstu to podchodzenie do problemu od złej strony.
Zaczynając rekrutację musisz odpowiedzieć sobie na kilka pytań: kogo poszukuję, do jakich klas problemów, do jakich typów zdań, z kim ma pracować i jakie są plany na przyszłość (co do tej osoby, co do całego teamu).
Pracując w różnych firmach jako lider techniczny zajmowałem się między innymi rekrutowaniem pracowników. Straciłem rachubę, ale w sumie przewinęło się ok 100 osób. Pamiętam jakie błędy robiłem, głównie na poziomie założeń. Teraz pomagam klientom znajdować odpowiednich ludzi na określone stanowiska.
Inny skillset będzie potrzeby dla kogoś kto dodaje ficzery biznesowe a inny dla kogoś kto rozwiązuje łamigłówki na poziomie optymalizacji lub algorytmicznym. Inny dla kogoś kto projektuje systemy/frameworki a inny dla kogoś kto implementuje aplikacje. Inny dla kogoś kto implementuje sexi gui a inny dla kogoś, kto modeluje złożone domeny biznesowe. Inny dla kogoś kto kreuje kierunek dla nowych projektów a inny dla kogoś, kto utrzymuje legacy.
Na skillset składa się wiele wymiarów - znajomość języków i technologii to tylko jeden z nich. Czasem wystarczający a czasem nie (czasem pomijalny?). Temat jest generalnie bardzo szeroki i wchodzi głęboko w psychologię - np. typy osobowości, z których wynikają sposoby myślenia, predyspozycje, słabe i mocne strony. Generalnie nie ma ludzi perfekcyjnych, każdy ma słabe strony, chodzi o to aby "w boju nie wystawiać tych miękkich części na ciosy" - po prostu dobierać ludzi do zadań/wyzwań
Ale to wymaga też zrozumienia wyzwań jakie pojawią się w projekcie oraz zrozumienia cech jakie lepiej lub gorzej predysponują śmiałka do podjęcia wyzwania:)
Strategia dla teamu jest prosta: wzajemne uzupełnianie się cech - a z tego gratisowo dostajemy efekty synergiczne:)
Podstawowym narzędziem może być model Belbina - ale to dopiero początek układanki.
Ignorowanie aspektów miękkich i kompletowanie zespołu na zasadzie: "dawać mnie tu sześciopak programistów" sprawdzi się owszem w niektórych przypadkach. W rekrutacji do projektów Komisji Europejskiej w Luksemburgu jednak już nie zadziała...
Wracając do zadania z sortowaniem kolekcji przez bibliotekę standardową... Zastanówmy się co ono sprawdza. Na pewno to czy ktoś już tego używał, czyli czy otarł się o ogólne programowanie w Javie (i ew. proste wykorzystanie w algorytmach). Oczywiście kandydat mógł nie mieć do tej pory okazji sortowania niczego - zdarza się.
Skoro tak, to nie miał tez okazji przeczytania żadnej książki poświęconej programowaniu w Javie. Po prostu nie da się nie trafić na sort. Prawdopodobnie ilość artykułów jakie pochłonął jest niewielka (tak elementarna rzecz jak sortowanie pojawia się z prozaicznej konieczności nawet w zaawansowanych tekstach).
Jaki wniosek możemy wysnuć na tej podstawie... hmmm... ja się wstrzymam z wnioskowaniem, czekam na propozycje w komentarzach. Nie zakładałbym jednak, że zawsze poszukujemy kogoś, kto "traci czas" na czytanie książek...
Ale na pewno jeżeli kandydat nie potrafi podać odpowiedzi, warto sprawdzić w jaki sposób ją zdobędzie i na ile ją zrozumie.
//==============================
Co do zadań ogólno-programistycznych to wdzięcznym tematem jest napisanie utilsa obliczającego pole prostokąta. Tak utilsa, statyczną metodę, beż żadnych Object Oriented, w przestrzeni Euklidesowej, zwykłe a * b.
Co sprawdzimy? Coś co można określić stylem.
1. nazwy parametrów - od czego zależy dobór? a,b czy może x,y albo width,height? szczegół? tak, ale jednak...
2. dobór typów. kiedy własne typy? co z jednostkami? wynik jest w kwadracie - jakie operacje są na nim dozwolone później? czy jest to problem naszego utilsa? szeroki temat do dyskusji...
3. sprawdzanie wartości wejściowych - sprawdzać czy nie, od czego to zależy?
4. nazwy, nazwy, nazwy
5. jakie wyjątki? dlaczego?
6. komentarze. a może nie ma takiej potrzeby? dlaczego?
7. testy! a może nie;P dlaczego?
Nikt nie zarzuci wymogu znajomości bibliotek (ew. junit/testng), egzotycznych algorytmów, wyrafinowanej składni, fizyki kwantowej. Wzoru na pole prostokąta jednak bym nie podawał w treści zadania;)
Tego typu zadanie oczywiście nie zaważy na przyjęciu/odrzuceniu kandydatury na stanowiska develoepra aplikacji postawionych na stosie 10 frameworków, ale potencjalnie może wskazać wygodne dla kandydata miejsce.
Post jest głosem w dyskusji, jaka toczy się na grupie Warszawa JUG, którą to czytam namiętnie ze względu na spektrum tematów oraz aktywność członków.
Dyskusja zaczęła od pytania o słuszność lub nie wymagania wiedzy na temat sortowania kolekcji obiektów w Javie. Chodzi o użycie biblioteki standardowej/trzeciej a nie o implementację od zera sortowania bąbelkowego (bo i o takich kuriozach się słyszy).
Wydaje mi się, że dyskutowanie nad treścią zadań bez określenia kontekstu to podchodzenie do problemu od złej strony.
Zaczynając rekrutację musisz odpowiedzieć sobie na kilka pytań: kogo poszukuję, do jakich klas problemów, do jakich typów zdań, z kim ma pracować i jakie są plany na przyszłość (co do tej osoby, co do całego teamu).
Pracując w różnych firmach jako lider techniczny zajmowałem się między innymi rekrutowaniem pracowników. Straciłem rachubę, ale w sumie przewinęło się ok 100 osób. Pamiętam jakie błędy robiłem, głównie na poziomie założeń. Teraz pomagam klientom znajdować odpowiednich ludzi na określone stanowiska.
Inny skillset będzie potrzeby dla kogoś kto dodaje ficzery biznesowe a inny dla kogoś kto rozwiązuje łamigłówki na poziomie optymalizacji lub algorytmicznym. Inny dla kogoś kto projektuje systemy/frameworki a inny dla kogoś kto implementuje aplikacje. Inny dla kogoś kto implementuje sexi gui a inny dla kogoś, kto modeluje złożone domeny biznesowe. Inny dla kogoś kto kreuje kierunek dla nowych projektów a inny dla kogoś, kto utrzymuje legacy.
Na skillset składa się wiele wymiarów - znajomość języków i technologii to tylko jeden z nich. Czasem wystarczający a czasem nie (czasem pomijalny?). Temat jest generalnie bardzo szeroki i wchodzi głęboko w psychologię - np. typy osobowości, z których wynikają sposoby myślenia, predyspozycje, słabe i mocne strony. Generalnie nie ma ludzi perfekcyjnych, każdy ma słabe strony, chodzi o to aby "w boju nie wystawiać tych miękkich części na ciosy" - po prostu dobierać ludzi do zadań/wyzwań
Ale to wymaga też zrozumienia wyzwań jakie pojawią się w projekcie oraz zrozumienia cech jakie lepiej lub gorzej predysponują śmiałka do podjęcia wyzwania:)
Strategia dla teamu jest prosta: wzajemne uzupełnianie się cech - a z tego gratisowo dostajemy efekty synergiczne:)
Podstawowym narzędziem może być model Belbina - ale to dopiero początek układanki.
Ignorowanie aspektów miękkich i kompletowanie zespołu na zasadzie: "dawać mnie tu sześciopak programistów" sprawdzi się owszem w niektórych przypadkach. W rekrutacji do projektów Komisji Europejskiej w Luksemburgu jednak już nie zadziała...
Wracając do zadania z sortowaniem kolekcji przez bibliotekę standardową... Zastanówmy się co ono sprawdza. Na pewno to czy ktoś już tego używał, czyli czy otarł się o ogólne programowanie w Javie (i ew. proste wykorzystanie w algorytmach). Oczywiście kandydat mógł nie mieć do tej pory okazji sortowania niczego - zdarza się.
Skoro tak, to nie miał tez okazji przeczytania żadnej książki poświęconej programowaniu w Javie. Po prostu nie da się nie trafić na sort. Prawdopodobnie ilość artykułów jakie pochłonął jest niewielka (tak elementarna rzecz jak sortowanie pojawia się z prozaicznej konieczności nawet w zaawansowanych tekstach).
Jaki wniosek możemy wysnuć na tej podstawie... hmmm... ja się wstrzymam z wnioskowaniem, czekam na propozycje w komentarzach. Nie zakładałbym jednak, że zawsze poszukujemy kogoś, kto "traci czas" na czytanie książek...
Ale na pewno jeżeli kandydat nie potrafi podać odpowiedzi, warto sprawdzić w jaki sposób ją zdobędzie i na ile ją zrozumie.
//==============================
Co do zadań ogólno-programistycznych to wdzięcznym tematem jest napisanie utilsa obliczającego pole prostokąta. Tak utilsa, statyczną metodę, beż żadnych Object Oriented, w przestrzeni Euklidesowej, zwykłe a * b.
Co sprawdzimy? Coś co można określić stylem.
1. nazwy parametrów - od czego zależy dobór? a,b czy może x,y albo width,height? szczegół? tak, ale jednak...
2. dobór typów. kiedy własne typy? co z jednostkami? wynik jest w kwadracie - jakie operacje są na nim dozwolone później? czy jest to problem naszego utilsa? szeroki temat do dyskusji...
3. sprawdzanie wartości wejściowych - sprawdzać czy nie, od czego to zależy?
4. nazwy, nazwy, nazwy
5. jakie wyjątki? dlaczego?
6. komentarze. a może nie ma takiej potrzeby? dlaczego?
7. testy! a może nie;P dlaczego?
Nikt nie zarzuci wymogu znajomości bibliotek (ew. junit/testng), egzotycznych algorytmów, wyrafinowanej składni, fizyki kwantowej. Wzoru na pole prostokąta jednak bym nie podawał w treści zadania;)
Tego typu zadanie oczywiście nie zaważy na przyjęciu/odrzuceniu kandydatury na stanowiska develoepra aplikacji postawionych na stosie 10 frameworków, ale potencjalnie może wskazać wygodne dla kandydata miejsce.
niedziela, 5 czerwca 2011
Obraz konkurencji
Osobiście nie miałem nigdy okazji pracować z developerami z Indii, ale z opowieści wielu znajomych z branży wnioskuję, że jest to ciekawa przygoda...
Natrafiłem dziś na artykuł, którego autor zdaje się znać sytuację od podszewki: Guru i trzej idioci.
//=====================
Tekst można polecić również tym, którzy narzekają na polski system edukacji:)
Natrafiłem dziś na artykuł, którego autor zdaje się znać sytuację od podszewki: Guru i trzej idioci.
//=====================
Tekst można polecić również tym, którzy narzekają na polski system edukacji:)
środa, 1 czerwca 2011
Sposoby enkapsulowania złożonej logiki biznesowej
W ostatnim poście poruszyłem pewne podstawowe zagadnienia z zakresu projektowania obiektowego, ale jak słusznie zauważyli komentatorzy przykład jest na tyle trywialny, że narzucające się proste rozwiązania są w zupełności wystarczające.
Podejdziemy do problemu raz jeszcze, tym razem w bardziej realistycznym kontekście aplikacji biznesowej.
Mamy zatem ten nieszczęsny OrderItem - nieszczęsny, ponieważ przykład pochodzi z książki, którą ostatnio recenzowałem. Klasyczny model zamówienia: Order-OrderItem-Product
W książkowym przykładzie OrderItem jest obiektem domenowym (rich model) i posiada oprócz stanu również zachowanie - odpowiedzialności biznesowe. Autor książki nawiązuje do Domain Driven Design, dlatego będę się posługiwał Building Blockami modelowania DDD.
Nasz OrderItem zawiera metodę getShippingCost(), która nie jest getterem a metodą biznesową - oblicza koszt dostawy danej pozycji na zamówieniu.
Sugerowana w książce rozbudowa modelu to dodanie nowych klas dziedziczących po OrderItem, które zmieniają sposób liczenia kosztu. Problemy z tym podejściem opisałem w poprzednim poście, w skrócie: w takim obiekcie biznesowym mamy wiele odpowiedzialności, więc prowadzanie dziedziczenia aby zmienić jedną z nich szybko doprowadzi do eksplozji kombinatorycznej bytów.
<dygresja>
Zacznijmy od tego, czy w ogólne taka metoda powinna należeć do tej klasy? W rzeczywistym systemie obliczenie kosztów dostawy zależy zapewne od wielu czynników - nie tylko od zamawianego produktu, ale również od tego kim jest klient, od miejsca dostawy, od reszty zamówienia itd.
Być może cała wiedza znajduje się w klasie OrderItem... Być może wyżej - w Order... Być może nie... wówczas należy wynieść cały problem do osobnego SerwisuBiznesowego (BuildingBlock modelowania DDD).
Nawet jeżeli dziś cała wiedza znajduje się w OrderItem czy nawet w Order, ale w przyszłości zmienią się wymagania i będziemy musieli implementować dziwne akrobacje aby w OrderItem/Order zdobyć potrzebne do obliczeń obiekty.
Generalnie serwis rokuje na lepszy design modelu (serwis biznesowy jest częścią modelu w DDD). W DDD nie chodzi o to aby całą logikę rozsmarować po encjach/agregatach/vo. Nic podobnego. Prosta reguła "kciuka" dla początkujących może być taka: encje/agregaty/vo posiadają metody używane wielokrotnie. Specyficzne metody używane jeden raz znajdują się w specyficznych serwisach biznesowych
Jednak te rozważania nie mają wpływu na dalszą część posta. Abstrahując od tego, którym miejscu umieścimy odpowiedzialność obliczeń wciąż możemy stosować techniki opisane poniżej.
W dalszej części, dla uproszczenia, zakładamy, że większość potrzebnej wiedzy znajduje się w OrderItem.
</dygresja>
W realnej aplikacji obliczenie kosztu dostawy:
- zależy od wielu czynników (rodzaj produktu, klient i jego rabaty, miejsce docelowe, składowe zamówienia, historia zamówień itd)
- jest złożone (załóżmy kilkanaście-kilkaset linijek kodu)
Ta więc rozważania z poprzedniego posta (i komentarzy) gdzie proponowaliśmy switche albo Mapy strtegii się nie aplikują.
Przy standardowym podejściu kod będzie wyglądał mniej więcej tak:
... i wcale nie spodziewajmy się "eleganckiego" switcha, który załatwia sprawę. Mamy tutaj do czynienia z potworkiem rzędu setek linijek, brak ustalenia jednego poziomu abstrakcji, nieustanne skoki mentalne po różnych poziomach oraz potencjał na to, że biznes będzie się komplikował i z czasem wyhodujemy ośmiotysięcznika.
Generalnie przy standardowym podejściu kodzik będzie przeplatał w sobie 3 rodzaje odpowiedzialności:
1. zdecydowanie który scenariusz/algorytm obliczania kosztu zastosować
2. sam algorytm
3. ew. pozyskanie dostępu do danych potrzebnych do obliczeń
Czyli w jednym miejscu mamy 3 rodzaje zależności, o których pisze Misko Hevery.
- colaboration
- construction
- call
Aby uniknąć tego poziomu smutnej złożoności, proponowane w poprzednim poście rozwiązanie (czwarte) sugeruje aby:
1. odseparować samą logikę obliczenia kosztu od reszty kodu.
Wprowadzamy interfejs Strategii, np
2. kawałki kodu stanowiące każdy z algorytmów pakujemy do osobnej klasy implementującej ten interfejs, np ForeginShippingCostCalculator, LargeItemShippingCostCalculator
2.a co jeżeli wysyłamy duży przedmiot za granicę? tworzymy trzecią klasę ForeginLargeItemShippingCostCalculator? Nie! Z pomocą przychodzi Decorator Design Pattern.
3. kawałki logiki wybierającej, którą konkretną politykę stworzyć chowamy w Fabryce.
I nie jest jeden switch ale kłębowisko ifów
Możemy fabrykować cały agregat:
albo jedynie strategię:
Wybór podejścia - co fabrykować: agregat czy strategię zależy do tego w którym momencie mamy wiedzą potrzebną do wyboru kalkulatora. Przed czy dopiero po powołaniu do życia OrderItema.
Generalnie lepiej jest posługiwać się Agregatem i ukrywać szczegóły typu istnienie strategii, ale nie zawsze jest to możliwe.
Tutaj jako agregat traktujemy OrderItem a nie Order, bo tak to wygląda w rzeczywistych systemach, np. klasy ERP - widziałem.
3a. co jeżeli aby wybrać konkretny kalkulator potrzeba znać np aktualnie zalogowanego użytkownika? Lub ogólnie: jakiś kontekst, którego brakuje w OrderItem?
Taka Fabryka może być komponentem zarządzanym (EJB, Speing Bean, itd) i możemy do niej wstrzykiwać potrzebne informacje.
3b. co jeżeli któraś konkretna strategia obliczania kosztu potrzebuje informacji o zewnętrznym kontekście (np zalogowanym userze)? Chodzi o przypadek gdy metoda z interfejsu strategii nie ma w parametrze tego co potrzebuje implementacja. Fabryka może przekazać kontekst do konkretnej strategii przez jej konstruktor.
Podsumowując:
Rozdzielamy silny coupling (współpracy, tworzenia, wywołania) do osobnych "pudełek".
Strategie: tylko obliczenia;
Fabryki: ecydowanie o typie strategii oraz ewentualnie składanie obiektów (przekazywanie szerszego kontekstu do strategii lub dekorowanie strategii)
Dzięki rozdzieleniu możemy niezależnie testować jednostkowo Agregaty i Strategie (Polityki w nomenklaturze DDD). A to za prawą fabryk, w których mieszkają operatory new. Agregaty są od nich wolne, więc mamy możliwość mockowania/stubowania strategii na czas testów.
//===============================================
Tak jak pisałem w poprzednim poście: wprowadzenie Strategii zaczyna się nam opłacać dopiero przy złożonych modelach biznesowych. Pokazałem tutaj dodatkowo złożenie Strategii i Dekoratora wraz z idiomem Fabryki oraz wystrzykiwaniem zależności. Dopiero w synergii technik tkwi prawdziwa siła.
Ważne jest to aby w ogóle modelować złożoność. Jeżeli nie modelujemy złożonego problemu a jedynie "zamiatamy go pod dywan" w postaci ukrytych gdzieś głęboko ifów to model szybko upada na twarz;)
Podstawowa zasada Domain Driven Desigm mówi: make explicit what is implict. Czyli wydobywajmy ważne koncepcje biznesowe na powierzchnię. Nie ukrywajmy ich w ifach w linijkacj od 5000 do 6000.
A czy jest coś ważniejszego w modelu biznesowym niż różne sposoby liczenia pieniędzy?;)
Na koniec jedna uwaga: zawsze gdy pokazuję na warsztatach tego typu przykłady część uczestników jest wręcz urzeczona "pudełkowaniem" złożonej logiki w małe, eleganckie pudełeczka. Inni z kolei preferują jedno pudło (np procedurę) ponieważ łatwiej im ogarnąć jedno pudło niż wiele małych, nawet kosztem tego, że w tym jednym pudle jest, powiedzmy delikatnie - bałaganik.
Wszyscy różnimy się mocno pod względem oceniania tego co jest proste. Więcej na ten temat: O programiście-pisarzu i programiście-konstruktorze.
Podejdziemy do problemu raz jeszcze, tym razem w bardziej realistycznym kontekście aplikacji biznesowej.
Mamy zatem ten nieszczęsny OrderItem - nieszczęsny, ponieważ przykład pochodzi z książki, którą ostatnio recenzowałem. Klasyczny model zamówienia: Order-OrderItem-Product
W książkowym przykładzie OrderItem jest obiektem domenowym (rich model) i posiada oprócz stanu również zachowanie - odpowiedzialności biznesowe. Autor książki nawiązuje do Domain Driven Design, dlatego będę się posługiwał Building Blockami modelowania DDD.
Nasz OrderItem zawiera metodę getShippingCost(), która nie jest getterem a metodą biznesową - oblicza koszt dostawy danej pozycji na zamówieniu.
Sugerowana w książce rozbudowa modelu to dodanie nowych klas dziedziczących po OrderItem, które zmieniają sposób liczenia kosztu. Problemy z tym podejściem opisałem w poprzednim poście, w skrócie: w takim obiekcie biznesowym mamy wiele odpowiedzialności, więc prowadzanie dziedziczenia aby zmienić jedną z nich szybko doprowadzi do eksplozji kombinatorycznej bytów.
<dygresja>
Zacznijmy od tego, czy w ogólne taka metoda powinna należeć do tej klasy? W rzeczywistym systemie obliczenie kosztów dostawy zależy zapewne od wielu czynników - nie tylko od zamawianego produktu, ale również od tego kim jest klient, od miejsca dostawy, od reszty zamówienia itd.
Być może cała wiedza znajduje się w klasie OrderItem... Być może wyżej - w Order... Być może nie... wówczas należy wynieść cały problem do osobnego SerwisuBiznesowego (BuildingBlock modelowania DDD).
Nawet jeżeli dziś cała wiedza znajduje się w OrderItem czy nawet w Order, ale w przyszłości zmienią się wymagania i będziemy musieli implementować dziwne akrobacje aby w OrderItem/Order zdobyć potrzebne do obliczeń obiekty.
Generalnie serwis rokuje na lepszy design modelu (serwis biznesowy jest częścią modelu w DDD). W DDD nie chodzi o to aby całą logikę rozsmarować po encjach/agregatach/vo. Nic podobnego. Prosta reguła "kciuka" dla początkujących może być taka: encje/agregaty/vo posiadają metody używane wielokrotnie. Specyficzne metody używane jeden raz znajdują się w specyficznych serwisach biznesowych
Jednak te rozważania nie mają wpływu na dalszą część posta. Abstrahując od tego, którym miejscu umieścimy odpowiedzialność obliczeń wciąż możemy stosować techniki opisane poniżej.
W dalszej części, dla uproszczenia, zakładamy, że większość potrzebnej wiedzy znajduje się w OrderItem.
</dygresja>
W realnej aplikacji obliczenie kosztu dostawy:
- zależy od wielu czynników (rodzaj produktu, klient i jego rabaty, miejsce docelowe, składowe zamówienia, historia zamówień itd)
- jest złożone (załóżmy kilkanaście-kilkaset linijek kodu)
Ta więc rozważania z poprzedniego posta (i komentarzy) gdzie proponowaliśmy switche albo Mapy strtegii się nie aplikują.
Przy standardowym podejściu kod będzie wyglądał mniej więcej tak:
class OrderItem{ Money getShippingCost(){ if if if if if //17 liniek obliczen if if if if //50 liniek obliczen z ifami } }
Generalnie przy standardowym podejściu kodzik będzie przeplatał w sobie 3 rodzaje odpowiedzialności:
1. zdecydowanie który scenariusz/algorytm obliczania kosztu zastosować
2. sam algorytm
3. ew. pozyskanie dostępu do danych potrzebnych do obliczeń
Czyli w jednym miejscu mamy 3 rodzaje zależności, o których pisze Misko Hevery.
- colaboration
- construction
- call
Aby uniknąć tego poziomu smutnej złożoności, proponowane w poprzednim poście rozwiązanie (czwarte) sugeruje aby:
1. odseparować samą logikę obliczenia kosztu od reszty kodu.
Wprowadzamy interfejs Strategii, np
interface ShippingCostCalculator{ Money calculateCost(...); }
2. kawałki kodu stanowiące każdy z algorytmów pakujemy do osobnej klasy implementującej ten interfejs, np ForeginShippingCostCalculator, LargeItemShippingCostCalculator
2.a co jeżeli wysyłamy duży przedmiot za granicę? tworzymy trzecią klasę ForeginLargeItemShippingCostCalculator? Nie! Z pomocą przychodzi Decorator Design Pattern.
ShippingCostCalculator calculator = new ForeginShippingCostCalculator(new LargrItemShippingCostCalculator()); }
3. kawałki logiki wybierającej, którą konkretną politykę stworzyć chowamy w Fabryce.
I nie jest jeden switch ale kłębowisko ifów
Możemy fabrykować cały agregat:
//fabrykowanie całego agregatu class OrderItemFactory{ OrderItem createOrderItem(...){ ShippingCostCalculator calculator = //100 ifów; return new OrderItem(calculator); } }
albo jedynie strategię:
//fabrykowanie jedynie strategii class ShippingCostCalculatorFactory{ ShippingCostCalculator createShippingCostCalculator(...) ShippingCostCalculator calculator = //100 ifów; return calculator; } }
Wybór podejścia - co fabrykować: agregat czy strategię zależy do tego w którym momencie mamy wiedzą potrzebną do wyboru kalkulatora. Przed czy dopiero po powołaniu do życia OrderItema.
Generalnie lepiej jest posługiwać się Agregatem i ukrywać szczegóły typu istnienie strategii, ale nie zawsze jest to możliwe.
Tutaj jako agregat traktujemy OrderItem a nie Order, bo tak to wygląda w rzeczywistych systemach, np. klasy ERP - widziałem.
3a. co jeżeli aby wybrać konkretny kalkulator potrzeba znać np aktualnie zalogowanego użytkownika? Lub ogólnie: jakiś kontekst, którego brakuje w OrderItem?
Taka Fabryka może być komponentem zarządzanym (EJB, Speing Bean, itd) i możemy do niej wstrzykiwać potrzebne informacje.
class ShippingCostCalculatorFactory{ @Inject LoggedUser loggedUser; //logged user to obiekt apliakcyjny (niebiznesowy), trzymany w sesji ShippingCostCalculator createShippingCostCalculator(...) //if (loggedUser.role == ADMIN) ShippingCostCalculator calculator = //100 ifów ; return calculator; } }
3b. co jeżeli któraś konkretna strategia obliczania kosztu potrzebuje informacji o zewnętrznym kontekście (np zalogowanym userze)? Chodzi o przypadek gdy metoda z interfejsu strategii nie ma w parametrze tego co potrzebuje implementacja. Fabryka może przekazać kontekst do konkretnej strategii przez jej konstruktor.
class ShippingCostCalculatorFactory{ @Inject LoggedUser loggedUser; //logged user to obiekt apliakcyjny (niebiznesowy), trzymany w sesji ShippingCostCalculator createShippingCostCalculator(...) ShippingCostCalculator calculator = //100 ifów ; calculator = new XyzCalculator(loggedUser.getUserId()); return calculator; } }
Podsumowując:
Rozdzielamy silny coupling (współpracy, tworzenia, wywołania) do osobnych "pudełek".
Strategie: tylko obliczenia;
Fabryki: ecydowanie o typie strategii oraz ewentualnie składanie obiektów (przekazywanie szerszego kontekstu do strategii lub dekorowanie strategii)
Dzięki rozdzieleniu możemy niezależnie testować jednostkowo Agregaty i Strategie (Polityki w nomenklaturze DDD). A to za prawą fabryk, w których mieszkają operatory new. Agregaty są od nich wolne, więc mamy możliwość mockowania/stubowania strategii na czas testów.
//===============================================
Tak jak pisałem w poprzednim poście: wprowadzenie Strategii zaczyna się nam opłacać dopiero przy złożonych modelach biznesowych. Pokazałem tutaj dodatkowo złożenie Strategii i Dekoratora wraz z idiomem Fabryki oraz wystrzykiwaniem zależności. Dopiero w synergii technik tkwi prawdziwa siła.
Ważne jest to aby w ogóle modelować złożoność. Jeżeli nie modelujemy złożonego problemu a jedynie "zamiatamy go pod dywan" w postaci ukrytych gdzieś głęboko ifów to model szybko upada na twarz;)
Podstawowa zasada Domain Driven Desigm mówi: make explicit what is implict. Czyli wydobywajmy ważne koncepcje biznesowe na powierzchnię. Nie ukrywajmy ich w ifach w linijkacj od 5000 do 6000.
A czy jest coś ważniejszego w modelu biznesowym niż różne sposoby liczenia pieniędzy?;)
Na koniec jedna uwaga: zawsze gdy pokazuję na warsztatach tego typu przykłady część uczestników jest wręcz urzeczona "pudełkowaniem" złożonej logiki w małe, eleganckie pudełeczka. Inni z kolei preferują jedno pudło (np procedurę) ponieważ łatwiej im ogarnąć jedno pudło niż wiele małych, nawet kosztem tego, że w tym jednym pudle jest, powiedzmy delikatnie - bałaganik.
Wszyscy różnimy się mocno pod względem oceniania tego co jest proste. Więcej na ten temat: O programiście-pisarzu i programiście-konstruktorze.
Koszt Dostawy
Odnośnie poprzedniego posta - Jacek L. zadał pytanie o przykład podejścia do liczenia kosztu dostawy.
Mamy kilka możliwości...
Załóżmy, że ma my klasę:
Co jeżeli mamy różne rodzaje/odmiany itemów, gdzie zachowanie tych metod różni się - innymi słowy algorytmy są różne?
1. Rozwiązanie na switchach:
Konsekwencje: wszelkie zmiany wymuszają "grzebanie" w corowym kodzie biznesowym, czyli jego "brudzenie" czyli ponowne testowanie (nie jest to problem jeżeli mamy pokrycie testami automatycznymi:)
2. Rozwiązanie struktura i algorytmy danych
Generalnie podobne do poprzedniego z tym, że z wyniesieniem kodu biznesowego na zewnątrz - podane dla uzupełnienia wachlarza możliwości:
3. SŁABE (ale to zależy od szerszego kontekstu) rozwiązanie z dziedziczeniem:
Tworzymy specjalne klaski dziedziczące po OrderItem, w których nadpisujemy odpowiednie metody biznesowe.
Konsekwencja: mnożenie bytów.
Co jeżeli:
- OrderItem1 nadpisze metodę metodaBiznesowa1
- OrderItem2 nadpisze metodę metodaBiznesowa2
a OrderItemBum chce mieć obie metody biznesowe 1 i 2 takie jak klasy wymienione poprzednio?
Eksplozja kombinatoryczna!
Dziedziczenie jest dobre, ale wówczas gdy klasa ma ściśle określoną odpowiedzialność (jeden powód do zmiany). Wówczas nie ma możliwości zajścia eksplozji kombinatorycznej oraz mamy zwykle pewność zachowania Liskov Substitution Principle.
Co jeżeli klasy dziedziczące są wymagane ponieważ dodają nowe atrybuty?
Wówczas rozwiązanie z dziedziczeniem zaczyna się bronić. Jednak wciąż może dojść do eksplozji klas.
Wówczas lepiej łączyć rozwiązanie z dziedziczeniem z rozwiązaniem 4.
Innym podejściem jest unikanie dziedziczenia i zastosowanie Archetypu Biznesowego Product.
4. Wprowadzenie wzorca strategii.
StrategiaBiznesowa1,2 są interfejsami. Konkretne implementacje zajmują się szczegółami.
Unikamy eksplozji kombinatorycznej z podejścia trzeciego dlatego, że w tym przypadku ilość klas to suma możliwych strategi dla każdej metody biznesowej a nie ich iloczyn.
Ale zostawmy autystyczne miary ilościowe... Ten design się po prostu "czuje". Tak jak w naiwnym szkolnym przykładzie: samochód zawiera skrzynię biegów - pasującą do interfejsu (śruby, tryby). Samochód w żadnym wypadku nie dziedziczy po skrzyni biegów:)
//Chociaż kiedyś na rekrutacji ktoś na pytanie o nieksiążkowy przykład dziedziczenia odpowiedział mi: "telefonistka dziedziczy po telefonie".
Skąd OrderItem posiada strategieBiznesowe?
a) OrderItem pobiera z fabrykStrategii - niedobre ze względu na testability
b) OrderItem ma je "wstrzyknięte" z zewnątrz przez FabrykiItemów, które go tworzą lub przez RepozytiumItemów, które pobierają go z bazy.
ew. Fabryki/Repozytoria tworzą nadrzędny agregat (np Order), który zawiera w sobie itemy, ale to zależy od domeny - mechanizm jest ten sam.
To jaką staregięBiznesową "wstrzyknąć" jest decyzją biznesową, tak więc Fabryki/Repozytoria zawierają taką logikę odciążając coupling tworzenia od coplingu używania.
Więcej o rodzajach couplingu dowiemy się od ekspertów Google.
Warto zauważyć, że sama Strategia semantycznie jest niczym innym jak Funkcją (w kontekście programowania funkcyjnego) przekazaną na prawach First Class Citizen. Jedynie syntaktycznie mamy niezręczność wynikającą ze składni Javy;)
Wprowadzenie Wzorca Strategii zaczyna się nam opłacać pod warunkiem, że:
- spodziewamy się pojawiania się kolejnych strategii w przyszłości
- chcemy testować jednostkowo niezależnie logikę OrderItema od logiki StrategiiBiznesowych
Pośrednim rozwiązaniem prowadzącym w kierunku wzorca - dającym potencjał na łatwy refaktoring w przyszłości jest:
1. wyciągnięcie interfejsu
2. dostarczenie jedynej implementacji jako serwisu ze słczami w stylu rozwiązania drugiego.
//=========================================
O opłacalności wprowadzenia Strategi możemy mówić również na poziomie modelowania.
Np w DDD nie mówi się Strategia lecz Polityka. Polityki są częścią modelu - obok encji i kilku innych "klocków".
Skutkuje to tym, że zaczynamy pokazywać wariacje istotnych zachowań biznesowych w modelu (diagramach, itd) a co za tym idzie zaczynamy o nich mówić (wchodzą do Ubiquotus Language)
Co najważniejsze: zaczynamy zauważać te arcy-ważne z biznesowego punktu widzenia komponenty - już nie siedzą w słiczach w linijce numer 5000 w serwisie-ośmiotysięczniku;P
Mamy kilka możliwości...
Załóżmy, że ma my klasę:
class OrderItem{ //pola metodaBiznesowa1(){..} metodaBiznesowa2(){..} metodaBiznesowa3(){..} }
Co jeżeli mamy różne rodzaje/odmiany itemów, gdzie zachowanie tych metod różni się - innymi słowy algorytmy są różne?
1. Rozwiązanie na switchach:
metodaBiznesowa1(){ switch(jakisParametr){ case x: return ... case y: return ... } }
Konsekwencje: wszelkie zmiany wymuszają "grzebanie" w corowym kodzie biznesowym, czyli jego "brudzenie" czyli ponowne testowanie (nie jest to problem jeżeli mamy pokrycie testami automatycznymi:)
2. Rozwiązanie struktura i algorytmy danych
Generalnie podobne do poprzedniego z tym, że z wyniesieniem kodu biznesowego na zewnątrz - podane dla uzupełnienia wachlarza możliwości:
class OrderItem{ //gettery i settery } class BusinessService1{ doSth(OrderItem oi){ switch(oi.getSth()) //... } }
3. SŁABE (ale to zależy od szerszego kontekstu) rozwiązanie z dziedziczeniem:
Tworzymy specjalne klaski dziedziczące po OrderItem, w których nadpisujemy odpowiednie metody biznesowe.
Konsekwencja: mnożenie bytów.
Co jeżeli:
- OrderItem1 nadpisze metodę metodaBiznesowa1
- OrderItem2 nadpisze metodę metodaBiznesowa2
a OrderItemBum chce mieć obie metody biznesowe 1 i 2 takie jak klasy wymienione poprzednio?
Eksplozja kombinatoryczna!
Dziedziczenie jest dobre, ale wówczas gdy klasa ma ściśle określoną odpowiedzialność (jeden powód do zmiany). Wówczas nie ma możliwości zajścia eksplozji kombinatorycznej oraz mamy zwykle pewność zachowania Liskov Substitution Principle.
Co jeżeli klasy dziedziczące są wymagane ponieważ dodają nowe atrybuty?
Wówczas rozwiązanie z dziedziczeniem zaczyna się bronić. Jednak wciąż może dojść do eksplozji klas.
Wówczas lepiej łączyć rozwiązanie z dziedziczeniem z rozwiązaniem 4.
Innym podejściem jest unikanie dziedziczenia i zastosowanie Archetypu Biznesowego Product.
4. Wprowadzenie wzorca strategii.
class OrderItem{ metodaBiznesowa1(){ return strategiaBiznesowa1.go(...); } metodaBiznesowa2(){ return strategiaBiznesowa2.run(...); } metodaBiznesowa3(){..} }
StrategiaBiznesowa1,2 są interfejsami. Konkretne implementacje zajmują się szczegółami.
Unikamy eksplozji kombinatorycznej z podejścia trzeciego dlatego, że w tym przypadku ilość klas to suma możliwych strategi dla każdej metody biznesowej a nie ich iloczyn.
Ale zostawmy autystyczne miary ilościowe... Ten design się po prostu "czuje". Tak jak w naiwnym szkolnym przykładzie: samochód zawiera skrzynię biegów - pasującą do interfejsu (śruby, tryby). Samochód w żadnym wypadku nie dziedziczy po skrzyni biegów:)
//Chociaż kiedyś na rekrutacji ktoś na pytanie o nieksiążkowy przykład dziedziczenia odpowiedział mi: "telefonistka dziedziczy po telefonie".
Skąd OrderItem posiada strategieBiznesowe?
a) OrderItem pobiera z fabrykStrategii - niedobre ze względu na testability
b) OrderItem ma je "wstrzyknięte" z zewnątrz przez FabrykiItemów, które go tworzą lub przez RepozytiumItemów, które pobierają go z bazy.
ew. Fabryki/Repozytoria tworzą nadrzędny agregat (np Order), który zawiera w sobie itemy, ale to zależy od domeny - mechanizm jest ten sam.
To jaką staregięBiznesową "wstrzyknąć" jest decyzją biznesową, tak więc Fabryki/Repozytoria zawierają taką logikę odciążając coupling tworzenia od coplingu używania.
Więcej o rodzajach couplingu dowiemy się od ekspertów Google.
Warto zauważyć, że sama Strategia semantycznie jest niczym innym jak Funkcją (w kontekście programowania funkcyjnego) przekazaną na prawach First Class Citizen. Jedynie syntaktycznie mamy niezręczność wynikającą ze składni Javy;)
Wprowadzenie Wzorca Strategii zaczyna się nam opłacać pod warunkiem, że:
- spodziewamy się pojawiania się kolejnych strategii w przyszłości
- chcemy testować jednostkowo niezależnie logikę OrderItema od logiki StrategiiBiznesowych
Pośrednim rozwiązaniem prowadzącym w kierunku wzorca - dającym potencjał na łatwy refaktoring w przyszłości jest:
1. wyciągnięcie interfejsu
2. dostarczenie jedynej implementacji jako serwisu ze słczami w stylu rozwiązania drugiego.
//=========================================
O opłacalności wprowadzenia Strategi możemy mówić również na poziomie modelowania.
Np w DDD nie mówi się Strategia lecz Polityka. Polityki są częścią modelu - obok encji i kilku innych "klocków".
Skutkuje to tym, że zaczynamy pokazywać wariacje istotnych zachowań biznesowych w modelu (diagramach, itd) a co za tym idzie zaczynamy o nich mówić (wchodzą do Ubiquotus Language)
Co najważniejsze: zaczynamy zauważać te arcy-ważne z biznesowego punktu widzenia komponenty - już nie siedzą w słiczach w linijce numer 5000 w serwisie-ośmiotysięczniku;P
Subskrybuj:
Posty (Atom)