Thursday, December 17, 2009

Dziedziczenie i kompozycja



Ostatnio wygrzebałem w polskiej blogsferze namiar na Aristotle's Error or Agile Smile. Wynika z niego, że nasza wspaniała obiektowość jest dziełem przypadku (a co nie jest?), a ideologię o modelowaniu świata rzeczywistego dorobili spece od marketingu. Jak było w rzeczywistości nie dociekałem. Skoro już obiektowość, a wraz z nią sztandarowe dziedziczenie.

Dziedziczenie


Na Rysunek 1 jest fragment jakiegoś tam systemu. Pewnie każdy z nas widział podobne rzeczy choćby w Java API, które roi się od podobnych lub o wiele bardziej skomplikowanych konstrukcji.

Choć programiście całkiem nieźle używa się klas zaprojektowanych w ten sposób, to gdy programista chciałby na bazie takich konstrukcji rozwijać swoją aplikację (czytaj: dalej nieograniczenie dziedziczyć), to pojawia się kilka problemów:
  • Aby zrozumieć działanie metody MultiUserRemoteBookStore.findBook() zapoznać się z całą hierarchią dziedziczenia,
  • Faktycznie uruchamiany kod metody tej metody jest rozsiany pomiędzy wiele klas w hierarchii,

  • Wymusza się na nas użycie konstruktorów (parametrowych) z nadklas, których wcale nie mieliśmy zamiaru używać,


  • Dodatkowo nie wiadomo co robią te kostruktory; a nóż przy tworzeniu mojego obiektu coś wybuchnie…


  • Trudno przetestować jednostkowo nową klasę. No, bo jak tu zamokować kod wywoływany w testowanej metodzie poprzez super.findBook()?


Kompozycja

Większość problemów wynikających ze swobodnego dziedziczenia rozwiązuje kompozycja. Zamiast wyprowadzać nową klasę AcmeBookStore z MultiUserBookStore implementujemy główny interfejs BookStoreService a do potrzebnych metod odwołujemy się poprzez delegację. I kawałek kodu:

public class AcmeBookStore implements BookStoreService {

private MultiUserBookStore multiUserBookStore;

public Book findBook( String title ) {
//...
multiUserBookStore.findBook( title );
//...
}

}
(Na marginesie warto zauważyć, że w ten sposób stworzyliśmy implementację Dekoratora.) To rozwiązanie jest zdecydowanie bardziej czytelne i unit-testing-friendly. Kłopot pojawia się gdy nie w architekturze, z którą pracujemy nie istnieje odpowiednik interfejsu BookStoreService. Wtedy już, chcąc nie chcąc, zazwyczaj godzimy się godzimy się na dziedziczenie.

Programowanie poprzez interfejsy

Mając na uwadze w/w mogę zastanawiać się: w jaki sposób mogę projektować architekturę mojego kodu tak, aby nie generował problemów z dziedziczeniem. Pierwszą rzeczą, która przychodzi mi na myśl jest programowanie poprzez interfejsy. Nie oznacza to bynajmniej, że każda nowa klasa UserManager ma swój interfejs UserManagerService, albo (w wersji hardcore) z każdą nową klasą pojawiają się trzy nowe byty w systemie (UserManagerService, UserManagerImpl, AbstractUserManager). Zerknijmy na rysunek: W tej architekturze centralnym punktem systemu (podsystemu, biblioteki) jest interfejs – kontrakt, który mają realizować poszczególne implementacje. Specyfika implementacji np. RemoteBookStore uzyskiwana jest nie poprzez odpowiednie klasy narzędziowe np. RemotingUtilty. Dzięki temu można tworzyć kolejne implementacje specjalizujące się w coraz to nowych rzeczach, bez konieczności dziedziczenia.

Podsumowując

Ujawniły nam się następujące rzeczy:
  • Preferowanie kompozycji ponad dziedziczenie,
  • Programowanie poprzez interfejsy.
    Są to jedne z kluczowych paradygmatów programowania obiektowego. Mamy z nich następujące korzyści:
  • Kod jest czytelny


  • Kod jest otwarty na testowanie.



Ostatecznie pozostaje jedno pytanie: czy dziedziczenie jest złe? Nie, jest bardzo dobre... w pewnych kontekstach… Złe jest jego nadużywanie. Jak więc rozpoznawać, które konteksty są odpowiedni dla dziedziczenia? Pewnie można wykombinować jakieś obiektywne kryteria, ale ja proponuję znać się na intuicję: czy dziedziczenie uprościło czy zagmatwała architekturę? Czy łatwiej testować, czy trudniej? Czy przyjemniej się pisze, czy - przeciwnie - chce Ci się...

Wednesday, December 16, 2009

Dziedziczenie zasobnikowe i inne idiotyzmy

Wrzucam przykłady fantazji w dziedziczeniu, które spotkałem (serio!) parę lat temu. Pierwsze można nazwać dziedziczeniem zasobnikowym. Od razu widać co zrobił kreatywny programista. Pisząc fragment systemu raportowego potrzebował przeczytać dane z pliku properties, a że pamiętał coś takiego ze Struts2, to beztrosko sobie odziedziczył.



Drugi kwiatek jest nieco trudniejszy. Dlaczego okrąg dziedziczy z punktu? Otóż dlatego, jak wyjaśnił autor koncepcji, że punkt jest środkiem tego okręgu…

