niedziela, 26 maja 2013

Od Zera Do Matchera

To jest materiał podstawowy, który przygotowałem do wewnętrznej prezentacji w pracy. Jeśli ktoś jest obcykany w matcherach niech nie traci czasu i poczyta sobie lepiej jakieś dzieła w stylu trzynastej księgi Pana Tadeusza.

Jeśli jednak ta tematyka jest Ci obca to zapraszam do lektury

Domena

Domenę biznesową tworzą dwie klasy - piosenka i techno mikser

 

public class Song {

 private String title;

 private int numberOfBits;
 
 public Song(String title,int numberOfBits) {
  this.title = title;
  this.numberOfBits = numberOfBits;
 }

 public void play(){
  System.out.println("plying "+title);
 }
 
 public String getTitle() {
  return title;
 }
 
 public int getNumberOfBits() {
  return numberOfBits;
 }
}

 

public class TechnoMixer {
 public List makeTechnoMix(Song... originalSongs){
  int technoMultiplier = 2;
  String technoPostfix = "_techno_mix";
  return mixSongs(technoMultiplier, technoPostfix, originalSongs);
 }
 
 public List makeThunderdomeMix(Song... originalSongs){
  int thunderdommeMultiplier = 4;
  String thunderdommePostfix = "_thunderdomme_mix";
  return mixSongs(thunderdommeMultiplier, thunderdommePostfix,
    originalSongs);
 }

 private List mixSongs(int bitsMultiplier,
   String titlePostfix, Song... originalSongs) {
  List mixedSongs=new ArrayList<>();
  for (Song originalSong : originalSongs) {
   int newNumberOfBits=originalSong.getNumberOfBits()*bitsMultiplier;
   String newTitle=originalSong.getTitle()+ titlePostfix;
   mixedSongs.add(new Song(newTitle, newNumberOfBits));
  }
  return mixedSongs;
 }
}

Działanie jest proste - TechnoMixer zwyczajnie zwiększa ilość bitów znacząco podnosząc komfort słuchania piosenki :) No i odpowiednio modyfikuje tytuł utworu oznajmując wszem i wobec, że słuchamy lepszej jej formy

Spróbujmy to przetestować...

Test próba pierwsza

 

@Test
public void shouldMixSongsToTechno() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 Assert.assertEquals(mixes.get(0).getNumberOfBits(), 200);
 Assert.assertEquals(mixes.get(1).getNumberOfBits(), 100);
 Assert.assertEquals(mixes.get(2).getNumberOfBits(), 300);
 
 Assert.assertEquals(mixes.get(0).getTitle(), "Equador_techno_mix");
 Assert.assertEquals(mixes.get(1).getTitle(), "Ona Tanczy Dla Mnie_techno_mix");
 Assert.assertEquals(mixes.get(2).getTitle(), "Harlem Shake_techno_mix");
}

Testy przechodzi ale wygląda kiepsko - a pamiętajcie, że to tylko prosty przykład. Przy bardziej skomplikowanych obiektach będzie jeszcze więcej syfu.

Test próba druga - zewnętrzna metoda

 

@Test
public void shouldMixSongsToTechno2() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 assertThatSongsHaveBits(mixes,200,100,300);
 assertThatSongsHaveTitles(mixes,"Equador_techno_mix","Ona Tanczy Dla Mnie_techno_mix","Harlem Shake_techno_mix");
}


private void assertThatSongsHaveBits(List mixes, int... expectedBites) {
 for (int currentSongNumber = 0; currentSongNumber < expectedBites.length; currentSongNumber++) {
  Assert.assertTrue(mixes.get(currentSongNumber).getNumberOfBits()==expectedBites[currentSongNumber]);
 }
}

private void assertThatSongsHaveTitles(List mixes, String... expectedTitles) {
 for (int currentSongNumber = 0; currentSongNumber < expectedTitles.length; currentSongNumber++) {
  Assert.assertEquals(mixes.get(currentSongNumber).getTitle(),expectedTitles[currentSongNumber]);
 }
}

