Man lever så länge man lär

Jag har egentligen inte i mitt tidigare liv stött på konceptet tillståndsmaskin när det handlar om programmering, även om jag har ett svagt minne av att begreppet dök upp i något av de tekniska ämnen jag läste på gymnasiet. Det var inte alls särskilt länge sedan ämnet dök upp i en programmeringsteknisk diskussion med sambon. Han, som har både mer formell utbildning och bättre teoretiska kunskaper än jag, förklarade principen och jag blev entusiastisk och lanserade konceptet på jobbet och fick positiv respons och sedan har det rullat på. Jag gillar tillståndsmaskiner!

Jag tror inte att jag är världsbäst på att förklara det hela, men kan ju ändå försöka ge min bild. Konceptet innebär att saker hela tiden befinner sig i ett visst tillstånd, och går vidare till andra tillstånd enligt ett förbestämt mönster eller schema – inte nödvändigtvis likadant från gång till gång. Ett eller flera tillstånd kan t ex upprepas eller måste inte finnas alls. Alla sådana saker specificeras av mönstret/schemat.

Förresten, det är ju inte så att det alltid finns en formell beskrivning heller – ibland sitter det hela bara i huvudet, man förstår själva processen. Det kan vara så enkelt som att man köper en sak: går in i affären, väljer vara (eller varor), betalar, får ett kvitto, går ut ur affären. Eller så låter man bli att betala och får därför inget kvitto. Eller kanske man går runt i butiken varv efter varv innan man till slut känner sig redo att passera kassan. Och så vidare. Processen är flexibel och kan varieras.

En fördel med att använda tillståndsmaskiner i programmeringen är att man slipper hålla hela bilden i huvudet. Det räcker med att se på den närmaste omgivningen och förstå var man man kommer ifrån, och vad man därför ska göra eller förvänta sig härnäst. Typ så ser jag på det. En tillståndsmaskin gör jobbet mycket enklare. Och ja, för att det påståendet ska vara sant måste det självfallet handla om ett problem som är lämpligt att lösa med en tillståndsmaskin, men det säger sig självt.

Utan tillståndsmaskinen kommer man antagligen att skriva kod som är kladdig och svårläst och därmed blir knepig både att följa, underhålla och vidareutveckla. Och kladdig och svårbegriplig kod tycker jag är bland det värsta en programmerare kan skriva. Det är att göra alla, både sig själv och framtida förvaltare av koden, en mycket stor otjänst. Om koden är kladdig från början blir den inte bättre när man ändrar i den. Inte ens om man själv är den som skrivit den från början. Tro mig.

Här skulle jag lätt kunna dra ut på sidospår för att prata programmeringsmetodik i största allmänhet (Code Complete-läsandet sätter sina spår och väcker sovande björnar), men nu tror jag ändå att det var historien om tillståndsmaskinen jag hade tänkt berätta så jag låter bli det. Kanske någon annan gång. Till programmeringen!

Låt säga att vi ska jobba oss igenom någon form av datastruktur, som är/behöver vara ordnad efter ett visst schema, och behöver titta på eller hantera varje bit data på olika sätt beroende på var i det här schemat vi (d.v.s. vi som lever i koden) befinner oss. Då tuggar vi oss igenom strukturen bit för bit och gör det som behövs för var och en av dem. För att hålla reda på var i schemat vi befinner oss kan vi t ex använda states, eller tillstånd om man nu ska prata svenska. Fine. Det finns ju i Java (och även andra språk) en datatyp för uppräkning och den kan vi använda.

Vi har en variabel som representerar det aktuella tillståndet med hjälp av ett värde ur en enumeration (uppräkning, här: lista över möjliga tillstånd), och så gör vi saker och sätter om värdet på variabeln vartefter vi tar oss fram i schemat/mönstret. Och på slutet kollar vi om det slutliga tillståndet stämmer med vad vi förväntat oss. Allt är fint, men koden känns inte riktigt bra och framförallt när man hanterar många olika tillstånd blir det väldigt oöverskådligt. Man tycker att det borde kunna finnas bättre sätt att lösa problemet. Och det gör det förstås. Det gör det nästan alltid.

Jag och sambon pratade vidare om tillståndsmaskiner och han planterade för några dagar sedan en tanke om att i stället representera tillstånden med klasser, som då skulle ha en hanteringsmetod med den logik som annars ligger i case-satserna. Det lät roligt, om än lite svårt att föreställa sig först, så jag blev sugen på att åtminstone testa. Sent igår gjorde jag sålunda ett provskott och kunde då konstatera att det var snabbt och lätt att skapa tillståndsklasser och peta in hanteringsmetoderna i dem.

