tifyty

pure Java, what else ?

Java 8 teszt

Az új nyolcas java-ban sok minden egyéb mellett a következőt lehet állítani:

Ha egy C osztály implementál egy I interfészt, és I-ben van egy absztrakt metódus, amit C nem implementál, akkor C nem fordul le, de futni azért még futhat.

Az állítás eleje igaz volt eddig is, de a kövérrel szedett rész az új. Ha lesz időm a hétvégén kipróbálom, és megírom, hogy hogyan is kell ezt érteni. Addig is lehet kommentekben tippelni.

Egájl Eszti Máson

Megterveztünk egy projektet. Jól megbecsültük, hogy mennyi energia, ráfordítás, óra, cukor, liszt stb kell a kakaós csiga elkészítéséhez. Aztán megterveztük részletesen, hogy pontosan hogyan is kellene tekerednie, mikor kerüljön bele a cukor, a liszt, és milyen irányba forogjon a keverőgép tárcsája. Aztán kiderült, hogy cukorbetegek is fogyasztani fogják a kakaós csigát, valamint gurtnik és gurmék ezért nem jó bele akármilyen kakaó.

És akkor nekiálltunk, és átterveztük, hogy gesztenye és durum liszt keveréke legyen az alapanyag, és xilit, aspartám és szarharin legyen cukor helyett. Igaz, ilyenkor más a tészta állaga, más hőfokú sütő kell, és más a trutymó viszkozitása is, ezért másmilyen keverőgépet kellett rendelni. Szerencsére a régi keverőgépet kvázi költségmentesen el lehetett dobni, az új meg a fejlesztés idejére amúgy is csak egy VM.

Közben az eredeti tervek szerint már nagyban keverni kellett volna a tésztát, darálni a krumplit és reszelni a sült húst. De tervezés nélkül nem lehet nekiugrani egy projektnek sem, akkor sem ha akármilyen agilisak vagyunk. Valami fogalmunk kell, hogy legyen arról, hogy mit is fogunk a végén kakaós csiga néven kipréselni magunkból. De nyugodtak voltunk, mert tudtuk, hogy az eredeti becslések elég konzervatívak voltak, és készítettünk már hasonlót, ha nem is kakaós csigát, csak éppen fahéjas kígyót, meg vasabival fűszerezett birkapéniszt. Ez is csak ugyanolyan csemege, mint amilyent az ügyfelek szeretnek. Ugyan nem mindig érthető, hogy miért, de nem is az a dolgunk, hogy megértsük, igaz-e? (Nem.)

A projekt menedzsment azonban nem volt ilyen nyugodt, mert a számok azt mutatták, hogy a kakaós csiga nem lesz kész karácsonyra, amikor is piacon kell lenni a vásáron, különben minek az egész. Így aztán az egyik hajnali merevedés során (gy.k. a standup meeting-re gondolok) elhangzott a következő mondat:

Újra kell esztimálni az effortot úgy, hogy a kakaós csiga rilízelhető legyen karácsonyig.

Nincs ezzel semmi gond. Megcsinálod fiam, és kész! Mert ha nem, akkor … na akkor mi lesz? Mivan, mivan, mivan? Nem kell szembenézni a realitással, ki kell adni a parancsot, és akkor meg lesz! Ilyen egyszerű. Ez a projekt menedzsment. Kihozni az emberekből ami bennük van! Hogy mi van az emberekben, arról meg a projek menedzsernek sok éves a tapasztalata, bár talán ha egy sebészt, vagy proktológust megkérdezett volna, pontosabb ismeretei lennének.

De nem baj. Becsülünk, vagy kicsülünk, mi itt együtt csücsülünk, azt majd mondunk valamit, hogy meddig tart a feladat. Konzervatívan becslünk, hogy legyen tartalék akkorra amikor a projekt menedzsment majd benyomja a turbo búsztot, a projekt menedzsment meg tudja, hogy van tartalék, hiszen mindig is volt, hiszen a programozó, fejlesztő egy lusta állat, mindig alulbecsül. És így éldegélünk egymással szórakozva, ismerve a másikat, és ha néha nem becsülünk elég nagyot, akkor elcsúszik a projekt, és olyankor balhé van.

Így kell ezt professzionálisan csinálni?

Díszklémer: (olyan klémer ami semmire se jó, de szép) A fenti történet nem a mi pékségünkben történt, és bármi hasonlóság élő, halott, élőhalott, még meg sem született vagy bármilyen más személyekkel, tárgyakkal, emberek tárgyakkal, tárgyak tárgyakkal… szóval nem valószínű.

Akit meg részletesebben érdekel, az olvassa el a Clean Coder című könyvet.

Auto boxing

Az autoboxing-gal minden Java fejlesztő tisztában van, amióta van 1.5 Java. (Vagy én vagyok nagyon naiv. Ha a matchbox jutott eszedbe, vagy hirtelen box kesztyűket húzó, és boxoló autókra asszociáltál, akkor vagy nem Java fejlesztő vagy, vagy ideje átismételni.) Van is róla jó kis tutorial az ORACLE oldalain. Az autoboxing az, amikor a Java fordító olyan kódot generál, amelyik valamelyik primitív típusból objektumot állít elő automatikusan, amikor arra van szükség. Tehát írhatjuk, hogy

Integer a = 42;

és ebből automatikusan generálódik egy olyan értékadás, ami az int 42 értéket Integer objektummá teszi. És ez annyira szép, és egyszerű, és annyira belefeledkezünk az évek során, hogy egyszer csak senior fejjel is rácsodálkozunk egyes részletekre, és egy ideig nem értjük.

Például azt, hogy van double.class és Double.class. Ugye mind a kettő egy objektum (mint minden osztály) és mind a kettő típusa Class. Sőt! Java 1.5 óta mind a kettő típusa Class<Double>.

No de, ha ugyanaz a típusuk, akkor kompatibiliseknek kellene lenniük egymással. Márpedig

public class TypeFun {
    public static void main(String[] args) {
        // public static final Class<Double>   TYPE = (Class<Double>)Class.getPrimitiveClass("double");
        System.out.println("Double.TYPE == double.class: " + (Double.TYPE == double.class));
        System.out.println("Double.TYPE == Double.class: " + (Double.TYPE == Double.class));
        System.out.println("double.class.isAssignableFrom(Double.class): " + (double.class.isAssignableFrom(Double.class)));
        System.out.println("Double.class.isAssignableFrom(double.class): " + (Double.class.isAssignableFrom(double.class)));
    }
}