Test wygląda już czytelniej. Metody są bliźniaczo podobne ale nie będziemy ich jeszcze refaktorować. Na razie zastanówmy się nad kilkoma rzeczami :

  • Czy wywołanie może być jeszcze czytelniejsze
  • Czy można gdzieś przenieść metody pomocnicze aby nie zaśmiecały testu
  • Zakłądając, że w bardziej skomplikowanej domenie obiekt Song mógłby wystąpić w większej ilości testów - czy można zrobić jakoś tak aby metod "reużyć"?

Można!

Matcher - wersja pierwsza

 

@Test
public void shouldMixSongsToTechnoWithMatcher() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 SongMatcher.assertThat(mixes).haveBits(200,100,300).and().titles("Equador_techno_mix","Ona Tanczy Dla Mnie_techno_mix","Harlem Shake_techno_mix");
}

 

public class SongMatcher {

 private List songs;

 public SongMatcher(List songs) {
  this.songs = songs;

 }

 public static SongMatcher assertThat(List songs) {
  return new SongMatcher(songs);
 }

 public SongMatcher haveBits(int... expectedBits) {
  for (int currentSongNumber = 0; currentSongNumber < expectedBits.length; currentSongNumber++) {
   Assert.assertEquals(expectedBits[currentSongNumber],songs.get(currentSongNumber).getNumberOfBits());
  }
  return this;
 }

 public SongMatcher titles(String... expectedTitles) {
  for (int currentSongNumber = 0; currentSongNumber < expectedTitles.length; currentSongNumber++) {
   Assert.assertEquals(expectedTitles[currentSongNumber],songs.get(currentSongNumber).getTitle());
  }
  return this;
 }

 public SongMatcher and() {
  return this;
 }

}

Test wygląda już ok ale w matcherze mamy te jakże podobne do siebie metody. A Gdyby tak obiekt miał 10 własności? Czas na jakiś refaktoring - na tyle na ile znam refleksję to wyjdzie coś takiego....

Matcher - wersja druga

 

public class SongMatcher2 {
 private List songs;

 public SongMatcher2(List songs) {
  this.songs = songs;

 }

 public static SongMatcher2 assertThat(List songs) {
  return new SongMatcher2(songs);
 }

 public SongMatcher2 haveBits(Integer... expectedBits) {
  assertValueOnProperty("NumberOfBits",expectedBits);
  return this;
 }
 
 public SongMatcher2 titles(String... expectedTitles) {
  assertValueOnProperty("Title",expectedTitles);
  return this;
 }


 private void assertValueOnProperty(String fieldName, Object[] expectedBits) {
  Assert.assertNotNull(songs);
  Assert.assertFalse(songs.isEmpty());

  try {
   assertValueOnPropertyUnsafely(fieldName, expectedBits);
  } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
   throw new RuntimeException(e);
  }
  
 }

 private void assertValueOnPropertyUnsafely(String fieldName,
   Object[] expectedBits) throws NoSuchMethodException,
   IllegalAccessException, InvocationTargetException {
  Method methodToInvoke = songs.get(0).getClass().getDeclaredMethod("get"+fieldName);
  for (int currentNumberOfValue = 0; currentNumberOfValue < expectedBits.length; currentNumberOfValue++) {
   Song currentSong = songs.get(currentNumberOfValue);
   Object invokeResult = methodToInvoke.invoke(currentSong,new Object[0]);
   Assert.assertEquals(expectedBits[currentNumberOfValue], invokeResult);
  }
 }

 

 public SongMatcher2 and() {
  return this;
 }
}

No i uważne oko zauważy, że wykształcił nam się taki matcher w matcherze. Zakładając, że obiektów w domenie będzie więcej to być może znowu jakoś tę logikę z refleksją da się gdzieś tam ładnie udostępnić. Możemy alb o delegować albo dziedziczyć. Niby książki mówią, żeby delegować ale tutaj z powodów nad którymi teraz nie chce mi się rozwodzić będziemy dziedziczyć.

Matcher - wersja trzecia

 

public class BaseMatcher {

 protected List objectsUnderTest;
 
 public BaseMatcher(List objectsUnderTest) {
  this.objectsUnderTest = objectsUnderTest;
 }