Monday, May 18, 2009

Takie będą systemy jak ich programistów chowanie

Zerknąłem na chwilę na blog Jacka. I przeczytałem następujący akapit.

Jest wiele podobnych [mb: do asm/cglib] tematów, których znajomość pomaga, ale nie będąc warunkiem koniecznym odkłada się je na półkę na bliżej nieokreśloną przyszłość. Możnaby mnożyć takich perełek bez liku, chociażby programowanie w języku funkcyjnym, albo nawet tak podstawowe jak wzorce projektowe


Zawrzałem. Jak, na wszystkie potwory Mordoru, ktoś kto pretenduje do poczytnego bloggera w obszarze Java EE, może pisać podobne rzeczy?!? (Dla sprawiedliwości dodam, że następne niecytowane zdania myśl rozwijają i nieco łagodzą). Po chwili zastanowienia zdałem sobie sprawę, że rzeczywiście nie trzeba znać wzorców, żeby programować, ale również:
  • nie trzeba używać sztućców, żeby się najeść
  • nie trzeba znać języka, żeby się komunikować za granicą
  • nie trzeba myć zębów
  • nie trzeba nosić czapki w zimę, a przy -30 kalesonów
  • nie trzeba czytać książek
  • nie trzeba się rozwijać
  • nie trzeba..., nie trzeba..., nie trzeba...

Żadnej z tych rzeczy nie trzeba. I z pewnością widzimy ludzi, którzy ich nie robią. Od nie robienia tych rzeczy się nie umiera. Przynajmniej nie od razu. Żadna z nich nie jest, wspomnianym przez Jacka, warunkiem koniecznym. Pytanie tylko czy chcemy tak żyć?

[mb: po dyskusji z komentatorami i przemyśleniu sprawy doszedłem do wniosku, że ten akapit to o 1 most za daleko...]Więc jeśli robisz coś, co jak mniemasz kształtuje świadomość programistów i tych, którzy dopiero zaczynają tę przygodę, to kieruj ich w jak najlepszą twoim zdaniem stronę. Nawet jeśli nie dotrą do ideałów, o których piszesz, to:

zostanie ta siła fatalna,
co tobie żywemu na nic, tylko czoło zdobi,
a będzie ich gniotła niewidzialna,
aż zjadaczy chleba w aniołów przerobi


Wpis kończę parafrazą myśli przypisywanej Zamoyskiemu, Modrzewskiemu albo Staszicowi: takie będą systemy, jak ich programistów chowanie!

Tuesday, April 21, 2009

Domain Specific Language - XML i Fluent Interface




Zamiast rozwodzić się nad definicjami i pojęciami zacznę od pewnego problemu, z którym się spotkałem. Jakiś czas temu do generowania raportów używaliśmy biblioteki JasperReports. To naprawdę rewelacyjna biblioteka, której ideę działania przedstawia Rysunek 1:

Przy całej swej wspaniałości korzystanie z tej (i pewnie z innych) biblioteki do generowania raportów ma następujące konsekwencje:

  • Szybko generuje się tylko płaskie raporty czyli takie, w których wynikowy raport można „pociąć” na części generowane z jednego szablonu np. spis treści, lista płac, podsumowanie. Jeśli chcielibyśmy zagnieżdżać raport w raporcie, to oczywiście JasperReports daje taką możliwość, ale o całe przygotowanie danych, odpowiednią konfigurację definicji wyglądu i dostarczanie silnikowi danych w odpowiednich porcjach należy zadbać własnoręcznie.

  • Jeżeli raport musi odwzorowywać wiedzę dziedzinową, np. raportowane są wyniki testu IQ i związana jest z tym złożona merytoryka, to albo bardzo skomplikowane jest przygotowywanie danych a konfiguracja definicji wyglądu w miarę prosta albo odwrotnie. Trudno znaleźć złoty środek.

  • Ubogacanie raportów wykresami jest możliwe, lecz bardzo pracochłonne.

  • Jeśli automatyzowane są raporty, które do tej pory były tworzone ręcznie, to...czeka nas baaaardzo dużo pracy.


Framework raportowy


Powstał pomysł, aby problemom zaradzić. Stworzone rozwiązanie powinno zapewniać następujące funkcjonalności:

  • Definiowanie fragmentów raportu (podraportów), które można ze sobą łączyć sekwencyjnie (jeden za drugim) lub poprzez agregację (jeden w drugim)

  • Raport można zasilać danymi z: zapytania SQL, zapytania MDX, wyrażenia XPath lub Expression Language

  • Raport można wzbogacić wykresem z dostępnego zbioru wykresów.

  • I najważniejsze – wszystko wyżej wymienione powinno odbywać się deklaratywnie.


Została stworzona nadbudówka (warstwa abstrakcji – uwielbiam to wyrażenie!) na JasperReports, która pozwoli deklaratywnie tworzyć raporty. Jak zatem opisywać (deklarować) raporty tak, aby sprostać wymaganiom? Wybór padł na XML. Rysunek 2 prezentuje szkic rozwiązania.

Pojawiły się nowe elementy całego rozwiązania: plik XML, w którym będzie deklarowany raport (a konkretnie podraporty oraz zależności pomiędzy nimi) oraz nadbudówka, która przejmuje kontrolę nad silnikiem JasperReports. Oczywiście pojawia się dodatkowa warstwa pośrednicząca pomiędzy klientem, który chce wygenerować raport, a biblioteką. Wszystko ma swoją cenę. W tym przypadku prostota tworzenia raportów kosztuje dodatkową pracę przy implementacji warstwy pośredniczącej.

