tifyty

pure Java, what else ?

Havi archívumok: november 2012

Többszörös öröklődés implementálása Java-ban

Van-e többszörös öröklődés Java-ban? Van. Interfész öröklődés van, de implementáció öröklődés nincs. Ha valami olyasmit szeretnénk csinálni, mint a többszörös öröklődés, akkor

  • Az okosok szerint valamit elrontottunk a kód tervezése során, és ebben valóban lehet valami mert mióta Java-zom nem sokszor éreztem a hiányát, de azért néha mégis, és erre meg azt mondják a szintén okos emberek, hogy
  • leszármazás helyett használjunk kompozíciót!

Oké, ha nincs ló, a szamár is jó (mondják a birkapásztorok), akkor csináljunk kompozíciót. Használjunk delegátor pattern-t! Ennél mi sem egyszerűbb:

package com.verhas.multiple.inheritance.examples;

class Example1 {
	public class Super1 {
		public void s1() {
		}
	}

	public class Super2 {
		public void s2() {
		}
	}

	public class Child extends Super1 {
		private Super2 super2 = new Super2();

		public void s2() {
			super2.s2();
		}
	}
}

Készen vagyunk? Ennyire egyszerű lenne? Valószínűleg nem, hiszen ez nem érne meg egy blogbejegyzést. Több gond is van. Például az, hogy a Child az leszármazás szerint nem terjeszti ki a Super2 osztályt, ezért nem is használható Super2 s = new Child(); hozzárendelésben. Nem fog lefordulni. Azt persze ne várjuk, hogy két meglevő osztályból varázsolok egy olyant, amelyik mind a kettőnek leszármazottja. Ezt a Java nem tudja. De interfészekkel sok minden megoldható.

package com.verhas.multiple.inheritance.examples;

class Example2 {
	public interface Supi1 {
		void s1();
	}

	public class Super1 implements Supi1 {
		public void s1() {
		}
	}

	public interface Supi2 {
		void s2();
	}

	public class Super2 implements Supi2 {
		public void s2() {
		}
	}

	public class Child extends Super1 implements Supi2 {
		private Super2 super2 = new Super2();

		public void s2() {
			super2.s2();
		}
	}
}

Bár a Super2 s = new Child(); még mindig hibás, de a Supi2 s = new Child(); már nem! Hurrá. Ezzel máris egy kicsit beljebb vagyunk, de még mindig nem vagyunk készen. Lehet ezen a dolgon tovább reszelni. Mi van akkor, ha a két osztály, amiből örökölni akarok absztakt? Van benne néhány olyan metódus, amit nem definiál, csak deklarál, és a nem absztrakt metódusokban használ. Ezeket a metódusokat a leszármazott osztályban kell definiálni. De mit tegyünk két, vagy több osztályból való leszármazás esetén?

Az egyik ősosztálytól tudunk örökölni, de a másikat ezzel ki is zártuk az öröklődésből. De ez nem baj. Ahogy az előbb megoldottuk a dolgot delegációval, most is meg tudjuk ezt tenni:

package com.verhas.multiple.inheritance.examples;

class Example3 {
	public interface Supi1 {
		void a1();

		void s1();
	}

	public abstract class Super1 implements Supi1 {
		abstract public void a1();

		public void s1() {
			a1();
		}
	}

	public interface Supi2 {
		void a2();

		void s2();
	}

	public abstract class Super2 implements Supi2 {
		abstract public void a2();

		public void s2() {
			a2();
		}
	}

	public class Super22 extends Super2 {
		private Supi2 supi;

		public Super22(Supi2 supi) {
			this.supi = supi;
		}

		public void a2() {
			supi.a2();
		}
	}

	public class Child extends Super1 implements Supi2 {
		private Super2 super2 = new Super22(this);

		public void a2() {

		}

		public void a1() {
		}

		public void s2() {
			super2.s2();
		}
	}
}

Hát, ez már nem olyan egyszerű, de azért nem kell megijedni. Most már két-két metódusunk van az interfészekben. Az egyiket implementálja az ősosztály, a másik absztrakt. Amelyik ősosztályt kiterjesztjük, ott minden rendeben, nincs szükség semmilyen trükkre. A másik osztályt viszont a delegációhoz példányosítani kell, és amennyiben az absztract ez nem megy. Ezért készítettünk belőle egy nem absztrakt Super22 leszármaztatott osztályt. Ez az osztály úgy implementálja az absztrakt a2() metódust, hogy delegálja azt egy valamilyen másik osztálynak, amelyik implementálja a Supi2 interfészt. A leszármazott osztályunkban pedig ezt, a Super22 osztályt úgy hozzuk létre, hogy a konstruktornak a delegáláshoz szükséges osztály paraméterben átadjuk a this-t. Így a delegálás megtörténik oda-vissza.

Nézzük meg akkor most általánosan, hogy mit is csináltunk. (Itt egy olyan rész jön, aminek a forrása ez a cikk. Ami itt következik, az nem fordítás, csak annak a cikknek egy fejezetét olvasva világosodtam meg a mixineket illetően.) Mixin-t gyártottunk. Mi az a mixin?

A mixin egy olyan osztály, amelyiknek a metódusait bele lehet keverni (érted: mix in!) egy leszármazott osztályba. Ahhoz, hogy egy osztály bekeverhető legyen, szükséges, hogy minden olyan metódus rendelkezésre álljon a leszármazott osztályban, amelyet a mixin használ, szükséges a működéséhez, de maga nem definiál, és máshonnan sem szerez meg. Ezek tipikusan a Java absztrakt metódusok. A mixin biztosít metódusokat (angolul provide), és megköveteli (angolul require) más metódusok rendelkezésre állását.

Ennek megfelelően egy mixin leprogramozásához két interfészt írhatunk: interface MProvides és interface MRequires. Az MProvides interfészt a mixin implementálja, az MRequires-t használja, tehát ahhoz szükséges egy olyan változó, amelyik olyan osztály példányára mutat, amelyik implementálja azt.

	public interface MRequires {
		void required();
	}

	public interface MProvides {
		void provided();
	}

	public class Mixin implements MProvides {
		private final MRequires parent;
		public Mixin(MRequires parent){
			this.parent = parent;
		}
		public void provided(){
			parent.required();
		}
	}

Ezzel egy kicsit kezd tisztulni a kép, alakul a Java mixin pattern. Ha van egy olyan osztályunk, amelyikbe szeretnénk ezt a Mixin-t beleilleszteni:

	public class Parent {
		public void required() {
		}
	}

akkor a következő leszármazott osztályt kell készítenünk:

	class Child extends Parent implements MRequires, MProvides {
		public Child() {
			super();
			this.mixin = new Mixin(this);
		}

		public void provided() {
			mixin.provided();
		}

		private final MProvides mixin;
	}

Egy kicsit sok az interfész, meg a mindenféle osztályok, de ezt már megszoktuk EJB2.0 idejében. Azért lehet egyszerűsíteni a dolgot. Kicsit. Az alap mixin pattern az amit itt leírtam.

Egy kis kitérő

Te Java programozó vagy, esetleg mazochista. Más változatot nem tudok elképzelni, ha idáig elolvastad. Legyen az a munkahipotézisünk, hogy Java programozó vagy. A mostani munkahelyemen, ha jelentkezel, simán előfordulhat, hogy én foglak interjúztatni. Ha most, ebben a pillanatban egy interjún azt a kérdést tenném fel neked, hogy lehet-e egy Java interfészben végrehajtható kód, akkor arra mit válaszolnál? Őszintén, csak magadnak.

A legjobb válasz eddig az volt, hogy “azt mondanám, hogy nem, de a helyes válasz az, hogy igen, mert különben miért kérdeznéd”. Azt azonban be kell vallanom, hogy a kérdést nem interjún tettem fel, és nem is fogom, mert ez egy szívatós kérdés. Azt meg interjún kerülni kell.

Kitérő vége

Ugyanezt a mixin implementációt átírhatjuk úgy is, hogy a mixin osztályt, mint egy interfész inner class-át implementáljuk. (Hááááá!!! Ezért volt a kitérő!!!! Őszintén? (Nem! Hazudj a szemembe!) Innen jött az egész cikk témája.)

package com.verhas.multiple.inheritance.examples;

public class Mixin2 {
	public interface MRequires {
		void required();
	}

	public interface MProvides {
		void provided();
	}

	public interface MixinInterface extends MRequires, MProvides {
		class Mixin implements MixinInterface {
			private final MRequires parent;

			public Mixin(MRequires parent) {
				this.parent = parent;
			}

			public void provided() {
				required();
			}

			public void required() {
				parent.required();
			}
		}

	}

	public class Parent {
		public void required() {
		}
	}

	class Child extends Parent implements MRequires, MProvides {
		public Child() {
			super();
			this.mixin = new MixinInterface.Mixin(this);
		}

		public void provided() {
			mixin.provided();
		}

		private final MProvides mixin;
	}
}

Persze itt még didaktikai okok miatt megtartottam az MRequires és MProvides interfészeket, de nem feltétlenül kellenek. Lehet egyszerűsíteni a kódot, ha tudjuk, hogy mit is csinálunk.

package com.verhas.multiple.inheritance.examples;

public class Mixin3 {

	public interface MixinInterface {
		void required();

		void provided();

		public class Mixin implements MixinInterface {
			private final MixinInterface parent;

			public Mixin(MixinInterface parent) {
				this.parent = parent;
			}

			public void provided() {
				required();
			}

			public void required() {
				parent.required();
			}
		}
	}

	public class Parent {
		public void required() {
		}
	}

	class Child extends Parent implements MixinInterface {
		public Child() {
			super();
			this.mixin = new MixinInterface.Mixin(this);
		}

		public void provided() {
			mixin.provided();
		}

		private final MixinInterface mixin;
	}
}

Csak az a fránya delegálás, és a sok delegáló metódus… Azokat le kell generáltatni. Eclipse, NetBeans le tudja generálni, de akkor ott van benne a kódban, keveredik a kézzel írt kód és a generált, ez pedig növeli a karbantartás költségeit. Vagy esetleg lehet lombok-ot használni:

package com.verhas.multiple.inheritance.examples;

import lombok.AllArgsConstructor;
import lombok.Delegate;

public class Mixin4 {
	interface MixInto {
		void required();
	};

	public interface MixinInterface extends MixInto {
		void provided();

		@AllArgsConstructor
		public abstract class Mixin implements MixinInterface {
			@Delegate(types = MixInto.class)
			private final MixInto parent;

			public void provided() {
				required();
			}
		}
	}

	public class Parent {
		public void required() {
		}
	}

	@AllArgsConstructor
	public class Child extends Parent implements MixinInterface {
		@Delegate(types = MixinInterface.class, excludes = MixInto.class)
		private final MixinInterface mixin;

		@Override
		public void required() {
		}
	}
}

Tiszta, egyszerű, és világos pattern! Vagy nem? Delombok után persze egy kicsit hosszabb, de hogy lássuk, hogy pontosan mit is csinálnak “forrás szinten” ezek az annotációk:

// Generated by delombok at Mon Nov 19 11:38:19 CET 2012
package com.verhas.multiple.inheritance.examples;

public class Mixin5 {
	
	
	interface MixInto {
		
		void required();
	}
	
	public interface MixinInterface extends MixInto {
		
		void provided();
		
		abstract class Mixin implements MixinInterface {
			private final MixInto parent;
			
			public void provided() {
				required();
			}
			
			@java.beans.ConstructorProperties({"parent"})
			@java.lang.SuppressWarnings("all")
			public Mixin(final MixInto parent) {
				
				this.parent = parent;
			}
			
			@java.lang.SuppressWarnings("all")
			public void required() {
				this.parent.required();
			}
		}
	}
	
	public class Parent {
		
		
		public void required() {
		}
	}
	
	public class Child extends Parent implements MixinInterface {
		private final MixinInterface mixin;
		
		@Override
		public void required() {
		}
		
		@java.beans.ConstructorProperties({"mixin"})
		@java.lang.SuppressWarnings("all")
		public Child(final MixinInterface mixin) {
			
			this.mixin = mixin;
		}
		
		@java.lang.SuppressWarnings("all")
		public void provided() {
			this.mixin.provided();
		}
	}
}

Mindenki döntse el maga, hogy mennyire szereti ezt a pattern-t.

Most, hogy írom ezt a cikket, elolvastam a megformázott változatot, a színes kód blokkokat, és annak ellenére, hogy én írtam… Egyre jobban elmegy a kedvem a többszörös öröklődéstől Java-ban. Nem kell. Ha kell, akkor inkább másképp fogalmazom meg a feladatot, vagy a megoldást.

Mondjuk Scala-ban?

Péterrel összes…

Péterrel összesen talán ha néhány hónapot dolgoztam “együtt” — ugyanazon a projekten, ő fejlesztés-támogatóként, én üzemeltetőként —, az alapvető, IT-világról alkotott filózófiáink ennél nem is állhatnának távolabb egymástól — ő MacOS, Linux, Java, nyílt forráskód, én Windows, .NET, zárt forráskód —, de a mai napig rengeteget hasznosítok a vele folytatott beszélgetések tapasztalataiból.

Had büszkélkedjek. Csak tudnám, hogy ki ő!

rrockout.tumblr.com

Azért jól esik és köszönöm.

Kirügyeznek a lombok

Annotációt írni elég egyszerű. Utána azt futási időben használni reflection-nel lehet, és szintén nem tartozik az ördöngösség kategóriájába. Persze mindenkinek más a bonyolult: amihez értek az már nem is bonyolult. Ugye?

Érdekelt az is, hogy hogyan lehet az annotációkat fordítási időben feldolgozni. Ha már írtam teljes interpretereket, fordítót, akkor ezt is meg kell, hogy értsem. Meg is értettem, de be kell vallanom őszintén, hogy annyi időt nem tudtam rá szentelni, hogy ki is próbáljam. [2014-03-21 UPDATE: azóta két olyan OS projektet is készítettem, ami használja ezt az API-t. https://github.com/verhas/fluflu és https://github.com/verhas/scriapt ] Nem is akartam, nem érzem olyan fontosnak a mostani és a várható feladataimban. Ennek ellenére jó tudni, hogy mi, hogyan tudna működni, vagy mi, hogyan működik. Tudom ajánlani a témában a ezt a cikket.

Röviden arról van szó, hogy lehet olyan Java kódot írni, amit a javac fordítási időben, mikor a szintaxis fát már elkészítette meghív, és amelyik kód hozzá fér a felépített fához. Pontosabban olvasni tudja a felépített fát, és ebben az annotációkat és ennek alapján lehet generálni riportokat (pl. Sonar), újabb osztályokat stb. De írni nem “lehet” a szintaxis fát, nincs rá API, amelyik megengedné, hogy menet közben módosítsunk a kódon.

És akkor egyszer csak látom, hogy kirügyeznek a lombok project.

Mi a lombok projekt?

A lombok projekt olyan annotációkat kezel fordítási időben, amelyekkel nagyon sok mechanikusan leírható kódot meg lehet spórolni. Ilyenek például a delegátorok, a setterek, getterek, log változók inicializálása. Persze ezeket le lehet generáltatni a kódba, például Eclipse-szel, de ez rontja a kód olvashatóságát, és karbantarthatóságát. Aránylag egyszerű szabály: több karakter, több lehetséges bug. Személy szerint egyébként is fázom az olyan megoldásoktól, amikor egy fájlon belül van generált és manuális kód. Még azt sem szeretem, ha egy könyvtárban vannak generált, és manuálisan szerkesztett fájlok. Ebből a szempontból például a maven gyönyörűen elválasztja a kettőt: minden ami generált megy a target könyvtárba.

Persze az olvashatóság csak akkor jobb, ha tudjuk, hogy az annotációk mit jelentenek. A lombok által nyújtott kiegészítésekben ezt a megértési szintet elég hamar el lehet érni. Nézzünk egy egyszerű osztályt:

package com.verhas.lombokTest;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.java.Log;

@Log
public class Person {

	@Getter
	@Setter
	private String name;
	@Getter
	@Setter
	private int age;

	public boolean isRetired() {
		log.fine("age of ther person '" + name + "' is " + age);
		return age > 65;
	}

}

Azt gondolom, hogy nem kell magyarázni, hogy az annotációk mit jelentenek. A legszebb a dologban, hogy nem csak a parancssori java fordítóba lehet bekonfiguálni, de egyszerűen egy dependency taggal a maven projekt fájlba is be lehet venni, és Eclipse-be is könnyen bekonfigurálható. Így már a kód hegesztése közben is működnek a piros, sárga aláhúzások, és a kód completion is. Maga az install csak annyiból áll, hogy letöltöd a lombok.jar-t, és kiadsz egy

$ java -jar lombok.jar

parancsot. Elindul a program, feldob egy Swinges ablakot, kiválasztod, hogy melyik Eclipse installációk alá tegye fel magát (nekem volt épp kettő is, sima meg egy STS) és be is rakja magát. Ha pedig megkérdezed ezen a felületen, hogy mit is fog csinálni, akkor röviden és érthetően elmondja, hogy nem setup.exe registry varázslat ami folyik (nekem Mac-en érdekes is lenne), hanem melyik fájlt hova másolja, melyik fájlba mit ír bele. Ha pedig van némi ismereted a Javac compiler hook-okról, akkor nem is tűnik érthetetlen boszorkánykodásnak.

Ha a fenti kódot lombok nélkül akarjuk, akkor a következőt kell írnunk:

package com.verhas.noLombok;

import java.util.logging.Logger;


public class Person {

	private static Logger log = Logger.getLogger(Person.class.getName());
	private String name;
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public boolean isRetired() {
		log.fine("age of ther person '" + name + "' is " + age);
		return age > 65;
	}

}

Szerintem átláthatóbb, és rövidebb a kód az annotációval. Azzal az állításommal, hogy átláthatóbb lehet vitatkozni. Azt, hogy rövidebb okos embernek eszébe sem jut vitatni.

De van ennél sokkal durvább dolog is, amit tud a lombok. Nézzük meg például ezt a kis kódot:

package com.verhas.lombokTest;

import lombok.experimental.ExtensionMethod;

import org.junit.Test;

@ExtensionMethod(Extensions.class)
public class ExtensionTest {
	@Test
	public void testExtensions() {
		String s = null;
		System.out.println(s.or("hello"));
	}
}

Mit fog kiírni? Virít róla a Null Pointer Exception (közkeletű nevén NPE, ami egyesek szerint a no problem exception rövidítése). És nem! A lombokkal, ha az Extension osztály a következő:

package com.verhas.lombokTest;

public class Extensions {
	public static <T> T or(T obj, T ifNull) {
		return obj != null ? obj : ifNull;
	}
}

A fenti programunk a hello stringet fogja kiírni? Hogy mi van?

Csak az van, hogy a lombok feldolgozza az Extensions osztályt és látja, hogy van benne egy olyan statikus metódus, amelyik alkalmazható String-re is, és lám pont akarunk is alkalmazni egy ilyen metódust egy String típusú változóra. Nosza, fogja a lombok, és lecseréli egy statikus hívásra, aminek az első paramétere maga a string. Hogy a String osztály final? Nem is írja át, nem is származtat le belőle, csak úgy néz ki. Figyelem! Ez egy fontos mondat: Nem az, csak úgy néz ki. Hogy jön ez össze a hápogással, a duck type irányvonallal?

Szóval itt van a megváltás. Vagy nem? Nem kell áttérni Scala-ra, hogy

final HashMap<String,Integer> x = new HashMap<String,Integer>();

helyett

val x = new HashMap<String,Integer>();

írhassunk. Ezt is tudja a lombok.

Mi a bajom a lombokkal?

Csak az, hogy nem szabványos. Csak az, hogy tönkreteheti a Java nyelvet. Kikerült a szellem a palackból, és mint az élet minden területén, kiereszteni könnyebb a szellemet, mint utána visszazárni. Persze lehet szeretni az ifriteket. Ízlés kérdése. Eddig azt lehetett mondani, hogy a Java nyelvben minden az, aminek látszik. Nem lehetett nyelvi elemeket létrehozni olyan szabadon, mint pl. groovy-ban, de a duck type elv működött. Mostantól, ha elszabadul a pokol…

Nézzük először azt, hogy nem szabványos.

Olyan API-t használ… Bocsánat. Éppen ez az, hogy nem API. Olyan metódushívásokat használ, amelyek nem API-k, nem garantáltak. A fordítási folyamat során visszanyúl a kódba és módosítja a fordító által legenerált, és a lombok számára csak olvasásra felkínált absztrakt szintakszis fát (abstract syntax tree = AST). A nyulat szeretjük, a visszanyúl viszont dögöljön meg. De él és virul és működik. Meddig?

A lombok nem futási idejű függőség, a generált csomagba (pl. WAR) nem kell, hogy a lombok.jar bekerüljön. Mivel a JVM és a byte kód szabvány, és azt végül a java compiler generálja a módosított AST alapján, az futni fog minden szabványos JVM-en. Emiatt nem kell aggódnunk. A lombok legyen ott a fejlesztő gépén (kivéve Dénest, aki ma is vi-t használ Java fejlesztésre, bár lehet, hogy már áttért emacs-ra) és legyen ott a build szerveren. Ugyan a lombok nem definiált hívásokat használ, de működik.

És működni fog egy java security update után is? Nem fog megváltozni az a hívási struktúra abban a fordítóban? Erre nincs garancia. És melyik fordítóval fog működni? Egy másik, teljesen szabványos java fordítóval? A stackoverflow oldalon gyönyörűen le van írva (az egyik lombok lead developer írta idén (2011) májusban):


A lombok belső API-t használ […]. Ugyanakkor, mivel a lombok csak a fordítási időben van jelen, ezért félrevezető az az állítás, hogy csak a sun VM-en futnak a programok. Csak az ecj vagy a sun javac fordítóval fordul. Ugyanakkor az elérhető VM-ek döntő többsége, ha egyáltalán szállítanak saját fordítót, ezen kettő közül valamelyiket használja. Például az Apple VM a szabvány javac fordítóval jön ki és így a lombok ragyogóan működik mac-en. Ugyanez igaz a soylatte VM-re.

Míg javac-cal kapcsolatban szorosan követnünk kell a frissítéseket, mert nagyon sok munka folyik ott most is, addig csupán egy apró módosítást kellett tennünk az Eclipse sok verziója alatt. Így, bár belső API-t használunk ezek relatíve elég stabilak.

Mi lesz akkor, ha alaposan átszabják a javac belső API-t? Mi lesz, ha a lombok projekt nem követi, mert mondjuk elfogy a pénzük? Hány dollár/euró a projektünk üzleti értéke, és mekkora a lombok? Rábíznánk magunkat egy 1 dolláros Ltd supportjára és arra, hogy a projektünk addig él, és a kódunk addig karbantartható, ameddig az él? (Nem tudom maga a lombok projekt mennyire stabil, ezek elvi kérdések.)

Persze vannak menekülő utak. A projekt része a delombok, amelyik legenerálja a lombok nélküli forráskódot, és onnan kezdve megy a projekt tovább lombok nélkül. Szóval feltételesen fogadjuk el, hogy használhatjuk a lombok-ot, nagy rizikót nem jelent a nem támogatott api használata, de hát legyen.

A másik dolog a szellem kiengedése a palackból. Ami a lombokkal elkezdődik az átalakítja a Java-t. Ez a nyelv már nem Java. Ez a nyelv már olyan nyelvi elemeket tartalmazhat, amit bármelyik fejlesztő maga kitalálhat. Nincs egy szigorú szabványosítási folyamat. Pár év múlva eléd tolnak egy kódot, amit a java fordító lefordít, hogy javítsd ki, hiszen Java programozó vagy, és fogalmad sem lesz, hogy mit csinál a kód, és hogyan kellene kijavítani, mert a fordítás során az AST-be belenyúlkál James Plaster félszenior, félisten Java fejlesztő által kitalált überfasza lombok klón, és onnan kezdve minden másképp volt. Az int lehet Integer, a Long viszont lehet long, és még az sem biztos, hogy egy foo.callMe() NPE-t dob ha foo történetesen null.

Majd lehet magyarázni, hogy Java fejlesztő vagy, de lombok vagy “gyokerek”, vagy “torzsek” kiegészítést nem ismered. Már a keretrendszereket sem lehet mindet ismerni, de ami ez után jön… Én nem akarok Java kódot duplán debuggolni: egyszer megnézni, hogy milyen byte kód generálódott, és egyszer, hogy az miért nem működik.

Igazából már a groovy-val elkezdődött, hiszen a Groovy deklaráltan odaadja az AST-t, hogy nyúlj bele. De talán a közösség kidobja magából az elvetemült AST masszírozó kiterjesztéseket. Talán a Java közösség eléggé felnőtt, és a Java maga eléggé fegyelmezettségre nevel.

Izgalmas ez a szakma!

Megjegyzések:

Ismereteim szerint a legutolsó Apple VM az 1.6 volt. Az 1.7-et már az ORACLE állítja elő. Pont mint a Windows esetén, mégis mekkora sírás volt, hogy megszűnik a Java OSX-ra. No mindegy. A másik, hogy az AspectJ is hasonló dolgokat csinál, mint a lombok, DE NEM nyúl bele az AST-be, hanem csak a legenerált byte kódba. Namármost lehet, hogy ez sokkal gusztustalanabb, viszont a bytekód az szabványos. Ha bele lehetne (bocs: szabadna) nyúlni az AST-be, akkor az AspectJ-nek is abba kellene belenyúlnia, mert ott (lenne) a helye az ilyesminek.

Mindezek ellenére azért a jövő héten megnézzük, hogy hogyan lehet lombokkal mixin-t készíteni.

Ösvény vakság, újratöltve

Minden nap átmész egy virágos réten. Sietsz. Mindig ugyanazon a lejtős ösvényen mész. Már egészen kitaposta a lábad a füvet, de még mindig figyelned kell a kövekre. Néha, néha eldobsz vagy elgörgetsz egy követ az útról, hogy könnyebb legyen az ösvényen menni, de mindig csak az ösvényre figyelsz. Van egy nagy kő, amit hiába görgetsz arrébb, valahova mindig visszagurul az ösvényre, és mindig elbotlasz benne. Nem látod meg, hogy ki is kerülhetnéd a követ, mert az a rét, és te már csak az ösvényt látod.

Ilyen az, amikor debuggolunk. Magyarul hibát keresünk. Nézzük a kódot, és próbáljuk megérteni, hogy miért nem azt csinálja amit szeretnénk. Persze azért, mert azt csinálja, amit utasításokba adtunk. Nem találja ki a gondolatainkat (most még). És olvassuk a kódot újra és újra, és ahogyan elsiklottunk a hiba felett, amikor beleírtuk, ugyanúgy elsiklunk felette, amikor olvassuk a kódot. Mert az az egy agyunk van, amit a koponyánk rejt, és az ugyanolyan minták alapján processzál, mint félórával ezelőtt. Bambán nézzük a képernyőt, és nem, és nem

Azután odaül mellénk valaki. Mindegy, hogy ki. A kolléga persze szabadkozik, mert ugyan most van 10 perce, nem ez a gond, de másik projekten van, és nem fogja megérteni, hogy mi is történik, és ha én, aki írtam a kódot nem találom a hibát, akkor ő, aki nem is érti az egészet, hogyan fogja megtalálni, vagy segíteni nekem?

Nem is kell.

Leül mellém, és ezzel megváltozik a gondolkodásom. Ha olyan lenne a személyiségünk, akár egy plüssmackó is ülhetne mellettünk (kipróbáltam, nem megy). (Tényleg kipróbáltam. A macskával is, azzal, lehet, hogy menne, de nagyon unja, és elmegy.) Akkor már egy másik agy dolgozik, módosultan, és ahogy magyarázunk, azért, hogy a másik megértse, és rámutasson a hibára egyszer csak meglátjuk, hogy mi a hiba! Sikerült letérni az ösvényről.

Megköszönjük a kollégának, aki persze szabadkozik, hogy ő aztán nem csinált semmit (dehogynem!), és javítunk, tesztelünk, boldogok vagyunk.

Ezt a jelenséget szoktam ösvényvakságnak hívni.

Ösvény vakság

Minden nap átmész egy virágos réten. Sietsz. Mindig ugyanazon a lejtős ösvényen mész. Már egészen kitaposta a lábad a füvet, de még mindig figyelned kell a kövekre. Néha, néha eldobsz vagy elgörgetsz egy követ az útról, hogy könnyebb legyen az ösvényen menni, de mindig csak az ösvényre figyelsz. Van egy nagy kő, amit hiába görgetsz arrébb, valahova mindig visszagurul az ösvényre, és mindig elbotlasz benne. Nem látod meg, hogy ki is kerülhetnéd a követ, mert az a rét, és te már csak az ösvényt látod.

Miről szól ez a poszt? A helyes megfejtők között kisorsolunk két rekettyést.