Mariusz Prowaźnik

o programowaniu w Javie, Scali i Clojure.


Projekt Clojure z Apache Maven

Do budowania projektów Clojure mamy do wyboru Maven'a i Leiningen'a. W tym poście opiszę jak można stworzyć prosty projekt przy użyciu tego pierwszego. Wykorzystałem:

  • Eclipse Juno z wtyczką Counterclockwise
  • Maven 3

Stworzenie projektu

Wtyczka Counterclockwise umożliwia utworzenie projektu Clojure w Eclipse. Jednak jeśli chcemy projekt oparty o Mavena, to myślę, że najlepiej zacząć tworząc zwykły projekt Javovy. Naciśnij Alt+Shift+N, wybierz Java Project. Podczas tworzenia projektu dodaj dwa katalogi na źródła: src/main/clojure, src/test/clojure.

Dodaj pom.xml

Dodaj plik pom.xml o treści:

<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/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>pl.mpr</groupId>
 <artifactId>clojure-example</artifactId>

 <packaging>clojure</packaging>

 <version>1.0-SNAPSHOT</version>
 <name>clojure-example</name>
 <url>http://maven.apache.org</url>
 <build>
  <plugins>
   <plugin>
    <groupId>com.theoryinpractise</groupId>
    <artifactId>clojure-maven-plugin</artifactId>
    <version>1.3.12</version>
    <extensions>true</extensions>
   </plugin>
  </plugins>
  <testResources>
   <testResource>
    <directory>${basedir}/src/test/clojure</directory>
   </testResource>
  </testResources>
 </build>
 <dependencies>
  <dependency>
   <groupId>org.clojure</groupId>
   <artifactId>clojure</artifactId>
   <version>1.4.0</version>
  </dependency>
  <dependency>
   <groupId>org.clojure</groupId>
   <artifactId>clojure-contrib</artifactId>
   <version>1.2.0</version>
  </dependency>
 </dependencies>
</project>
Wykonaj:
mvn eclipse:eclipse
i odśwież projekt w Eclipse. W tym momencie powinno być dodane wsparcie dla Clojure:

Dodanie testu i kodu w Clojure

Zgodnie z prawidłami TDD zaczniemy od napisania testu. Będzie to test dla funkcji zwracającej łańcuch tekstowy "Hello world!". Stwórz w katalogu na testy paczkę, a w nim plik HelloTest.clj:

(ns pl.mpro.clojureexample.HelloTest
  (:use pl.mpro.clojureexample.Hello)
  (:use clojure.test))

(deftest get-hello-test
  (let [hello "Hello world!"]
    (is (= (get-hello) hello))))

Wybieramy z menu: Clojure->Run file in Repl. Oczekiwany rezultat to:

Oczywiście testujemy coś, czego jeszcze nie jest napisane, stąd powyższy błąd. Teraz czas na implementację. Stwórz w katalogu src/main/clojure plik Hello.clj:

(ns pl.mpro.clojureexample.Hello)

(defn get-hello []
  "Hello world!")

Ponownie załaduj HelloTest.clj. Tym razem się to powinno udać. W repl'u wpisz

(run-tests)
Spodziewany rezultat to:
Testing pl.mpro.clojureexample.HelloTest

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
{:type :summary, :pass 1, :test 1, :error 0, :fail 0}
W oknie REPL możesz pisać kod clojure i zostanie on od razu wykonany. Jest jeszcze jedna wygodna funkcjonalność. W pliku Hello.clj dopisz:
(println "Test")
zaznacz i naciśnij Ctrl+Enter. To powinno wykonać w REPL zaznaczony kod.

Wykonanie testów przy użyciu Mavena

Przy obecnej konfiguracji, do wykonania testów clojure powinno wystarczyć:

mvn test
A

Testing pl.mpro.clojureexample.HelloTest

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13.812s
[INFO] Finished at: Thu Nov 15 00:20:20 CET 2012
[INFO] Final Memory: 6M/16M
[INFO] ------------------------------------------------------------------------
Jeśli się to nie powiodło, sprawdź, czy w pliku pom.xml jest:
<packaging>clojure</packaging>
Skompilować projekt można używając standardowo:
mvn package

