Archive

Posts Tagged ‘opowieści kombatanckie’

Jak to z błędem było: prezentowanie wyników wyszukiwania, a stosowanie if

05/09/2011 Dodaj komentarz

Ostatnio natknąłem się na pewien rodzaj błędu. Związany on jest z wyszukiwaniem; gdzie sytuacja jest prosta jak budowa cepa :): podanie kryteriów, wykonanie zapytania, i zaprezentowanie wyników użytkownikowi. Błąd ten jest o tyle złośliwy, ponieważ pojawia się tylko w części przypadków, dlatego też gdy już zostanie wprowadzony trudniejsze jest jego wykrycie.

Geneza jego jest następująca:
Na początek mamy typową wyszukiwarkę, schemat całego postępowania można przedstawić na poniższym obrazku.

Tak więc użytkownik definiuje kryteria wyszukiwania; co przekłada się następnie na odpowiednie zapytanie do bazy. Wynik, z wykonania zapytania, pakowany jest do obiektów (DTO), których to zbiór służy do wygenerowania prezentacji dla użytkownika.

Za pierwszym razem wszytko działa jak należy, ale klient życzy sobie pewnych modyfikacji. W prezentowanych wynikach, w niektórych przypadkach dla zwracanych rekordów wartość X jest nieprawidłowa (jest pusta). Związane jest to z brakiem odpowiednich danych w bazie. Braki te nie są zmartwieniem klienta, chodzi o prezentacje. Dlatego też, życzy on sobie, aby prawidłową wartość X dla tego przypadku pozyskiwać na podstawie innych wartości. Co w kodzie kończy się taką zmianą:

  public String getWartoscX() {
    if (wartoscX == null) {
      // tutaj poprawka
      return wartoscA + wartoscB;
    }
    return wartoscX;
  }

Zmiana jest wprowadzana; teraz wartości w każdym przypadku prezentują się poprawnie; zgłoszenie uznaję się za rozwiązany ;). Jednak wkradł się już błąd. Tutaj łatwiej go zauważyć: kryterium X dla modyfikowanych przypadków nie jest już poprawne. Jak już wspomniałem podmiana wartości X nie występuje zawsze, powiedzmy w 10% zwracanych rekordów, dlatego też błąd ten może istnieć długo zanim zostanie odkryty.

Dlaczego taka sytuacja powstała?
Oczywiście tutaj łatwiej widać absurdalność wykonania tej poprawki. W tym przypadku odpowiednim podejściem może być chociaż wprowadzenie widoku na bazie, co mogłoby wyglądać tak:

select NVL(wartoscX, wartoscA || wartoscB) as wartoscX, wartoscY, wartoscZ from tabela;

Jednak celem moim jest zastanowienie się dlaczego taka sytuacja mogła zajść (Skłoniło mnie do tego znalezienie w ostatnim czasie kliku przypadków takiego błędu):

  • pierwszą i główną przyczyną jest specyficzność przypadku, który dotyczy niewielkiego wycinka danych. Prowadzi to do tego, że osoba wykonująca poprawkę, może się błędnie sugerować prostotą rozwiązania jakie trzeba zastosować, i nie zastanawiać się bardziej nad jego prawidłowością.
  • drugą przyczyną jest złe skupienie uwagi co do miejsca w którym należy rozwiązać błąd. Nie na prezentacji wyników (tam umieszczać logikę rozwiązania), ale na zapytaniu i zwracanych wynikach. Powodem tego może być opis samego zgłoszenia, gdzie całość jest przedstawiona w łopatologicznej formie: „weź wartość A i wartość B; i dodaj do siebie”. Prawie jak poprawka literówki :).
  • kolejna sprawa to sugerowanie się domyślnym językiem używanym w pracy. Jak ktoś programuje w javie to łatwiej jest mu rozwiązać problem w tym języku, niż silić się na prace w sql.
  • jeszcze jeden powód to złożoność kodu, skutkujący tym że poprawiający ma trudność w uchwyceniu całego kontekstu. W przypadkach, które znalazłem, było to związane z dużym zamieszaniem w przekazaniu obiektów z danymi. Nie były to proste DTO, ale obiekty encji JPA przepakowane do pośrednich obiektów przy użyciu skomplikowanych reguł.