 protected void assertValueOnProperty(String fieldName, Object[] expectedValues) {
  Assert.assertNotNull(objectsUnderTest);
  Assert.assertFalse(objectsUnderTest.isEmpty());

  try {
   assertValueOnPropertyUnsafely(fieldName, expectedValues);
  } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
   throw new RuntimeException(e);
  }
  
 }

 private void assertValueOnPropertyUnsafely(String fieldName,
   Object[] expectedBits) throws NoSuchMethodException,
   IllegalAccessException, InvocationTargetException {
  Method methodToInvoke = objectsUnderTest.get(0).getClass().getDeclaredMethod("get"+fieldName);
  for (int currentNumberOfValue = 0; currentNumberOfValue < expectedBits.length; currentNumberOfValue++) {
   Object currentObject = objectsUnderTest.get(currentNumberOfValue);
   Object invokeResult = methodToInvoke.invoke(currentObject,new Object[0]);
   Assert.assertEquals(expectedBits[currentNumberOfValue], invokeResult);
  }
 }

 

 public BaseMatcher and() {
  return this;
 }
}

 

public class SongMatcher3 extends BaseMatcher{

 public SongMatcher3(List songs) {
  super(songs);
 }

 public static SongMatcher3 assertThat(List songs) {
  return new SongMatcher3(songs);
 }

 public SongMatcher3 haveBits(Integer... expectedBits) {
  assertValueOnProperty("NumberOfBits",expectedBits);
  return this;
 }
 
 public SongMatcher3 titles(String... expectedTitles) {
  assertValueOnProperty("Title",expectedTitles);
  return this;
 }
}

Na razie możemy testować tylko listy ale to na potrzeby przykładu i jeszcze do tego wrócimy. Wygląda całkiem obiecująco ale pojawia się pewien problem otóż kompilator zgłasza błąd w teście :

 

@Test
public void shouldMixSongsToTechnoWithMatcher3() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 SongMatcher3.assertThat(mixes).haveBits(200,100,300).and();// O TUTAJ JEST BŁĄD .titles("Equador_techno_mix","Ona Tanczy Dla Mnie_techno_mix","Harlem Shake_techno_mix");
}

Dzieje się tak dlatego, że nasz matcher bazowy nic nie wie o metodach matchera domenowego. Ostatni szlif i ostatnia sztuczka własnie przed nami. Musimy jakoś tę wiedzę do nadklasy przekazać.

Matcher - wersja czwarta

 

public class BaseMatcher2 {

 private K self;
 
 protected List objectsUnderTest;
 
 public BaseMatcher2(List objectsUnderTest,Class selfClass) {
  this.objectsUnderTest = objectsUnderTest;
  this.self=selfClass.cast(this);
 }

 protected void assertValueOnProperty(String fieldName, Object[] expectedValues) {
  Assert.assertNotNull(objectsUnderTest);
  Assert.assertFalse(objectsUnderTest.isEmpty());

  try {
   assertValueOnPropertyUnsafely(fieldName, expectedValues);
  } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
   throw new RuntimeException(e);
  }
  
 }

 private void assertValueOnPropertyUnsafely(String fieldName,
   Object[] expectedBits) throws NoSuchMethodException,
   IllegalAccessException, InvocationTargetException {
  Method methodToInvoke = objectsUnderTest.get(0).getClass().getDeclaredMethod("get"+fieldName);
  for (int currentNumberOfValue = 0; currentNumberOfValue < expectedBits.length; currentNumberOfValue++) {
   Object currentObject = objectsUnderTest.get(currentNumberOfValue);
   Object invokeResult = methodToInvoke.invoke(currentObject,new Object[0]);
   Assert.assertEquals(expectedBits[currentNumberOfValue], invokeResult);
  }
 }

 public K and() {
  return self;
 }
}

 

public class SongMatcher4 extends BaseMatcher2{

 public static SongMatcher4 assertThat(List songs) {
  return new SongMatcher4(songs);
 }

 public SongMatcher4(List objectsUnderTest) {
  super(objectsUnderTest, SongMatcher4.class); 
 }

 public SongMatcher4 haveBits(Integer... expectedBits) {
  assertValueOnProperty("NumberOfBits",expectedBits);
  return this;
 }
 
 public SongMatcher4 titles(String... expectedTitles) {
  assertValueOnProperty("Title",expectedTitles);
  return this;
 }
}

Teraz powinno być ok

Co dalej?

