Archiwum

Archive for the ‘Java’ Category

Wyświetlanie nazwy wykonywanej metody

29/01/2013 Dodaj komentarz

Ostatni miałem potrzebę tworzenia wielu (testowych) podobnych metod. Technika kopiuj, wklej i zmiana dla potrzeb danego przypadku. Pierwsza wpis w każdej metodzie to wyświetlenie krótkiej informacji o tym co jest wywoływane. Dawało to informacje o tym co się dzieje w historii wywołań. Metody przybierały postać jak poniżej:

	public void doSomthing1() {
		System.out.println("doSomthing1");
		// ...
	}
	public void doSomthing2() {
		System.out.println("doSomthing2");
		// ...
	}

Tak więc zawsze występowała potrzeba poprawy nazwy w pierwszej linii. Było to trochę uciążliwe, więc wynikła potrzeba wprowadzenia udogodnienia, co poskutkowało takim kodem:

	public void doSomthing3() {
		System.out.println(MethodHelper.getExecutedMethodName());
		// ...
	}

Implementacja tej metody poniżej. Nie jest ona może najpiękniejsza, ale spełnia swoje zadania. Po fakcie w internecie znalazłem wpisy o tym, że nie tylko ja miałem potrzebę posiadania takiej metody.

	public static String getExecutedMethodName() {
		StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
		boolean next = false;
		for (StackTraceElement stackTraceElement : stackTrace) {
			String string = stackTraceElement.getMethodName();
			if (next) {
				return string+" line:"+stackTraceElement.getLineNumber();
			}
			if ("getExecutedMethodName".equals(string)) {
				next = true;
				continue;
			}
		}
		return "Metchod Name Not Found";
	}

Jeszcze na koniec dokonałem pewnego usprawnienia. Dodałem wpis o linii w której jest wykonywane wywołanie ;).
Tak wiec rozpoznanie co się dzieje w kodzie w postaci brutalnego stylu jak poniżej, także jest łatwiejsze

	public void doSomthing3() {
		System.out.println("doSomthing3: 1");
		// some code
		System.out.println("doSomthing3: 2");
		// some code
		System.out.println("doSomthing3: 3");
		// some code
	}

i może wyglądać tak:

	public void doSomthing3() {
		System.out.println(MethodHelper.getExecutedMethodName());
		// some code
		System.out.println(MethodHelper.getExecutedMethodName());
		// some code
		System.out.println(MethodHelper.getExecutedMethodName());
		// some code
	}
Kategorie:Java Tagi: ,

Kilka pytań rekrutacyjnych

10/01/2013 Dodaj komentarz

Kilka ciekawych pytań sprawdzających znajomość javy.

StringBuffer vs. StringBuilder
Pytanie może wyglądać tak: jaka jest różnica pomiędzy StringBuffer, a StringBuilder? lub który z nich jest wydajniejszy?

Sprawdzane jest tutaj to czy wie się że StringBuilder jest młodszym bratem StringBuffer (wprowadzony w wersji 1.5) i że główna różnica pomiędzy nimi to brak słowa synchronized przy publicznych metodach w przypadku StringBuilder. Pośrednio z tej wiedzy wynika także odpowiedź na pytanie o wydajności.

Patrząc pobieżnie na projekty w których pracowałem i kod który został użyty do klejenia stringów, to widzę że przewaga jest po stronie StringBuffer ;). Jest tak raczej z przyzwyczajenia, po za tym operacje te nie są krytyczne i jest ich nie wiele. Mam jednak postanowienie na przyszłość: jak powołuje do życia obiekt klasy do klejenia stringów, i ten obiekt zakończy swój żywot w obrębie tego samego bloku kodu (ciała metody) to używać należy StringBuilder, przykład:

  public static String getCustomStackTrace(Throwable aThrowable) {
    final StringBuilder result = new StringBuilder( "StackTrace: " );
    result.append(aThrowable.toString());
    final String NEW_LINE = System.getProperty("line.separator");
    result.append(NEW_LINE);

    //add each element of the stack trace
    for (StackTraceElement element : aThrowable.getStackTrace() ){
      result.append( element );
      result.append( NEW_LINE );
    }
    return result.toString();
  }

Reprezentacja pieniędzy
Pytanie może wyglądać tak: czego byś użył do reprezentacji pieniędzy (np. należności za zamówienie)?. Dalsza wersja tego pytania może być uzasadnienie podanej odpowiedzi.

Mi od razu nasuwa się BigDecimal, jako że od zawsze tego używałem. Robiłem to nie z powodu jakichś głębszych przemyśleń, ale z przyzwyczajenia, że tak powinno być. Jeśli kiedyś znałem jakieś uzasadnienie tego, to ugrzęzło ono w odmentach pamięci ;). Tak też powiedzenie na przykład dlaczego nie float wprowadziło mnie w lekkie zakłopotanie.
Szukając trochę, znalazłem że proste typy numeryczne nie gwarantują poprawności co do oczekiwanego wyniku na operacjach z liczbami zmiennoprzecinkowymi.
Prosty przykład:

		double badValue = 0.33;
		System.out.println(10*badValue);
		
		BigDecimal goodValue = new BigDecimal("0.33");
		System.out.println(goodValue.multiply(BigDecimal.TEN));

I wyniki są następujące:

3.3000000000000003
3.30

Co ciekawe jeśli zainicjujemy BigDecimal trochę inaczej (nie jako string, ale przekazując prosty typ):

		BigDecimal goodValue = new BigDecimal(0.33);
		System.out.println(goodValue.multiply(BigDecimal.TEN));

to możemy otrzymać coś takiego:

3.300000000000000155431223447521915659308433532714843750