http://evo-java.googlecode.com/svn/clojure-example


Co lubię w Clojure cz.2

Kolejną fajną rzeczą w Clojure są funkcje wyższego rzędu map, filter, reduce. (funkcje wyższego rzędu to takie które przyjmują inne funkcje jako argument, lub ich wartością zwracaną jest funkcja).

Zanim podam przykłady użycia tych funkcji, wspomnę, że w Clojure wygodniej niż w Javie tworzy się listy, mapy, zbiory i wektory:

(def some-list1 '(1 2 3 4 5))
(def some-list2 (list 1 2 3 4 5))
(def some-vector1 [1 2 3 4 5])
(def some-vector2 (vec 1 2 3 4 5))
(def some-set1 #{1 2 3 4 5})
(def some-set2 (hash-set 1 2 3 4 5))
(def some-map1 {:a 1 :b 2 :c 3 :d 4})
(def some-map2 (hash-map :a 1 :b 2 :c 3 :d 4))

Funkcja filter

Zwraca sekwencję elementów, pochodzącej z danej kolekcji, które spełniają określony warunek. Przykładowo, chcemy liczby pierwsze z wektora some-vector1. Zdefiniujmy funkcję zwracającą true dla liczb pierwszych, false dla złożonych:

(defn prime? [x]
     (if (> x 1)
       (let [divisors
             (->> x (Math/sqrt) (int) (inc) (range 2))]
         (not (some #(zero? (mod x %)) divisors))
         )
       false))
Mając taką funkcję, przefiltrowanie wektora można zapisać w jednej linijce:
(filter prime? some-vector1) ; rezultat: (2 3 5)

Funkcja map

Ta funkcja przyjmuje dwa argumenty: inną funkcję i kolekcję. Rezultatem jest sekwencja elementów, powstałych na skutek zastosowania przekazanej w argumencie funkcji na elementach kolekcji. Zatem, mając wektor liczb, możemy w jednej linijce podnieść wszystkie do kwadratu:

(map #(* % %) some-vector1) ; rezultat: (1 4 9 16 25)
Programując w Javie często spotykam się z przypadkiem, że mając listę obiektów, potrzebuję utworzyć listę, w której każdy element to określone pole. Na przykład z listy osób stworzyć listę ich imion. W clojure coś takiego robi się bardzo elegancko:
(def peoples [{:name "Jan" :age 20} {:name "Tomasz" :age 15} {:name "Conan" :age 60}])
(map :name peoples) ; rezultat: ("Jan" "Tomasz" "Conan")

Funkcja reduce

Kolejną ciekawą funkcją wyższego rzędu jest reduce. Działa w ten sposób, że stosuje funkcję podaną w argumencie na pierwszych dwóch elementach kolekcji, potem na wyniku i trzecim elemencie itd. Przykład

(reduce + some-vector1) ; rezultat 15

Leniwe sekwencje

Rezultatem funkcji map jest leniwa sekwencja. Oznacza to, że żaden element tej sekwencji nie jest obliczany, dopóki nie zostanie użyty.

Wcześniej napisana funkcja prime? dla większych liczb wykonuje się dosyć długo:

(time (prime? 200560490131)) ; "Elapsed time: 51.440427 msecs"
(time (prime? 2147483647)) ; "Elapsed time: 4.971057 msecs"
(time (prime? 87178291199)) ; "Elapsed time: 84.389674 msecs"

dlatego użyję jej w przykładzie.

(def prime-vec [87178291199 200560490131 2147483647])
(time (def prime-bools (map prime? prime-vec))) ; "Elapsed time: 0.057807 msecs"
(time (nth prime-bools 1)) ; "Elapsed time: 31.690193 msecs"
(time (nth prime-bools 1)) ; "Elapsed time: 0.029071 msecs"

Funkcja memoize

Za pomocą memoize można stworzyć wersję funkcji, która "zapamiętuje" parametry z jakim została wywołana i zwracane wartości, dzięki czemu, gdy zostanie wywołana potem z takimi samymi argumentami, wynik nie musi być liczony od nowa. Przykład:

(def cached-prime? (memoize prime?))
(time (cached-prime? 200560490131)) ;"Elapsed time: 275.652701 msecs"
(time (cached-prime? 200560490131));"Elapsed time: 0.105393 msecs"

Co lubię w Clojure, cz. 1

Zachęcony zwiększającą się popularnością języka Clojure, postanowiłem zapoznać się z jego możliwościami. Przy okazji, opublikuję serię postów, w których pokażę co ciekawego oferuje ten język.

Clojure działa na JVM, jest dialektem Lisp'a i językiem funkcyjnym. Różni się od Javy o wiele bardziej niż Java od C++. O technicznych cechach języka można poczytać na [clojure.pl], polecam również zajrzeć na [hammerprinciple.com], gdzie zobrazowane są opinie internautów na temat Clojure w porównaniu z Javą. Rzuca się w oczy to, że kod w Clojure uważany jest za bardziej zwięzły - ja mam podobne odczucia. Przejdźmy teraz do praktyki.

Szybki start

By zacząć pisać w Clojure, wystarczy ściągnąć blibliotekę z [clojure.org] i wykonać:

java -jar clojure-1.4.0.jar

To uruchomi REPL, w którym można pisać kod i zostanie on od razu wykonany. Inna możliwość to wypróbować REPL online na stronie [tryclj.com].

Funkcje jako argumenty innych funkcji

Zatem, co jest ciekawego w Clojure, czego nie ma w Javie? Np to, że funkcje mogą być przekazywane jako argumenty do innej funkcji. W rezultacie, coś co w Javie uzyskujemy poprzez wzorzec strategii, w Clojure jest czymś zupełnie naturalnym i prostym. Prosty przykład:

public class StrategySnippet {
 public static void main(String[] args) {
  new StrategySnippet().go();
 }

 public void go() {
  useStrategy(new AddStrategy(), 2, 3);
  useStrategy(new MultiplyStrategy(), 2, 3);
 }

 public void useStrategy(Strategy s, int a, int b) {
  System.out.println(s.execute(a, b));
 }

 interface Strategy {
  int execute(int a, int b);
 }

 class AddStrategy implements Strategy {

  @Override
  public int execute(int a, int b) {
   return a + b;
  }

 }

 class MultiplyStrategy implements Strategy {

  @Override
  public int execute(int a, int b) {
   return a * b;
  }

 }
}

W Clojure analogiczny kod to:

(defn use-strategy[strategy a b]
  (println (strategy a b)))

(defn add-strategy [a b]
  (+ a b))

(defn multiply-strategy [a b]
  (* a b))

(defn go []
  (use-strategy add-strategy 2 3)
  (use-strategy multiply-strategy 2 3)
 )
(go)

W dodatku + i * same w sobie są funkcjami, zatem taki sam efekt uzyskamy poprzez:

(defn go []
  (use-strategy + 2 3)
  (use-strategy * 2 3)
 )
(go)
W kolejnym przykładzie kod sortujący listę String'ów po ich długości. W Javie użyjemy metodę Collection.sort i anonimową klasę implementującą Comparator:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class AnonymousClassSnippet {

 public final static List<String> SOME_LIST = Arrays.asList(new String[] { 
"Jan", "Zbych", "Boromir", "Urlich","Rupert","Jaro" });

 public void sortAndPrint(List<String> list) {
  ArrayList<String> l = new ArrayList<String>(list);
  Collections.sort(l, new Comparator<String>() {

   @Override
   public int compare(String arg0, String arg1) {
    if (arg0.length() > arg1.length())
     return 1;
    else if (arg0.length() < arg1.length())
     return -1;
    else
     return 0;
   }

  });
  System.out.println(l);
 }
 public static void main(String []args){
  new AnonymousClassSnippet().sortAndPrint(SOME_LIST);
 }
}
W Clojure można zapisać to tak:
(def some-vector ["Jan" "Zbych" "Boromir" "Urlich" "Rupert" "Jaro"])
(defn sort-and-print [vec]
  (println 
    (sort #(compare (.length %1) (.length %2)) vec)
  ))
(sort-and-print some-vector)
Użyta tu została funkcja anonimowa - zapisujemy ją:
#( ciało_funkcji)

przy czym %1 i %2 to parametry. Prawda, że krócej?


Polskie znaki w pdf z JasperReports

JasperReports to całkiem ciekawe narzędzie do tworzenia raportów w Javie. Jednak można napotkać na problemy z wyświetlaniem polskich znaków w pdf, a w internecie trudno znaleźć działające rozwiązanie. Dlatego zamieszczam przykład.

Korzystałem z:

  • Eclipse Juno
  • Jaspersoft iReport Designer 4.6.0

Krok 1: szablon w iReport

Przy użyciu iReporta stworzyłem bardzo prosty szablon raportu. Dodałem zwykły statyczny tekst, oraz pole w którym będzie się wyświetlać wartość parametru:

Należy pamiętać o dodaniu parametru:

Krok 2: projekt w Eclipse

Stworzyłem prosty projekt, który będzie tworzył raport na podstawie szablonu. Potrzebna będzie zależność w pom.xml:

<dependency>
 <groupId>net.sf.jasperreports</groupId>
 <artifactId>jasperreports</artifactId>
 <version>4.7.1</version>
</dependency>
Szablon powinien być w src/test/resources/badReport.xml. Poniżej fragment testu:
@Test
public void test() throws IOException, JRException {
 final byte[] bytes = getResourceAsByteArray("badReport.xml");
 final DefaultJasperReportsContext context = DefaultJasperReportsContext.getInstance();
 final JasperReport jasperReport = JasperCompileManager.compileReport(new ByteArrayInputStream(bytes));

 Map<String, Object> params = new HashMap<String, Object>();
 params.put("MOJTEKST", "Zażółć gęślą jaźń z parametru");

 final byte[] pdf = JasperRunManager.runReportToPdf(jasperReport, params);
 FileOutputStream fos = new FileOutputStream("C:/report.pdf");
 fos.write(pdf);
 fos.close();
 Runtime.getRuntime().exec(
   "\"C:\\Program Files (x86)\\Foxit Software\\Foxit Reader\\Foxit Reader.exe\"" + " " + "C:/report.pdf");
}

private static byte[] getResourceAsByteArray(String filePath) throws IOException {
 InputStream is = JasperTest.class.getClassLoader().getResourceAsStream(filePath);
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 byte[] buffer = new byte[1024];
 int read = 0;
 while ((read = is.read(buffer, 0, buffer.length)) != -1) {
  baos.write(buffer, 0, read);
 }
 baos.flush();
 return baos.toByteArray();

Krok 3: usuwamy pierwszy problem

Gdy teraz uruchomimy test, to poleci wyjątek:

java.lang.NoClassDefFoundError: org/codehaus/groovy/control/CompilationFailedException

Rozwiązanie: usunąć atrybut language elementu jasperReport w badReport.xml

language="groovy"

Krok 4: usuwamy kolejny problem

Gdy teraz uruchomimy, raport będzie pustą stroną. By to zmienić dodajemy atrybut elementu jasperReport:

whenNoDataType="AllSectionsNoDetail"

Krok 5: usuwamy problem z czcionkami

Teraz raport będzie się generował prawie normalnie:

Rozwiązanie: dodajemy element reportFont wewnątrz jasperReport:

<reportFont name="Arial_Normal" isDefault="true" fontName="Arial" size="8" pdfFontName="Helvetica" pdfEncoding="Cp1250" isPdfEmbedded="false" />

W rezultacie, raport powinien się generować tak jak należy:

Poczatkowy fragment badReport.xml:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
 name="polishFonts" pageWidth="595" pageHeight="842" columnWidth="555"
 leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20"
 whenNoDataType="AllSectionsNoDetail">
 <reportFont name="Arial_Normal" isDefault="true" fontName="Arial"
  size="8" pdfFontName="Helvetica" pdfEncoding="Cp1250" isPdfEmbedded="false" />
 <parameter name="MOJTEKST" class="java.lang.String" />

Kod przykładu jest do ściągnięcia tu:

http://evo-java.googlecode.com/svn/jasper-example


"Działaj teraz" - recenzja

Nie samą literaturą techniczną żyje programista - nie zaszkodzi przeczytać od czasu do czasu jakiś poradnik psychologiczny. Mój wybór padł na książkę Zbigniewa Ryżaka "Działaj teraz", a ponieważ myślę, że wybór ten był dobry, podzielę się wrażeniami.

To nie jest poradnik w stylu "jak zarobić milion w tydzień", czy "jak zdobyć każdą kobietę". Autor nie obiecuje, że jego książka jest jak magiczna pigułka, po zażyciu której świat stanie się idealny, ani nie wmawia, że naprawdę byłbyś sobą, gdybyś był kimś innym. Zamiast tego proponuje książkę o działaniu w świecie, który jest daleki od ideału i o byciu skutecznym pomimo osobistych wad i niedoskonałości.

Poradnik zawiera wiele anegdot i przykładów obrazujących mechanizmy ludzkiej psychiki, oraz sześć zadań związanych z realizacją osobistego przedsięwzięcia.

Zadanie 1: wybierz swój projekt

Wybierając przedsięwzięcie zastanawiamy się dlaczego jest ono ważne, dlaczego chcemy je zrealizować. Czy rzeczywiście wynika ono z naszych własnych pragnień, czy nie jest czasem rezultatem oczekiwań innych ludzi wobec nas. Może nie jest ważne, tylko pilne? Może jest tylko unikaniem czegoś o wiele ważniejszego, czego boimy się ruszyć. Czy rzeczywiście czujemy do tego motywację? Z czego jesteśmy gotowi zrezygnować?

Według powszechnej mądrości, gdy nie czujesz do czegoś motywacji, to znaczy, że jesteś leniem i powinieneś się bardziej postarać, zmusić się do działania. Tymczasem przyczyna może leżeć gdzieś indziej:

A może to, do czego z takim uporem się zmuszasz nie jest takie cenne jak ci się teraz wydaje?
A może twoje lenistwo nie jest objawem twojej słabości, ale siły? Może jest sygnałem, którego nie potrafisz odczytać. Sygnałem, który mówi:  Coś w twoim życiu czeka na zrobienie i nie jest tym to, co robisz teraz!

Ciekawą uwagą jest to, że wybór projektu do wykonania powinien być raczej odkrywaniem swojej misji, niż ustanawianiem celu. Cele się wymyśla, ale swoją misję się odkrywa. Jest to coś, do czego czujesz, że jesteś stworzony i jednocześnie jest to pożyteczne dla innych ludzi.

Zadanie 2: zrób pierwszy krok

Drugim zadaniem jest przełamać się rozpocząć realizację projektu. Wymaga to pokonania lęku związanego rozpoczynaniem czegoś nowego. Często kryje się on pod pozorem braku chęci, sił, bądź czasu, ale tak na prawdę to strach, bo ważne osobiste przedsięwzięcia często wymagają opuszczenia strefy komfortu i poruszania się po obszarze, w którym nie czujemy się zbyt pewnie.

Radą jest, by zacząć pracować nad projektem, choćby po pięć minut. Im dłużej zadanie jest odkładane, tym wydaje się trudniejszym - jest to związane z działaniem dysonansu poznawczego. Jednak po rozpoczęciu pracy, często okazuje się, że wcale nie jest takie straszne.

Wielu ludzi chce pracując odczuwać "flow" i czeka z pracą aż będzie w tym stanie, będzie mieć sprzyjające warunki i odpowiedni nastrój. Jednak, by pojawił się "flow", najpierw trzeba zaakceptować pewien początkowy dyskomfort i zacząć działać.

Zadanie 3: kolejne dni

Kolejny etap to zwiększanie nakładu pracy i łapanie rytmu. W tej części książki przeczytamy o tym, co robić, gdy mija początkowy entuzjazm, oraz gdy pojawiają się zaległości i rozczarowanie. Wygórowane oczekiwania niektórych ludzi wobec siebie biorą się z tego, że traktują swoje szczytowe osiągnięcia jako normę. Innym poruszonym problemem jest symulowanie działania, poprzez zajmowanie się nieistotnymi detalami i unikanie pracy nad kluczowym zadaniem. W programowaniu ekstremalnym jest zasada You_ain't_gonna_need_it, którą można stosować również w nieprogramistycznych realiach.

Mistrza gry na skrzypcach zapytano
– Jak długo trzeba codziennie ćwiczyć, by osiągnąć dobre efekty?
– Cały dzień, jeżeli ćwiczy się wyłącznie rękoma, półtorej godziny, jeżeli ćwiczy się cały sercem.

Zadanie 4: zaplanuj zmianę nawyków

Silna wola jest często bardzo przeceniana. Jest wyczerpywalnym zasobem, który wymaga regeneracji. Niebezpiecznie jest uzależniać od niej powodzenie własnego projektu. Lepszym sposobem jest praca nad nawykami, a konkretnie zamiana tych szkodliwych na pożyteczne. Istotne jest to, że nie chodzi o pozbywanie się nawyków, ale ich wymienianie. Nawet te szkodliwe, dają w jakiś sposób chwilową ulgę, dlatego jeśli nie znajdziemy zamiennika, to powrócimy w końcu do tych złych.

W tej części książki autor zachęca do tego, by przyjrzeć się swoim nawykom w różnych obszarach życia: pracy, odpoczynku i relaksu, rozwoju, relacji z innymi ludźmi, zdrowia.

Zadanie 5: rozwiąż jeden problem

Dlaczego zadanie to polega na usunięciu jednego problemu. Autor uzasadnia to w ten sposób:

Gdy próbujesz rozwiązać naraz dwa problemy, zazwyczaj wymaga to od ciebie dwa razy więcej pracy, niż gdybyś rozwiązywał je po kolei. Gdy próbujesz rozwiązać trzy na raz, kosztuje cię to dziesięć razy więcej energii, a do tego niewiele z tego wychodzi. Gdy próbujesz rozwiązać cztery – nie tylko nic nie poprawiasz, ale tworzysz dodatkowe problemy.

Dalej czytamy o twórczym pokonywaniu barier, oraz o tym jak ograniczenia z którymi się borykamy mogą sprawić, że stworzymy coś unikalnego. Identyfikujemy założenia, które przyjęliśmy za prawdziwe, dlatego, że inni ludzie w ten sposób postępują, czy dlatego, że zawsze do tej pory tak robiliśmy. Sprawdzamy, czy da się je podważyć.

Przy okazji tego zadania, autor napisał sporo o wygórowanych oczekiwaniach wobec siebie z punktu widzenia psychologa i trenera - dlaczego zbyt ambitne plany są formą ochrony przed lękiem, oraz jak można osiągnąć więcej, oczekując mniej.

Większość ludzi przecenia to, co może osiągnąć w ciągu jednego roku i nie docenia tego, co są w stanie zrobić w ciągu dziesięciu lat.

Zadanie 6: ustaw swój sukces

W popularnej koncepcji wyznaczania celów SMART dobry cel powinien być konkretny, mierzalny, osiągalny, ważny i czasowo ograniczony. Autor proponuje zastosować nieco zmodyfikowaną wersję - cel powinien być:

  • konkretny, czyli tak określony, że osoba, która "stoi z boku" i nie zna Twoich odczuć jest w stanie stwierdzić, czy udało Ci się osiągnąć cel, czy nie;
  • zależny od Ciebie;
  • istotny, czyli sprawiający różnicę;
  • minimalny.
Szczególnie interesujący jest ostatni punkt. Polega on na szukaniu możliwości zrobienia czegoś istotnego mniejszym wysiłkiem. By spełnić to założenie, upraszczamy swój cel i okrajamy go tak, by nadal był istotny i atrakcyjny.

Podsumowując, polecam tę książkę. Wiele wyjaśnia, motywuje i jest w ciekawy sposób napisana.


Agile Software Development, Principles, Patterns and Practices - recenzja

"Agile Software Development, Principles, Patterns, and Practices " autorstwa Roberta C. Martina porusza wiele zagadnień związanych ze zwinnym tworzeniem oprogramowania, poczynając od wartości, które kryją się za manifestem agile, poprzez praktyki programowania ekstremalnego, po reguły projektowania i wzorce projektowe.

Spodobał mi się sposób w jaki ta książka jest skomponowana. Najpierw zapoznajemy się z czynnikami wpływającymi na powodzenie projektu i powszechnie napotykanymi trudnościami, potem z rozwiązaniami proponowanymi przez metodyki zwinne, które są krok po kroku uszczegóławiane. Takie podejście sprawia, że każdą zasadę zaproponowaną w książce łatwo odnieść do ogólnej idei i wiadomo jaki cel ma realizować. Mimo, że nie jest to dla mnie nowy temat, rozdziały o programowaniu ekstremalnym przeczytałem z dużym zainteresowaniem.

Już od pierwszych stron autor sugeruje, by używać jak najprostszych narzędzi, przechodząc do tych bardziej zaawansowanych dopiero, jak się okaże, że dotychczasowe są niewystarczające. By uniknąć niepotrzebnej złożoności, jednocześnie zachowując elastyczność systemu, potrzebne jest dobre zrozumienie zasad SOLID. Często są one opisywane na blogach, jednak dopiero umiejscowienie ich w kontekście pracy programisty, wzorców projektowych i poparcie wieloma przykładami, pozwala dobrze uchwycić ich istotę.

Wzorcom projektowym poświęcona jest spora część książki. Dwa duże studia przypadku pokazują proces refaktoryzacji kodu i wprowadzania wzorców, co ułatwia zrozumienie ich przeznaczenia, oraz kosztu ich zastosowania.

Podsumowując, mocną stroną tej książki jest przedstawienie różnych zasad i praktyk w tworzeniu oprogramowania, w taki sposób, że tworzą spójną całość. Dzięki temu, nawet osoba, która spotkała się z przedstawionymi zagadnieniami, wiele wyniesie z tej lektury, bo lepiej zrozumie jak te zagadnienia łączą się ze sobą, jak mogą współistnieć i wzmacniać swoje efekty.


Konfiguracja NetBeans IDE C/C++ i Cygwin

Niedawno postanowiłem odświeżyć nieco znajomość C/C++. Na co dzień programuję w Javie, więc postanowiłem wykorzystać NetBeans IDE. Przy okazji zamieszczam krótki tutorial opisujący konfigurację środowiska.

Potrzebne oprogramowanie:

Paczka instalacyjna NetBeans'a nie zawiera kompilatorów dla C/C++, dlatego trzeba się o nie zatroszczyć samemu (chyba, że używasz Linux'a - wtedy ten tutorial nie jest dla Ciebie). W tym celu instalujemy Cygwin'a. Dzięki niemu będziemy mogli użyć zestawu kompilatorów GCC.

Zatem do dzieła:

  1. Instalujemy Cygwina. W instalatorze wybieramy opcję "Install from Internet", oraz podajemy katalog instalacji (najlepiej by ścieżka nie zawierała spacji, bo podobno powoduje to problemy). Przechodzimy do wyboru pakietów, rozwijamy gałąź "Devel" i wybieramy:
    • gcc
    • gcc-core
    • gcc-g++
    • gdb
    • make
  2. Testujemy Cygwin'a Po zainstalowaniu uruchamiamy terminal Cygwin'a, tworzymy plik z prostym kodem:
    $echo "#include<stdio.h>
    int main(){
     printf(\"hello\");
    }" > hello.c
    
    kompilujemy:
    $ gcc hello.c -o hello.exe
    i uruchamiamy:
    $ ./hello.exe
  3. Dodajemy zmienne środowiskowe. Dodaj zmienną środowiskową CYGWIN_HOME ze ścieżką do katalogu Cygwina (np. C:/Cygwin), oraz edytuj zmienną PATH, dodając na końcu %CYGWIN_HOME%/bin;. W tym momencie powinno się dać kompilować pliki c spod konsoli windowsa:
    cd %CYGWIN_HOME%/home/%USERNAME%
    rm hello.exe
    gcc-3 hello.c -o hello.exe
    hello.exe
  4. Instalujemy NetBeans IDE. W tym momencie kompilatory powinny być wykryte automatycznie, można sprawdzić w Tools->Options->C++:
    Można stworzyć teraz nowy projekt i uruchomić:


Jak rozdzielić testy jednostkowe i integracyjne (z wykorzystaniem JUnit i Maven)

Jakiś czas temu pisałem o użyciu kategorii w testach JUnit. Możliwość oznaczenia testu kategorią jest przydatna, pod warunkiem, że w jakiś sposób Maven, wykonując testy, będzie w stanie je rozróżnić i wykonać tylko testy określonej kategorii, bo jednak ręczne dodawanie testów do zestawów (TestSuite) mija się z celem. Na szczęście taka konfiguracja Maven'a jest to możliwa.

Ok, mamy dwa interfejsy, które będą markerami dla naszych testów:
package com.evojava;
public interface IntegrationTest {}
package com.evojava;
public interface UnitTest {}
Testy niech będą następujące:
package com.evojava;

import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(UnitTest.class)
public class SmallTest {

 @Test
 public void test() {
  System.out.println("SmallTest");
 }
}
package com.evojava;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(IntegrationTest.class)
public class BigTest {

 @Test
 public void test() {
  System.out.println("BigTest");
 }
}

Za pomocą wtyczki Surefire, możemy skonfigurować projekt tak by uruchamiały się testy oznaczone określoną kategorią. Przykładowy plik pom.xml:
<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>com.evojava</groupId>
 <artifactId>categories</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>categories</name>
 <description>categories</description>
 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.10</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.11</version>
    <configuration>
     <groups>com.evojava.UnitTest</groups>
    </configuration>
    <dependencies>
     <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>2.12</version>
     </dependency>
    </dependencies>
   </plugin>
  </plugins>
 </build>
</project>
Wykonujemy: mvn clean package
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Concurrency config is parallel='none', perCoreThreadCount=true, threadCount=2, useUnlimitedThreads=false
Running com.evojava.SmallTest
SmallTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Teraz wystarczy zmienić konfigurację, by przy wykonaniu mvn integration-test uruchomiły się pozostałe testy. Odpowiedni fragment pliku pom.xml:
<plugin>
 <artifactId>maven-surefire-plugin</artifactId>
 <version>2.11</version>
 <executions>
  <execution>
   <id>default-test</id>
   <phase>test</phase>
   <goals>
    <goal>test</goal>
   </goals>
   <configuration>
    <groups>com.evojava.UnitTest</groups>
   </configuration>
  </execution>
  <execution>
   <id>integration-tests</id>
   <phase>integration-test</phase>
   <goals>
    <goal>test</goal>
   </goals>
   <configuration>
    <excludedGroups>com.evojava.UnitTest</excludedGroups>
   </configuration>
  </execution>
 </executions>
 <dependencies>
  <dependency>
   <groupId>org.apache.maven.surefire</groupId>
   <artifactId>surefire-junit47</artifactId>
   <version>2.12</version>
  </dependency>
 </dependencies>
</plugin>
Wtedy, po uruchomieniu mvn clean integration-test
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Concurrency config is parallel='none', perCoreThreadCount=true, threadCount=2, useUnlimitedThreads=false
Running com.evojava.BigTest
BigTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec

Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Na koniec krótkie wyjaśnienie do tej konfiguracji.
  • Zostały skonfigurowane dwa uruchomienia testów: <id>default-test</id>, <id>integration-tests</id>.

  • default-test został przypięty do fazy test, integration-test do fazy integration-test.

  • W domyślnym cyklu budowania są między innymi fazy: validate, compile, test, package, integration-test, verify, install, deploy. Po mvn test będą uruchomione fazy do test włącznie, po mvn integration-test do integration-test włącznie (czyli po drodze również fazę test). Więcej na ten temat

  • default-test uruchamia tylko testy oznaczone kategorią UnitTest, integration-test wszystkie oprócz oznaczonych kategorią UnitTest. W rezultacie mvn test uruchamia testy jednostkowe, mvn integration-test wszystkie testy (bo faza test również się wykona)

  • Nazwa default-test jest istotna, bo jeśli nie skonfigurujemy jawnie tego uruchomienia, uruchomi domyślnie wszystkie testy

  • Jeśli w integration-test mają uruchomić się tylko testy oznaczone kategorią IntegrationTest, a nie wszystkie oprócz UnitTest, wystarczy zmienić:
    <configuration>
      <excludedGroups>com.evojava.UnitTest</excludedGroups>
    </configuration>
    na:
    <configuration>
      <groups>com.evojava.IntegrationTest</groups>
    </configuration>
    Akurat w tym przypadku na jedno wychodzi, ale jeśli pojawią kolejne kategorie, to już będzie różnica.
Kod można ściągnąć z https://evo-java.googlecode.com/svn/maven-int-test-example/