Spójrzmy na zamieszczony przykład pliku opisującego raport (na potrzeby artykułu nieco uproszczonego).

<reportConfiguration>
<datasources>
<datasource name="hsql" default="true" type="sql">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:hsql://localhost/mydb" />
<property name="username" value="sa" />
<property name="password" value="" />
</datasource>
</datasources>

<tableOfContents enabled="true" position="begin" designFile="tableOfContents.jrxml" />

<subreports>
<subreport type="static" name="introduction" designFile="introduction.jrxml" />

<subreport type="sql" name="employeesList" designFilePath="employeesList.jrxml"
query="select * name,surname from emplyees" />
</subreports>
</reportConfiguration>


Pojawiły się tu słowa (w postaci znaczników XML) jak reportConfiguration, tableOfContents, subreport, desigFilePath. Nie są to słowa związane z językiem programowania, lecz przeznaczone do opisu jednego konkretnego problemu – deklaratywnego tworzenia raportów. Przy użyciu XML zdefiniowaliśmy język opisu raportów, język opisu specyficznego problemu – Domain Specific Language (DSL).

DSL - implementacja oparta o XML


Z punktu widzenia użytkownika opisującego raport XML jest wygodnym rozwiązaniem. W tym przypadku użytkownikami byli programiści, którzy dodawali funkcjonalność generowania raportów do systemów nad którymi pracowali. Ale czy takie rozwiązanie jest najlepsze z perspektywy całego zespołu, w tym grupy odpowiedzialnej za rozwijanie Framework raportowego? Aby odpowiedzieć na to pytanie należy przyjrzeć się sposobowi zaimplementowania tego rozwiązania.

Pierwszym krokiem jest odczyt konfiguracji z pliku XML. Wygląda to na dość żmudne zadanie. Ale zauważmy, że składnia XML ma charakter obiektowy. Znacznik odpowiada obiektowi, a atrybuty znacznika, atrybutom obiektu. Powstaje pytanie: czy istnieje biblioteka pozwalająca automatycznie przekształcić plik XML do odpowiadającego mu modelu obiektowego? Istnieje – Apache Betwixt (być może są też inne). Rozwiązanie przybiera następującą postać ja na Rysunek 3.

Każdemu znacznikowi z pliku XML odpowiada klasa Java. Biblioteka Betwixt jest odpowiedzialna za przekształcenie struktury XML do analogicznej struktury klas. Aby wykonać to zadanie potrzebna jest dodatkowa konfiguracja. W przypadku tej biblioteki konfigurowanie polega na utworzeniu dla każdej klasy modelu obiektowego pliku .betwixt, w którym zawarte są informacje o mapowaniach znacznik XML – obiekt. Rozwiązanie to przedstawia Rysunek 4.

Zauważmy, że struktura obiektowa odpowiada strukturze pliku XML (Listing 1). DSL w postaci pliku XML ma następujące zalety:

  • Jest intuicyjny i jego utrzymywaniem mogą zajmować się nieprogramiści;

  • Opis raportu jest oddzielony od kodu aplikacji;

  • Można stworzyć elastyczne reguły walidacji składni w oparciu o plik XSD;

  • Oparcie na standardzie XML daje możliwość edytowania pliku w już istniejących narzędziach WYSIWYG lub stworzenia nowego narzędzia do graficznego opisywania raportu;

  • Konwersja do nowej wersji DSL jest łatwa do zaimplementowania, np. za pomocą transformat XSLT lub po stronie języka Java.


Jednocześnie to rozwiązanie ma następujące wady:

  • Występuje dodatkowa warstwa pośrednicząca w postaci biblioteki Betwixt;
  • Framework korzysta już z 3 rodzajów konfiguracji:

    1. Pliki jrxml – wymagane przez bibliotekę JasperReports definiują wygląd graficzny podraportów;

    2. Pliki betwixt – wymagane przez bibliotekę Betwixt mapują znaczniki XML na obiekty Java;

    3. Plik konfiguracyjny tworzonego frameworka – opisuje zależności pomiędzy podrap ortami, jest to punkt centralny naszego rozwiązania;

  • Podczas dodawania nowych funkcjonalności najbardziej pracochłonne są modyfikacje związane z plikiem konfiguracyjnym: mapowania, konwersje danych, obsługa błędów;

  • Ze względu na narzut prac nad plikiem konfiguracyjnym czas oczekiwania na nową wersję biblioteki jest dość długi;

  • Refaktoryzacja kodu na styku kod Java plik XML jest żmudna i trudna do przeprowadzenia;

  • Testowanie konfiguracji XML jest niewygodne – bardzo szybko rozmnażają się testowe pliki konfiguracyjne zawierające różne wersje konfiguracji do przetestowania;

  • Refaktoryzacja testów jest bardzo pracochłonna ze względu na istnienia bardzo wielu plików konfiguracyjnych;



DSL - implementacja oparta o Fluent Interface


W poszukiwaniu alternatywy do implementacji DSL we frameworku raportowym przyjrzyjmy się koncepcji Fluent Interface. Fluent Interface to sposób projektowania API, który łatwo można zobrazować na przykładzie biblioteki JMock.