Därefter åkte jag hem och fortsatte där med att skapa fiktiv exempelkod, helt tagen ur luften, och visa för sambon hur hans lösning förvanskats och implementerats av min hjärna och nu tänkte jag att jag kunde visa lösningen även för mitt framtida jag. Och naturligtvis även för er som läser detta inlägg och eventuellt är intresserade.

Så här ser det ut när jag representerar mina tillstånd med hjälp av enumerationer:

/* metoden som gör det övergripande jobbet */ {
	State currentState = Begin;

	while (we.haveMoreThings()) {
		switch (currentState) {
		case Begin:
			currentState = we.handleBegin(thing);
			break;
		case StateA:
			currentState = we.handleStateA(thing);
			break;
		case StateB:
			currentState = we.handleStateB(thing);
			break;
		case StateC:
			currentState = we.handleStateC(thing);
			break;
		case End:
			currentState = we.handleEnd(thing);
			break;
		default:
			throw new IllegalStateException("Hey, what!?");
		}
	}

	if (!currentState.isTheEnd()) {
		throw new IllegalStateException("We are not done yet!");
	}
}

private State handleBegin(Object thing) {
	// Här gör vi massor av spännande hantering...
	// ... och sedan returnerar vi nytt tillstånd:
	return StateA;
}
// ... här blir det alltså en metod per hanterat tillstånd.

Och så här ser det ut när jag i stället representerar mina tillstånd med klasser:

/* metoden som gör det övergripande jobbet */ {
	State currentState = Begin;

	while (we.haveMoreThings()) {
		currentState = currentState.handle(thing);
	}

	if (!currentState.isTheEnd()) {
		throw new IllegalStateException("We are not done yet!");
	}
}

Det vill säga: tillståndet sköter hanteringen. Jag vet vilken version jag föredrar.

Så, kan man kanske undra, hur ser det då ut bakom kulisserna?

I det första fallet lade jag (av flera olika skäl) mina tillstånd i egen, separat fil:

	enum State {
		Begin, StateA, StateB, StateC, End;
	
		public boolean isTheEnd() {
			return this == End;
		}
	}

… och i det andra fallet hamnar handle-metoderna i stället i ett State-interface, där jag även lagt tillståndsklasserna (som varken behövs eller bör synas utifrån). Det är en bra idé att stoppa undan ovidkommande information för att undvika förvirring.


interface State {

	public State handle(Object thing);
	public boolean isTheEnd();

	public static final State Begin = new Begin();
	public static final State StateA = new StateA();
	public static final State StateB = new StateB();
	public static final State StateC = new StateC();
	public static final State End = new End();
	
	public class Begin implements State {

		@Override
		public State handle(Object thing) {
			// Här gör vi massor av spännande hantering...
			// ... och sedan returnerar vi nytt tillstånd:
			return StateA;
		}

		@Override
		public boolean isTheEnd() {
			return false;
		}

		private Begin() { /* no outside construction, please */ }
	}
	// ... här blir det alltså en klass per hanterat tillstånd.
}

Hade detta varit i den riktiga världen hade jag valt att lägga tillståndsklasserna i egna, separata filer och gjort dem ”package private”, men i exempelkoden ville jag hålla strukturen likadan utifrån sett för att göra jämförelsen dem emellan enklare.

Nåväl, så här ser det ut just nu. Exemplet ovan gör inga anspråk på att vara vare sig korrekt eller komplett eller ens i närheten av realistiskt, men jag tycker att det fyller en funktion och hoppas att någon av mina läsare får glädje eller nytta av det.

Själv kommer jag garanterat att fundera vidare och skruva och fixa och dona och fiffa och mojja och vad man nu gör med kod. Kod, och ens tänkande kring den, evolverar ständigt. Ibland går det snabbt och ibland går det långsamt, men jag finner det i praktiken omöjligt att stå stilla. Det går alltid att göra koden bättre!

Kommentarer, funderingar, förslag?