eredménye

Double.TYPE == double.class: true
Double.TYPE == Double.class: false
double.class.isAssignableFrom(Double.class): false
Double.class.isAssignableFrom(double.class): false

vagyis a Double primitív párja a double.class (nem meglepő), mégis az egyik a másikból nem assign-olható. (na ezt fordítsa le valaki úgy, mint az aggódások szétválasztását)

Persze utána lehet nézni a dolgoknak, hiszen a Double osztály benne van a Java RT-ben, és open source. Ott viszont azt látjuk, hogy a

public static final Class<Double>	TYPE = (Class<Double>) Class.getPrimitiveClass("double");

Miért nem double.class? Hiszen az a Double típus primitív megfelelője, és minden olyan osztályhoz, amelyiknek van primitív párja a TYPE mező a primitív párját adja vissza.

A válasz nem triviális és elég mélyen bele kell vájni a Java és a JVM lelki világába, hogy megértsük. Mivel a double nem osztály, ezért nincs is olyan igazán, hogy double.class. A Java fordító, és a run time library itt is összefonódik egy kicsit, és a fordító tudja, hogy a Double osztály definiál egy TYPE típusú konstans mezőt, amelyik a Double primitív megfelelőjének az osztálya, és valahányszor a kódban double.class-t lát valójában Double.TYPE-ot fordít. (Ezt érdemes kipróbálni és megnézni javap-pal.) Emiatt nem írhattak a RT fejlesztői sem

public static final Class<Double>	TYPE = double.class;

kódot a Double forrásába, hiszen abból

public static final Class<Double>	TYPE = TYPE;

lett volna, aminek nem lenne sok értelme. És hogyan megy az autobox-ing pontosan?

Double b = (double)1.0;

forrásból

         0: dconst_1      
         1: invokestatic  #2                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
         4: astore_1 

lesz, ha viszont megfordítjuk, és kicseréljük a két d betűt

double b = (Double)1.0;

akkor

         0: dconst_1      
         1: invokestatic  #2                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
         4: invokevirtual #3                  // Method java/lang/Double.doubleValue:()D
         7: dstore_1    

ami elég sok mindent megmagyaráz. A double.class és a Double.class nem assignolható egymásból, de az autoboxing ezt megoldja. Rég volt már a Java 1.4 szerencsére el is felejtettük.

Házi feladat: ismételjük át önállóan, hogy mi a helyzet az autoboxing-gal túltöltött metódus argumentumoknál!

Hídeljárások és volatile metódusok

Ha valaki valamikor játszott már a reflection-nel, és esetleg elengedte a getDeclaredMethods() eljárást bizonyos osztályokra, érhették meglepetések. Nevezetesen az, hogy nem csak azok a metódusok vannak az osztályban, amelyek a Java forráskódban szerepelnek. Vagy esetleg megnézte az eljárások módosítóit, és kiderült, hogy van közöttük törékeny (volatile). Ez egyébként egy jó interjú kérdés: mit jelent az, amikor egy metódus volatile? A helyes válasz: metódus nem lehet volatile. És mégis: a reflection getDeclaredMethods() esetleg getMethods() által visszaadott metódusok között lehet olyan, amelyikre a Modifier.isVolatile(method.getModifiers()) igaz értéket ad vissza.

Ilyen történt például az immutátor project egyik felhasználójával is. Azt találta, hogy a generált immutátor, amelyik egyébként elég mélyen nyúlkál a Java lelki világába, olyan kódot generált, amit aztán maga sem tudott lefordítani, működésről , meg mint a boszorkányokról ezek után szó sem eshetett.

Nah, de mi is történik itt? Mik azok a szintetikus metódusok, és mik a híd (bridge, most) metódusok?

Láthatóság

Nem tudom feltűnt-e fészkelt osztály (nested class) készítése során (ugye nested class, meg inner class nem ugyan az) a belső private változók kívülről is elérhetőek. Ezt ki is használja például az immutable embedded builder pattern. Ez egyébként le van írva a Java nyelvi speckóban is, hogy így kell lennie: ami a legkülső osztályon belül van, az elérhető.
(Jut eszembe: ebből a szempontból a belső és a beágyazott osztályok között nincs is különbség.)

JLS7, 6.6.1 Determining Accessibility

… if the member or constructor is declared private, then access is
permitted if and only if it occurs within the body of the top level class (§7.6)
that encloses the declaration of the member or constructor…

package synthetic;

public class SyntheticMethodTest1 {
    private A aObj = new A();

    public class A {
        private int i;
    }

    private class B {
        private int i = aObj.i;
    }

    public static void main(String[] args) {
        SyntheticMethodTest1 me = new SyntheticMethodTest1();
        me.aObj.i = 1;
        B bObj = me.new B();
        System.out.println(bObj.i);
    }
}

A gond akkor jön, amikor lefordítjuk az osztályt. A JVM-nek ugyanis hiába mondaná bárki, hogy belső osztály, meg beágyazott, mert fogalma nem lesz, hogy azok mik. A JVM nem ismeri ezeket a fogalmakat, a Java fordító minden osztályból “külső” osztályt csinál. Így készülnek a mindenféle ...$..class fájlok:

 $ ls -Fart
../                         SyntheticMethodTest2$A.class  MyClass.java  SyntheticMethodTest4.java  SyntheticMethodTest2.java
SyntheticMethodTest2.class  SyntheticMethodTest3.java     ./            MyClassSon.java            SyntheticMethodTest1.java

Ha készítesz egy belső osztályt (static class), akkor abból lesz egy teljes értékű külső osztály. Ha készítesz egy beágyazott osztályt (nem static csak sima class), akkor annyival megspékeli, hogy az összes konstruktornak átadja a referenciát ami a körülfogó osztályra mutat, és amin keresztül eléri a külső osztály mezőit. A beágyazott osztály beágyazott osztályáról most ne essen szó.

Igen, csak mi lesz a privát metódusokkal és mezőkkel? Ha azok kikerülnek egy külső osztályba, akkor hogyan lesznek elérhetőek?

Úgy, hogy minden ilyen metódushoz, konstruktorhoz, vagy mezőhöz legenerál egy/két szintetikus metódust, vagy konstruktort amelyiken keresztül az eredeti elérhető. Konstruktornál plusz egy szintetikus konstruktort hoz létre, metódusnál egy szintetikus metódust, mezőnél pedig egy szintetikus setter-t és egy szintetikus getter-t. A szép benne az, hogy ezt ráadásul okosan csinálja, és ha például egy mezőhöz csak az olvasáshoz kell a szintetikus metódus, mert írni csak a belső osztályon belülről írjuk, akkor setter-t nem fog generálni, csak getter-t, és persze fordított esetben is. Csak azokat a metódusokat fogja legenerálni, amelyek tényleg kellenek a láthatóság kiterjesztéséhez.