Więc i tu też może być haczyk ;). Ale główna wiedza jaka jest sprawdzana przez takie pytanie to świadomość o możliwości utracie precyzji przy wykorzystaniu prostych typów (tutaj też znalazłem fajne streszczenie tego).

Działanie equals() i hashCode()
Pytania tutaj mogą być przeróżna, ale ich cel to sprawdzenie czy ktoś rozumie po co są te metody i jak je używać.

Jak dla mnie pierwsze ze skojarzeń jakie mam z tym to pamiętanie o tym aby zaimplementować je w tworzonej klasie jeśli chce się przechowywać obiekty tej klasy w kolekcjach. Wiedząc o tym, przy tworzeniu nowej klasy, uruchamiamy odpowiedni skrót w IDE i otrzymuję wygenerowane metody. I na tym może się kończyć nasz refleksja nad sensem tych metod, co też może prowadzić do zdziwień w przykładach jak poniżej.

Załóżmy, że mamy klasę, w których implementacja tych metod są takie że: equals zwraca zawsze false, a hashCode zawsze 1 (to budzi lekkie zdziwienie po co tworzyć takie coś, ja sam nie znajduję sensownej przyczyny robienia czegoś takiego ;)). Idźmy dalej: powiedzmy że klasa ta to Person i w konstruktorze przyjmuję pojedynczą wartość String, będącą nazwą danej osoby.
Tak więc tworzymy trzy instancje tej klasy dwie z argumentem „Jan”, jednak z „Tomasz” i dodajemy jej do kolekcji typu HashSet. Pytanie jest, ile obiektów jest w kolekcji?.
Jest to pytanie na zrozumienie zasad stosowania tych dwóch metod. Wiedza ta jest tu sprawdzana trochę w zagmatwany sposób. Tym się usprawiedliwiam :), bo gdy usłyszałem to pytanie w takiej postaci udzieliłem złej odpowiedzi. Wychodząc z założenia, że w kolekcjach typu Set nie może być duplikatów, twierdziłem że będzie tylko jeden „Jan” w towarzystwie „Tomasz”. Ignorując zupełnie fakt, że nadpisałem metodę equals, które mówi mi o tym czy dany obiekt znajduję się już w kolekcji. Zakładając bez większego zastanowienia że słówka hash w HashSet gra tu istotną role i próbowałem strzelać co do odpowiedzi bez większego namysłu. Podczas gdy hashCode zadecyduje tylko o rozkładzie w „kubełkach” tej kolekcji (a właściwie tylko w jednym „kubełku” co zapewniliśmy sobie pisząc w taki sposób metodę hashCode), a equels zadecyduję i tak czy dany obiekt jest duplikatem.
Kończę usprawiedliwianie swojej pomyłki: podane przykład powinien mieć pomocnicze pytanie (lub samemu powinno się je zadać sobie): dlaczego taka implementacja equals i hashCode jest błędna?

Kategorie:Java Tagi:

Jak szybko stworzyć aplikacje webową, która zwraca JSON-a

16/07/2012 Dodaj komentarz

Ostatnio sprawdzałem możliwości JavaScriptu w aplikacjach webowych. Tutaj udział część serwerowej sprowadzał się do obsługi wywołań AJAX-owych (zwracania wyników w JSON-e). W skrócie nie chciałem się zajmować serwerem, wystarczało że zwracał mi jakiś kawałek JSON-a, tak abym mógł skupić się tylko na HTML, CSS i JS.

Bez Javy, ale tak bym chciał
Tak więc na początek trafiłem na takie całkiem fajne rozwiązanie w pythonie.

python -m SimpleHTTPServer 8080

Jak ma się zainstalowanego pythona, to jest to najprostszy sposób na postawienie serwera HTTP. Gdzie wystawioną zawartością, poprzez URL, jest to co znajduje się w katalogu, w którym z linii poleceń uruchomiło się to. Tak więc jeśli mamy tam katalog
dane, a w nim plik test.json, to możemy do tego pliku odwołać się poprzez taki url:

http://localhost:8080/dane/test.json

Zatem statyczne kawałki JSON-a leżą sobie w plikach na dysku, obok może znajdować się plik app.js, który odwołuję się do nich, i który to jest głównym przedmiotem pracy. Całość mogę edytować po dowolnym narzędziem, i każda zmiana od razu jest widoczna w przeglądarce (wystarczy F5). Piękne 🙂 nie trzeba się martwić synchronizacją z serwerem, można się skupić na pracy i od razu widzieć efekty w przeglądarce.
Rozwiązanie to daje jednak tylko statycznego JSON-a. Aby wprowadzić więcej dynamizmu trzeba jednak napisać trochę po stronie serwera. Nie patrzyłam już czy SimpleHTTPServer oferuję jakieś większe możliwość przeszedłem od razu do javy, w końcu jak na razie w tym głównie piszę.

Z Javą, próba odtworzenia
Zacząłem od stworzenia prostego projektu mavenowego, tak aby móc łatwo zarządzać zależnościami, po za tym i tak zwykle jest to projekt tego typu. Idą za wskazówkami zawartymi w tym poście wybrałem SpringMVC. Cała moja praca po stronie serwera ograniczyła się do stworzenia jednej klasy, która produkowała mi JSON-a jakiego chciałem.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/app")
public class AppController {
	
	private static List<String> names = new ArrayList<String>();
	
	@RequestMapping(value = "/add", method = RequestMethod.GET)
	public @ResponseBody void add(@RequestParam(value="name") String name){
		names.add(name);
	}
	
	@RequestMapping(value = "/listAll", method = RequestMethod.GET)
	public @ResponseBody List<String> listAll(){
		return names;
	}
	
	@RequestMapping(value = "/remove", method = RequestMethod.GET)
	public @ResponseBody void remove(@RequestParam(value="name") String name){
		names.remove(name);
	}

}