Jak już wcześniej zwróciliśmy uwagę powyższe matchery nadają się jedynie do testowania list. Można by klasę bazową zamienić w fabrykę wyspecjalizowanych matecherów co ma sens bo testowania czy ilość elementów jest większa od zera nie ma sensu dla pojedynczych obiektów. I coś takiego jest już gotowe, nazywa się FEST i gorąco polecam

Co Jeszcze?

Przy okazji pisania artykułu użyłem JUnit 4.11, który ma nowy ciekawy patent : @FixMethodOrder(MethodSorters.NAME_ASCENDING). Od czasu przesiadki na Javę 7 JUnit zaczął wywoływać metody w losowej kolejności co czasami psuje testy integracyjne bo tam np. testując flow transformacji plików dla zaoszczędzenia czasu kolejne testy korzystają ze stanu pozostawionego przez poprzednie fazy. A ta małą adnotacja działa jak do rany przyłóż.

poniedziałek, 20 maja 2013

GeeCON 2013 - rekonstrukcja zdarzeń

Programowanie Funkcyjne

Ten temat pojawił się trzeciego dnia, więc pamiętam go najlepiej bo jako odpowiedzialny kierowca byłem wtedy trzeźwy i wyspany. Generalnie do czasu tej konferencji programowanie funkcyjne kojarzyło mi się jedynie z przetwarzaniem danych pomiędzy zbiorami gdzieś tam wewnątrz aplikacji (tak jak na matematyce dyskretnej w klasie 1 studiów). Aż tu na jednej prezentacji pojawił się rysunek podobny do poniższego (odtworzone z pamięci) :

Pewnie dla wyjadaczy scali, klołżerów i innych takich ten temat jest oczywisty jak składanie pita37 ale dla mnie to był moment oświecenia, że jako funkcję można przedstawić dowolne "coś" co dostaje na wejściu "cokolwiek" i zwraca "coś innego". I teraz taki Wzorzec Dekorator wydaje się być hakiem na obiekcie gdzie czymś bardziej naturalnym byłoby wywołanie f(g(x)) albo F(f1(x),f2(x),...) - jeśli dobrze zrozumiałem F to funkcja wyższego rzędu a fn funkcje niższego rzędu - czyli tutaj to mogą być np poszczególne walidatory w trakcie jednej wielkiej walidacji.

Jest jeszcze jedna ciekawa a być może nawet ważniejsza korzyść jaką może przynieść programowanie funkcyjne. Generalnie we wszystkich książkach o OOP we wstępie jest napisane, że właśnie ten sposób jest naturalny dla ludzi i dlatego wszystko pójdzie gładko. A to gówno prawda bo ludzie myślą w sposób proceduralny przez co bliżej nam do programowania imperatywnego co ma swoje potwierdzenie w kodzie większości studentów - masa ponakładanych na siebie ifów,forów i innego świństwa zamkniętych w 8000 linijkową metodę.

A jeśli do kogoś to nie przemawia to niech pomyśli sobie co dzieje się w jego głowie gdy idzie pozmywać naczynia po obiedzie:

Czy coś takiego :

Mam do dyspozycji obiekt klasy Zlew, obiekt klasy Ludwik oraz kolekcję obiektów klasy NAczynie oraz obiekt klasy Człowiek (czyli JA)

 
zlew.umieść(naczynia)
ludwik.nalejDo(zlew)
kran.odkręć()
ja.zmyjNaczynia(zlew)


Czy Może naturalniej jednak będzie :

Jest do pozmyawania sterta naczyń
 
JA muszę wrzucić te wszystkie naczynia do zlewu
JA muszę teraz odkręcić wodę 
JA muszę teraz nalać ludwika
JA teraz dla każdego talerza
      muszę go umyć
      jeśli jest już czysty 
          to JA go odłożę na suszarkę
      jeśli nie
          to JA go myję dalej
      W międzyczasie zakręcę wodę
      Dobra zrobię sobie krótka przerwę
      (...)
      
No jeśli ktoś myśli o abstrakcjach i interfejsach w trakcie zmywania naczyń to jakieś dobre zioło namierzył albo po ASPięknych.