mock.expects(once()).method("method").withAnyArguments().isVoid();

Trudno o bardziej trafną nazwę niż Fluent Inteface. Wywołania metod można przeczytać jak zdanie w języku naturalnym. Sam koncept nie jest nowy, ja doszukiwałbym się korzeni w kaskadowym korzystaniu ze strumieni w C++ za pomocą operatorów << i >>.

Zaprojektujmy zatem Fluent API do konfiguracji raportu. Powinno ono spełniać następujące założenia:

  • Wywołania kaskady metod czyta się jak zdanie w języku naturalnym;

  • API powinno sugerować właściwą kolejność wywoływania metod.

  • Nowe API nie powinno ingerować w istniejący model obiektowy.


Na początek zajmijmy się klasą DatasourceBean. Potrzebujemy udostępnić metody o odpowiednich nazwach, które będą konfigurowały obiekty tej klasy. Jednocześnie API nie powinno ingerować w już istniejące rozwiązanie. Wymóg separacji API od modelu reprezentującego opis raportu wynika z tego, że nie ma potrzeby, aby obiekty wiedziały w jaki sposób są konfigurowane. W terminologii angielskiej używane jest przy tej okazji słowo agnostic.

Rozwiązaniem spełniającym powyższe kryteria jest wzorzec budowniczego

Odpowiedzialnością klasy DatasourceBeanBuilder jest udostępnienie Fluent API do konfigurowania obiektu klasy DatasourceBean. Praca z API mogłaby wyglądać następująco:


builder.isSqlDatasource()
.withDriver( "org.hsqldb.jdbcDriver" )
.withUrl( "jdbc:hsqldb:hsql://localhost/mydb" )
.withUsername( "sa" )
.withPassword( "" )
.isDefault();

Zapewne domyślasz się, że sztuczka pozwalająca na kaskadowe wywoływanie metod polega na zwracaniu referencji do budowniczego w każdej metodzie. Poniżej implementacja tego budowniczego.

public class DatasourceBuilder {

private DatasourceBean bean = new DatasourceBean();


public DatasourceBuilder withUsername(String username) {
bean.addProperty( new PropertyBean("username", username) );
return this;
}

public DatasourceBuilder withPassword(String password) {
bean.addProperty( new PropertyBean("password", password) );
return this;
}

public DatasourceBuilder withUrl(String url) {
bean.addProperty( new PropertyBean("url", url) );
return this;
}

public DatasourceBuilder withDriver(String driver) {
bean.addProperty( new PropertyBean("driver", driver) );
return this;
}

public DatasourceBuilder isSqlDatasource() {
bean.setType( "sql" );
return this;
}

public DatasourceBuilder isDefault() {
bean.setDefaultDatasource( true );
return this;
}
}


Dla każdej klasy modelu obiektowego będzie istniał budowniczy odpowiedzialny za udostępnienie API do konfiguracji obiektu (Rysunek 6).

Jednym z założeń projektowanego interfejsu było, aby sugerował on programiście kolejność wywoływania metod. Osiągnęliśmy to poprzez wprowadzenie wielu budowniczych. W ten sposób użytkownik, w danym momencie pracuje tylko z jednym budowniczym i ma dostęp do metod, które mają sens w danym kontekście.

Centralnym punktem API jest ReportConfigurationBeanBuilder, który udostępnia użytkownikowi bardziej szczegółowych budowniczych za pomocą swoich metod (Rysunek 7).

Przy obecnym rozwiązaniu użytkownik korzysta z API w następujący:

ReportBuilder report = new ReportBuilder();
report.withMasterDesing( "master.jrxml" )

report.newDataSource( "hsql" )
.isSqlDatasource()
.withDriver( "org.hsqldb.jdbcDriver" )
.withUrl( "jdbc:hsqldb:hsql://localhost/mydb" )
.withUsername( "sa" )
.withPassword( "" )
.isDefault();

report.withTableOfContents()
.designFrom( "tableOfContents.jrxml" )
.atTheBegining();

report.addStaticSubreport( "introduction" )
.designFrom( "introduction.jrxml" )

report.addSqlSubreport( "employeeList" )
.designFrom( "employeeList.jrxml" )
.withQuery( "select * name,surname from emplyees" )
.getDataFrom( "hsql" );

Możemy połączyć konfigurację w jeden ciąg wywołań dodając do każdego budowniczego metodę public ReportBuilder and(). W ten sposób możliwe jest korzystanie z API w następujący sposób:

ReportBuilder report = new ReportBuilder();
report .withMasterDesing( "master.jrxml" )
.newDataSource( "hsql" )
.isSqlDatasource()
.withDriver( "org.hsqldb.jdbcDriver" )
.withUrl( "jdbc:hsqldb:hsql://localhost/mydb" )
.withUsername( "sa" )
.withPassword( "" )
.isDefault()
.and()
.withTableOfContents()
.designFrom( "tableOfContents.jrxml" )
.atTheBegining()
.and()
.addStaticSubreport( "introduction" )
.designFrom( "introduction.jrxml" )
.and()
.addSqlSubreport( "employeeList" )
.designFrom( "employeeList.jrxml" )
.withQuery( "select * name,surname from emplyees" )
.getDataFrom( "hsql" );

Wydaje się jednak, że to rozwiązanie jest mniej czytelne niż poprzednie.

