Montag, 11. November 2013

Implementierung des Flow Design Lösungsansatzes



Aufbauend auf den vorigen Eintrag Flow Design:


Ich konnte es nicht lassen und habe es nun nicht nur gezeichnet sondern auch implementiert. Dazu habe ich TypeScript verwendet, da ich raufinden wollte, wie das damit funktioniert und ob das Ergebnis einfach und übersichtlich wird.
Diese Kombination TypeScript – Flow Design – EventBasedComponents scheint mir noch zu wenig bearbeitet zu sein. Dies ist auch der Grund, warum ich meine Erkenntnisse zusammenschreibe. Vielleicht findet sich jemand, der Verbesserungen sieht oder vielleicht hilft es zumindest jemanden, der sich auch damit beschäftigen will.

Da ich bereits vor ein paar Wochen ein TypeScript Beispiel mit Event Based Components versucht habe, begann ich mit diesem Ansatz. Dazu kann man sehr gut die node.js Event-Loop einsetzen. Im Anschluss daran habe ich das Beispiel kopiert und versucht die node:event wieder rauszunehmen, und so nur mit gewöhnlichen Callback Funktionen zu arbeiten.

Die beiden Lösungen findet man auf github OrderedJobs-FD-Kata-TS.
(entspricht dem 3. Lösungsansatz vom vorhergehenden Eintrag: Flow Design)


Ich bin eigentlich auf der Suche, wie man so ein FD Diagramm möglichst einfach nach JavaScript bekommt. Dazu will ich mir ein paar Methoden aus dem Sourcecode rausholen, um es in Zukunft vielleicht einfacher zu haben.


1. Lösung mit Callbacks:


1.1 Eine ganz einfache Transformation, mit nur einem Ein- und Ausgang:




    function generateOrderString(
             jobDefinitionList: jd.Domain.JobDefinition[],
             outCallback: (string) => void): void {

        var orderString = "";
        jobDefinitionList.sort(function (left, right) {
                return left.order === right.order ?
                0 : (left.order < right.order ? -1 : 1)
            });
        jobDefinitionList.forEach(function (jobDefinition) {
            orderString += jobDefinition.job;
        });
        outCallback(orderString);
    }


1.2 Zwei Ausgänge - auch kein Problem:


    function forAllJobDefinitions(
             jobDefinitionList: jd.Domain.JobDefinition[],
             outCallback: (IJobDefinition) => void,
              afterCallback: (any) => void): void {
        var self = this;
        jobDefinitionList.forEach(function (jobDefinition) {
            outCallback(jobDefinition);
        });
        afterCallback(jobDefinitionList);
    }





1.3 Mehrere Eingänge


Das klappt so nicht, dazu muss man dann schon eine Klasse schreiben.



    class CompareWithLastNumberAndMaxCount {
        private numberOfSortedJobs: number;
        private maxJobsCount: number;
        private _jobDefinitionList: jd.Domain.JobDefinition[];
        public initialize(jobDefinitionList: jd.Domain.JobDefinition[]): void {
            this.numberOfSortedJobs = 0;
            this.maxJobsCount = jobDefinitionList.length;
            this._jobDefinitionList = jobDefinitionList;
        }
 

        public process(
             jobsAreSortedCount: number,
             outAllDoneCallback: (any) => void,
             notAllDoneButIncreasedCallback: (any) => void): void {
            var LastNumberOfSortedJobs = this.numberOfSortedJobs;
            this.numberOfSortedJobs = jobsAreSortedCount;
             ...
         
        }
    }





1.4 "Verdrahten":



    function sort(outCallback: (string) => void): void {
        createJobDefinitionList(
            function (jobDefinitionList: jd.Domain.IJobDefinition[]) {
                sortJobDefinitions(jobDefinitionList, outCallback);
            });
        }



Bei mehreren Callbacks in einem Aufruf wird diese Schreibweise jedoch sehr verwirrend. Dazu ein komplexeres Beispiel:



    function forEachJobMarkOrderIfPossible(
             jobDefinitionList: jd.Domain.JobDefinition[],
             jobDefinitionListCallback: (any) => void): void {
        var setOrderAndIncrement = new SetOrderAndIncrement();
        var testIfAllPreJobsAreDone = new TestIfAllPreJobsAreDone();
        testIfAllPreJobsAreDone.initialize(jobDefinitionList);
        forAllJobDefinitions(jobDefinitionList, function (jobDefinition) {
            testIfJobIsSorted(jobDefinition, function (jobDefinition) {
                testIfAllPreJobsAreDone.process(jobDefinition,
                    function (jobDefinition) {
                        setOrderAndIncrement.process(jobDefinition,
                            function () { });
                    });
                })
            }, function (jobDefinitionList: jd.Domain.JobDefinition[]): void {
                jobDefinitionListCallback(jobDefinitionList);
            });
    }