Ogólna rada na przyszłość to: jeśli poprawka dotyczy wycinka pewnego zbioru przyjrzeć się lepiej rozwiązaniu, traktować to bardziej podejrzliwie niż w przypadku gdy zmiany obejmują cały zbiór. Potrzeba wprowadzenia if_a powinna zapalać czerwone światełko :). Skłaniać do zastanowienia się czy w dobrym miejscu ten if jest umieszczany i czy znane są wszystkie miejsca w których powinien on być.
A co do wyszukiwarek dobrze jest rzeźbić je w sql, i nawet jeśli grozi to mnożeniem bytów, stosować szyte na miarę DTO (a nie kombinować z ponownym użyciem innych klas). Także nie umieszczać logiki biznesowej w prezentacji :).

Kategorie:Java Tags: , , , ,

O testowaniu

28/11/2010 Dodaj komentarz

Ostatnio doświadczyłem jak pożytecznym narzędziem potrafią być testy. Chodzi o przypadek, w którym pomagają one skrócić czas, jaki jest pomiędzy zrodzenia się pomysłu, a wdrożeniem go i sprawdzenia czy jest dobry.
Czytaj dalej…

Dokumentacja struktury bazy

25/08/2010 Dodaj komentarz

Trochę o generowaniu dokumentacji, czyli dobrze kiedy znaczną jej cześć można właśnie wygenerować.
Miałem za zadanie stworzyć dokumentacje struktury bazy dla klienta. Mogłem zakasać rękawy i mozolnie pokrótce opisać każdą z tabel oraz jej powiązania z innymi tabelami (dokument głównie to miało zawierać). Całość przy około 250 tabelach mogło mi zająć około 3 dni :). Ponieważ nie było to naglące i miałem trochę luzu postanowiłem poszukać sposobów na automatyzacje tego. Ostatecznie zajęło mi to ponad 5 dni 😉 (ale nie w całości poświęconych temu), no ale dzięki temu wykonanie ewentualnej aktualizacji jest teraz o wiele szybsze. Czytaj dalej…

Podejście do mavena

07/06/2010 Dodaj komentarz

Maven w połączeniu z m2eclipse jest o wiele przystępniejszy ;).
Jeśli pracuje się w eclipse, to taki zestaw oferuje wygodny sposób na sprawdzanie różnych koncepcji czy bibliotek, a to poprzez zarządzanie zależnościami. Zarządzanie jako takie oferuje san maven, ale plugin (m2eclipse) sprawia że jest to wygodniejsze (prywatne odczucie :)). Ma się także dzięki temu dostęp do ładnego edytora pomów, jednak treścią tego wpisu będzie to, że taki zestaw daje większą kontrole nad procesem budowy realizowanym przez maven.

Tak więc maven to wspaniałe narzędzie, pozwalające na szybkie wystartowanie z pracą. Wszytko pięknie jak postępuje się zgodnie z ustaleniami. W chwili kiedy sypnie błędem; albo nie, ale nie robi tego co się oczekiwało, mogą zaczynać się schody (znowu prywatne odczucie :)). Jest w tym chyba także trochę winy szybkości z jaką można tworzyć nowe projekty czy dodawać rozszerzenia. Ponieważ kiedy potrzebne jest aby pakować wynikową aplikacje do wara to odszukuje się w sieci odpowiedni wycinek skryptu (jakiegoś pluginu) i już jest war; trzeba wygenerować jakieś klasy, to kopiowany jest następny wycinek i generowanie działa. I bardzo często (mi się tak zdarzało) pracując w taki sposób robi się to bez dokładniejszego wczytania się w dokumentacje danego plugina (jeśli jest na przyzwoitym poziomie) i zadowala się tym, że teraz wydaje się że działa tak jak chcemy. Później natomiast okazuje się że przestaje działać (np. w połączeniu z innymi pluginami) i trzeba szukać dlaczego.
Tak więc mamy na początku zachętę w postaci szybkiego efektu, ale jeśli chce się coś więcej to późniejsza nauka narzędzia (plugin_a) nas nie o minie ;).
Przerywając te wywody skupie się na konkretnym przypadku i przedstawieniu w nim jak można się zabrać za maven_a (jego pluginy), kiedy coś do końca nie działa.