DSL oparty o Fluent Interface ma następujące zalety:

  • Jest bardzo czytelny i wygodny w użyciu;

  • Łatwość tworzenia i utrzymania testów jednostkowych – ponieważ wszystko napisane jest w Javie (innym języku);

  • Wsparcie dla refaktoryzacji kodu i testów jednostkowych;

  • Zdecydowanie szybsza implementacja poprawek i nowych funkcjonalności w porównaniu z rozwiązaniem opartym na XML.


Przy powyższych niewątpliwych zaletach należy mieć świadomość następujących konsekwencji tego rozwiązania:

  • Tylko programista może konfigurować raport;

  • Zaprojektowanie dobrego Fluent API wymaga sporo czasu i wysiłku;

  • Stworzenie narzędzia WYSIWYG do konfigurowania raportu wymaga znacznego nakładu prac.



Krótkie podsumowanie


Zarówno XML jaki Fluent Interface mają swoje plusy dodatnie i ujemne. Można próbować określić zakresy przydatności każdego z rozwiązań. W omawianym przykładzie framework raporty był biblioteką programistyczną. Jedynymi jej użytkownikami byli programiści. Gdybym jeszcze raz brał udział w tym projekcie z pewnością przychylałbym się ku Fluent Interface. Głównym moim argument są korzyści wynikające z łatwości testowania, refaktoryzacji i możliwości szybkiej rozbudowy narzędzia. Gdyby jednak framework miał być wykorzystywany przez zewnętrzne systemy np. narzędzia WYSIWYG to XML, mimo narzutów implementacyjnych, jest rozwiązaniem bezkonkurencyjnym.

Przedstawione tu rozwiązania to nie jedyny sposób tworzenia DSL. Gdy tworzyliśmy framework raportowy pojawiały się pomysły napisania języka do opisu raportów przy użyciu Ruby. Wtedy jednak nie nazywaliśmy tego jeszcze DSL. Zainteresowanych tematem odsyłam do bliki Martina Fowlera.

Tuesday, April 7, 2009

Nawyki programisty

Artykuł ukazał się w kwietniowym numerze Software Developer's Journal

Jak myślisz, co ma wpływ na Twoją skuteczność, kiedy programujesz? Wśród wielu czynników z pewnością wymienisz: wiedzę, umiejętności techniczne, doświadczenie, motywację, zaangażowanie, współpracę, chęć uczenia się i wiele, wiele innych. Na ogólnym poziomie wymienione rzeczy dzielą się na dwie grupy: czynniki techniczne oraz czynniki nietechniczne. Zakładamy, że skoro jesteś programistą, to doskonale wiesz, czego potrzebujesz w obszarze umiejętności technicznych. Dlatego proponujemy Ci przyjrzenie się umiejętnościom z drugiej grupy – umiejętnościom nietechnicznym.

Rzucamy wyzwanie


Zaprezentujemy kilka prostych czynności, które konsekwentnie stosowane każdego dnia mogą wprowadzić ogromną jakościową zmianę w Twojej pracy.
Nie próbujemy Cię przekonywać, nie prosimy, abyś uwierzył na słowo. Sprawdź
i przekonaj się sam.

Zawrzyjmy kontrakt, że przez najbliższe trzydzieści dni będziesz stosować opisywane nawyki, a potem ocenisz efekty. W tym względzie wymagamy sumienności. Trzydzieści dni to trzydzieści dni i ani jednego mniej. To bardzo niewielkie wymaganie, biorąc pod uwagę fakt, że jakość Twojej pracy znacząco wzrośnie. Jeśli zrobisz przerwę w trakcie eksperymentu, to zacznij odliczanie od nowa. Mamy umowę?

Nawyk 1. – Rozpoczynaj pracę o stałej porze


Nie bez powodu metodyki z nurtu Agile zalecają trzymanie się ustalonej długości iteracji. Gdy wykonujesz rzeczy regularnie, w określonym porządku – nabierasz rytmu. W ten sposób skupiasz się na rzeczach najistotniejszych i nie zaprzątasz sobie głowy nieustannym organizowaniem swojego dnia pracy, ponieważ masz pewien nawykowy schemat postępowania.

Wielu programistów ma elastyczny czas pracy. Muszą być być na swoim stanowisku, np.: od 10 do 16, a pozostałe godziny mogą odpracować w domu. To bardzo wygodne rozwiązanie, ale powoduje, że dużo energii poświęcają na zorganizowanie każdego dnia, ponieważ każdy dzień jest inny. Stosowanie stałego rozkładu dnia pozwoli Ci skoncentrować się na rzeczach najważniejszych. Na początek rozpoczynaj dzień pracy o stałej porze.

Nawyk 2. – Planuj swój dzień


Jak często zdarza Ci się mówić: Nie mam czasu? To zdanie to bardzo dobry pretekst lub wymówka. Masz czas. Masz dokładnie tyle samo czasu, co każdy inny programista na Ziemi – 24 godziny w ciągu doby (życie na innych planetach pomijamy). Najważniejsze pytanie brzmi – co robisz z czasem, który masz do dyspozycji?

Przeprowadź eksperyment. Przeznacz jeden dzień na przyglądanie się wszystkiemu, co robisz w trakcie pracy. Zanim cokolwiek zrobisz – zapisz to. Przykładowe czynności, które możesz zanotować: napisanie testu metody, napisanie implementacji metody, wdrożenie aplikacji na serwer testowy, wyszukanie informacji w Internecie, rozmowa telefoniczna, sprawdzanie poczty, rozmowa, problemy z oprogramowaniem lub sprzętem.