Dzięki temu dostałem trzy url-e, z których mogłem korzystać po stornie JavaScript:

http://localhost:8080/RestTest/rest/app/add?name=dwa
http://localhost:8080/RestTest/rest/app/remove?name=dwa
http://localhost:8080/RestTest/rest/app/listAll

Ostatni może zwracać takiego JSON-a:

["jeden","dwa","trzy"]

Takie coś w zupełności wystarcza do symulowania logiki po stronie serwera, a jak widać w przedstawionej klasie, rozbudowanie tego nie powinno być trudne.
Oprócz tej klasy potrzebne były jeszcze techniczne pliki, wszytko wziąłem z projektu w przytoczonym poście.
Tak wiec w web.xml potrzebne są takie wpisy aby postawić kontekst Springa.

	<servlet>
		<servlet-name>mvc-dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>mvc-dispatcher</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

Najistotniejsze jest zwrócenie skąd się bierze rest w URL (RestTest to nazwa mego wara), a plik mvc-dispatcher-servlet.xml opisujący co Spring ma robić jest trywialny:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

	<context:component-scan base-package="test.controller" />

	<mvc:annotation-driven />

</beans>

jak widać jedyne co trzeba dodać to wpis włączający interpretacje adnotacji w napisanej klasie. Do dopełnienie wszystkiego jest jeszcze plik mavena, który zaprezentuje na końcu.

Jest Java, ale jakby krok wstecz
Tak więc miałem już dynamicznego JSON-a, i sposób jak go dostosowywać. Mogłem się zabrać za JavaScript. Jednak utraciłem tutaj szybką pętle zwrotną, w weryfikacji wprowadzanych zmian. Teraz pomiędzy zapisaniem zmiany w pliku, a odświeżeniem widoku w przeglądarce, doszedł krok budowy wara i wgrania go na Tomcata lub inny WebSerwer. A ja chciałem tylko zmieniać pliki JS lub HTML, część serwerowa nie zmieniałaby się w ogóle.

Z Javą, już prawie idealnie
Rozwiązań tego pewnie jest parę. Ja zwróciłem się w kierunku wbudowanego kontenera aplikacje webowych, czyli użyłem jetty.
W pom.xml pojawia się taki wpis jak niżej. Jetty uruchamia się taka komendą mvn jetty:run.

			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<version>6.1.24</version>
				<configuration>
					<stopPort>9966</stopPort>
					<stopKey>RestTest</stopKey>
					<!-- Redeploy every x seconds if changes are detected, 0 for no automatic redeployment -->
					<scanIntervalSeconds>0</scanIntervalSeconds>
					<!-- make sure Jetty also finds the widgetset -->
					<webAppConfig>
						<contextPath>/RestTest</contextPath>
						<!-- -->
						<baseResource implementation="org.mortbay.resource.ResourceCollection">
							<resources>src/main/webapp</resources>
						</baseResource>
					</webAppConfig>
				</configuration>
			</plugin>

Ważny jest parametr scanIntervalSeconds, mówi on o tym czy zmiany w naszych klasach będą automatycznie redeployowane. Trwa to trochę, ale można z tym żyć. To i tak nie dotyczy naszego HTML-a i JS, to co powoduję że zmiany w tych plikach są widoczne od razu w przeglądarce, to parametr baseResource. Wskazanie na katalog webapp, gdzie trzymane są te pliki powoduje, że zmiany są propagowane (wykomentowałem ten parametr i funkcjonalność ta nadal działało co pewnie świadczy o tym że jest to wartość domyśla). Nie wczytywałem się za bardzo w szczegóły ustawień i może trochę podorabiałem teorii do praktyki, ale działa tak jak chciałem. Mam co chciałem i mogę spokojnie pracować nad plikami JS, i zmiany w nich widzieć od razu w przeglądarce.

Taki mały zgrzyt
Pojawia się zgrzyt, podobno tylko dla Windowsa, pliki uaktualniają się tylko za pierwszym razem, kolejny raz nie można już zapisać, bo są blokowane. Ten wpis opisuje problem i możliwe rozwiązania. Ja użyłem dodatkowego wpisu w web.xml, na razie nie martwię się tym że brudzi to ten plik. Dodatkowy element to:

	<servlet>
	    <servlet-name>default</servlet-name>
	    <servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
	    <init-param>
	      <param-name>useFileMappedBuffer</param-name>
	      <param-value>false</param-value>
	    </init-param>
	    <load-on-startup>0</load-on-startup>
	  </servlet>

I to wszytko, można się zabrać za JavaScript. Teraz mam szybką odpowiedź zwrotną w przeglądarce, wiec jak dla mnie bardzo dobre warunki do pracy z tak krnąbrny językiem 😉 , o czym może napisze następnym razem.

A całość pom.xml prezentuję się tak:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>test</groupId>
	<artifactId>RestTest</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<spring.version>3.0.5.RELEASE</spring.version>
	</properties>

	<dependencies>

		<!-- Jackson JSON Mapper -->
		<dependency>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-mapper-asl</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Spring 3 dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

	</dependencies>

	<build>
		<finalName>RestTest</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<version>6.1.24</version>
				<configuration>
					<stopPort>9966</stopPort>
					<stopKey>RestTest</stopKey>
					<!-- Redeploy every x seconds if changes are detected, 0 for no automatic 
						redeployment -->
					<scanIntervalSeconds>0</scanIntervalSeconds>
					<!-- make sure Jetty also finds the widgetset -->
					<webAppConfig>
						<contextPath>/RestTest</contextPath>
						<!-- -->
						<baseResource implementation="org.mortbay.resource.ResourceCollection">
							<resources>src/main/webapp</resources>
						</baseResource>
					</webAppConfig>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