Stworzenie problemu
Istniała potrzeba generowania dwóch jarów (część kliencka i serwerowa), użyty plugin to maven-ejb-plugin. Pierwsza wersja (skopiowana/sklejona radośnie z znalezionych przykładów) to:

	 
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-ejb-plugin</artifactId>
	<configuration>
		<generateClient>true</generateClient>
		<ejbVersion>3.0</ejbVersion>
		<clientIncludes>
			<clientInclude>com/test/client/**</clientInclude>
		</clientIncludes>
	</configuration>
</plugin>

i działa, pojawia się drugi jar kliencki z klasami ze wskazanych pakietów. No i hula to jakiś czas, aż przychodzi prośba aby jar serwerowy miał też wyselekcjonowane klasy (nie zawierał części klienckiej). Nie ma problemu, w dokumentacji pisze, że tak można, więc wpis wygląda teraz tak:

	 
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-ejb-plugin</artifactId>
	<configuration>
		<generateClient>true</generateClient>
		<ejbVersion>3.0</ejbVersion>
		<clientIncludes>
			<clientInclude>com/test/client/**</clientInclude>
		</clientIncludes>
		<excludes>
		  <exclude>com/test/client/**</exclude>
		</excludes>
	</configuration>
</plugin>

Uruchamia się proces budowy i nie widać efektu, jar serwera ciągle zawiera klasy ze wszystkich pakietów. Wykonało się dla pewności clean_a, ale ewidentnie widać, że nie robi tego co miało i nie skarży się też żadnymi wyjątkami.
Tutaj powinienem spokojnie przeczytać dokumentacje i zrobić dokładnie jak tam jest, ale nastąpił pewien etap blokady i nie widziałem dość oczywistego błędu który popełniałem.
Zamiast tego zacząłem szukać rozwiązania na około.

Dochodzenie do rozwiązania problemu
Jako że ewidentnie plugin nie robił co twierdziła dokumentacja, a że był napisany w javie to postanowiłem dobrać się do jego źródeł. Najpierw poprzez dodanie odpowiedniej zależności:

	 
		<dependency>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-ejb-plugin</artifactId>
			<version>2.2.1</version>
		</dependency>

sprawiłem że jar plugina był w moim projekcie. Potem wykorzystując możliwości jakie dawał m2eclipse pobrałem źródła i mogłem przejrzeć w miarę prosty kod pluginu(dwie klasy z czego jedna to help). Okazała się że jest tam obsługa owego parametru, który powoduje odfiltrowanie klas z części serwerowej. W takim razie jedno zostało wyjaśnione, to że dokumentacja zgodna jest z kodem. Wykorzystałem więc następną możliwość jaką dawał mi plugin do eclipsa i odpaliłem cały proces budowy maven w trybie debug stawiając pułapki wewnątrz kodu podejrzanego plugina (było trochę zabawy, aby to zrobić, ale da się zmusić eclipsa do tego). W ten sposób zobaczyłem że moje pułapki w (wykonywany) kodzie zupełnie rozjeżdża się z tym co prezentowane jest w podglądzie kodu, tak jakbym patrzył na zupełnie inny kod. Co wskazało na błąd, który tu popełniałem. Budowałem na starszej wersji, która nie obsługiwała tego co chciałem zrobić. To że nie wskazałem wersji definiując plugin (co dziwne zrobiłem to kiedy chciałem podciągając źródła) sprawiło że w tym wypadku maven podciągną wersje starszą (ciekawa jak jest polityka wybierania wersji?). Tak więc wpis uległ zmianie:

	 
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-ejb-plugin</artifactId>
	<version>2.2.1</version>
	<configuration>
		<generateClient>true</generateClient>
		<ejbVersion>3.0</ejbVersion>
		<clientIncludes>
			<clientInclude>com/test/client/**</clientInclude>
		</clientIncludes>
		<excludes>
		  <exclude>com/test/client/**</exclude>
		</excludes>
	</configuration>
</plugin>

i działa tak jak trzeba (do następnych problemów).

Wnioski
Nauczka na przyszłość to pamiętać, w którym miejscu można popełniać błędy w mavenie:

  • poprzez niedokładne czytanie dokumentacji i pośpiech w realizacji (to akurat jest bardziej ogólne ;));
  • nie wskazywanie wersji pluginu może dużo namieszać.

A co do tego jak poszukiwać rozwiązania to dużą zaletą jest możliwość:

  • szybkiego podglądu kodu pluginu;
  • a jeśli to nie pomaga to prześledzenie jego pracy.

Trik z ładowniem klas

15/05/2010 Dodaj komentarz

Wprowadzenie
Ostatnio udało mi się zrobić ciekawy użytek z tego jak java ładuje klasy w trakcie życia aplikacji. Proces ten nawet zwięźle jest opisany tutaj. Dotychczas mogłem przypominać sobie o tym kiedy musiałem walczyć z tym. Na przykład kiedy na serwerze aplikacyjnym zaplątały się dwie takie same biblioteki, ale w różnych wersjach. Dobrze było kiedy coś takiego objawiało się wyjątkiem, w gorszym wydaniu mogło otrzymywać się informacje o tym, że coś nie działa tak jak powinno i kombinuj co jest nie tak, zwykłe „u mnie działa” nie pomagało.

Opis problemu
Jednak znalazły się okoliczności kiedy można wykorzystać te „specyficzne” właściwości aby ułatwić sobie życie. Miałem problem z wykorzystaniem zewnętrznej biblioteki. W trakcie działania aplikacja sypała warning_ami. No i z faktu pojawiania się tego mogłem wiedzieć tylko tyle że coś tą zewnętrzną bibliotekę boli, ale co jest przyczyną tego to już nie bardzo.

Dochodzenie do rozwiązania
Więc na początek google i szukanie czy komuś coś takiego już się objawiło. W idealnym świecie powinien być to post o potędze internetu i poprawnym poszukiwaniu informacji w nim, ale tą drogą nie wiele znalazłem. Dowiedziałem się, że inni też spotykają się z czymś takim, a jedyne znalezione rozwiązanie tego problemu, to odpowiednia zabawa z poziomem logowania i zamiatanie tego pod dywan 😉
No, ale można się oprzeć na idei wolnego oprogramowania. Jako, że zewnętrzna biblioteka miała dostępne źródła, to pobrało i podłączyło się je pod projekt w eclipsie. Tak więc teraz znalazłem tą linijkę, w zewnętrznym kodzie, gdzie występował warning. Uruchamiając aplikację w trybu debug mogłem widzieć więcej co jest nie tak.
I to w zasadzie wystarczało do rozwiązania konkretnego przypadku. Jednak ja takich przypadków miałem wiele i rozwiązanie każdego z nich wymagało zmiany w różnych miejscach. Tak więc praca w trybie debug wyglądała by męcząco, gdybym chciał rozwiązać każdy przypadek w taki sposób. Jak na razie dowiedziałem się o klasie występujących problemów, teraz potrzebowałem tylko pozyskiwania zwięzłych informacji w każdym przypadku. Idealnie byłoby gdyby warning_i, od których to wszystko się zaczęło, niosły te informacje. Jednak budowanie ze źródeł feralnego jara z rozszerzonymi informacjami w logach wydawało mi się przesadą, dlatego postanowiłem użyć triku z ładowaniem klas.
Miałem źródła zewnętrznej klasy, w której chciałem rozszerzyć logowanie. Więc w ramach kodu projektu nad którym pracowałem stworzyłem odpowiedni pakiet i w nim kopie tej klasy ze zmianami jakie chciałem. Dzięki temu teraz kiedy uruchamiałem aplikacje „moja zewnętrzna” klasa była ładowana przed oryginałem i mój kod był wykonywany dostarczając mi dodatkowych informacji, które musiałbym pozyskiwać w czasochłonnym trybie debug :).

Wnioski
Co z tego można wyciągnąć na przyszłość?:

  • Czasem to co nam uprzykrza życie, w innych przypadkach może ułatwiać;
  • Jest to rozwiązanie, które polegało na dodaniu tymczasowego nieprodukcyjnego kodu aby ułatwić sobie pracę. Robiąc podmianę klas jako stałe rozwiązanie, zastanowiłbym się dobrze czy na pewno nie mogę zrobić tego samego w bardziej czysty sposób, tak aby nie zaszkodzić samemu sobie w przyszłości.

Jeszcze na koniec o samej istocie rozwiązywanego tu problemu. Był on związany z logowanie, ostatnio ukazała się seria ciekawych artykułów związanych z tym zagadnieniem, a opisany przypadek pasuje zwłaszcza do tego.

Kategorie:Java Tags: ,