Man sieht in diesem Beispiel auch, dass hier die "Kästchen" mit mehreren Eingängen, dann instanziiert werden müssen. Man könnte sie aber auch global instanziieren, allerdings hat man dann immer noch das "Problem", dass die Methode (in unserem Beispiel die Methode process) aufgerufen werden muss und man so wieder kein einheitliches Bild erhält.

       testIfJobIsSorted(jobDefinition, function () { });


       setOrderAndIncrement().process(jobDefinition, function () { });   




2. Lösung mit Event Based Components:



Wie oben schon erwähnt, wurde dazu die Node.js Event-Loop verwendet. Dazu braucht man eine Referenz auf node.

/// <reference path='../../../DefinitelyTyped/node/node.d.ts'/>


import events = require("events");


(Diese Referenzen werden angeblich mit Visual Studio Unterstützung selbst aufgelöst.)

Dieselben Beispiele wie oben, bei der Callback Variante:


2.1 Eine ganz einfache Transformation, mit nur einem Ein- und Ausgang:



    class GenerateOrderString extends events.EventEmitter {
        public process(jobDefinitionList: jd.Domain.JobDefinition[]): void {
            var orderString = "";
            jobDefinitionList.sort(function (left, right) {
                return left.order === right.order ?
                    0 : (left.order < right.order ? -1 : 1)
            });
            jobDefinitionList.forEach(function (jobDefinition) {
                orderString += jobDefinition.job;
            });
            this.emit('out', orderString);
        }
    }




2.2 Zwei Ausgänge:


    class ForAllJobDefinitions extends events.EventEmitter {
        public process(jobDefinitionList: jd.Domain.JobDefinition[]): void {
            var self = this;
            jobDefinitionList.forEach(function (jobDefinition) {
                self.emit('out', jobDefinition);
            });
            this.emit('after', jobDefinitionList);
        }
    }




2.3 Mehrere Eingänge


Das ist im Gegensatz zu oben kein Ausreißer, sondern sieht aus wie alle anderen Klassen. Man hat eben eine Methode mehr.


    class CompareWithLastNumberAndMaxCount extends events.EventEmitter {
        private numberOfSortedJobs: number;
        private maxJobsCount: number;
        private _jobDefinitionList: jd.Domain.JobDefinition[];
        public initialize(jobDefinitionList: jd.Domain.JobDefinition[]): void {
            this.numberOfSortedJobs = 0;
            this.maxJobsCount = jobDefinitionList.length;
            this._jobDefinitionList = jobDefinitionList;
        }
        public process(jobsAreSortedCount: number): void {
            var LastNumberOfSortedJobs = this.numberOfSortedJobs;
            this.numberOfSortedJobs = jobsAreSortedCount;
            if (jobsAreSortedCount === this.maxJobsCount) {
                this.emit('allDone', this._jobDefinitionList);
            } else {
             ...
            }
        }
    }




2.4 "Verdrahten"


Das sieht jetzt ganz anders aus wie in der Callback Variante und meiner Meinung nach viel übersichtlicher.


    export class Sort extends events.EventEmitter {
        constructor() {
            var createJobDefinitionList = new CreateJobDefinitionList();
            var sortJobDefinitions = new SortJobDefinitions();
            this._firstTask = createJobDefinitionList;
            createJobDefinitionList.on("jobDefinitionList", 
                 sortJobDefinitions.process.bind(sortJobDefinitions));
            sortJobDefinitions.on("out", this.result.bind(this));
            super();
        }

        private result(sortedJobs: string): void {
            this.emit('out', sortedJobs);
        }

        private _firstTask: ICreateJobDefinitionList;
        public process(): void {
            this._firstTask.process();
        }

    }



Eine Schwierigkeit dabei ist dieses JavaScript Problem mit dem this Pointer. Leider wurde es auch unter TypeScript nicht versteckt. (Zumindest bisher noch nicht - ist ja noch in Version 0.9.1.1, aber ich befürchte, dass es auch nicht gelöst wird)
Will man in der process Methode auch den richtigen this Pointer zugreifen, weil man die Methode _firstTask aufrufen will, dann muss man dies, bei der Definition des Events mitgeben!
Statt
            createJobDefinitionList.on("jobDefinitionList", 
                sortJobDefinitions.process);

also
            createJobDefinitionList.on("jobDefinitionList", 
                sortJobDefinitions.process.bind(sortJobDefinitions));


Das macht es schon sehr unschön. Vielleicht gibt es da eine einfache Lösung, die ich noch nicht gesehen habe.