Małe uaktualnienie: taki sam efekt z wbudowanym kontenerem aplikacji webowych można uzyskać z tomcat-maven-plugin. Nie ma się tylko przeładowania klas (w każdym razie nie znalazłem tego). Ale nie trzeba brudzić swego pliku web.xml i nie ma problemu z blokowaniem edytowanych plików. Tak więc w pomie zamiast jetty należy dodać:

			
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>tomcat-maven-plugin</artifactId>
				<version>1.1</version>
			</plugin>

i uruchomić z linii poleceń komendą: mvn tomcat:run

log4j z WebSpherem

08/02/2012 Dodaj komentarz

Krótki opis przypadku (frustracji).

Problemy z uruchomieniem logowania na WebSperze (dokładnie to WAS 6.1, ale to chyba mało istotne).

Objawy
Użycie standardowej konfiguracji log4j nie daje spodziewanego efektu. Logi się nie odkładają, mimo że analogiczna aplikacji osadzona np. na JBossie zachowuję się poprawnie.

Rozwiązanie
Pomocny okazał się ten wpis.
Za umieszczonym tam opisem zrobiłem tak:
1. Stworzyłem plik commons-logging.properties i umieściłem go w katalogu WEB-INF\classes (war). Zawartość taka sama jak w przytaczanym wpisie.

priority=1
org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger

2. Następnie na WASie w opcjach konfiguracji aplikacji; w opcji zarządzania ładowanie klas zaznaczyłem obie opcje odwrotnie niż to było domyślnie. Głównie chodzi o ładowanie najpierw klas aplikacji z wara. Zrzut konfiguracji poniżej.

Po tych zabiegach logowanie zaczęło działać tak jak chciałem.

Może to pomoże komuś innemu. Choć podejrzewam że rozwiązanie nie zawsze będzie dokładnie takie same. Jednak przynajmniej da to pojęcie gdzie szukać.

Kategorie:Java Tagi: ,

Social coding

02/02/2012 Dodaj komentarz

Tytuł jest trochę naciągany, ale w pewien sposób oddaję to co opiszę poniżej.

Lubię przeglądać cudzy kod ;). Tak bez szczególnego celu. Może to być w formie śledzenia (podczas synchronizowania się) tego co zostało wkomitowane przez kolegów w pracy; czy przeglądania kodu projektów dostępnych w sieci. Często można zaleźć coś ciekawego, co zmusi do zastanowienia i w rezultacie pozwoli na odkrycie ciekawej biblioteki czy konstrukcji w kodzie.

Szczególnym przypadkiem jest przeglądanie plików pom.xml, wymaganych przez mavena. Zawiera się tu techniczny (ogólny) opis danego projektu: to jakie bibliotek czy pluginy zostały w nim użyte. Można zobaczyć co jest stosowane w podobnych przypadkach, z jakimi się stykamy, i dzięki temu odkryć coś użytecznego, np. wspominane już biblioteki.

I tutaj rodzi mi się pomysł na udział społeczności. Fajnie byłoby mieć narzędzia, które na podstawie pom, mojego aktualnego projektu, przeszuka sieci pod kątem innych pomów dostępnych gdzieś tam. Wynikiem tego przeszukania mógłby być wykaz potencjalnych bibliotek, którymi mógłbym być zainteresowany. Coś w stylu tego co ma np. amazon: propozycji książek którymi mógłbym być zainteresowany. Tutaj brzmiałby to tak: użytkownicy biblioteki X i Y stosowali także bibliotek Z – może warto się nią zainteresować.

Pliki pom są tworzone w ustalonym formacie dlatego też napisanie parsera który przeszedłby po wszystkim co znajdzie i zebrał statystyki nie powinno być trudne. Tak samo osoby dostarczające swoje pom_y (przyjmując że jest to jakiś serwis dostępny globalnie), jako kryterium zapytania, powodowałyby że poziom dostępnych statystyk rósł by.
Statystyki taki naturalnie nie dostarczą precyzyjnej wiedzy, ale pomogą rzucić światło na panujący trend.

Działania takiego narzędzia wyznaczają nawyki użytkowników. W moim przypadku jest to przeglądanie pom_ów i na podstawie tego decydowanie jaką bibliotek poznać bliżej. Mogą być inne zachowania związane z przeglądaniem kodu, które można sformalizować i spróbować zatrudnić do tego komputer. Na pewno nie otrzymamy tu wyczerpującego sprawozdania, ale coś co pozwali nam ułatwić choć trochę życie ;).

Kategorie:Java Tagi: , ,

Pisanie procedur składowanych w hibernate

20/01/2012 Dodaj komentarz

Taka koncepcja urodziła mi się dziś: pisanie procedur składowanych w JPA – tutaj zawężam do implementacji Hibernate, a z baza można wskazać Oracle, ale to bez znaczenia.
Nie chodzi o wywoływanie procedur składowanych poprzez JPA czy zwykłe JDBC. Nie chodzi tu także o pisanie w javie ciała procedury składowanej (co na Oraclu da się).
To o co mi chodzi to wprowadzenia pewnej magicznej transformacji, która z kodu javy operującym na encjach JPA wygeneruje odpowiedni kod procedury (w tym wypadku PL/SQL) i stworzy taki kod na bazie. Na javie mogłoby wyglądać to tak:

	@AsStoredProcedure
	public void addCats(Long ownerId, Date date){
		List<Cat> cats = em.createQuery(
		    "select cat from Cat as cat where cat.birthdate < ?1")
		    .setParameter(1, date, TemporalType.DATE)
		    .getResultList();
		Owner owner em.find(Owner.class, ownerId);
		owner.getCats().addAll(cats);
	}