Generalnie zarówno programowanie obiektowe jak i funkcyjne nie jest naturalne dla naszego umysłu ALE. Klasy i obiekty to wynalazek greckich filozofów i chyba ktoś ich kalibru ma odpowiednie doświadczenie aby stworzyć poprawny model obiektowy. W przeciwieństwie do humanistycznej obiektówki programowanie funkcyjne opiera się na zrozumieniu ścisłych zasad matematyki. A enkapsulacja i re-używalność jest równie dobra.

?Nowe spojrzenie na dług techniczny

Temat długu technicznego, kompatybilności wstecz, dodawania nowych ficzerów itd pojawił się na kilka prezentacjach pierwszego i drugiego dnia. I w sumie tutaj tez jest mały mindfuck bo do tej pory TechDebt traktowałem jako coś bezpośrednio związanego z jakością kodu i architekturą systemu.

A tutaj pojawia się taka ciekawostka. Jeśli mamy np. takie wymagania :

  • Zrobić funkcjonalność 1
  • Zrobić funkcjonalność 2
  • Zrobić funkcjonalność 3


To one tak naprawdę brzmią

  • Zrobić funkcjonalność 1
  • Zrobić funkcjonalność 2 i nie popsuć funkcjonalności 1
  • Zrobić funkcjonalność 3 i nie popsuć funkcjonalności 1 i też nie popsuć funkcjonalności 2

No to teraz jeśli założymy, że mamy w naszym systemie N (dla uproszczenia) podobnych funkcjonalności, to koszt dodania funkcjonalności N+1 będzie:

  • Tam gdzie jakimś cudem osiągnęliśmy legendarny poziom modularyzacji - Cost~F(n+1)
  • Tam gdzie ficzery są od siebie zależne ale system jest gotowy na rozbudowę - Cost~F(n+1) + M(n) - gdzie M(n) koszt utrzymania przy życiu pozostałej części systemu
  • Tam gdzie jest skomplikowana interakcja (zamierzona lub nie) pomiędzy ficzerami - Cost~F(n+1)+M(n^2)
  • Tam gdzie gówno pływa swobodnie po kodzie, "big ball of mud" i wszystko jest powiązane ze wszystkim w 10000 linijkowych klasach nazwanych "Menażer" - Cost~F(n+1)+M(2^n)

Oczywiście poprzez refkatoringi, metodyki projektowe i inne takie można poprawić charakterystykę funkcji kosztu ALE - można też zmniejszyć pracochłonność zmniejszając N poprzez usunięcie częsci fukncjonalności. Niby szok kulturowy bo funkcjonalność - szczególnie ta działająca - zarabia dla nas pieniądze - no poza momentami kiedy ich nie zarabia!

Kiedyś czytałem w focusie artykuł o ludziach, którzy patologicznie kolekcjonują rzeczy przez co nie mogą się poruszać po swoim mieszkaniu. U mnie w komórce przez 10 lat też leżały jakieś deski bo a nuż kiedyś się przydadzą. Nikt ich nie używał to poszły na przemiał i podobnie trzeba wywalać jakieś dziwne opuszczone ficzery aplikacji, których nikt nie używa. Czyli nowy rodzaj długu technicznego : doskonale działąjąca funkcjonalność, której (praktycznie) nikt nie używa

Inne takie

Inna ciekawa rzecz to : http://code.google.com/p/spock/ w sumie fajna rzecz ale na Groovym . Po PHP trzymałem się z dala od języków dynamicznych ale trzeba się chyba przemów.

czwartek, 9 maja 2013

Jak się nie odkleić od rzeczywistości - lekarstwo na zarządzanie zasobami.

Odczuwam pierwsze objawy choroby, z którą zawsze walczyłem. Przez lata widziałem jak ludzie sprowadzani do roli pionka w projekcie zamieniają się w zombie pozbawione pasji. Wszędobylski palec wskazujący wsparty lśniącym gantem stanowił narzędzie wyroczni określającej byt pokornej jednostki ( w sensie - "ty Zdzichu tera robisz to i masz na to dwie godziny").