Zapisanie zajmie najwyżej kilka sekund, zatem koszt naszego eksperymentu jest znikomy. Na koniec dnia powinieneś mieć spis rzeczy, które robiłeś w ciągu dnia. Następnie oblicz, jaki procent czasu przeznaczyłeś na programowanie. Jak bardzo zdziwił cię wynik?
Niech planowanie dnia stanie się Twoim codziennym rytuałem. Zanim zaczniesz pracę zaplanuj, co będziesz robić danego dnia i ustal, co jest dziś najważniejsze. Przeanalizuj jak wiele zrobiłeś i zaznacz wykonane zadania w swoim planie. Być może właśnie postanowiłeś, że najpierw przeczytasz jakąś supermądrą książkę na temat zarządzania czasem, zainstalujesz odpowiednie oprogramowanie i dopiero wtedy będziesz gotowy do działania zgodnie z planem. Nic z tych rzeczy, absolutnie tego nie rób. Jesteśmy bardzo dumni z Twojej kreatywności, ale książki i oprogramowanie nie są Ci w tej chwili potrzebne. Wystarczy kartka papieru i długopis. Zachowuj te kartki, niech to będzie Twój Osobisty Dziennik Sukcesów.

Nawyk 3. – Opracuj rozwiązanie przed implementacją


Słynny fizyk John Wheeler powiedział kiedyś: Nigdy nie zabieraj się do obliczeń, dopóki nie znasz odpowiedzi. Możemy sparafrazować tę myśl: nigdy nie zbieraj się do programowania, dopóki nie znasz rozwiązania. Dość często programiści rzucają się do implementowania, nie zadając sobie sobie podstawowego pytania: Czy to ma sens?. Gdy programujesz pod wpływem impulsu, napotykasz szereg problemów, na które nie jesteś przygotowany. Poświęcasz na ich rozwiązanie wiele energii i jednocześnie odczuwasz frustrację, że to zajmuje aż tyle czasu. Po kilku godzinach lub co gorsza po kilku dniach okazuje się, że funkcjonalność najlepiej zaimplementować w zupełnie inny sposób. Dochodzisz wtedy do wniosku, że wykonałeś kawał wspaniałej, lecz niepotrzebnej roboty.

Nigdy nie zabieraj się do programowania, dopóki nie znasz rozwiązania. Najpierw przemyśl swój pomysł, naszkicuj kilka diagramów, zbadaj konsekwencje rozwiązania, zastanów się nad alternatywami. Czasem kilkanaście minut prac koncepcyjnych oszczędza wiele godzin programowania.

Nawyk 4. – Szacuj zadania


Wykonanie zadania zajmie przeważnie co najmniej tyle czasu, ile na nie przeznaczysz. Jeśli na zaimplementowanie jakiejś funkcjonalności przeznaczysz 16 godzin, a uda Ci się zakończyć zadanie w 10, to kolejne 6 spędzisz na udoskonalaniu i poprawianiu rozwiązania albo nawet na sześciogodzinnej refaktoryzacji. Co więcej, znajdziesz bardzo wiele racjonalnych powodów, dla których było to absolutnie konieczne.

Zanim przystąpisz do programowania, oszacuj czas potrzebny na to zadanie. Prawdopodobnie miałeś już do czynienia z podobnymi zadaniami, a nawet jeśli nie, to znasz kogoś, kto robił coś podobnego. Będziesz mieć punkt odniesienia dla szacowania. W skrajnym przypadku oszacuj intuicyjnie. Od razu uprzedzamy, że często będziesz się mylić – to nieuniknione. Wszelkie informacje dotyczące odchylenia od przewidywanego czasu zapisuj w swojej bazie szacowań. Zaglądaj do niej często i koryguj przewidywania według zapisanych obserwacji. Bardzo szybko staniesz się ekspertem w tym obszarze.

Nawyk 5. – Odłącz się od internetu


Internet jest niecenioną pomocą w naszej pracy, lecz potrafi przykuć uwagę na długie godziny. Zastanów się jak często poszukujesz informacji w sieci i jak często kończysz surfowanie, gdy znajdziesz potrzebne rzeczy. Czy odsyłacze do innych stron nie kuszą Cię? Czy podczas zapoznawania się z API danej metody, nabierasz chęci zapoznania się z całą dostępną dokumentacją biblioteki lub frameworka? Jeśli tak,to odłącz się od internetu na czas pracy. Być może przychodzą Ci teraz do głowy myśli, że nie możesz dobrze pracować bez internetu, że będziesz potrzebował absolutnie niezbędnych informacji. Jeśli tak właśnie myślisz, to zajrzyj do notatek, które zrobiłeś w trakcie ćwiczenia dotyczącego planowania dnia (lub zrób je jeszcze raz na bardziej szczegółowym poziomie). Ile czasu poświęcasz poszukiwanie informacji potrzebnych do pracy, a ile na czytanie ciekawych artykułów nie związanych z tym co aktualnie robisz, na sprawdzanie poczty co pięć minut, na korzystanie z komunikatorów internetowych? Porównaj wyniki z czasem, który przeznaczasz na odnajdywanie informacji związanych z Twoim bieżącym zadaniem.