Stojąca za tym idea mogłaby być taka: jeśli wskazana metoda (tutaj oznaczoną odpowiednią adnotacją) może być prze-konwertować poprawnie na odpowiedni ciąg instrukcji PL/SQL, to zebrać to w jedną procedurę składowaną i stworzyć takie coś na bazie. Następnie w miejsce samej metody wstawić odpowiednie proxy, które to będzie wołane zamiast oryginalnej procedury.

Za i przeciw
Dlaczego tak się bawić?
Główna kwestia to wydajność. Stosowanie JPA często i tak kończyło się na wybiegach w postaci procedur składowanych dla poprawienia wydajności. To jednak wprowadzało rozbicie kodu na dwa miejsca, o które trzeba dbać i uaktualniać wraz ze zmianami.
Oczywiści można z tym żyć. Jeśli nałoży się odpowiedni reżim dbania o kod, większość błędów się nie prześliźnie. Jednak ze swojego doświadczenia widzę że jeśli jakiś kod można prosto napisać w JPA to się tak robi. Łatwiej jest to zrobić niż stworzyć analogiczną procedurę na bazie (w końcu programujemy w javie, a nie w sql :)), a to nie zawsze prowadzi do wydajnego kodu (zwłaszcza kiedy realizuję on scenariusz: wyciągnięcia wielkiej kolekcji danych, prostych zmian na jej elementach, podłączenie wszystkiego do innej encji i na koniec utrwalenie całości).
Dlatego też zamarzyło mi się właśnie coś takiego, co sprawiłoby że pisanie prostych procedur byłoby przezroczyste.

Wadą tu jest wprowadzenie dodatkowej abstrakcji (ktoś w debugu mógłby się zastanawiać dlaczego ten kod się nie wykonuję), ale to można by załatwić wprowadzając przemyślane API.

Sposoby realizacji
Realizacja tego jest możliwa, w końcu w logach widać sql który jest faktycznie wykonywany. Więc wystarczy go wziąć, trochę sparsować, opakować w kod tworzący procedurę, zarejestrować to na bazię, i wygenerować odpowiedni zamiennik w postaci proxy, który to będzie wołany zamiast metody w javie.
Najbardziej problematyczne może być pisanie w kodzie tak aby było to zamieniane na odpowiedni kod, pewnie nie każde wyrażenie z PL/SLQ może być zrealizowane.

Co do wgrywania procedur składowanych to jest taki mechanizm opis tutaj, poprzez zwykłe executeUpdate.

Kategorie:Java Tagi: , , ,

Ćwiczenie z wariacją

09/12/2011 Dodaj komentarz

Ten filmik (budzi podziw :)) zainspirował mnie do wykonania pewnego ćwiczenia. Polegało ono na odgadnięciu tego samego, ale przy pomocy komputera – zadanie jak z pierwszego roku studiów, ale potrafi przynieść satysfakcje.
W poniższej implementacji została użyta brutalna siła :). Generowane są zbiory możliwych kombinacji działań matematycznych (wariacja z powtórzeniami) i każdy z nich jest sprawdzany we wszystkich konfiguracjach danych liczb 😉 (wariacja bez powtórzeń). Po przeliczeniu, ponad 2 milionów przypadków, otrzymałem 8 sposobów na dojście do tej liczby (tak na prawdę były 2, reszta wynikała z przemienności mnożenia). Jeden ze sposobów, inny niż na filmie, to:
(3 + 100) = 103,
(103 * 6) = 618,
(618 * 75) = 46350,
(46350 / 50) = 927,
(927 + 25) = 952

Kod realizujący to sprowadzał się do:

		BigDecimal expected = new BigDecimal(952);
		
		String[] allowedOperations = {Value.ADD, Value.MULTIPLY, Value.SUBTRACT, Value.DIVIDE, Value.DIVIDE2};
		Integer[] givenValues = {25,50,75,100,3,6};
		
		long operationCount = 1;
		long expectedCount = 0;
		for (String[] operations : Permutation.variationWithRepetition(5, allowedOperations)) {
			for (Integer[] values : Permutation.variationWithoutRepetition(6, givenValues)) {
				// wykonanie dzialania
				Value value = Value.value(values[0]);
				for (int j = 0, k = 1; j < operations.length; j++,k++) {
					value = value.compute(operations[j], values[k]);
				}
				// drukuj jesli oczekiwana
				if (expected.equals(value.value)){
					expectedCount++;
					System.out.println(value.computation);
					System.out.println(operationCount);
				}
				operationCount++;
			}
		}

		System.out.println(operationCount);
		System.out.println(expectedCount);

Gdzie klasa Value pozwalająca na zapisywanie działań w taki sposób:

Value value = value(5).compute(MULTIPLY,5).compute(SUBTRACT, 5).compute(DIVIDE, 2);

Wygląda tak:

public class Value {
	
	public static final String DIVIDE = "divide";
	public static final String DIVIDE2 = "divide2";
	public static final String MULTIPLY = "multiply";
	public static final String SUBTRACT = "subtract";
	public static final String ADD = "add";

	public final BigDecimal value;
	public final String computation;
	
	private Value(BigDecimal value, String computation) {
		this.value = value;
		this.computation = computation;
	}
	
	public static Value value(int value){
		return new Value(new BigDecimal(value),""+value);
	}

	public static void main(String[] args) {
		System.out.println("Test: "+Value.class.getSimpleName());
		
		Value value = value(5).compute(MULTIPLY,5).compute(SUBTRACT, 5).compute(DIVIDE, 2);
		
		System.out.println(value.computation);
		System.out.println(value.value);
	}