Odczuwam pierwsze objawy choroby, z którą zawsze walczyłem. Kiedy jesteś odpowiedzialny tylko za swoją pracę to dostaniesz wpi***** tylko za swoją pracę. Kiedy jesteś odpowiedzialny za pracę wielu to cios może przyjść z każdej strony. Umysł sam zaczyna się bronić szukając wszędzie potwierdzenia, że jesteśmy kryci - "czy to już jest gotowe" lub " na kiedy to będzie skończone" . Kiedy sam sobie piszesz kod doskonale wiesz na jakim etapie jesteś - kiedy masz dostać wpi**** za czyjś kod nagle zaczyna cię bardzo ciekawić na jakim etapie on jest - chyba dlatego ci ludzie zawsze tak bardzo wielbili raporty...

Odczuwam pierwsze objawy choroby, z którą zawsze walczyłem... i będę walczył dalej. Najpierw trzeba zrozumieć objawy, później opracować lekarstwo...

Objawy

Objawy są naturalne i występują u wszystkich znajdujących się w określonej sytuacji - no może prawie wszystkich. Aby to sobie przećwiczyć zerknijmy na obrazek powyżej, który pochodzi z gry kolonizacja. Jeśli ktoś o niej nie słyszał to idea w uproszczeniu wygląda tak - dopływamy do brzegu w 1492 roku, tworzymy miasto a w nim zbieramy surowce i produkujemy towary. Jeśli akurat z Europy wyemigruje specjalista to będziemy zbierać tych zasobów i produkować towarów więcej. Ale z drugiej strony jak już na przykład nazbieramy odpowiednio dużo rudy żelaza to przerzucamy górnika do pasania świń bo tak maksymalizujemy zyski miasta. Brzmi znajomo?

Widziałem to wiele razy w projektach bazujących na ganto-excelu - Dobra tego taska mamy zrobionego to przesuńmy risorsa do łatania layoutów, a później zbierzemy resztę risorsów do aktualizacji dokumentacji

Ba byłem nawet na studiach dla managerów i widziałem na własne oczy Ćwiczenia z MS Projecta polegające na "zrobieniu tak aby paski były zielone" . I ch** kogo obchodziło, że programista nazwany przez instrukcję ćwiczenia "Janem" będzie pracował przez 3 godziny and jednym taskiem, później przez godzinę nad innym i na koniec na godzinę wróci do pierwszego. Grunt, ze gantt będzie zielony.

No i pamiętam jak w jednej takiej firmie zawsze na koniec dnia manager robił obchód po sali i podbijał do każdego "raportuj, raportuj gdzie jesteś na gancie" - not kool , not f*** kool

Umysł dąży do uproszczenia modelu. A jeśli do tego psychika danego człowieka jest na etapie "jak najlepiej zaspokoić moje potrzeby" to mamy gotowy przepis na ganta-style-managera.

Co można z tym zrobić jeśli już sobie uświadomimy problem? Natura, mechanika czy elektronika już znalazły rozwiązanie. Co ma wspólnego układ hormonalny, lodówka i tranzystor z napięciem zasilania Baza-Kolektor?

Ujemne sprzężenie zwrotne!

Zaciśnij dłoń i uderz nią w ścianę - powinno zaboleć co powstrzyma cię przed kontynuowaniem tej czynności (no chyba, że bardzo to lubisz) - właśnie zostałeś poddany działaniu ujemnego sprzężenia zwrotnego. Jak tę koncepcję zastosować w IT? - proste - Jak już coś wymyśliłeś to musi cię to też zaboleć. Można to łatwo osiągnąć będąc cały czas blisko kodu. Ta koncepcja została ładnie opisana w tej książce ---> http://37signals.com/rework - wszystkie ręce na front! - jak już coś obiecałeś to też się przy tym namęczysz. A przy okazji będziesz wiedział co się dzieję w projekcie i nie będziesz ludzi męczył pytaniami (no dobra w praktyce na razie mi to tak w 90% przypadków wychodzi)

Dlatego cały czas intuicyjnie byłem przeciwny koncepcji team lidera, który biega sobie tylko na spotkania, naobiecuje i deleguje. Powiedziałem moim mocodawcom, że chcę być jak Mel Gibson w Patriocie (przełożeni z USA) z bagnetem na pierwszej linii frontu aniżeli jak jakiś generał siedzieć wygodnie w namiocie spokojnie przesuwający figury na mapie

No i akurat w tym tygodniu ktoś wrzucił taki fajny symboliczny rysunek na fejsa :