Aby pożytecznie korzystać z internetu, najlepiej podporządkować to aktualnemu zadaniu. Proponujemy następując schemat postępowania:
  • Zaplanuj swój dzień (patrz: Nawyk 2.);
  • Opracuj rozwiązanie przed implementacją (patrz: Nawyk 3.);
  • Zdobądź informacje potrzebne do wykonania zadania;
  • Odłącz internet i programuj przez dwie godziny;
  • Zrób przerwę, przeanalizuj to, co udało się wykonać i przejdź do kolejnej części zadania.

Musimy wyjaśnić jeszcze jedną rzecz. Przyczyną naszej propozycji odłączenia się od sieci nie jest internet sam w sobie, a celem wskazanego sposobu postępowania nie jest odcięcie Cię od źródła informacji. Naszym zamierzeniem jest zapanowanie na nawykowym i częstym, niż to potrzebne, korzystaniem z internetu, które rozprasza programistę i negatywnie wpływa na jego efektywność.

Nawyk 6. – W pierwszej kolejności poszukuj błędów w swoim kodzie


Za chwilę przeczytasz bardzo kontrowersyjny akapit, zatem zrób krótką przerwę. Przerwij na chwilę czytanie, popatrz na drzewa, zanuć ulubioną piosenkę.

Uwaga! Z naszego doświadczenia i z doświadczenia liderów, z którymi współpracujemy wynika, że większość przyczyn błędów w oprogramowaniu leży po stronie programisty, natomiast ten poszukuje ich na zewnątrz: w technologiach, w bibliotekach, w niedociągnięciach innych programistów, w złośliwości rzeczy martwych.

Przed Tobą ostatnie i najtrudniejsze ćwiczenie. Najtrudniejsze, ponieważ dotyka Twoich przekonań. Wiemy, że to niełatwe zdanie. Odwagi, uda Ci się!
Za każdym razem, gdy napotkasz irytujący błąd w kodzie, nad którym pracujesz, załóż, że to Ty zrobiłeś coś, co go spowodowało. Poszukuj przyczyny przez założony czas i dopiero, gdy to nie przyniesie efektu, przyjmij, że możesz szukać na zewnątrz.

Nie ma drogi na skróty


Być może zadajesz sobie pytanie: Ile czasu mam poświęcić, aby stać się wysoce efektywnym programistą?. Odpowiedź brzmi: Tyle, ile trzeba. Nie ma drogi na skróty. Nabywanie nowych umiejętności i rozwijanie ich wymaga czasu, pracy, cierpliwości i sumienności. Jeśli zaakceptujesz ten fakt, nauka stanie się łatwiejsza i przyjemniejsza. Nawyki, które opisaliśmy, mogą pomóc Ci skoncentrować działanie na ważnych rzeczach i zorganizować sposób pracy, lecz odpowiedzialność za ich wprowadzenie w życie leży po Twojej stronie – Ty decydujesz.

Wednesday, January 14, 2009

mistrzprogramowania.pl

Z przyjemnością informuję, że BNS IT wydało swoją pierwszą książkę!
Nosi piękny tytuł Jak całkowicie odmienić sposób programowania używając REFAKTORYZACJI.

Autorem książki jest Mariusz Sieraczkiewicz.

Szczegóły na mistrzprogramowania.pl

Możesz pobrać wersję bezpłatną (jest to ok. połowa książki i zawiera trochę reklam BNS IT).

Miłej lektury!

Friday, January 2, 2009

7 Kroków Zła w Projektach IT




Czy zastanawiałeś się już jak to się dzieje, że na początku projektu jesteś maksymalnie zmotywowany, piszesz wspaniałe testy (wszystkie są oczywiście green-bar), używasz swojego najpiękniejszego stylu kodowania, klasy, interfejsy metody, ba! nawet pola mają swoją własną, niepowtarzalną odpowiedzialność. Rysujesz piękne diagramy, używasz wzorców projektowych w rozsądnej ilości, a Twój kod czyta się jak najbardziej przejrzystego ebooka. A potem...potem pojawia się niewielki, ledwo zauważalny zgrzyt, mała rysa na wspaniałym krysztale projektu. Ignorujesz to, bo przecież nic się nie stało. Rysa rośnie, a przez nią wsączają się kolejne niedociągnięcia i niedoróbki, aż po jakimś czasie nie poznajesz już tego wspaniałego projektu, siadasz do niego z największym obrzydzeniem i myślisz tylko kiedy to się skończy? Lecz projekt się nie kończy, wciąż są nowe wymagania, wciąż trzeba go utrzymywać. I to, mój przyjacielu, jest Programistyczne Piekło!