	public Value compute(String operation, int i) {
		if (this.value == null){
			// nie mozna wyknywac operacj, poprostu zwroc orginal
			// pozwala na unikniecie NPE w trakcie przetwarzania łańcucha
			return this;
		}
		BigDecimal eval = null; 
		BigDecimal value2 = new BigDecimal(i);
		String evalStrin = "";
		
		if (ADD.equals(operation)){
			
			eval = this.value.add(value2);
			evalStrin = evalStrin(this.value, "+" ,value2);
			
		} else if (SUBTRACT.equals(operation)){
			
			eval = this.value.subtract(value2);
			evalStrin = evalStrin(this.value, "-" ,value2);
			
		} else if (MULTIPLY.equals(operation)){
			
			eval = this.value.multiply(value2);
			evalStrin = evalStrin(this.value, "*" ,value2);
			
		} else if (DIVIDE.equals(operation)){
			if(BigDecimal.ZERO.compareTo(value2) != 0){
				eval = this.value.divide(value2, MathContext.DECIMAL32);
			} 
			evalStrin = evalStrin(this.value, "/" ,value2);
			
		} else if (DIVIDE2.equals(operation)){
			if(BigDecimal.ZERO.compareTo(this.value) != 0){
				eval =  value2.divide(this.value, MathContext.DECIMAL32);
			} 
			evalStrin = evalStrin(value2, "/" ,this.value); // odwrocenie
			
		}
		String computation = this.computation + "; "+ evalStrin + " = " + eval;
		Value v = new Value(eval, computation);
		return v;
	}
	
	private static String evalStrin(BigDecimal v1,String operation, BigDecimal v2){
		return "( "+v1 + " "+operation+" " + v2+" )";
	}
	
}

Klasa Permutation, generująca zbiory, została zamieszczona na końcu.

Wnioski z tej zabawy
Fajnie jest czasem pomyśleć nad innymi problemami niż te z którymi ma się zwyczajowo styczność.

Kawałek kodu do generowania możliwych zbiorów pewnie kiedyś się jeszcze przyda. Pierwsze co przychodzi do głowy to generowanie haseł :), ale pewnie są bardziej życiowe przykłady. Chociaż trochę dziwne że nie można czegoś takiego dla javy łatwo znaleźć (może w jakichś matematycznych bibliotekach), dla pythona od razu znalazłem przykład.

Ciekawym pomysłem byłoby aby w generatorze zbiorów dać możliwość sterowania kolejnością zwracanych rezultatów. Tak aby zbiory z wieloma powtórzeniami (*,*,*,*,*) były zwracane na końcu, podczas gdy zróżnicowane w miarę na początku (+,*,*,-,/). Jakoś powinno da się ustawić pożądaną kolejność zwracania zbiorów dla danej klasy problemu, ten facet z filmiku pewnie musi tak robić :).

Można stracić dużo czasu na cyzelowanie implementacji. Zwłaszcza było tak w przypadku Permutation. Kod dający rozwiązanie powstał w jakieś 4 godzin, a zabawa z nim aby go poprawić (przyspieszyć i zrobić bardziej czytelnym) odwlekła się w czasie o jakiś miesiąc ;). I tak nie jest to piękne, ale najgorszy bałagan jest na samym dole. Ciekawe czy tak praca zwróci się w postaci łatwego zrozumienia o co tu biega, jak za jakiś czas wrócę do tego kod.

public class Permutation {

	public static void main(String[] args) {
		int count = 1;
		long start = System.currentTimeMillis();
		for (Integer[] intSet : variationWithoutRepetition(6, new Integer[] { 25, 50, 75, 100, 3, 6 })) {
			System.out.println(Arrays.toString(intSet) + " - " + (count++));
		}
		System.out.println("Czas = " + (System.currentTimeMillis() - start));
	}

	public static <E> Iterable<E[]> variationWithoutRepetition(final int wordLength, final E ... values) {
		return new Iterable<E[]>() {
			public Iterator<E[]> iterator() {
				return new Variation<E>(
						new UpdateContextWithoutRepetition(values.length, wordLength) 
						, values);
			}
		};
	}
	public static <E> Iterable<E[]> variationWithRepetition(final int wordLength, final E ... values) {
		return new Iterable<E[]>() {
			public Iterator<E[]> iterator() {
				return new Variation<E>(
						new UpdateContextWithRepetition(values.length, wordLength)
						, values);
			}
		};
	}
	
	private static class Variation<E> implements Iterator<E[]>  {
		E[] dictionery = null;
		UpdateContext conetxt;
		
		private Variation(UpdateContext conetxt, E ... dict) {
			this.dictionery = dict;
			this.conetxt = conetxt;
		}

		@Override
		public void remove() {
		}
		
		@Override
		public boolean hasNext() {
			return  conetxt.hasNext();
		}

		@Override
		public E[] next() {
			return remapToDictionery(conetxt.nextWord());
		}

		private E[] remapToDictionery(int[] currentIndex) {
			E[] nextWord = create(currentIndex.length);
			for (int i = 0; i < currentIndex.length; i++) {
				nextWord[i] = dictionery[currentIndex[i]];
			}
			return nextWord;
		}

		private E[] create(int length) {
			Class<?> componentType = this.dictionery[0].getClass();
			@SuppressWarnings("unchecked")
			E[] newInstance = (E[])java.lang.reflect.Array.newInstance(componentType, length);
			return newInstance;
		}
		
	}
	
	abstract static class UpdateContext {
		int dictioneryLength;
	    int[] currentIndexWord;
		long finishCount;
		long count = 0;
		int startFromLastIndex;
		private int currentIndex;
		
		public UpdateContext(int dictioneryLength, int wordLength) {
			this.dictioneryLength = dictioneryLength;
			
			this.finishCount = this.finishCount(wordLength);
			this.startFromLastIndex = wordLength - 1;
		}
		
		public boolean hasNext() {
			return (finishCount != count);
		}
		