A metódusok neve pedig… nos az lesz, amit a fordító akar.

package synthetic;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class SyntheticMethodTest2 {

    public static class A {
        private A(){}
        private int x;
        private void x(){};
    }

    public static void main(String[] args) {
        A a = new A();
        a.x = 2;
        a.x();
        System.out.println(a.x);
        for (Method m : A.class.getDeclaredMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getName());
        }
        System.out.println("--------------------------");
        for (Method m : A.class.getMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
        }
        System.out.println("--------------------------");
        for( Constructor<?> c : A.class.getDeclaredConstructors() ){
            System.out.println(String.format("%08X", c.getModifiers()) + " " + c.getName());
        }
    }
}

Erre a kódra az a Java fordító és JVM amit éppen használok a következőt adja ki:

2
00001008 access$1
00001008 access$2
00001008 access$3
00000002 x
--------------------------
00000111 void wait
00000011 void wait
00000011 void wait
00000001 boolean equals
00000001 String toString
00000101 int hashCode
00000111 Class getClass
00000111 void notify
00000111 void notifyAll
--------------------------
00000002 synthetic.SyntheticMethodTest2$A
00001000 synthetic.SyntheticMethodTest2$A

A programban értéket adunk az x mezőnek, és meghívjuk az ugyanilyen nevű metódust is. Ezek ahhoz kellenek, hogy a fordító legenerálja a szintetikus metódusokat. Látszik, hogy három metódus keletkezett, sejthetően az x mező getter-e és setter-e, valamint egy szintetikus metódus az x() metódushoz. Ezek azonban nem látszanak a következő listában, hiszen ezek csak szintetikus metódusok, kívülről nem hívhatók meg, olyanok, mint a privát metódusok.

A hexa számok a java.lang.reflect.Modifier osztályban definiált konstansok segítségével fejthetők meg:

00001008 SYNTHETIC|STATIC
00000002 PRIVATE
00000111 NATIVE|FINAL|PUBLIC
00000011 FINAL|PUBLIC
00000001 PUBLIC
00001000 SYNTHETIC

Konstruktorból is kettő van: van egy privát és van egy szintetikus. Privát azért van, mert definiáltunk egyet, tehát annak lennie kell. Viszont van egy szintetikus is, mert meghívjuk ezt a privát konstruktort a külső osztályból. Hidunk eddig még nincs.

Genrikusok, öröklődés

Ez eddig egyszerű, és nagyszerű volt, de nem jutottunk el a “volatile” metódusokig.

A java.lang.reflec.Modifier forráskódjába belenézve láthatjuk, hogy a 0x00000040 konstans kétféleképpen is definiálva van, egyszer VOLATILE egyszer pedig BRIDGE néven (ez utóbbi nem publikus, csak package private).

Ahhoz, hogy ilyen metódust generáljon nekünk a java fordító egy nagyon egyszerű program is elég:

package synthetic;

import java.lang.reflect.Method;
import java.util.LinkedList;

public class SyntheticMethodTest3 {

    public static class MyLink extends LinkedList<String> {
        @Override
        public String get(int i) {
            return "";
        }
    }

    public static void main(String[] args) {

        for (Method m : MyLink.class.getDeclaredMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
        }
    }
}

Mit látunk? Azt, hogy van egy láncolt listánk, aminek a get(int) metódusa String-eket ad vissza. Hogy ez mennyire szép és tiszta kód, most ne menjünk bele. A példa erőltetett, azért, hogy egyszerű legyen. A való életben, szép kódban is előjön, csak sokkal bonyolultabban. A kimenet szerint

00000001 String get
00001041 Object get

két get() metódus is lett. Az egyik, amelyik szerepel a forráskódban, a másik pedig szintetikus és híd metódus. (És mind a kettő publikus.) A javap azt mondja, hogy a kód

public java.lang.String get(int);
  Code:
   Stack=1, Locals=2, Args_size=2
   0:   ldc     #2; //String
   2:   areturn
  LineNumberTable:
   line 12: 0


public java.lang.Object get(int);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   iload_1
   2:   invokevirtual   #3; //Method get:(I)Ljava/lang/String;
   5:   areturn

A két metódus szignatúrája megegyezik, csak a visszatérési érték más, és a híd metódus nem tesz mást, mint meghívja a deklaráltat.

De kinek kell ez a szintetikus metódus? Például annak a kódnak, amelyik meg akarja hívni a get metódust, de a változó, amin keresztül meg akarja hívni nem MyLink, csak…

        List<?> a = new MyLink();
        Object z = a.get(0);

Ez nem tudná meghívni a String-et visszaadó metódust, hiszen olyan a List-ben nincs. Talán még tisztább lesz, ha nem a get() hanem az add() metódust írjuk felül:

package synthetic;

import java.util.LinkedList;
import java.util.List;

public class SyntheticMethodTest4 {

    public static class MyLink extends LinkedList<String> {
        @Override
        public boolean add(String s) {
            return true;
        }
    }

    public static void main(String[] args) {
        List a = new MyLink();
        a.add("");
        a.add(13);
    }
}

Ha megnézzük, hogy milyen bridge metódus keletkezik, akkor láthatjuk:

public boolean add(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   checkcast       #2; //class java/lang/String
   5:   invokevirtual   #3; //Method add:(Ljava/lang/String;)Z
   8:   ireturn

hogy nem csak meghívja az eredeti metódust, hanem futási időben el is végzi azt a típus ellenőrzést, amit a generikusok kiirtása miatt a run-time magától nem tud megtenni. Amint az várható a 18. sorban el is dobja kivételt.

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at synthetic.SyntheticMethodTest4$MyLink.add(SyntheticMethodTest4.java:1)
	at synthetic.SyntheticMethodTest4.main(SyntheticMethodTest4.java:18)

Ezek után senki sem fog meglepődni, ha egy interjún azt a kérdést kapja, hogy mit jelent, ha egy metódus volatile.

Gradle, ant és maven

Amikor először, talán két éve, kipróbáltam a gradle építési eszközt (build tool) azt találtam, hogy a groovy használata miatt lassú, és nem igazán jött át, hogy mi olyan nagy truváj benne. Az volt a gondolatom, hogy megpróbáltak egy ANT Maven keveréket létrehozni, amelyik amolyan “best of both word” tool akar lenni.

Én soha nem dolgoztam addig ANT-tal, és ezt az állapotomat (ant szűz, vagy szűz hangya?) azóta is sikerült valahogy megőrizni. Ezért aztán nem is nagyon ellenkeztem a maven “convention over configuration” megközelítése ellen. Nincs is azzal semmi gond, és a legnagyobb ellenkezés általában abból fakad, hogy a programozó, ha egyszer megszokta, hogy Java-ban programoz, ami imperatív nyelv, akkor a build tool-nak is szeretni megmondani, hogy az mit csináljon. Különben is, hogy jön egy maven ahhoz, hogy kitalálja, hogy tegyem az src/main/java könyvtárba a java forrást. Mi a fenének ez a mély hierarchia? Nincs is más forrásom, csak java, azt a néhány resource fájlt is be tudom oda pakolni, és mi az a main egyáltalán? Úgyse írunk unit tesztet, az csak az amatőröknek való, a profik hibátlan kódot írnak. (Ha ismersz, tudod, hogy mikor tréfálok, ha nem, akkor meg használd az agyad, és ne higgyél el mindent amit a felnőttek mondanak. Apu nem azért fekszik anyun, hogy helyet spóroljanak.)

No, de akár tetszik, akár nem, el kell ismerni, hogy a maven nem söpörte ki az ant-ot, és a hangyák az elmúlt pár évben nem jutottak a dinoszauruszok sorsára, és nem haltak ki. Szépen megpróbálták felvenni a versenyt a maven vitathatatlan erősségeivel (pl. dependency management és itt az ant oldaláról az ivy-ra gondolok), és ment tovább a bevált, és elfogadott hagyományos command driven build-elés.

Akkor jött a gradle, és úgy tűnt, hogy csak annyit mond, hogy ne erőltessük a fejlesztőkre a konvenciókat, ha annyira nem akarják. Persze az okosabbja akarja, azokat is szolgáljuk ki, de legyen parancs orientált is. És ha már programozók vagyunk, akkor ne kelljen XML fájlokkal borzolnunk a torkunkat, jó lesz valami programozási nyelv is, például a zseniális groovy (ugye mindenki tudja, hogy mikor viccelek). És lassú volt, mert egy groovy-t beindítani összemérhető egy dízel ZIL (nem Jason) tehergépkocsi beindításával -20C fokban a laktanyában (1985. február: meg lehet nézni a meteorológiai archívumokat, az a hideg felolvadhatatlanul belém égett).

Így aztán kipróbáltam, és ment a többi, “erre még figyelni kell a jövőben” technológia közé.

Aztán eltelt két év, és most újra elővettem, és egy kicsit másképp látom a dolgokat. Ez a cucc nem arról szól, hogy elegünk van a convention over configuration-ből, és jobb keverni a command driven-t meg a (nem írom le mégyegyszer!) a convention over configuration-t (copy paste volt). Hanem egyszerűen arról, hogy a a convention over configuration-t csak ott lehet használni, ahol már kialakult az általánosan elfogadott iparági best practice. Például a java build-elésnél. Persze lehet ágálni, és soha nem lesz 100% aki az src/main/java-ba rakja a forrásokat, de a maven hatására a legtöbb ez. Az se problámás, hogy az eredmény a target-be kerüljön.

Viszont egy projekt dokumentációs site publikálása már nem olyan egyértelmű. Mehet scp-vel egy web site-ra, pakolhatjuk ftp-vel, vagy git-et használhatunk, github-ot. Vannak hozzá plugin-ok maven-hez, de az már pilótavizsgás. Mondjuk aki Java programozó az eleve pilóta vizsgás, és ha mint Java programozó nézem, akkor egy maven plugin megírása nem olyan nagy kaland. De amikor programozok, akkor nem az eszközeimet akarom fejleszteni, hanem az alkalmazást. És ezek a plugin-ek jók ugyan, de amelyiket használom, az pont azt nem tudja, ami nekem kellene, vagy van benne valami kis bug, amire lehet valami kikerülést csinálni, vagy együt télni vele, de mégsem vagyok boldog.

És a gradle pont azt mondja, hogy keverjük a kettőt. De nem azért, mert nem szeretem a convention over configuration-t (hahh, még mindig benne volt a tészta tárolóban), hanem azért, mert convention-t csak ott lehet használni, ahol van már valami megegyezés. Ellentétben a politikai nagy gyűlésekkel (angolul convention), amelyeket meg éppen azért tartanak, hogy legyen. Ahol nincs, mert még nem forrtak ki annyira az eszközök (SVN, GIT, egyéb infrastrukturális elemek, és akkor melyik, és hogyan), ott jobb ha nem pluginokat kell írogatni, hanem a build leíró nyelvén le lehet írni (groovy-ban), hogy akkor ezt hogyan is képzeltük.

Ennek persze meg van a maga átka. Ha valamit könnyű elkurjantani, azt sokan el fogják. Ha valamihez maven plugint kellett írni, azért aki odáig eljutott, az már átgondolta. ANT scriptet, meg mindenki tud írni. És groovy-t is.

És közben megvilágosodott az is, hogy nem azért nem volt előbb a maven, és nem azért lett az ant olyan, amilyen, mert Jason van Zyl nem világosodott meg hamarabb (illetve azért), hanem mert nem voltak kiforrva azok a szokások, amik lehetővé tették volna.

Hogy mi lesz a jövő? Ki fog halni végre az ant? A maven? Szerintem nem. Bár az ant szerepe egyre kisebb lesz, de az ivy repo-k kikerülhetetlenek. A gradle támogatja is őket, meg az ant taskokat is a groovy-n keresztül. A maven repo is alap, és a maven java build is benne van a gradle-ben. De azt gondolom, hogy a maven szerepe is vissza fog szorulni, és lesz sok kisze kusza gradle projekt, amelyik össze vissza fogja konfigurálni a build-ek nem sztenderd folyamatait (a rosszak meg a sztenderd-et is). Lassan majd kialakulnak azok a sztenderdek, amik a dokumentációról szólnak, site buildelésról, release menedzsmentről, source code versioning-ről stb.

És akkor majd jön egy új tool, ami azt mondja, hogy a gradle rossz és legyen csak convention over configuration megint az addigra kitaposott utak mentén.

Vagy nem.

Turing kegyelmet kapott

Alan Turing kegyelmet kapott. A részletekről, hogy ki volt Turing (aki ezt a blogot olvassa valószínűleg tudja) és, hogy miért ítélték el itt lehet bővebbet
olvasni. Itt most a véleményem következik ebben a témában:

Kegyelmet kapott Turing. Ez jó? Nem. Ez nem jó, csak éppen nem rossz.

Egyrészről nem kapta meg azt az elismerést, sok más munkatársával együtt, akik a Bletchley parkban dolgoztak. Ez azonban nem tragédia.

Másrészről viszont nem kegyelmet kellene posztumusz kapnia, hanem ki kellene mondania az angol igazságszolgáltatásnak, hogy emberiesség ellenes volt a homoszexuálisok kémiai kasztrálása. Arra hivatkozni, hogy az akkori törvényeknek megfelelően jártak el elég álságos, és rossz szájízt hagy maga után Nürnberg óta.

Turing nem azért kell, vagy kellett volna annak idején, hogy kegyelmet kapjon, mert egy hős volt, tehát megbocsátható volt a homoszexualitása. A hősöknek sem lehetnek ilyen “előjogai”: nem törhetnek be, ölhetnek, rabolhatnak jogszerűen büntetlenül. Tehát ha bűnös volt, nem lenne szabad kegyelmet kapnia. A homoszexualitás viszont mai felfogásunk szerint nem is bűn, nem is kellett volna, hogy elítéljék.

Nem jó tehát, hogy Turing kegyelmet kapott. A jó az lenne, ha azt mondanák ki, hogy annak ellenére, hogy akkor a homoszexualitás bűncselekmény volt, a kémiai kasztrálás embertelen eljárás, és minden ilyen ítéletet visszamenőleg hatályon kívül kellene helyezni.

Ez nem csak jó, hanem fontos is lenne. Nem Turing és a többi hasonló módon elítélt számára: nekik már mindegy, még ha néhányan élnek is. A mi számunkra, akik szeretnénk szabadon élni, és megtartani saját kezünkben azokat a döntéseket, amelyekhez semmi köze nem lehet sem államnak, sem más hatalmi szervnek. Még Angliában sem.

A mi számunkra akik olyan világban akarunk élni, ahol a körülvevő hatalom szembe tud nézni a realitásokkal. Első lépésként azzal, hogy 60 éve a hatalom, hogy mi magunk: hibáztunk. Ahol nem csak a vesztesek torkán lehet lenyomni (pl. Nürnberg) – amúgy mai erkölcsi értékrendünk szerint teljesen helyénvalóan -, hogy bár a törvények szerint cselekedtek, mégis bűnösök voltak, hanem ahol ezt az uralkodó hatalom maga magáról is ki tudja mondani.

Ezt követően pedig, amikor szembenézünk saját múltunkkal nem Turingnak, hanem az igazságszolgáltatásnak kellene kegyelmet kapnia. Bűnösök vagyunk, de megbocsátunk, és tanulunk.

Tudom, hogy ami most történt az egy lépés ebbe az irányba, és ez nem rossz.

Tervezési minta: belső építő megváltoztathatatlan objektumokhoz

A múlt héten a Javax0 blogon jelentettem meg egy kis agymenést arról, hogy jók-e egyáltalán a tervezési minták. Ezen a héten pedig ezt cikket írtam, aminek ez kb. a (félre)fordítása.

Ez a cikk egy egyszerű (vagy nem is olyan egyszerű) tervezési mintát ír le, amelyikkel módosíthatatlan (immutable) objektumokat lehet létrehozni belső építővel.

Az építő minta az, amikor egy osztály egy másik objektumot épít fel. Az eredeti szándék, hogy elválasszuk az objektum létrehozási fázisát, és a használatát. A létrehozás egyszerű esetben csak egy new Object(), de általában ennél jóval összetettebb folyamat. Sokszor az építő az építés során dönti el, hogy milyen objektumot hozzon létre, így a folyamat végén a felépített objektum akár különböző osztályok példánya is lehet. Ez jó példája az aggódások szétválasztásának (separation of concerns).

A megváltoztathatatlan objektumok, mint a nevük is mutatja nem változtathatóak miután felépítettük őket.

Az építők, és a megváltoztathatatlan objektumok a legnagyobb természetességgel illenek össze.

Mivel az építők és az épített osztályok nagyon erősen kötődnek egymáshoz, ezért általában egy csomagba szoktuk őket rakni. De miért vannak külön osztályban? Természetesen külön osztályoknak kell lenniük, különben nem válna szét az építő és az épített. De miért nem lehet az építő osztály az épített osztályon belül? Az építő az építési folyamat során gyűjti az információt arról, hogy milyen objektumot kell építenie, és amikor minden információ megvan, akkor ezeket arra használja, hogy elkészítse az épített objektumot. Ez a “használat” általában azt jelenti, hogy az értékeket bemásolja az épített objektumba. Ha az építő az épített osztály belső osztálya, akkor az információt nem kell bemásolnia, hanem rögtön az épített objektum mezőváltozóiban tárolhatja azokat az építés során. Ha ezek a mező privátak az sem baj, hiszen az építő belül van. Az építő létrehoz egy félkész objektumot, amit nem ad át senkinek, amíg el nem készül, és amikor a build() metódust meghívjuk (általában ez szokott a neve lenni, de nevezhetnénk Jakab-nak is) akkor csak a végső simításokat kell elvégeznie.

Ezt a mintát követi például a Google Guava projektje is a megváltoztathatatlan gyűjteményeknél (immutable collections). Az építők statikus belső osztályok. Ha megnézed a kódot a ImmutableList oldalon, láthatod, hogy a Builder egy belső osztály az absztrakt osztályon belül.

De nem ez az egyetlen lehetőség. Mi lenne ha megfordítanánk az egészet, és nem az építőt raknánk az épített osztályba, hanem az épített osztályt raknánk bele az építőbe? (8. utas a halál, megvan?) Csak az építő az egyetlen, amelyiknek hozzá kell férnie az épített osztály módosító metódusaihoz, mezőihez, kezelőorvosához, gyógyszerészéhez. Ha van olyan felületünk (interfész), amelyik definiálja a lekérdező metódusokat akkor annak elégnek kell lennie mindenki más számára. És ha már eljutottunk idáig, és van egy lekérdező interfészkünk, egy építőnk, és abban az interfész implementációja, az épített objektum osztálya, akkor miért nem csinálunk egy teljes matrjoschka baba sorozatot?

Legyen interfész. Legyen az építő ebben az interfészben egy belső osztály (publikus és statikus, mert ugye más nem is lehet). Legyen az interfész implementációja az építőn belül egy privát statikus belső osztály. És lőn:

public interface Knight {
    boolean saysNi();

    class Builder {
        private Implementation implementation = new Implementation();

        public Builder setState(String say) {
            implementation.say = say;
            return this;
        }

        public Implementation build() {
            Implementation knight = implementation;
            implementation = null;
            return knight;
        }

        private static class Implementation implements Knight {
            private String say;

            public boolean saysNi() {
                return say.indexOf("ni") != -1;
            }
        }
    }
}

Lefordul? De le ám! Ami talán meglepő lehet, az az, hogy az implementation.say = say működik, hiszen a say privát változó a belső osztályon belül, de ha elolvassuk a Java szabvány 6.6.1 fejezetét, akkor világossá válik a dolog (JLS1.7, section 6.6.1 Determining Accessibility). A privát változókat a top level osztályon (interfészen) belül egyenértékűen lehet látni. Mindegy, hogy azon belül alosztály, alosztályának az alosztályában vagyunk-e. Ebben nincs a Java-ban hierarchia, ha egy fájlon belül vannak (persze lehet egy fájlban több top level osztály, de ha valaki még egyszer ezzel veszi el másfél napomat, azt …) akkor látják egymást.

Az implementációhoz nem fér hozzá más, csak az építő (eltekintve a reflection, és egyéb byte kód abuzálástól, amitől most a karácsonyra való tekintettel eltekintek), és amint egyszer elkészült, többet már nem tud rajta változtatni. Az implementáció megváltoztathatatlan és garantáltan megtartja az állapotát.

Na akkor ez most pattern, vagy antipattern? Van annyira bonyolult, hogy átlagprogramozó elbaltázza, vagy szuper jó, és biztonságos (maraton)?

Boldog karácsonyt, és jövőre folytatjuk.

Szabadság

Ez nem lesz túl technikai. Egyrészt nyár van (ezt se ma írtam), és ezért azon a néhány napsütéses napon, amikor a véletlen úgy hozza, hogy éppen szombat vagy vasárnap van, inkább kimegyek a szabadba. Persze lesznek technikai cikkek is, mert ami késik az nem múlik. Szokták mondani, hogy ami késik az az órám, de ez itt Svájcban annyira nem jellemző. Itt Schaffhausen nem óra, és Winterthur nem nyugdíjbiztosító: mind a kettő város. Persze tudtam, de mégis furcsa volt látni. És furcsák egy kicsit az emberek is: mi nagyobb szabadsághoz szoktunk. Persze kérdés az, hogy mi a szabadság. Amikor a szabályok pontosan meghatározzák, hogy mit szabad, és mit nem, olyankor kisebb a szabadságunk. Amikor nincsenek szabályok, akkor nagy a szabadság. Itt nincs olyan nagy szabadság, itt majdnem mindent szabályoznak. A nagy különbség, hogy ezt a szabályozást magukénak érzik az emberek, nem érzik úgy, hogy valami külső hatalom erőltette rájuk.

Ha perl-ben programozunk, nagy a szabadság. Nagyokat is lehet bukni vele. Ha Java-ban programozunk, sokkal kisebb a szabadság. Rosszul éljük meg? Általában nem (persze van, aki igen, de ő nem is programoz Java-ban). Mi választottuk ezt a nyelvet, és élvezzük a szabályokkal járó előnyöket. Mert a szabályok nem csak arra jók, hogy betartsuk, hanem arra is, hogy mások is betartják, és ez az igazi előny. Persze az lenne a legjobb, ha a szabályokat csak másoknak kellene betartaniuk, és nekem nem, de ez nem megy. És nem csak azért, mert nem vagyunk egyedül a társadalomban (szerencsére), hanem azért sem, mert ha nem tartom be a szabályokat, akkor nem csak másokkal tolok ki, akik csak nehezen tudják majd a kódomat karbantartani, hanem magammal is, amikor fél év múlva elém kerül. Ezért kell fegyelmezettnek lenni, és betartani a sebességkorlátozást, mert nem csak a kisgyerekek halhatnak meg az úton az autód alatt, hanem te is fának mehetsz, akár egyedül is.

A szabályok egy része adott. Rétegekben, mint a hagyma, vagy mint az ogre. Minél lejjebb haladunk, annál több a szabadságunk abban, hogy mennyi szabályt alkotunk és tartunk be. Például elég kevés hatásunk van arra, hogy hat-e ránk a gravitáció. Ez általános fizikai szabály, amin a mindenható esetleg tud változtatni, és érdekes lehet azon filozofálni, hogy elképzelhető-e olyan univerzum, amelyben mások a fizikai törvények, és mégis “konzisztens” az egész, de nem tudom, hogy a szellemi felfrissülésen kívül van-e értelme. Egyébként elárulom: a válasz igen. Elképzelhető. Mert az ember sok mindent el tud képzelni. De csak ezért.

Aztán vannak olyan szabályok, mint az, hogy ha elégetjük a föld szénkészletét, akkor pont olyan lesz a légkör, mint amilyen akkor volt, mielőtt a széndioxidot elkezdték baktériumok felzabálni, és megmérgezték magukat a zoxigénnel. Ezen elvileg tudna az emberiség változtatni, de nem fog, és mivel nincs statisztikai sokaság, hogy azt mondhassuk, hogy van olyan emberiség, amelyik megteszi, van amelyik nem, ezért értelmetlen azt mondani, hogy megtehetnénk, de nem tesszük meg. Mert nincs különbség aközött, hogy megtehetnénk, meg aközött, hogy nem tehetjük meg, mert ilyen a természetünk. Mint a skorpiónak. Ezen a folyón nem jutunk keresztül.

És mehetünk tovább: vannak törvények, regionális, országos szinten, amit magunk hozunk magunknak, jó esetben. És vannak még alacsonyabb szintű szabályok, amiket kis közösségben hozunk, és végül hozunk szabályokat magunknak. Például nem veszek bérletet, és inkább rossz időben is küzdök a kerékpárral, mert ha vennék, akkor jó időben is vonatra szállnék. Ezért, ismerve magam: nincs bérlet. Persze ebben a döntésben sokat segít a bérlet ára is, ami a budapesti négyszerese, miközben a fizetés meg nem. (Azért nem kell sajnálni.) Kellemetlenek a szabályok? Amikor felfelé tekerek, és ki akar ugrani a tüdőm, meg a szívem a helyéről, akkor igen. Amikor meg kell írni a unit tesztet, meg a dokumentációt, akkor igen. De van értelme. Egy idő után már nem kell annyira küzdeni, és egyre jobban megy a unit teszt, meg a dokumentáció készítés is. És érdekes módon a unit teszt készítése visszahat a kód minőségére, amíg észrevétlenül eljutunk arra a szintre, hogy előbb írjuk a unit tesztet, mint a kódot. És nem kell küzdeni, amikor lehajolok bekötni a cipőmet, és nem kell azon gondolkoznom, hogy a harmadikra lifttel menjek-e vagy sem: nincs lelkiismeret furdalásom, mert mozgok eleget a napi 16km kerékpározással (mire gondoltál, hogy gyalog megyek fel by default?). Persze nem gond, ha gyalog kell felmenni 🙂

Mennyi szabály kell? Minél fegyelmezettebbek, és minél belátóbbak (okosabbak) az emberek a közösségben annál kevesebb kell. Csupa kiemelkedő programozóból álló csapatban, ahol nem gond, hogy a kiemelkedő képességű embereknek kiemelkedő fizetést kell általában adni ragyogó alkalmazásokat lehet készíteni JavaScript-ben, PHP-ban, Perl-ben, és után a jönnek a nagy megmondások, hogy lám lám. De nem mindenki zseni, és még kevesebb a fegyelmezett zseni. Mostani környezetemben minden kód átmegy a review-n és bizony szembe kell néznem néha magammal: több fegyelem kell(ene). Aztán persze a review fenyegetése megteremti a több fegyelmet, mert ha meg is engednék magamnak némi pongyolaságot, tudom, hogy úgyse megy át a review-n, nem kerül bele a master-be, a master halad tovább, és egyre nagyobbat fogok szívni, mire a kódot szebbre, jobbra kijavítva újra kérhetem a review-t egy git pull origin master után. Bizony így megy ez 20 év fejlesztői tapasztalattal is. Ifjú kollégáim rámpirítanak, nincsenek tekintettel a tapasztalatomra, és szólnak ha valami olyat alkotok, amit nem kellene, nem illene. Pedig a múltkor igen öregnek éreztem magam:

Arról volt szó, hogy a reguláris kifejezések a Java-ban a Perl nyelvből jönnek-e. És akkor elmondtam, hogy igen is, meg nem is, mert volt előzmény: awk, meg sed és hasonlók, és akkor ifjú kollégám megkérdezte, hogy melyik könyvben olvastam ezekről. Szóval neki ez már történelem, amiről csak könyvben lehet olvasni. Én meg ezeket használtam.

Hát így… Lehet tovább gondolkodni. Azt szeretem, ha sok a komment.

javax0 a top 100-ban a programcreek szerint

Ez itt a reklám helye.

A javax0 blogomat (ami kb. a tifity-nek felel meg, csak angolul) a Programcreek az első 100 Java blog közé sorolta, nem tudom milyen sorrend szerint, de olyan impozáns társaságba kerültem, mint Martin Fowler. A blog cikk itt érhető el:

100 High-Quality Java Developers’ Blogs

semver

Szoftvereket készítünk, és minden szoftverből készítünk különböző verziókat. A legegyszerűbb verziókezelést egy kis magyar KFT-nél láttam, diszkrécióm és szenilitásom megakadályozza, hogy megmondjam a nevét. Itt a verziózás abból állt, hogy volt egy aktuális forráskód állapot, az ami a fejlesztő notebook-ján volt, és abból build-eltek, és az a verzió volt. Ez a megoldás sok sebből vérzik, talán magyaráznom sem kell.

Aztán vannak más verzió kezelési módszerek és sémák is, amiket legjobban talán a Wikipédia szoftver verziókról szóló oldala foglal össze. Nekem a TeX és a METAFONT programok verzió számozása tetszik a legjobban. A TeX harmadik verziója után a 3.1-est adták ki, aztán a 3.14-at, majd 3.141-et és így tovább. Most valahol a 3.1415626 környékén járnak, és Donald Knuth szerint halála után az utolsó update a verzió beállítása pi-re lesz az utolsó verzió. A METAFONT struktúrája ugyanez, de ott a határérték e.

Ez a verzió számozás azonban a legtöbb esetben nem praktikus. A TeX és a METAFONT programok életciklusa nem nevezhető igazán szokásosnak. Sokkal célra vezetőbb a valamilyen a.b.c.d.e.f… alakú verziózás. Persze ebben sem szabad túl messzire menni. A tapasztalat azt mutatja, hogy két verzió szám gyakran kevés, négy meg általában már felesleges, így megragadunk a a.b.c alaknál. Az egyes verziószámok jelentése pedig major verzió, minor verzió valamint patch, vagy bug-fix verzió szám. Ezek persze csak elnevezések, a pontos jelentés az, amit a fejlesztő akar, de javasolt, hogy ebben is legyen valami egységesség, hiszen nem attól lesz valaki jobb, vagy rosszabb fejlesztő, hogy valami szuper új verziózási sémát talál ki. Nem lehetetlen, de elég kicsi a valószínűsége. A mostanában elfogadott szokás, vagy ha úgy tetszik szabvány a szemantikus verziózás, semantic versioning.

Formailag a leírás olyan, mint egy szabvány, és részletezettségében, precizitásában is. Ezt a verziózást és a verziószámok ilyetén használatát nem a felhasználói programok számára javasolja. Ott sokkal fontosabb a marketing, és olyan verzió “számok” működnek, mint Vista, Maverick vagy Blue Death. A szemantikus verziózás a library-k esetében fényleg magasan, és ott látszik az igazi nagy haszna. A library-k esetében a felhasználó programok szemantikus verziózás esetén támaszkodhatnak a szabványban leírtak. Mindjáűrt meglátjuk, hogy hogyan is.

A szabvány azt mondja, hogy ha egy library verziója M.m.p ami a Major, minor és patch (a Major azért van nagybetűvel írva, mert a jelentése nagy, és mivel pont mint a minor ‘m’-mel kezdődik, nehéz lenne tudni, hogy m, mint major vagy m, mint minor), akkor minden olyan változás, ami a kompatibilitást visszafelé sérti, az a Major verzió növekedését kell, hogy maga után vonja. Ha tehát van egy alkalmazásod, a melyik a log4j 1.1.3 verzióját használja, immár 12 éve, akkor ne várd, hogy csak a könyvtárat kicserélve a loj4j 2.0-beta9-re minden működni fog. (Amúgy ez a verziózás nem is felel meg a semver-nek, mert 2.0.0-beta9 lenne a helyes forma.)

Ha csak olyan módosítás történt a programban, amelyik visszafelé kompatibilis, azaz az újabb verzió valószínűleg használható a programodban a régi helyett, de fordítva nem biztos. Vagyis valószínűleg áttérhetsz a log4j 1.2.11-es verziójára, de az 1.0.4-re visszafelé nem biztos, mert lehet, hogy használsz olyan funkciót, api-t, ami abban még nem volt meg. Viszont az 1.2.11-esnek minden olyan funkciót támogatnia kell ami az 1.1.3-ban benne volt, és pontosan ugyan úgy, ahogy az 1.1.3-ban volt, kivéve, ha az adott működés, amit kihasználsz egy bug eredménye. (Na ezért kell a dokumentáció, mert különben mi különbözteti meg a bug-ot a feature-től? Az, hogy a feature le van dokumentálva, hogy így lett kitalálva, a bug meg csak van.)

A patch verziót akkor növeljük ha semmi új nincs a funkcionalitásban, csupán hibát javítottunk.

A verziószámokat mindig növeljük, és ha M-et növeljük, akkor m.p 0.0 lesz, ha m-et növeltük akkor p lesz 0, M marad ami volt. A verziók sorrendje értelemszerűen balról jobbra történik, tehát 1.2.3 korábbi verzió, mint 2.1.1, ami korábbi, mint 2.2.0 ami viszont későbbi, mint 2.1.56413

A növelés mértéke általában 1, de ha egy release készítés elromlik valamiért, de valami azzal a, mondjuk 1.5.3 verziószámmal mégis kikerül, akkor semmi baj: kiadunk egy új verziót, ami az 1.5.4 lesz és dokumentáljuk, hogy az 1.5.2 után az 1.5.4 jön, az 1.5.3 pedig mintha nem is lenne, még akkor is, ha ezzel a verzióval látszik is valami a nexus centrálban, vagy bárhol is. Ez egyébként a gyakorlatban éppen tegnap fordult elő velem, 22 perces build során kedves kolléga belekommittált a release branch-ba, így a release nem tudta visszaírni konfliktus okán az új development verziót és törött, mint a háromezer éves kínai váza a rossz filmekben.

Ezen kívül a szemantikus verziózás megengedi, hogy - után olyan pre-release verzió jelöléseket adjunk meg, mint alpha1, alpha2, beta9 és itt a - utáni részben is lehetnek pontok. A sorrendiség szempontjából ilyenkor is balról jobbra történik az összehasonlítás, ASCII sorrendben. Azaz 1.1.0-1 korábbi, mint 1.1.0-alpha. A verzió szám végére + után oda lehet tenni build információkat is, de két verzió nem szabad, hogy csak ebben különbözzön, és két verzió időbeli sorrendjében sem vesz részt.

Milyen verziót használjunk fejlesztés közben?

A maven hagyományok szerint a fejlesztés során a.b.c-SNAPSHOT alakú verziót használunk, és release készítés során lesz ebből a.b.c. Vagy a.(b+1).0. Vagy (a+1).0.0. Mert lehet, hogy kiadtuk a 1.0.0 verziót, és amit szerkesztünk az épen a fejlesztő környezetben az 1.0.1-SNAPSHOT néven fut, de lehet, hogy rögtön a 2.0.0-t fogjuk kiadni. De az is lehet, hogy az 1.1.0-t. Vagy az 1.0.1-et. Más nem nagyon lehet a szemantikus verziózás szabályai szerint. Vagy felkészítjük a release folyamatot arra, hogy ezt kezelje, vagy két lépésre vágjuk a dolgot, és az a.b.c készítése előtt a forráscsomagban átírjuk a verziót a.b.c-SNAPSHOT-ra, ha nem az lett volna. Az első megoldás összetett release folyamat, például három nagyon hasonló TeamCity projekt, amelyek csak abban térnek el, hogy milyen verzió számot generálnak a fejlesztői verzióból. A második verzióban viszont manuális verzió szerkesztgetés történik, ami hibaforrás, legalábbis peer review-t kíván (jobb helyeken, bocs).

Persze el tudom fogadni azt az érvelést is, hogy amikor fejlesztesz, akkor a release előtt valamivel már tudni fogod, hogy milyen verziót fogsz kiadni, és akkor illik átírni, hogy a release processznek csak a -SNAPSHOT részt kelljen levágnia. Valóban nem a release folyamán derül ki, hogy ami a kódban van az csak hibajavítás, visszafelé kompatibilis új lehetősége, vagy teljesen új szoftver verzió.

És van, amikor a szemantikus verziókezelés sem segít.

Egy projektben egy régebbi verzióban hiba van. Mondjuk a verzió 7.2.0. Nosza gyorsan, ügyfél AAA Bt. kér egy hibajavítást, és lesz 7.2.1. De közben kiderül még egy hiba is, egy másik, BBB Kft. ügyélnél és így azt is ki kell javítani a 7.2.0-ban. No akkor most mi lesz? Lesz egy 7.2.1a meg egy 7.2.1b ? Mert az ‘a’ hiba a BBB Bt-nél nem okoz gondot. Nem csak nem jött elő, de ők olyan módon használják a szoftvert, hogy az ‘a’ hiba abszolúte semmi gondot nem okozhat. A javítása, viszont potenciálisan hibaforrás, ezért ők nem akarnak egy 7.2.1-re épülő 7.2.2 verziót használni. Ők csak arra a hibára akarnak egy javítást, amelyik a ‘b’ hibát javítja a 7.2.0 verzióban. Hasonlóan van ezzel az AAA Bt. is.

Itt véget ér a szemantikus verzió határa, mert ha kiadjuk a 7.2.1-et és kiadunk egy 7.2.2-t, akkor implicite azt kellene jelentse, hogy ami hiba kijavult a 7.2.1-ben az a 7.2.2-ben sincs benne. Ilyenkor jöhetnek a szemantikus verzió által megengedett mínusz és valami, mint a 7.2.1-AAA.bt meg a 7.2.1-BBB.kft verziók, amik… hát nem szépek, de semmi sem tökéletes, nincs bölcse köve, ami mindent megoldana.

Ha az informatikában mindenre lenne egyszerű megoldás nem lenne szükség informatikusokra.