Jako przestrogę podam Ci kilka prostych wskazówek jak się tam znaleźć. Ścieżka prowadząca wprost do Programistycznego Piekła jest szeroka, gładka i bardzo prosta. Wystarczy siedem kroków.

  1. Bądź obojętny
    Czy to ja powinienem zająć się tym //FIXME? Nieee, to nie ja pisałem, niech ktoś inny to zrobi.
    Czy często zdarza ci się rozumować w ten sposób? Jeśli tak, to gratuluję! Osiągnąłeś pierwszy poziom wtajemniczenia. Obojętność wobec niewielkich rys na projekcie to początek drogi w dół. Być może pomyślisz: oj, jeśli w jednej klasie będzie niewielki bałagan, to przecież nic się nie stanie. Pamiętaj, że małe rzeczy się kumulują. Nie zauważysz od razu różnicy pomiędzy metodą nazwaną divideByTwo(), a div2()
    , bo łatwo umyka wśród reszty kodu. Lecz z pewnością dostrzeżesz różnicę za pół roku, gdy tego typu nazewnictwo będzie obecne w całym projekcie. Ziarnko do ziarnka...
  2. Bądź w miarę wierny swoim ideałom
    Cóż, w zasadzie stosuję wzorce projektowe...to znaczy w niektórych projektach. Tych w domu. Piszę też testy...prawie wszystkie przechodzą. Piszę zgodnie ze standardem kodowania...właściwe to w 30%, bo reszta jest niepotrzebna. Odpowiedzialność? Taa, fajny koncept, ale moim zdaniem....
    Znasz to? Czy uważasz, że techniki programowania obiektowego teoretycznie są bardzo dobre ale faktycznie się nie sprawdzają? Jak wyrobiłeś sobie takie zdanie? Acha, przetestowałeś to, rozumiem. Wiesz, to że nie działają, to tylko jedna możliwość. Druga to taka, że używasz ich niewłaściwie. Co wybierasz?
  3. Postępuj zgodnie z procedurami
    Pokrycie kodu ma być co najmniej 90% więc w tym tygodniu już nie muszę pisać żadnego testu! Ta metoda jest nieczytelna...no, ale CheckStyle się nie czepia. Diagramy muszą być tylko do istotnych elementów systemu...moja jest chyba nieistotna?
    Procedury, reguły, zasady są dobre. Precyzują sposób pracy i optymalizują sposób wytwórczy. Często jednak, niemal automatycznie, uważa się, że stosowanie procedur w zupełności wystarczy, zatem można zwolnić się z myślenia. Kłopot w tym, że nawet najbardziej wyrafinowane zasady nie przewidują wszystkich sytuacji, które mogą wystąpić w życiu. Bardzo wiele problemów, z którymi się spotkasz, nie da się rozwiązać za pomocą Księgi Procedur używanej w twoim zespole. Jednak z każdym z nich można sobie poradzić przy użyciu zdrowego rozsądku.
  4. Graj dla zespołu
    Nie wychylaj się. Bezkrytycznie akceptuj opinie bardziej doświadczonych kolegów. Postaw cele zespołu nad swoimi własnymi
    Przede wszystkim: dlaczego pracujesz w tym zespole? Wymień dziesięć przyczyn. Zrób to teraz!
    Jeśli praca, która wykonujesz nie jest zgodna z twoimi osobistymi celami, nie prowadzi cię do jakiegoś wyżej postawionego celu, to prędzej czy później, zaczniesz sabotować to, co robisz. Krok po kroku, niepostrzeżenie, zaczniesz wprowadzać w życie misterny plan obniżania jakości swojej pracy. Być może już teraz warto wybrać się na rozmowę z samym sobą.
  5. Dziel się odpowiedzialnością
    Jest błąd w mojej części kodu? ...ale Paweł kazał mi tak napisać...powiedział, że będzie działać...
    Zadziwiające, że jeśli chodzi o sukces, to chętnie podpisujemy się pod nim sami. Jeśli chodzi o wzięcie odpowiedzialności za błąd i niepowodzenie chętnie dzielimy się odpowiedzialnością z innymi.
  6. Ceń ludzi takich jak ty
    MY programiści i ONI analitycy. MY javowcy i ONI .NETowcy. MY od kluczowego projektu i ONI od maintenance. MY starzy pracownicy i ONI nowi...
    Dzieląc swoich i innych sprawia, że nadajemy grupie osób wspólne cechy odróżniające ich od nas samych, czyniących w jakimś stopniu gorszymi. Etykietowanie zapewnia poczucie bezpieczeństwa i porządkuje świat. Daje złudzenie, że nie zginiemy w zespole jako indywidualność. Jak na ironię, takie postępowanie likwiduje indywidualność członków zespołu i blokuje efekt synergii, obejmującą ludzi nic nie znaczącą etykietą oni.
  7. Wierz w najlepsze rozwiązanie
    Ok, pracujemy w Agile, teraz już będzie dobrze. JEE rozwiąże wszystkie problemy. Nowy framework sprawi, że praktycznie kod będzie pisał się sam.
    Gdyby istniało rozwiązanie/technologia/framework rozwiązująca wszystkie problemy to już dawno by zostało wymyślone i żylibyśmy w świecie, w którym nie byłoby wojen, nikt nie przekraczałby prędkości, a programy by się nie zawieszały. Nie istnieje najlepsze rozwiązanie (w ogóle). Co najwyżej może istnieć rozwiązanie najlepsze, w sensie: najbardziej odpowiednie i optymalne, w danej chwili, dla danego problemu, w danym zespole. Jedyne co może zrobić nowa technologia, framework, biblioteka, czy język to przenieść ciężar problemu w inne miejsce. To coś jak dźwignia pozwalająca zużyć mniej energii do uzyskania tego samego efektu. Pamiętaj jednak, że zawsze istnieją dwie strony medalu. Coś co rozwiązuje jedne problemy zazwyczaj generuje nowe.

Tekst powstał z inspiracji artykułem Philipa Zimbardio Evil's Seven Step Seduction Scenario opublikowanym w Charaktery 9/2008