		public int[] nextWord() {
			if (this.currentIndexWord == null){ // start
				this.currentIndexWord = this.createStartIndexWord();
			} else {
				updateIndex(startFromLastIndex);
			}
			count++;
			return this.currentIndexWord;
		}
		
		private void updateIndex(int lastLetterInWordToChangeIndex) {
			updateCurrentIndex(lastLetterInWordToChangeIndex);
			if (isLastPosibleWord()) {
				// ostatni mozliwy index
				// ustaw na poczatek i zwieksze w poprzedzajacym
				if (lastLetterInWordToChangeIndex > 0) {
					updateIndex(lastLetterInWordToChangeIndex - 1);
				}
				// po powrocie z rekurencyjnego wywolania uaktualnij kontekst
				updateCurrentIndex(lastLetterInWordToChangeIndex);
				setLettet(getFirstPosibleLetter());
			} else {
				setLettet(getNextPosibleLetter());
			}
		}
		
		public void updateCurrentIndex(int currentIndexValue){
			currentIndex = currentIndexValue;
			update(currentIndex);
		}
		public boolean isLastPosibleWord() {
			return currentIndexWord[currentIndex] == getLastPosibleLetterInWord();
		}
		public int getNextPosibleLetter(){
			return getNextPosibleLetterForGivenIndex(currentIndex);
		}
		public int getFirstPosibleLetter() {
			return firstPosibleLetter();
		}
		public void setLettet(int letter){
			currentIndexWord[currentIndex] = letter;
		}
		
		protected abstract int firstPosibleLetter();

		protected abstract int[] createStartIndexWord();

		protected abstract int getNextPosibleLetterForGivenIndex(int lastWordToChangeIndex);

		protected abstract void update(int lastWordToChangeIndex);

		protected abstract long finishCount(int dictLength);
		
		protected abstract int getLastPosibleLetterInWord();
	}
	
	private static class UpdateContextWithoutRepetition extends UpdateContext {
		
		int[] letterThatCanBeUsed;

		public UpdateContextWithoutRepetition(int dictioneryLength, int wordLength) {
			super(dictioneryLength, wordLength);
		}
		@Override
		public int firstPosibleLetter(){
			return letterThatCanBeUsed[0];
		}
		@Override
		public int getLastPosibleLetterInWord(){
			return letterThatCanBeUsed[letterThatCanBeUsed.length - 1];
		}
		@Override
		public int getNextPosibleLetterForGivenIndex(int lastWordToChangeIndex){
			int letterIndexInPosibleWords = findItPositionInArray(this.currentIndexWord[lastWordToChangeIndex], letterThatCanBeUsed);
			return letterThatCanBeUsed[letterIndexInPosibleWords+1];
		}
		
		private static int findItPositionInArray(int it, int[] array){
			for (int i = 0; i < array.length; i++) {
				if(array[i] == it){
					return i;
				}
			}
			return -1;
		} 
		
		@Override
		protected void update(int lastWordToChangeIndex) {
			this.letterThatCanBeUsed = getLetterThatCanBeUsed(lastWordToChangeIndex, currentIndexWord);
		}
		
		@Override
		public int[] createStartIndexWord() {
			return createRange(0, dictioneryLength);
		}
		
		private static int[] createRange(int start, int to){
			int[] range = new int[to - start];
			for (int i = 0; i < range.length; i++) {
				range[i] = i+start;
			}
			return range;
		}
		
		private int[] getLetterThatCanBeUsed(int lastWordToChangeIndex, int[] indexWord) {
			int[] arrayCopyFristValuesToGivenIndex = copyToGivenIndex(indexWord,lastWordToChangeIndex);
			int[] letterThatCanBeUsed = subtractFromRange(arrayCopyFristValuesToGivenIndex);
			return letterThatCanBeUsed;
		}
		
		private static int[] copyToGivenIndex(int[] remove, int toIndex) {
			int[] dest = new int[toIndex];
			System.arraycopy(remove, 0, dest, 0, dest.length);
			return dest;
		}
		
		private int[] subtractFromRange(int[] toRemove) {
			int[] range  = createRange(0, dictioneryLength);
			int resultSize = range.length - toRemove.length;
			if (resultSize == 0) {
				return range;
			}
			int[] result = new int[resultSize];
			
			int signeAsToRemove = -1; // value not in range
			for (int asIndexInRaneg : toRemove) {
				range[asIndexInRaneg] = signeAsToRemove; 
			}
			
			int index = 0;
			for (int valueFromRange : range) {
				if (valueFromRange != signeAsToRemove){
					result[index++] = valueFromRange;
				}
			}
			
			return result;
		}
		
		@Override
		public long finishCount(int dictLength){
			return factorial(dictioneryLength) / factorial(dictioneryLength - dictLength);
		}
		
		private static long factorial(int value) {
			long accumulator = 1L;
			for (long counter = value; ! (counter == 0); counter--) {
				accumulator = (counter * accumulator);
			}
			return accumulator;
		}
		
	}
	
	private static class UpdateContextWithRepetition extends UpdateContext {
		public UpdateContextWithRepetition(int dictioneryLength, int wordLength) {
			super(dictioneryLength, wordLength);
		}
		@Override
		public int firstPosibleLetter() {
			return 0;
		}
		@Override
		public int getLastPosibleLetterInWord() {
			return dictioneryLength - 1;
		}
		@Override
		public int getNextPosibleLetterForGivenIndex(int lastWordToChangeIndex) {
			return this.currentIndexWord[lastWordToChangeIndex] + 1;
		}
		@Override
		protected void update(int lastWordToChangeIndex) {
			// nic nie trzeba robic
		}
		@Override
		public long finishCount(int dictLength){
			return (long)Math.pow(dictioneryLength, dictLength);
		}
		@Override
		public int[] createStartIndexWord() {
			int[] startWord = new int[dictioneryLength];
			for (int i = 0; i < startWord.length; i++) {
				startWord[i] = 0;
			}
			return startWord;
		}
		
	}
	
}
Kategorie:Java Tagi: ,

Wzbogacenie debugu w IDE

24/10/2011 Dodaj komentarz

Przyszedł mi do głowy pomysł na wzbogacenie możliwości pracy w trybie debuge w mym IDE. Pracuje w eclipsie, ale wydaje mi się że rozwiązanie to nie będzie zawężone tylko do tego produktu.
Zacznijmy od nawyku jaki mam w pracy w tym trybie. Wiem że istniej widok „Variables”, w którym mogę przeglądać zmienne będące w aktualnym kontekście. Wiem też o istnieje widoku „Expressions”, w którym mogę wykonywać poszczególne kawałki kodu. Jednak przeglądanie zmiennych nie zawsze daje to co się chcę, a edytowanie kodu w widok „Expressions” nie jest wygodnie.
Dlatego też często stosuje taki coś: Stojąc w pułapce, pisze kod bezpośrednio w tym miejscu gdzie się zatrzymałem. Mam dzięki temu zapewnione wszystkie udogodnienia w edycji kodu. Nie zapisuje pliku więc, taki kod nic nie przeszkadza, i tak zostanie zaraz usunięty. Kod ten następnie wykonuję poprzez zaznaczenie go i wybór z menu kontekstowego „Inspect” (skrót Ctrl + Shift + I).
Jak widać na załączonym obrazku: można wykonać nawet coś co jest komentarzem.

Teraz przechodzę do pomysłu.
Skoro mogę wykonać kod w ten sposób, i wykonując to mam dostęp do całego obecnego kontekstu, to mogę w tym miejscu podpiąć się z jakimś kodem narzędziowym. Kod taki może mi udostępnić informacje jaki moje IDE nie jest w stanie. Mogę na przykład: wyświetlić bardziej precyzyjne informacje o istniejącym grafie obiektów. Mogę też ten obiekt zapisać do pliku i powołać go do życia podczas testów. Mogę także wstrzyknąć swój obiekt, który zbierze jakieś diagnostyczne informacje.
Szybka implementacja narzędzia do zrzutu obiektu do pliku przy użyciu xstream może wyglądać tak:

	public static void storeToFile(Object object) throws IOException {
		XStream stream = new XStream();
		stream.toXML(object, WriterFactory.newXmlWriter(new File("temp.xml")));
	}

	public static Object restoreFromFile() throws IOException {
		XStream stream = new XStream();
		Object object = stream.fromXML(ReaderFactory.newXmlReader(new File("temp.xml")));
		return object;
	}
Kategorie:Java Tagi: ,

Opakowanie wynik zapytani JPA do DTO – jeszcze raz

14/09/2011 Dodaj komentarz

W ostatnim wpisie, prawiłem jak to dobrze jest w niektórych przypadkach napisać trochę więcej kod, aby nie gmatwać się później. Tą większą ilością kodu było tworzenie nowej klasy typu DTO, do użycia w zapytaniach JPA. Gmatwaniną był bałagan powstający ze zbyt uporczywej re-używalności encji jako DTO.
Niedługo po tym zdarzył mi się przypadek, który wymagał zastosowania się do tej zasady (przy okazji wyszło że łatwo jest gadać jakie to „ma być”, a jak już ma się to zastosować, to wychodzi że nie jest to takie oczywiste i bezbolesne ;)).
Przechodząc do setna: realizacja tego dla przykładowych encji:

@Entity
@Table(name = "PARENT")
public class Parent {

	@Id
	@GeneratedValue
	@Column(name = "ID")
	private Long id;
	
	@Column(name = "NAME")
	private String name;
	
    @OneToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
    @JoinColumn(name="CHILDREN_ID")
	private Children children;
	
	// getters, setters
}
...
@Entity
@Table(name = "CHILDREN")
public class Children {

	@Id
	@GeneratedValue
	@Column(name = "ID")
	private Long id;
	
	@Column(name = "NAME")
	private String name;

	// getters, setters
}

sprowadza się to do takiego kodu:

		List<Object[]> tempList = entityManager
		.createQuery("select d.name, d.children.name from Parent d")
		.getResultList();
		List<Parent> returnList = null;
		if (tempList == null) {
			// to jest trochę defensywne zachowanie, 
			// niby specyfikacja nie wspomina o tym że może być null
			returnList = Collections.emptyList();
		} else {
			returnList = new ArrayList<Parent>();
			for (Object[] record : tempList) {
				Parent parent = new Parent();
				parent.setName((String)record[0]);
				parent.setChildren(new Children());
				parent.getChildren().setName((String)record[1]);
				returnList.add(parent);
			}
		}

Tutaj dla uproszczenia nie wprowadzałem już osobnego DTO. Widać tu jednak jak można konstruować zapytania tak aby pobierać to co się chce i żeby było to umieszczane w odpowiednich miejscach.
Jest oczywiście prostsza metoda, z delegacją cześć zadania do konstruktora, przykład:

		List<Parent> returnList = entityManager
		.createQuery("select new Parent(d.name, d.children.name) from Parent d")
		.getResultList();

Jednak w tym przypadku rozdzielamy informacje o tym „co” „gdzie” ma być umieszczone. To też było kłopotem dla mnie, opisywałem to dawniej; przedstawiałem tam także zarys sposobu jak chciałem to usprawnić. Jednak z czasem zebrana wiedza uleżała się i dzięki sposobowi który opisywałem jeszcze w innym poście wiem jak to zrobić. Co też opisze dalej.
Czytaj dalej…

Kategorie:Java Tagi: , ,

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 Tagi: , , , ,