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

Konstrukcja try inaczej

21/08/2011 Dodaj komentarz

Sposób na przedstawienie inaczej tego co jest wyrażana za pomocą konstrukcji try. Normalnie try w całej okazałości prezentuje się tak:

try {
   // wykonywane w try
} catch (NullPointerException e) { 
   // wykonywane w catch, w tym przypadku dla NullPointerException 
} finally { 
   // wykonywane w finally 
}

A ostatnio wpadłem na sposób jak można to zrobić zastępując całość specjalnym obiektem. W kodzie wygląda to jak poniżej:

// deklaracja
OtherTry otherTry = new OtherTry();
otherTry.addCatchBlock(new CatchBlock<NullPointerException>() {
    public void doWith(NullPointerException e) {
        // wykonywane w catch, w tym przypadku dla NullPointerException 
    }
});
otherTry.addFinallyBlock(new FinallyBlock() {
    public void doIt() {
        // wykonywane w finally 
    }
});
// użycie
otherTry.start();
    // wykonywane w try
otherTry.close();

Jest to trochę „sztuka dla sztuki” 🙂 choćby z powodu tego że zapis jest bardziej rozwlekły. Choć obiekt można zdefiniować tylko raz i potem używać w wielu miejscach (wtedy to tylko 2 linijki).
Całość ma jednak jedną wadę: nie zadziała z checked exception (kompilator nie pozwoli, i trzeba użyć starego try_a).

Ciekawej jest to, że można takie dziwo wyprodukować. W przypadku wyjątku pożądany kod obsługi się wykona, i jeszcze można zdefiniować sekcje finally. Możliwe jest to poprze dobranie się do bieżącego wątku i czasowe zmodyfikowanie domyślnego sposobu w jaki obsługiwane są nieprzechwycone wyjątki.
Czytaj dalej…

Kategorie:Java Tagi: ,