Es ist in dieser Variante, der Schreibaufwand zwar größer, aber es ist immer gleich und auch bei komplexeren "Verdrahtungen" einfach zu verstehen:

 

    class ForEachJobMarkOrderIfPossible extends events.EventEmitter {

        constructor() {
            var forAllJobDefinitions = new ForAllJobDefinitions();
            var testIfJobIsSorted = new TestIfJobIsSorted();
            var testIfAllPreJobsAreDone = new TestIfAllPreJobsAreDone();
            var setOrderAndIncrement = new SetOrderAndIncrement();
            this._firstTask = forAllJobDefinitions;
            this._testIfAllPreJobsAreDone = testIfAllPreJobsAreDone;
            forAllJobDefinitions.on("out",
                testIfJobIsSorted.process.bind(testIfJobIsSorted));
            testIfJobIsSorted.on("notSorted",
                testIfAllPreJobsAreDone.process.bind(testIfAllPreJobsAreDone));
            testIfAllPreJobsAreDone.on("allPreJobsDone",
                setOrderAndIncrement.process.bind(setOrderAndIncrement));
            forAllJobDefinitions.on("after", this.result.bind(this));
            super();
        }

        private _firstTask: IForAllJobDefinitions;
        private _testIfAllPreJobsAreDone: ITestIfAllPreJobsAreDone;
        private result(jobDefinitionList: jd.Domain.JobDefinition[]): void {
            this.emit('jobDefinitionList', jobDefinitionList);
        }

        public process(jobDefinitionList:
            { job: string; preJobs: string[]; order: number }[]): void {
                this._testIfAllPreJobsAreDone.initialize(jobDefinitionList);
                this._firstTask.process(jobDefinitionList);
        }

    }


Für JavaScript Programmierer ist die erste Variante mit den Callbacks inzwischen sehr üblich, auch durch die asynchroner Programmierung (mit Promises z.B.).

Mir gefällt aber im Moment doch die EBC Variante besser.
Allerdings sollte man bedenken: Hätte ich von dieser Methodik noch nie gehört und würde mir den Code ansehen, ich würde ihn für sehr eigenartig halten.


Leider scheint sich TypeScript für die FlowDesign Implementierung nicht besser zu eignen, als C#.
Vielleicht bin ich aber auch noch nicht tief genug in TypeScript eingetaucht und es gibt noch Notationen, die praktischer sind.

Montag, 4. November 2013

Flow Design


Flow Design finde ich wirklich toll und bin überzeugt, dass man damit bessere Programme schreiben kann (wartbarer, verständlicher, testbarer). Immer wieder lese ich in Ralf Westphals Blogs (z.B. One Man Think Tank Gedanken) darüber und habe auch schon manchmal probiert das umzusetzen.

Doch es sind immer nur kleine Ausflüge. In der täglichen Arbeit mache ich es noch nicht. Da arbeite ich wild drauf los, mache funktionale Abhängigkeiten nach der Reihe und habe all die Probleme, die Flow Design lösen würde. Was habe ich so schon alles verbrochen?! :-)

Der Grund, warum ich nicht diesen FD Richtlinien folge, ist wohl, dass ich noch zu unsicher bin und es in der Umsetzung in C# oder Javascript auch ein wenig umständlich ist. Nein, das stimmt so nicht. Es ist eher ungewöhnlich für mich. Es ist mir einfach noch nicht in Fleisch und Blut übergegangen.

Ich brauche Übung und somit habe ich das Angebot von Ralf Westphal sehr gerne angenommen, ein Design abzuliefern und hoffe bald Anmerkungen dafür zu erhalten.

Die Aufgabenstellung:


Mein Lösungsansatz:
Ich habe nicht nach einer Rekursion gesucht, keinen Baum aufgebaut und ihn flachgeklopft und nicht mal auf Geschwindigkeit getrimmt. Ich habe eine Lösung verfolgt, wie ich es händisch relativ einfach machen könnte.

Was mir schon ein wenig im Hinterkopf rumschwebte, war, dass ich irgendwann eine schöne Fehlermeldung erhalten will, welche Abhängigkeiten Probleme machen und so war es mir wichtig, die vermutlich dazu nötige Infos, dann zur Verfügung zu haben.

Kurz erklärt:
Bei register() wird eine Liste aufgebaut, die dann als Start für sort() verwendet wird.
Und bei sort() wird zuerst eine Vereinfachung der Jobliste durchgeführt, sodass es für jeden Job nur einen Eintrag gibt.
Diese Liste wird dann nach der Reihe abgearbeitet und jene Jobs werden durchnummeriert, die ausgeführt werden können, weil sie keine Abhängigkeiten haben, bzw. die abhängigen Jobs bereits erledigt sind. Am Ende wird überprüft, ob es noch nicht sortierte Jobs gibt, dann wird die Liste ein zweites Mal abgearbeitet. Das wird so lange wiederholt, bis alle Jobs sortiert sind oder in einer Runde keine neuen Jobs mehr nummeriert werden konnten, dann wird eine Exception geworfen. Ansonsten muss die Jobliste dann nur mehr anhand der Nummerierung ausgegeben werden.