Kommentarer till: "Programmering: Tillstånd – från en stor maskin till flera mindre klasser" (9)

  1. Har du eller Daniel läst ”Refactoring to Patterns” av Joshua Kerievsky? Det verkar nästan så, för det du beskriver är hans refaktorisering ”Replace State-Altering Conditionals with State”.

    Kolla den här länken: http://www2.fiit.stuba.sk/~polasek/courses/ooans-sk/files/OOANSKerievskyRefactoringToPattern.pdf så ser du den på sidan 13. I min värld är det här en av en trilogi böcker man bör läsa som objekt orienterad programmerare: Dels GoFs ”Patterns in object oriented programming”, Martin Fowlers ”Refactoring” och sen den här.

    • @Leif: Jag kan bara svara för mig själv och nej, jag har inte läst den boken. Ännu. Men jag köpte den [tydligen] i december 2010 och hade glömt att jag hade den, tills nu när jag sökte i mailboxen för att kolla. Den står dock, av andra skäl, på min ”att läsa”-lista (Goodreads) – dessvärre tillsammans med (just nu – antalet ökar nästan varje dag) 54 andra it-relaterade böcker. So many books, so little time…

      De två första du nämner har jag dock läst och håller med dig om att de bör läsas om man jobbar med OO-programmering. Dock måste jag samtidigt flagga för att det krävs en viss ”mognad” och intresse för att böckerna ska komma till nytta… har konstaterat att alla som jobbar med programmering inte nödvändigtvis har det intresset, tyvärr. Och skrämmande många verkar också negativa till att läsa böcker – och jag förstår inte det. Alls. För mig är böcker den primära källan till kunskap. Jag läser hellre bok än går kurs, t.ex.

      • Den behöver inte läsas pärm till pärm, men jag rekommenderar att slå upp den och läsa delen om ”code smells” och kika på mappningen code smell -> refaktorisering. Jag använder den framförallt som referenslitteratur.

        Själv föredrar jag konferenser, böcker tvåa, pluralsight trea och kurser fyra….

        Jo, försöker förklara för många att det finns bara två lägen: Utveckling eller avveckling… deprimerande att se hur vissa slösar bort sin begåvning.

  2. @Leif: Tihi, jag läser mina böcker från pärm till pärm, jag – är det värt att göra, är det värt att göra ordentligt. :) Sedan gör det ju inget att jag via min arbetsgivare har fri tillgång till Safari Books Online – där finns ganska många, om än inte alla, av böckerna jag vill läsa. E-bok går ju bra att läsa i telefon eller på platta, när som helst och i princip var som helst. Lite jobbigt med vissa kodexempel, dock, när inte den lilla 5-tumsskärmen räcker till.

    Konferenser… näe, då måste man träffa folk – och sedan förväntas man efteråt hålla dragningar för sina kollegor om vad som hänt och vad man upplevt. Inte lockande.

    Pluralsight känner jag inte till, så det får jag väl läsa på lite om.

    • Att titta på ett verktyg för att veta när det skall användas, mja, det tycker jag man kan göra. Jag använde uppslagsverk förut, utan att för den skull läsa dom pärm till pärm.

      Pluralsight är undervisningsfilmer, lite långsamma men spelaren har inbyggd uppsnabbning som gör dom riktigt trevliga. Utan att låta som smurfar… :)

      • @Leig: Att man kan göra det – absolut. Jag vill dock själv läsa böckerna från pärm till pärm för att ha en aning om vad de innehåller, få en grundläggande förståelse för ämnet. Sen, vid ett senare tillfälle, kan jag gå tillbaka och fräscha upp minnet vad gäller detaljer. Men (oftast) vill jag läsa från pärm till pärm. Gäller inte uppslagsböcker, märk väl! :)

  3. @Leif: Och jo, jag ska kolla i boken så snart jag kommer hem! Code smells som begrepp känner jag igen, men hade för mig att det var i någon annan bok. Eller så har jag faktiskt läst Refactoring to Patterns, utan att lägga det på minnet.

    • Jag undrar om inte Pragmatic Programmer också tar upp begreppet…. Men Kerievsky ger väldigt handfasta exempel på hur man löser dom.

      • @Leif: Jag kikade som hastigast i boken vid hemkomst och… well, jag blev inte sugen på att läsa den. Alls. Det är något med färg och form som tar emot – tror att det kan bero på att någon annan bok jag läst från samma ”serie” var urtrist.

        Men det finns hopp! Jag kollade precis nu hos Safari Books och kunde konstatera att Refactoring to Patterns finns där, så då kan jag i stället läsa den i e-form och slippa själva utseendet från pappersboken. :)

Kommentera

Fyll i dina uppgifter nedan eller klicka på en ikon för att logga in:

WordPress.com Logo

Du kommenterar med ditt WordPress.com-konto. Logga ut / Ändra )

Twitter-bild

Du kommenterar med ditt Twitter-konto. Logga ut / Ändra )

Facebook-foto

Du kommenterar med ditt Facebook-konto. Logga ut / Ändra )

Google+ photo

Du kommenterar med ditt Google+-konto. Logga ut / Ändra )

Ansluter till %s

Etikettmoln

%d bloggare gillar detta: