Gabi und Sascha
Kategorien : Alle | Berlin | Bücher | Fotografie | Java | Linkhalde | Weichware | Verfassung

Ich habe ein kleines Programm geschrieben, um CSV Dateien mit SQL Befehlen von der Kommenadozeile ad-hoc zu untersuchen: sql4csv. Ziel war es ein Query wie das folgende von der Bash Kommandozeile ab zu setzen:


    > scq select distinct author from books.csv

Das Ergebnis wird dann wieder in Form einer CSV Datei ausgegeben:


    author  
    Douglas Adams  
    Terry Pratchet

Bei der Entwicklung habe ich Wert auf einfache Verwendung gelegt. So soll es nicht nötig sein die Werttypen (integer, numeric oder Datumsangaben) der Spalten vor der Benutzung. Vielmehr soll dies das Tools möglichst selbst erkennen und die Spaltentypen entsprechend anpassen. Damit sollen auch Abfragen wie


    > scq select ABC from xyz.csv where VALUE < 10

möglich sind.

Für integer und numeric Typen ist das auch ohne grosse Probleme möglich. Kniffelig ist es bei Datums- und Zeitabgaben. Hier gibt es zig unterschiedliche Formate. Deswegen habe ich mich für einen pragmatischen Weg der Umsetzung entschieden: es werden aktuell die drei für mich wichtigsten Datumsformate für den Gregorianischen Kalender unterstützt :-)

Datums- und Zeitformate

  • Y - steht für das Jahr. Jahre werden immer vierstellig angegeben
  • M - steht für den Monat
  • D - steht für den Tag im Monat
  • h - steht für Stunden
  • m - steht für Minuten
  • s - steht für Sekunden

Datumsangaben

YYYY-MM-TT

ISO 8601 ähnlich.

Unterstützt wird auch eine sogenannte lazy Schreibweise, bei der auf einen führende Null (0) bei Zahlen unter 10 verzichtet werden kann. Dies bedeutet, dass auch ein Datum 2016-9-6 als 6. September 2016 erkannt wird.

MM/DD/YYYY

Amerikanische Datumsschreibweise.

Wie bei der ISO 8601 Schreibweise wird auch hier eine lazy Schreibweise ohne führende Null unterstützt.

DD.MM.YYYY

Entspricht der numerischen DIN 5008.

Wie bei der ISO 8601 Schreibweise wird auch hier eine lazy Schreibweise ohne führende Null unterstützt.

Zeitangaben

Zeiten werden immer mit einem Doppelpunkt getrennt. Dabei unterstützt sql4csv nur die Reihenfolge hh:mm:ss. Dieses Format allerdings auch lazy, also ohne führende Null und die Sekunde kann ebenfalls weggelassen werden. In diesem Fall wird die Sekunde intern auf Null gesetzt..

Zeitstempel

Zeitstempel sind eine Kombination aus Datum und Zeit. sql4csv erlaubt alle Kombinationen von Datums- und Zeitangaben. Dabei kann als Separator das ISO 8601 T verwendet werden oder ein Leerzeichen.

Beispiele

  • 2016-7-3 1:16
  • 8/30/1969T23:5:07
  • 11.11.2011 11:11:11

Implementierung

sql4csv wurde in Java 8 implementiert. Ich habe es auch geschrieben, um die neuen Sprachfeatures von Java 8 in der Praxis zu lernen und anzuwenden. So kommen intensiv Lambdas zum Einsatz und auch Methodenreferenzen. Die gesamte Datums- und Zeitbearbeitung basiert auf dem java.time API.

Als Datenbank verwende ich Derby als in-memory Datenbank. Ich verwende nicht SQLite - wie dies einige andere Implementierungen für ein solches Tool tun - da dies entweder eine vorhandene Installation von SQLite voraussetzt oder ich die Binaries für die unterschiedlichsten Plattformen mitliefern müsste. Das ist zu kompliziert und wird der Einfachheit des Werkzeuges nicht gerecht.

Probleme

SQL Queries und die Bash als Kommandozeileninterpreter vertragen sich nur mässig miteinander. Beispielsweise interpretiert die Bash das Asterisk Zeichen (*) anders als in SQL vorgesehen. Und diese Interpretation kann auch nicht einfach ausgeschaltet werden. Ein Query wie


    > scq select * from books.csv where author = 'Douglas Adams '

ist nicht möglich. Die Bash ersetzt das * Zeichen durch die Dateinamen des aktuellen Verzeichnisses. Als Abhilfe kann das Hilfstool scq-cols verwendet werden:


    > scq select $(scq-cols books.csv) from books.csv where author = 'Douglas Adams'

Das ist nicht schön, aber eine pragmatische Lösung. Nach einigen malen benutzen hatte ich mich daran gewöhnt.

Ebenso müssen runde Klammern (z.B. für Gruppierungen) und spitze Klammern <> für grösser und kleiner Vergleiche durch eine Backslash (\) maskiert werden. Insgesamt kann durch diese Einschränkungen die Lesbarkeit des Queries beeinträchtigt werden.

Download und Installation

Im sql4csv Repository auf Github findet ihr eine Download und Installationsanleitung, um das Tools selbst einmal ausprobieren zu können.

Gerade schlug mir Netbeans vor das folgende Codeschnipsel in einen Lambda Ausdruck umzuwandeln.


    private final Map originalToReplacement = new TreeMap<>(new Comparator<String>() {
        @Override
        public int compare(final String s1, final String s2) {
            return s1.length() - s2.length();
        }
    });

Das Ergebnis würde so aussehen:

    private final Map originalToReplacement =
            new TreeMap<>((String s1, String s2) -> s1.length() - s2.length());

Keine Frage, die Lambdaschreibweise ist wesentlich kürzer. Allerdings finde ich sie auch noch nicht so verständlich wie die lange Version. Fürs Erste habe ich mich entscheiden in diesem Fall keinen Lambda Ausdruck zu verwenden.

["ANDROID GBC BALL COUNTER LOGO - BÄLLE (FUSSBALL, BASKELBALL) IN EINEM SIGMA ZEICHEN.] In the last few weeks I had developed an Android based counter for Lego® Great Ball Contraption systems. The counter should measure contactless the throughput of passing balls.

The hypothesis was: a colored or white Lego ball moves in front of a black background. The Android system camera captures every X milliseconds an image. The first image is captured with no passing ball and the image data is stored in memory for comparison. Every following image is checked for color changed against the first image. If the amount of pixels with a changed color (against the background from the first image) reached a percentage threshold, the ball count will be increased by one. Because of the camera sensor noise the color change value must also reach a threshold to be measured. In the implementation these two parameters can be configured. This improves the detection rate depending on the light environment of the GBC setup.

Measure the color change also reduces false positive ball counts because of environment light changes.

Nevertheless, Lego® also offers not only colored balls but also black and white only balls (footballs/soccer balls). If the user has also black and white balls he can also switch on a brightness detection based on the luminance. This follows the same rules like the color change detection but has a different threshold configuration parameter. Usage of the brightness detection might increase the false positive rate.

After a ball detection

After the systems has detected a passing ball in front of the camera, the system stops analyzing the given images for an amount of time. Otherwise it might happen that the same ball is counted twice.

The delay is depending on the layout of the GBC system and for this the delay time is also configurable.

Two good parameters are:

Capture delay
Delay between two image captures of the camera.
Value: 30ms
Capture delay after detection
The delay of time between the detection of a ball in an image and the next time checking again for a passing ball.
Value: 150ms

This allows a theoretical throughput of 5 Lego balls per second. Note: GBC is defined with 1 Lego ball per second. But as you know we ignore the standard in our constructions. Because of this the Android GBC Ball Counter implementation contains also a balls per second calculation.

Limitation

[SCREENSHOT ANDROID GBC BALL COUNTER] If two balls are passing very close succession there are false negative counts. This means, the two balls are counted as one ball. This is the greatest limitation of this non-contact method. The problem can be limited only by structural measures on GBC system.

Implementation

Image format

I use the YUV 4:2:0 8 bit image format delivered by the Camera2 Android API. This API was introduced with Android 5.0 (Lollipop).

The YUV image format has some advantages over the JPEG format: it doesn't pipe the image data through the RGB calculation and compression routines which takes a lot of time and other resources and doesn't produce more important information.

Android 5.0 Bug

Due to a bug in Android 5.0 the YUV subsystem doesn't deliver correct UV (chrominance) information. only the first 600+ bytes contains color information. The rest of the bytes are always zero. Only the Y (luminance) values are correct. This means: for Android 5.0 the implementation only supports the brightness ball passing detection algorithm.

More information

The implementation of the GBC is open source under the terms of the GNU General Public License 3 and the source code is available on Github. There you can dive deeper into the ball detection strategy.

How it works in practice

Ich habe heute Android 6.0 (Marshmallow) auf meinem Motorola Moto G (2. Generation) installiert. Hauptsächlich wegen eines Bugfixes im YUV_420_888 Image capturing. Das Camera2 API liefert bei Android 5.0 (Lollipop) keine korrekte Farbinformation (siehe auch hier). Diese Farbinformation benötigt ich aber, um den Lego® GBC Ball Counter weiter zu optimieren.

Bei der Installation hatte ich angegeben, dass die SD-Card als interner Speicher verwendet werden soll. Dadurch werden Daten beispielsweise nicht im Speicher des Smartphone gespeichert, sondern auf der Speicherkarte selbst. Vorteil: der interne Speicher des Gerätes wird weniger belastet. Nachteil: mit dem Android Device Monitor aus dem Android Studio kann ich nicht mehr direkt auf das Verzeichnis "/storage/emulated/0/Android/data/de.speexx.lego.gbc.ballcounter/files" zugreifen. Damit kann ich nicht einfach die Beispielbilder, die ich gespeichert habe, herunter laden.

Android 6.0 kann die Daten trotzdem erreichen und kopieren. Dazu muss ich die Einstellungen öffnen. Und dort dann über

Speicher & USB → SD‑Karte von SanDisk* → Erkunden → Android → data → de.speexx.lego.gbc.ballcounter

navigieren. Im Unterverzeichnis files liegen dann die erzeugten *.yuv Dateien.

Da Android mit der Endung yuv nichts anfangen kann, können die Dateien auch nicht geöffnet werden. Allerdings können sie mit einem längeren berühren des Namens ausgewählt werden, auch mehrere Dateien. Die so ausgewählten Dateien können dann geteilt werden. Ich habe die Dateien in Google Drive geteilt und dann von dort wieder herunter geladen.

Ziemlich komplizierte Prozedur und sicherlich nicht für die regelmässige Anwendung geeignet.

Gute Nachricht

Die Chrominanzwerte U und V sind jetzt vorhanden und auch die Strides Angaben machen jetzt Sinn: jeweils 4 Luminanzwerte mit einem U und einem V Wert.

Todo

In den nächsten Tagen werden ich den GBC Ball Counter aufwärtskompatibel anpassen.


* Name SanDisk kann eventuell abweichen.

based architecture is a current hype in architecture. I am therefore not yet clear with it from the point of architecture.

I'm not clear with it

The good
It reduces the complexity in the code artifacts for a simpler maintenance.
The bad
It increases complexity of governance of the software.
The ugly
It moves the complexity of the software to the network.

The ugly

11 years ago died. Wheeler formulated one of the most important phrases for software architectures:

All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections.

I must ask myself: what are the benefits to add the network as a layer of indirections? Handling software with networks is not trivial. The network might be not available or under heavy load. Timeouts are commonplace and many developers have never heard of the design pattern.

The bad

Governance of software is on of the most underestimated factor. In bigger systems you can't do what you want. Rules have introduced, established and must be respected. Without these rules a growing software system will collapse after a while - or maintenance costs will increase dramatically.

REST based microservice architectures are related to systems. And SOA without a strong governance will create an epic cluster fuck also.

Fine grained microservices will increase the complexity of the governance. The diagrams to visualize are increasingly confusing. And we need visualizations to get an overview and understanding of the system. And as we know, a process diagram with more than 7 boxes/tasks/whatever has the tendency to create chaos in our head.

The good

Software artifacts gets smaller. A microservice implementation should not have more than a few hundred lines of code - howsoever you measure it. This is good for refactoring or throwing the code away and write it new from scratch.

But I'm not quite sure that this is really a benefit. Do you really rewrite an implementation? In normal cases it is not a very complex tasks to refactor a code system with a few hundred lines of code. The benefit of rewriting comes in when you switch the technology stack. With microservices this is not a huge change-it-all tasks but you can change one service after the other.

More often than rewriting or refactoring a microservice (that works) is to refactor the entire system. Here REST based microservice based only on can become a pain in the ass. Refactoring a not typed software system is a mess. Also the communicating of code intention without a type system. Without a type system also design and governance (see: The bad) can become very problematic.

Although some advocates of Microservice architectures do not want to read: exports not only JSON, but also XML hedged with or any other technology that simple adds type safety.

Find the whole code for the proof of concept on Github

A proof of concept to use the incredible JCommander API from Cédric Beust together with CDI in a Java SE environment.

The goal was to use the commands API with the @Parameters annotation together with CDIs natural plugin API Instance.

This means, a lot of sub commands with different parameters can be executed by a main command from command line. The parameters of the main command should be injected to the sub command implementation. The sub command can have own, different parameters.

An example for such command system is git.

Example


    git -c author=sascha.kohlmann@example.com commit -am "a commit"

Implementation

The main command configuration

The class with the main method must contain @Produces annotated method which returns a static variable with the only MainConfiguration instance.


    public class Application {

        private final static MainConfiguration MAIN_CONFIG = new MainConfiguration();

        @Produces @Config
        private MainConfiguration configuration() {
            return MAIN_CONFIG;
        }

        public static final void main(final String... args) {
            // do something
        }
    }    

The main method contains only the initialization of the Weld-SE container and runs the application.


    public static final void main(final String... args) {        
        final Weld weld = new Weld();
        try (final WeldContainer container = weld.initialize()) {
            result = container.select(Application.class).get().run(args);
        }
    }

    String run(final String... args) {
        // the application
    }

MainConfiguration

The MainConfiguration class is a straight forward data holder. The configuration field are annotated with @Parameter.


    public class MainConfiguration {

        @Parameter(names = "-m")
        private String main;

        public String getMain() { return main; }
    }

The sub commands

Sub commands implements a common command API like the following:


    public interface Command {
        String execute();
    }

Implementations of the command must have at least the @Parameters type annotation with the names attribute given. They may have sub command specific parameters and the parameter data of the main command.


    @Parameters(commandDescription = "Simple sub command", commandNames = "subcmd")
    public class SubCommand implements Command {

        @Inject @Config
        private MainConfiguration mainConfig;

        @Parameter(names = "-p")
        private String parameter;

        @Override
        public String execute() {
            return this.mainConfig.getMain() + this.parameter;
        }
    }

After the CDI initialization all sub command with the injection point for the MainConfiguration contains now the static instance from the main class. But the configuration is yet not initialized. So having a method with @PostConstruct annotated, using the main configuration will not work. Also using the sub command specific parameters in such a method will not work.

The JCommander initialization follows in the next step. But before, we must enhance the Application class with the CDI plugin API.

Instance<Command>

The Application class gets an injectable Ìnstance<Command> field. This field will be filled by CDI after the Weld initialization with all available Command implementations.


    public class Application {

        @Inject
        private Instance<Command> commands;     

        public static final void main(final String... args) {
            // do something
        }
    }

JCommander initialization

The run method initialize the JCommander with the following steps:

  1. Create a new JCommander instance with the static MAIN_CONFIG:
    JCommander jc = new JCommander(MAIN_CONFIG);
  2. Add all instances of private Instance commands to the JCommander instance:
    this.commands.forEach(cmd -> jc.addCommand(cmd));

Afterwards, parse the command line arguments with the JCommander instance.


    jc.parse(args);

That's it.

Execute the sub command

The last step is now to get the parsed sub command name an fetch the sub command implementation from JCommander. Then call the execute() method.


    final String parsedCommand = jc.getParsedCommand();

    for (final Map.Entry cmdEntry : jc.getCommands().entrySet()) {
        final String name = cmdEntry.getKey();
        if (name != null && name.equals(parsedCommand)) {
            ((Command) cmdEntry.getValue().getObjects().get(0)).execute();
        }
    }

Testing

Testing ist straight forward:


    @Test
    public void test_sub_command_execution() {
        Application.main("-m", "main", "subcmd", "-p", "sub");
        assertThat(Application.result, is(equalTo("mainsub")));
    }

Conclusion

Using CDI and JCommander with complex commands in a Java SE environment is quite simple. Using the CDI natural plugin API (Instance) is also very simple. Together this is a strong duo to simplify the development of Java command line tools.


Find the whole code of the proof of concept on Github.

Choosing a JavaScript MVC Framework

Ich bin ja nicht so der Webentwickler. Die Übersicht ist allerdings gelungen.

Autonomes Fahren: Nicht vor 2020

Ähnliche sehe ich es auch und habe es in Moore's Law und die Automobilindustrie auch schon beschrieben. Vielleicht müssen wir die Infrastruktur in Innenstädten sogar mit Transpondern ausstatten, damit der autonome Verkehr dort besser fliessen kann. Das würde einmal in die Zukunft gerichtet sein.

Understanding Method References

Die zweitwichtigste Änderung in Java 8 kurz, knapp und ausreichend erklärt.

The Modular Java Platform and Project Jigsaw

Mark Reinhold über die wichtigste Änderung in Java 9. Endlich, Java bekommt einen Linker :-) Aber schon klasse, was sich damit und mit der geplanten Modularität alles machen lässt. Was ich noch nicht ganz verstanden habe, was auch nicht erklärt wird: wie ändert sich die Java Lizensierung mit dem Modulsystem und dem Linker. Das gilt für Java core und die Runtime-Umgebung.

How Facebook’s Own Algorithms Are Killing Facebook

Edgerank completely fails to have the effect it was originally designed for. Sehr spannend beschrieben, was die strukturellen Schwächen von Facebooks Suche sind - und vor allen Dingen die Werbevorhersage.

Gerade bei der Werbevorhersage scheint mir Google besser aufgestellt. Bei Google ist die Werbung direkter, denn sie wird unmittelbar mit der Suchanfrage eingeblendet, ist also deutlich besser auf den Kontext abgestimmt.

It's not a Photograph Until It's a Print

Das postuliert Jeff Guyer. Und ich denke es ist Unsinn. Nur weil etwas auf Papier und in einem Rahmen ist macht es kein Foto aus. Es ist das Motiv und was das Motiv in uns bewegt. Papier und Rahmen können das nur verstärken. Und Papier? Wird vermutlich auch durch Moore's Law weggemendelt.

Papier und Print wird das neue Vinyl.

Lambda State - Final

Brian Goetz mit dem finalen Statusbericht zu Lambdas in Java. Über Lambdas (a.k.a. Closures) in Java hatte ich erstmal am 22. August 2006 geschrieben. 2014 wird es jetzt endlich soweit sein. Soviel zu: das geht alles so wahnsinnig schnell in der IT.

10 Lessons Andre Kertesz Has Taught Me About Street Photography

Höhere Perspektive eingeommen, Horizont erweitert. Klappt.

Mandantenfähige Java Virtual Machine

Java goes Betriebssystem. Oder: IBM macht Java fit für die Cloud. Sehr spannend. Da fallen mir gleich zig Anwendungsszenarien ein.

Ich habe mich in den letzten Tagen mit der Entwicklung eines Java Annotation Processors befasst. So ein Prozessor hängt sich seit Java 6 transparent in den Compiler ein und kann Java Annotationen verarbeiten. Um dies zu erreichen muss die Implementierung des Processors für das Service API vorbereitet werden. Und damit bekam ich das erste Problem, welches zu lösen war.

Ich verwende Maven für die Umsetzung des Projektes. Wenn in einem Maven Projekt die entsprechenden Ressourcen angelegt werden, dann sind diese bereits beim Compilerlauf erreichbar. Da die Implementierung aber noch nicht vorhanden ist, kommt es zu einem Fehler und Abbruch des Compilerlaufs.


    MAVEN_PROJECT
         ├─ pom.xml
         └─┬─ src
           └─┬─ main
             └─┬─ resources
               └─┬─ META-INF
                 └─┬─ services
                   └── javax.annotation.processing.Processor

Um dies zu vermeiden, musste ich die META-INF/services/ javax.annotation.processing.Processor nachträglich in das erzeugte JAR einbringen. Dies wird zur Zeit mit dem Maven TrueZIP Plugin erledigt. Dabei sieht das Project Layout wie folgt aus:


    MAVEN_PROJECT
         ├─ pom.xml
         └─┬─ src
           └─┬─ main
             ├─── resources
             └─┬─ service-data
               └─┬─ META-INF
                 └─┬─ services
                   └── javax.annotation.processing.Processor

Der Nachteil dieser Lösung: der Processor kann nicht im gleichen Projekt getestet werden. Deswegen habe ich für de Tests auf das Maven Invoker Plugin zurückgegriffen. Dabei wird innerhalb des Maven Projektes in der src-Hierarchie ein it-Hierarchie eingerichtet. it steht für Integration Test. Innerhalb dieser it-Hierarchie werden dann weiter Integrationstest Projekte angelegt. Diese Projekte sind ganz normale Maven Projekte. Innerhalb ihres <dependencies> Elements wird auf das Basisprojekt des Annotation Processors referenziert. Als >scope> wird dabei compile verwendet, da der Processor nicht für die Runtimeumgebung benötigt wird, sondern nur für die Erstellung von Artefakten.


    MAVEN_PROJECT
         ├─ pom.xml
         └─┬─ src
           ├─┬─ main
           │ ├─── resources
           │ └─┬─ service-data
           │   └─┬─ META-INF
           │     └─┬─ services
           │       └── javax.annotation.processing.Processor
           └─┬─ it
             ├── test-project-1
             ├── test-project-2
             └── …

Die POM Datei des Basisprojektes sieht dann ungefähr wie folgt aus (unwichtige Teile wurden der besseren Lesbarkeit weggelassen):


    <project>

        <modelVersion>4.0.0</modelVersion>

        <groupId>processor-project</groupId>
        <artifactId>simple-annotation-processor</artifactId>
        <version>0.1.0-SNAPSHOT</version>

        <properties>
            <archive>${project.build.directory}/${project.build.finalName}.jar</archive>
        </properties>

        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>truezip-maven-plugin</artifactId>
                    <version>1.1</version>
                    <executions>
                        <execution>
                            <id>copy-into</id>
                            <goals>
                                <goal>copy</goal>
                            </goals>
                            <phase>package</phase>
                            <configuration>
                                <fileset>
                                    <directory>${basedir}/src/main/service-data/</directory>
                                    <includes>
                                        <include>**/*</include>
                                    </includes>
                                    <outputDirectory>${archive}</outputDirectory>
                                </fileset>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>

        <profiles>
            <profile>
                <id>run-its</id>
                <build>
                    <plugins>
                        <plugin>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-invoker-plugin</artifactId>
                            <version>1.8</version>
                            <configuration>
                                <projectsDirectory>src/it</projectsDirectory>
                                <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
                                <pomIncludes>
                                    <pomInclude>test-project-1/pom.xml</pomInclude>
                                    <pomInclude>test-project-2/pom.xml</pomInclude>
                                </pomIncludes>
                                <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
                                <goals>
    	                            <goal>clean</goal>
                                    <goal>test</goal>
                                </goals>
                            </configuration>
                            <executions>
	                        <execution>
                	            <id>integration-test</id>
	                            <goals>
                                        <goal>install</goal>
                                        <goal>integration-test</goal>
                                    </goals>
                                </execution>
                            </executions>
                        </plugin>
                    </plugins>
                </build>
            </profile>
        </profiles>
    </project>

Die Integrationstest Projekte POMs sehen ungefähr wie folgt aus:


    <project>

        <modelVersion>4.0.0</modelVersion>

        <groupId>processor-project</groupId>
        <artifactId>simple-annotation-processor-integration-test-1</artifactId>
        <version>0.1.0-SNAPSHOT</version>

        <dependencies>
            <dependency>
                <groupId>processor-project</groupId>
                <artifactId>simple-annotation-processor</artifactId>
                <version>0.1.0-SNAPSHOT</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    </project>

Fazit

Die Maven Projekt Konfiguration für Java Annotation Processor Implementierung ist komplexer als für normale Projekte. Am Ende lohnt sich der Aufwand. Durch die Prozessorimplementierung für Annotationen muß weniger Code manuell implementiert werden. Der Prozessor übernimmt diese fehleranfällige Arbeit der Codeerzeugung.

Maven ist ein hervorragendes Werkzeug um grosse, nicht triviale, Java Projekte zu verwalten. Trotz aller Stärken von Maven gibt es einige Dinge, die im Bereich des Abhängigkeitenmanagements vermieden werden müssen. Nicht alles was machbar ist, muss auch gemacht werden.

Regel

Verwende Versionsproperties nur innerhalb eines Projektes. Und auch nur dann, wenn die Versionsbezeichnung mehr als einmal verwendet wird.

Beschreibung

In Projekt A wird ein Property mit einer Artefaktversion definiert. Dieses Property wird in Projekt B wieder verwendet. Problematisch ist bei dieser Art der Benutzung, dass in Projekt A nicht bestimmt werden kann, für was das Property verwendet wird. Der direkte Bezug zu einem Artefakt fehlt.

Beispiel:

Projekt A


    <project>
        <artifactId>A</artifactId>
        …

        <properties>
            <version.artifact.xyz>1.2.0</version.artifact.xyz>
        </properties>
    </project>

Projekt B


    <project>
        <parent>
            <artifactId>A</artifactId>
            …
        </parent>
        <artifactId>B</artifactId>

        <dependendies>
            <dependendy>
                <artifactId>xyz</artifactId>
                <version>${version.artifact.xyz}</version>
            </dependendy>
        </dependendies>
    </project>

In so einem Fall ist es besser den dependencyManagement Mechanismus zu verwenden. In diesem Fall werden die POM Dateien wie folgt angepasst. Dabei wird in Projekt A im dependencyManagement Element das Artefakt samt Version definiert. Erst in Projekt B wird diese Abhängigkeit dann aber wirklich aufgelöst:

Projekt A


    <project>
        <artifactId>A</artifactId>
        …

        <dependencyManagement>
            <dependendies>
                <dependendy>
                    <artifactId>xyz</artifactId>
                    <version>1.2.0</version>
                </dependendy>
            </dependendies>
        </dependencyManagement>
    </project>

Projekt B


    <project>
        <parent>
            <artifactId>A</artifactId>
            …
        </parent>
        <artifactId>B</artifactId>

        <dependendies>
            <dependendy>
                <artifactId>xyz</artifactId>
            </dependendy>
        </dependendies>
    </project>

Merke

Any problem in computer science can be solved with another layer of indirection. But that usually will create another problem.- David Wheeler

Und Versionsproperties sind nicht anderes als eine Indirektion. Diese können für einen menschlichen Bearbeiter der Information schwer zu durchschauen sein. Für eine Maschine fehlt jegliche semantische Information.

Über das Loggen habe ich schon mehrfach geschrieben. Für meinen Arbeitgeber haben wir ein Framework geschaffen, welches loggen etwas verbessert. Dies hinsichtlich eindeutiger IDs, Internationalisierung und Validierung während des compilierens. Die Implementierung reduziert grep Monster, mit denen Operatoren sonst hantieren mussten.

Ich bin mit dem Loggen immer noch unzufrieden

Wir kennen heute zwar unterschiedliche Log-Level und unterschiedlichste Konfigurationsmöglichkeiten für die Logger. Aber im Grund ist es immer noch ein simples raushauen von Nachrichten, deren Informationsgehalt mehr oder minder wertvoll ist. In den meisten Situationen bin ich sogar der Meinung, dass der Informationsgehalt fast gegen Null geht. Das liegt am Ziel des Loggens. Log-Ausgaben sind so gut wie immer nur für die Post-mortem-Analyse interessant. Also dann, wenn ein Fehler analysiert werden muss.

Für Post-mortem-Analyse nicht die richtigen Informationen

Für die Post-mortem-Analyse sind in der Log-Ausgaben meistens nicht die richtigen Daten vorhanden. Das liegt daran, dass wir uns abgewöhnt haben das Ergebnis jeder erfolgreich abgeschlossene Funktionalität zu loggen. Grundsätzlich ist das ist kein Mangel. Wir hätten sonst wieder die unendlichen Textwüsten vergangener Tage. In den wenigen vorhandenen Daten sind dann aber meistens die entscheidenden Informationen nicht vorhanden. Dabei geben die Entwickler diese (Kontext)-Informationen meistens aus. Aber aus Gründen der Performanz wird die Ausgabe unterdrückt.


    if (LOG.isEnabled(DEBUG)) {LOG.debug("Current…");}

Kontextinformation für Post-mortem-Analyse

Sinnvoll würde es sein die Kontextinformationen von debug Logausgaben für eine Post-mortem-Analyse zu bekommen.

Anmerkung

Viele Logging Systeme sind von Log4j inspiriert. Leider hat die damalige Genialität von Log4j dazu geführt, dass sich Log-Systeme seit über einem Jahrzehnt nicht wirklich weiter entwickelt haben. Daran ändert auch Logback Implementierung von Ceki Gülcü nichts.

Der Einfachheit halber verwende ich die Log4j Nomenklatur für Log-Level. Diese wird auch durch Abstraktions APIs wie commons-logging oder slf4j nachgebildet.

Dazu müssen aktuelle Logging Systeme derart erweitert werden, dass sie

  1. zu einem bestimmten Zeitpunkt mit der Aufzeichnung von debug Meldungen beginnen.
  2. aufgezeichnete debug Informationen im Fehlerfall über das Log-System ausgeben.

Der Eintrittspunkt für die Speicherung kann als Stack gespeichert werden, um den Umfang des Kontext zu erweitern oder dynamisch zu verschieben. Alternativ kann ein Name verwendet werden. Hier ist die Idee noch nicht weiter ausgereift.

Logging goes Architecture

Egal ob auf Namen basierend oder Stack orientiert, Loggen muss mit dem Ansatz etwas mehr koordiniert werden. Wo findet der Eintritt in einen Log-Kontext statt? Kontexte dürfen nicht zu eng gezogen werden, aber auch nicht zu weit auseinander liegen. Dies sind Entscheidungen, die mehr auf architektonischer Ebene getroffen werden müssen, weg vom Entwickler.

Wie weiter?

Das weiss ich zu diesem Zeitpunkt auch noch nicht. Eine Herausforderung ist die Umsetzung. Sie muss in bestehende Logging Systeme eingepasst werden. Java Util Logging oder Log4j werden noch über Jahre hinaus in der Java Welt die bestehenden Logging Systeme bleiben. Um sie herum ist einfach ein zu grosses Ökosystem gewachsen. Aber zumindest die Frontend Systeme wie Apache commons-logging oder slf4j können ersetzt werden. Hier kann angesetzt werden.

Ich habe ein eigenes Maven Git Plugin für die Zimory geschrieben. Anders als das pl.project13.maven:git-commit-id-plugin Plugin benötigt es nicht in jedem Maven Projekt einen konfigurierten Pfad zum .git Verzeichnis. Es wird von uns nur in dem Master POM einmalig konfiguriert, dass war es dann. Vom Codeumfang ist es etwas schlanker als das pl.project13.maven:git-commit-id-plugin und basiert auf dem Eclipse JGit Projekt in aktueller Version. Als zusätzliches Feature lässt es den Build Prozess abbrechen, wenn das Repository aus bestimmten Gründen dirty ist. Dies soll z.B. verhindern, dass es bei einem Releasebuild lokale Änderungen gibt. Die Statusinformationen des Repositories werden in einem Property gesetzt und können z.B. in der META-INF/MANIFEST.MF Datei hinterlegt werden.

Wir schreiben zusätzlich mit Hilfe des Plugins die Revisionsnummer und das Datum der Revision, sowie den Namen des Repositories als unveränderliche Kennung in die META-INF/MANIFEST.MF Datei. Diese Informationen können mittels z.B. Logging oder JMX ausgegeben werden.

Implementierungsaufwand ca. 2 Tage.

Ich mach gerade wieder etwas mit DITA. Dabei werden die Lizenzen der Abhängigkeiten eines Maven Projektes ermittelt und in eine domänenspezifische XML Datei geschrieben. Diese Datei wandel ich dann mittels XSL in eine DITA reference um. Danach wird das DITA Dokument in eine Publishing Pipeline geschickt und am Ende kommen XHTML, PDF und EPUB heraus.

Leider hat die Umwandlung mittels DITA-OT (1.5.4) nicht funktioniert. Die Fehlermeldung war: null.

Sehr aussagekräftig :-(

Ich habe zig DITA-OT Versionen probiert; anstatt eine table habe ich ein properties erzeugt. Nichts ging. Das Format war in Ordnung, validieren ergab keine Probleme.

Nachdem ich schon fast hinschmeissen wollte und einen Docbook Transformator schreiben wollte, fiel mir ein Hyperlink URL auf. Der begann nicht mit http:// sondern nur mit einem Slash (/LICENSE.TXT). DITA-OT ist darüber gestolpert, wenn dies der Wert eines href Attributes im xref Element ist. Da ein solcher URL für mich keinen Sinn macht, habe ich flux einen Test eingebaut, der vorschreibt, dass ein URL mit http:// beginnen muss. Ansonsten wird keine xref erzeugt. Nach dieser Änderung lief das DITA-OT Script weiter.

Jetzt hängt es endlos beim Einlesen der DITA-OT topicpull.xsl Datei… Nächste Baustelle.


Und noch eine Unschönheit. DITA-OT scheint hart gegen den Xerces XML Parser verdongelt zu sein. Findet DITA-OT nicht die Klasse org.apache.xerces.xni.grammars.XMLGrammarPool, bricht es mit einer NoClassDefFoundError ab.

Okay, damit kann ich leben.

Beim Arbeiten mit Maven immer wieder gerne vergessen: regelmässig das lokale Repository löschen.

Heute habe ich ein eigenes Doclet über das Maven Javadoc Plugin in ein Projekt eingebaut. Dabei das Doclet über <docletArtifact> referenziert. Sieht ungefähr so aus:


  <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-javadoc-plugin</artifactId>
      <executions>
          <execution>
             <id>create-logging-msg</id>
             <phase>pre-integration-test</phase>
             <goals>
                  <goal>javadoc</goal>
             </goals>
             <configuration>
                <doclet>com.zimory.common.logging.tools.doclet.LoggingDoclet</doclet>
                <docletArtifact>
                     <groupId>com.zimory.common.tools.logging</groupId>
                     <artifactId>logging-doclet-tool</artifactId>
                     <version>0.1.0-SNAPSHOT</version>
                </docletArtifact>

Problem: Das Maven Javadoc Plugin scheint die weiteren benötigten Artefakte nicht transitiv aufzulösen. Das heisst, eine der gemeinsam verwendeten Bibliotheken wird nicht aus dem remote Repository nachgeladen. Dies führt wiederum dazu, dass der Prozess abbricht, weil eine Abhängigkeit nicht aufgelöst werden kann.

Gefunden habe ich das Problem nur, weil ich regelmässig (einmal am Tag) das lokale Repository lösche. Und diesmal nur das Projekt mit dem Doclet neu gebaut habe.

Die Lösung ost übrigens nicht ganz trivial. Nachdem einige Versuche mit dem Maven Shade Plugin gescheitert sind, habe ich die benötigte Bibliothek als optional Dependency mit in das Projekt aufgenommen. Das ist keine schöne Lösung, aber eine Lösung.

Der Konfigurationsparameter additionnalDependencies hat in der verwendeten Maven Javadoc Pluginversion 2.8.1 nicht funktioniert. Die Verwendung bricht mit der folgenden Fehlermeldung ab:


  [INFO] Failed to configure plugin parameters for: org.apache.maven.plugins:maven-javadoc-plugin:2.8.1

  Cause: Error loading class 'org.apache.maven.plugin.javadoc.AdditionnalDependency'

Die nicht ganz verstandenen Dinge

Heute zwei Dinge, die ich noch nicht ganz verstanden habe.

Objects#requireNonNull(…)

Einmal in java.util.Objects#requireNonNull(T obj, String message). Wurde in Java 7 neu ein­ge­führt. Und ich frage mich, warum die message ein String sein muss. Warum nicht Object und darauf toString() aufrufen - wenn es benötigt wird. So muss die Nachricht leider schon vorher zusammen gebaut werden. Das kann eventuell teuer sein. Einzige Erklärung: beim toString()-Aufruf kann natürlich eine Exception auftreten.

dotGitDirectory Parameter im git-commit-id-plugin

Im pl.project13.maven:git-commit-id-plugin muss immer der komplette Pfad zum .git Verzeichnis an­ge­ge­ben werden. Das führt dazu, dass das Plugin in jedem Projekt hinterlegt werden muss - um den dotGitDirectory zu konfigurieren. Dabei kann das .git Verzeichnis doch einfach gesucht und gefunden werden, indem im Verzeichnis­baum nach oben gewandert wird. Irgendwann muss das Verzeichnis ja kommen. Oder die Wurzel des Dateisystems. Noch habe ich keine Ahnung warum das nicht implementiert ist.

Hmm… Vendor branch?

Ich stolpere immer wieder über das Unix ln Kommando. Unter Unix ist es ja eigentlich immer so: von ist der erste Parameter, nach ist der Zielparameter. Einfach gesprochen: es ist eigentlich immer von-nach. Beim Link Kommando ln ist das anders. Dort wird erst das Ziel angegeben und dann der zu erzeugende Link. Der erzeugte Vektor ist dann aber genau umgekehrt, da der Link auf die ursprüngliche Datei zeigt. Ich gebe das beim ersten Versuch fast immer falsch an – womit ihr natürlich messerscharf schliesst: oft macht der Kohlmann das nicht.

Im Java 7 API wurde mit dem neuen java.nio.file Package auch die Möglichkeit geschaffen Links im Dateisystem zu erzeugen. Dort wurde es genau anders herum implementiert. Von Link nach Ziel. Für die Java Welt ist das konsistent. Im Zusammenspiel mit unixartigen Systemen nicht. Aber auch das werde ich irgendwann drin haben.

Hardcore sind die Entwickler, die bei einem Fehler eine Exception schmeissen. Einfach so Exception, kein Subtype. Vollkommen scherzbefreit sind aber die, die nicht einfach eine Exception werfen, sonder einen InternalError. Der erbt von VirtualMachineError und die Dokumentation ist recht knapp und klar:

Thrown to indicate some unexpected internal error has occurred in the Java Virtual Machine.

Frei übersetzt heisst das soviel wie: keine Ahnung was passiert ist, aber hier geht gar nichts mehr. Wohlgemerkt, nicht für die Anwendung, sondern für die VM, dem Herz der Runtimeumgebung. Tritt so ein Fehler auf gilt es zu retten was zu retten ist, wenn dies noch irgendwie möglich ist. Ganz bescheuert wird es aber, wenn ich innerhalb von ein paar Minuten den Code so umgebaut habe, dass dieses Sub-System zwar nicht funktioniert, der Rest aber weiter machen kann als sei nichts geschehen.

Habe mir am Wochenende überlegt die ganzen Applikationen auf diesem Server auf aktuelle Versionen anzuheben. Bei der Blogsoftware wird das eine komplexere Geschichte. Dort hat sich fast alles geändert und ich muss neben dem CSS auch noch die eigenen Plugins umschreiben.

Dann habe ich mir das JSPWiki angeschaut. Was hat sich getan zwischen Version 2.0.x (aus 2006) die hier läuft und der aktuellen Version 2.8.2? Sogut wie nichts wichtiges im Markup. Deswegen wurde auch hier beschlossen: never touch a running system.

Wordpress würde für mich viel zu stressig sein :-)

Java 7 ist nun also erschienen. Nach langen, langen, sehr langen Geburtswehen. Leider nicht für Mac OS X. Und Apple wird es auch nicht mehr offiziell unterstützen - fuck you Steve. Leider wurde auch das von mir sehnlichst erwartete Feature, Type Annotations, auf Release 8 verschoben. Gerade in diesem Bereich sehe ich sehr viel Potential für die Sprache und die Entwicklung stabiler, skalierender Software. Weniger in den Ergebnissen des Project Coin, dem neuen IO Framework für Dateien oder in der invokedynamic Umsetzung.

Vor knapp 8 Monaten habe ich mal geschrieben, was das alles für Konsequenzen hat.

In Java existiert das sogenannte Konzept der checked exceptions. Jede Exception, die nicht vom Typ RuntimeException ist, muss in Methoden deklariert werden, wenn sie geworfen wird. Und sie muss explizit gefangen werden, wenn eine Methode, die die Exception deklariert, aufgerufen wurde. Alternativ kann die aufrufende Methode die Exception – oder einen generelleren Typen – fangen oder weiter werfen. Soweit so bekannt, so langweilig.

Problematisch wird es wenn Methoden Mengen solcher checked exceptions werfen oder in einem try-catch Block mehrere Methoden mit unterschiedlichen checked exception aufgerufen werden. Dann kann es zu elend langen catch Sequenzen kommen. Java 7 wird hier etwas Abhilfe schaffen. Trotzdem kann es nervig sein, sich durch die Exceptionwüsten zu arbeiten.

Der grösste Vorteil von checked exceptions ist meiner Meinung nach aber der dokumentierende Charakter. Dadurch, dass eine checked exception explizit in der Methode dokumentiert wird, weiss der Entwickler was ihn erwartet – und kann entsprechend handeln. Beispielsweise ist eine meiner Lieblingsexceptions die IOException. Sie dokumentiert, dass eine IO-Operation Probleme machen kann. Passiert dies, tritt ein Problem auf, kann ich als Entwickler oft noch versuchen zu reagieren. Ich kann beispielsweise versuchen noch einmal eine Datei anzulegen, in eine Datei oder auf einen Socket zu schreiben. Erst wenn dies ein paarmal nicht funktioniert hat, hat kann aufgegeben werden. Oder ein Subtyp der Exception besagt glasklar: hier geht nichts mehr.

Würde die IOException eine RuntimeException sein, dann muss sie nicht dokumentiert werden. Weder im Code in der Methodensignatur, noch in den Javadocs. Und genau diese Dokumentation wird dann auch ganz gerne einmal vergessen. Ist dies der Fall kann ich nur erahnen, dass ein IO Problem aufgetreten ist, um entsprechend zu reagieren oder ich muss auf Verdacht eine IOException fangen. Geholfen ist mir damit nicht viel.

Leider wird viel zu oft bei Exceptions überhaupt nicht reagiert und z.B. bei Datenbankoperationen stumpf ein Rollback durchgeführt. Das Exceptions signalisieren: versuch es noch einmal oder nimm einen alternativen Weg ist bei der Geschwindigkeit, in der heute Dinge entwickelt werden leider kaum zu vermitteln. Deswegen fehlt meistens eine entsprechende Strategie im Umgang mit Exceptions. Auf Exceptions nur mit einem totalen Abbruch zu reagieren ist ein nach-mir-die-Sintflut programmieren. Qualität kann so nicht entstehen. Ich denke, dass dies einer der Gründe ist, warum sich das Konzept der checked exceptions nicht durchgesetzt hat.