Skip to content

Föderalismus der Streaming-Anbieter

Nachdem vor kurzem schon die Ministerpräsidenten der Bundesländer das Hohelied auf den Föderalismus gesungen haben, und dafür zu Recht kritisiert wurden, machen es die großen Contentproduzenten jetzt nach. Konzerne wie Disney verabschieden sich von Netflix und machen ihre eigene Abo-Platform auf. Der geneigte Zuschauer/Konsument muss also in Zukunft immer mehr Abos abschließen um seine Lieblingsfilme und Serien sehen zu können. Das dass kein guter Trend ist, muss sicher nicht noch extra betont werden. Aber erschreckend daran ist nicht nur die allgemeine Tendenz, sondern auch die Reaktionen darauf. Die beschränken sich nämlich weitestgehend auf "Dann saug' ich halt Illegal. Dann merken die schon was sie davon haben!" Und während die Analyse, warum diese Tendenz schlecht ist, richtig ausfällt sind Schlussfolgerungen wie die eben genannte nicht nur kurzsichtig sondern gehen grundlegend in die verkehrte Richtung.

Abgesehen davon, dass es überaus unwahrscheinlich ist, dass die Anbieter "merken, was sie davon haben" - wäre es nicht viel sinnvoller, wenn es Leute gäbe, die Gesetze erlassen könnten, die die Anbieter zur Interoperabilität zwingen würden? Nennen wir diese Leute spaßeshalber "Politiker". Diese könnten ein Gesetz erlassen, dass alle Anbieter verpflichtet, eine offene API anzubieten, über die man ihren Content kaufen oder leasen kann, und mit allen anderen Anbietern, die das wünschen, entsprechende Verträge/Peerings einzugehen. Und bei der Gelegenheit kann man gleich noch eine äquivalente Verpflichtung für die Socialmedia-Anbieter mit rein schreiben!

Alle FDP-Wähler und Teile der CDU (dazu zählt auch die SPD) werden jetzt schreien: "So eine Regulierung geht zu weit! Das schadet unserer Wirtschaft!" Halt das Standardargument derer, die erst den Mund aufmachen bevor sie denken. Sonst würden sie ja vielleicht merken, dass alle großen Socialmedia- und Contentanbieter in den USA sitzen und "unsere Wirtschaft" davon eigentlich nur profitieren könnte! Und für alle anderen, die ebenfalls immer noch der Meinung sind, dass das nicht geht, machen wir jetzt ein kleines Gedankenexperiment!

Wir stellen uns vor, dass es im Internet kein Peering zwischen den einzelnen Netzen und ISPs gäbe. Dann müsste ein Telekomkunde auch noch einen Vertrag mit Vodafone machen, wenn er Inhalte von Servern in deren Netz abrufen will. Und wenn der Betreiber dieser Server plötzlich zu Unitymedia wechselt, braucht der gebeutelte Telekomkunde auch noch einen Vertrag (mithin also ein Abo) mit Unitymedia!
Klingt absurd? Wäre es wohl auch. So absurd, dass die Netzbetreiber - in den meisten Fällen - gar nicht erst dazu gezwungen werden müssen, Peerings einzugehen.

Wenn also die Contentanbieter dazu gezwungen werden müssen - warum nicht? Ist ja nicht so, als gäbe es ein Naturgesetz, dass diese Anbieter machen dürfen, was sie wollen. Viele Leute scheinen sich schon so daran gewöhnt zu haben, in einer Demokratie zu leben, die bestimmte Strukturen aufweist, dass ihnen nicht (mehr) bewusst ist, dass Demokratie sich nicht darauf beschränkt alle vier Jahre zwei Kreuze auf einem Wahlzettel zu machen!
Demokratie meint "Herrschaft des Staatsvolkes"! Und wenn das Staatsvolk sich nicht länger von den Socialmedia- und Contentanbietern gängeln und ausbeuten lassen will, dann ist es nicht nur legitim, sondern vielmehr angebracht - ja, erforderlich - das zu fordern und letztlich in Gesetzesform zu gießen!

Und was den Faible unserer Ministerpräsidenten für Föderalismus angeht, so sollten sich diese mal klar machen, dass das Staatsvolk aus mehr besteht als nur den Einwohnern ihres jeweiligen Bundeslandes. Und das "Konkurrenzfähigkeit" beim Thema Bildung - und darum geht es denen augenscheinlich nämlich - nicht nur absurd ist, sondern zum Schaden für alle Bürger.
Der Föderalismus - nicht nur im Bildungssektor - wurde von den Alliierten (wieder) eingeführt, als Lehre aus der Nazizeit. Es sollte damit eine "zu Missbrauch verleitende Machtkonzentration" verhindert werden. Nun ist aber diese Gefahr heute nicht mehr gegeben. Unter anderem, weil der (Rechts)populismus heute dank der Digitalisierung, mehr Möglichkeiten hat, als ein Bildungssystem unter Kontrolle des Bundes jemals bieten könnte. Wir haben also keinen Vorteil mehr vom Bildungsföderalismus, dafür aber jede Menge Nachteile!
Aber dazu wollte ich eigentlich gar nicht so viel schreiben. Denn das hat Richard David Precht schon getan, der ein ganzes Buch zu dem Thema heraus gebracht hat: "Anna, die Schule und der liebe Gott"

Also: statt illegal zu saugen, beschwert euch mal lieber bei eurem Abgeordneten darüber! Die meisten von denen sind durchaus offen für Argumente und bei weitem nicht so abgehoben und ignorant wie die Spitzen der etablierten Parteien!

Atlassian ist schlechter als sein Ruf!

Neulich haben wir das Budget für einen selbst gehosteten BitBucket Server bekommen.
Nun war mir schon bewusst, dass BitBucket nicht gleich BitBucket Server ist. Genau genommen, sind BitBucket (Cloud) und BitBucket Server zwei verschiedene Produkte, die außer dem Namen und dem Einsatzzweck nicht viel gemeinsam haben.

Aber wie beschissen die Situation ist, war mir nicht klar. Zu behaupten, dass Atlassian die Server-Variante von BitBucket stiefmütterlich behandelt, wäre noch untertrieben.

Das fängt damit an, dass Änderungen an Pull-Requests keine Webhooks triggern können. Das ist dummerweise nicht sofort ersichtlich, das es durchaus eine Checkbox dafür gibt.
Erst bei genauerem Hinsehen fällt auf, dass das nur für Änderungen an "Titel, Beschreibung oder Zielbranch" eines Pull-Request gilt. Da fragt man sich doch, was sich häufiger bei einem Pull-Request ändert - ein weiterer Commit zum Nachbessern, oder Titel und Beschreibung.

Das ist zwar schon beschissen genug, aber man denkt sich halt: OK, dann muss ich das Ding halt minütlich mit Requests gegen die REST-API bombardieren ...bis man sich mit den Einzelheiten der REST-API beschäftigt.
Denn auch diese ist fundamental anders, als die API der Cloud-Version und lässt ungefähr die Hälfte der Features vermissen!
Angaben über den Status eines Pull-Request? Pustekuchen! Von den Feldern die tatsächlich vom Server zurück kommen, ist auch nur die Hälfte dokumentiert.
Und auch an anderer Stelle ist die Doku für den Arsch! Das fällt spätestens dann auf, wenn man versucht die Kommentare eines Commit oder Pull-Request zu holen. Das geht nämlich nicht!
Kommentare können nur pro File (eines Commit/PR) abgerufen werden. Nur steht das nicht in der Doku. Auch nicht, dass der dazu gehörige Parameter "path" verpflichtent ist. Als Beschreibung steht da nur "the path to stream comments for a given path" - was zum Geier soll mir das sagen?
Auch das Anlegen eines Kommentar ist nicht weniger bekackt. Dafür braucht es nämlich zusätztlich zur Commit/PR-Id und dem Pfad noch einen "fromHash" und "toHash" -.-

Und nur um zu demonstrieren, dass sich dieses Verhalten nicht auf BitBucket (Server) beschränkt: In JIRA kann man für ein Projekt den default assignee nur auf den Projekt-Lead oder unassigned setzen. Es gibt seit Jahren ein Ticket, um auch andere User zu erlauben - bislang ohne Erfolg.

Und um noch mal auf BitBucket zurück zu kommen: wenn man eine Lizenz für BitBucket Server kauft, bekommt man ein Jahr Support - was Updates mit einschließt. Aber nach dem Jahr gibt es nicht nur keine Feature-Updates mehr. Es gibt gar keine Updates mehr - also auch keine Sicherheitsupdates!
Dafür dass das Zeug verdammt viel Geld kostet, ist das nicht nur ziemlich armselig sondern einfach nur dreist!

return null;

In seinem Buch "Clean Code" empfiehlt Robert C. Martin, nicht explizit "null" zurück zu geben. Weil es Arbeit erzeugt und den Code mit null-checks unleserlich macht.
Ich bin aber der Meinung, dass man das nicht verallgemeinern kann und sollte. An folgenden Beispiel will ich das einmal deutlich machen.

These:
Wenn wir einen DAO haben, mit einer Methode "findItemById(id)" dann soll "null" zurück geben werden, wenn kein Item mit dieser Id existiert!

Begründung:
Zum einen bekommen wir mit dem Wert "null" genau das zurück, was wir erwarten würden. Denn wir müssen davon ausgehen, dass in dem Repository oder der DB die wir da abfragen kein Eintrag mit dieser Id existiert.
Es wäre idiotisch zu erwartet, dass die DB einen Eintrag für jede Id hat, die wir uns ausdenken können - es zwingt uns schließlich niemand, der Methode nur Ids zu übergeben, für die ein Eintrag existiert!
Die Methode in dem DAO gibt uns also genau das zurück, was in der DB steht: kein Eintrag! Und "null" ist IMO die beste Repräsentation von "kein Eintrag".

Ich habe schon Implementationen gesehen, wo im Fall von "kein Eintrag" eine UnknownEntryException geschmissen wird. Aber das ist ja nicht richtig.
Eine Exception - also eine Ausnahme - impliziert ja, dass ein Fehler aufgetreten ist, der so nicht zu erwarten war. Aber das stimmt ja nicht. Wie bereits erläutert mussten wir ja zwingend davon ausgehen, dass so ein Fall eintreten könnte. Und es ist auch kein Fehler! Einen Eintrag mit einer beliebigen Id in einer DB nicht zu finden ist kein Fehler sondern normal.

Uncle Bob empfiehlt in seinem Buch "special case"-Objects zu verwenden. Aber in dem Fall würde das ja nur noch mehr Verwirrung stiften. Denn es wäre noch schwerer zu erkennen ob er den Eintrag in der DB gefunden hat, und dieser einfach nur leer ist, oder ob der Eintrag nicht existiert. (Special-Case Objekte sind nur dann sinnvoll, wenn ich nicht explizit diese Unterscheidung machen muss, weil sie ggfs. nicht relevant ist)

Prüfen muss ich es in dieser Konstellation nämlich in jedem Fall!
Das heißt, egal was ich mache um das Zurückgeben von "null" zu vermeiden - die Arbeit, den Fall "kein Eintrag gefunden" zu checken, kann ich mir dadurch nicht ersparen!
Wenn ich es doch machen würde, hieße das: ich frage explizit etwas an, schere mich aber einen Dreck um das Ergebnis - das wäre hochgradig Verantwortungslos. (Zumindest in den allermeisten Fällen)

Da wir nun also festgestellt haben, dass wir eine Überprüfung machen müssen, schauen wir doch mal, wie diese am Einfachsten realisiert werden kann (KISS-Prinzip).

Fall 1 - "return null":
ResultObject result = dao.findItemById(42);
if(result == null) {
    return;
}
Ich denke dazu brauch ich nicht viel schreiben: kurz, auf den Punkt, sofort verständlich.


Fall 2 - "throw Exception":
ResultObject result;
try {
    result = dao.findItemById(42);
} catch(UnknownEntryException e) {
    return;
}
Doppelt so viele Zeilen wie in Fall 1.
Es macht den Code außerdem unleserlicher und kann zu Verwirrung führen, da nicht auf den ersten Blick erkenntlich ist, ob hier tatsächlich ein Fehlerfall behandelt wird.
Dass es keine Option ist, das try-catch-statement einfach weg zu lassen, muss ich hoffentlich nicht extra erwähnen (das hab ich oben schon genug getan).


Fall 3 - "Special case object 1"
ResultObject result = dao.findItemById(42);
if(result.getId() == 0) {
    return;
}
Wie ein Special-Case-Object gestalltet wird, ist natürlich nicht fest vorgegeben. Hier also der Fall, dass es einfach ein normales ResultObject mit leeren Feldern ist.
Und wir sehen direkt die Probleme die damit einher gehen! Zum einen ist nicht klar, ob "0" nicht eventuell eine gültige Id sein könnte.
In diesem Fall setzen wir auch voraus, dass die Id, mit der wir anfragen auch Teil des Ergebnis ist. Wenn sie das nicht wäre, könnten wir nicht mehr unterscheiden, ob der Eintrag gefunden wurde und einfach nur leer ist oder ob er nicht gefunden wurde.
Das nächste Problem damit, erläutere ich gleich in Fall 4.


Fall 4 - "Special case object 2"
ResultObject result = dao.findItemById(42);
if(result.isNoEntry()) {
    return;
}
In diesem Fall haben wir dass Problem, dass wir unser Ergebnis-Objekt um eine Methode oder ein Feld erweitern müssen, um diesen Fall explizit abzufragen.
(bzw. wird es sich wohl eher um eine DataStructure handeln, aber in Java gibt es da keine so explizite Unterscheidung, also nennen wir es der Einfachheit halber ein Objekt)
Das führt direkt zu einem weiteren Problem: wie benennt man so etwas? "isEmpty()" wäre z.B. schlecht, weil jemand der den Code ließt, davon ausgehen könnte, es handelt sich um ein Objekt dass von Collection erbt.
Aber auch bei "isNoEntry()" stellt sich die Frage, ob es sich dabei um einen Wert handelt, der so im Eintrag in der DB steht.
Und was das schon in Fall 3 angeteaserte Problem angeht: bei diesem Code wird die verwendete IDE vermutlich anmeckern, dass "result" null sein könnte. Aber selbst wenn nicht: normalerweise arbeitet man ja nicht alleine. Und sobald ein Kollege den Code anfasst, wird ihm (oder ihr) wahrscheinlich auffallen, dass dort ein null-check fehlt! Und dann sieht der Code ganz schnell so aus:

ResultObject result = dao.findItemById(42);
if(result == null || result.isNoEntry()) {
    return;
}
Was fällt uns auf? Genau: der Code sieht so aus wie in Fall 1 - nur mit einem zusätzlichen Aufruf von "isNoEntry"!
Wäre es nicht einfacher, diesen zusätzlichen Aufruf einfach weg zu lassen? Dann könnten wir auch darauf verzichten, das ResultObject zu erweitern.


Schlussfolgerung:
Wenn wir in einem Repository ein bestimmtes Item abfragen, dann ist es sinnvoll "null" zurück zu geben, wenn dieses Item nicht existiert!


Zusätzliche Anmerkung:
Und dass mir jetzt keiner mit dem Vorschlag kommt, doch stattdessen eine Liste mit nur einem Eintrag zurück zu geben!
Nicht nur, dass wir dem Aufrufer (also dem Nutzer des DAO) damit zusätzliche und unnötige Arbeit aufhalsen, weil dieser auch noch den unwahrscheinlichen Fall abfragen muss, falls mehr als ein Item in der Liste ist.
Es ist unnötige Ressourcenverschwendung, dass Item noch mal in eine Liste zu packen, die sonst keinen Zweck erfüllt.
Außerdem wird das API des DAO dadurch kontra-intuitiv und umständlicher zu benutzen (um es mal vorsichtig zu formulieren).

Nachtrag:
Eine zusätzliche Methode im DAO "existsItemWithId(id)" die ein boolean zurück gibt, halte ich im Hinblick auf Performance ebenfalls für untauglich. Die Datenbank ist ohnehin schon das Bottleneck der gesammten Applikation - da muss man nicht noch eine zweite Abfrage absetzen, wenn eine auch reichen würde.

Maven in Eclipse

Wie beschissen das m2e-Plugin in Eclipse ist, sollte ja hinlänglich bekannt sein.

Die letzte halbwegs funktionierende Version von m2eclipse war 0.12.1 - bevor es von Sonatype zu Eclipse gewandert ist und dort bis zur Unbenutzbarkeit verhunzt wurde. Die repositories/update-sites für die alten Versionen gibt es natürlich schon lange nicht mehr. Das Plugin selber funktioniert an und für sich noch mit aktuellen Eclipse-Versionen - sofern das Eclipse-eigene m2e-plugin nicht installiert ist.

Ich war so frei, die nötigen Daten zusammen zu stellen:
m2eclipse-0.12.1.tar.gz

Das Plugin selbst, benötigt noch einige andere Standard-Plugins (wie zum Beispiel die Web- und XML-Tools). Zum "installieren" müssen dann einfach nur noch die Verzeichnisse "features" und "plugins" in das Eclipse Install-dir kopiert werden.

Das ist zwar alles nicht das Gelbe vom Ei, sollte aber um einiges besser funktionieren, als das aktuelle m2e-Plugin, dem immer irgendwelche Module fehlen!

Netbeans oder Eclipse?

Nachdem die letzte Version "Juno" von Eclipse bei mir unterirdisch langsam war - beinahe unbenutzbar - entschied ich mich, nach Jahren, mal wieder Netbeans auszuprobieren.
Um zwei Dinge gleich vorweg zu nehmen: Netbeans ist nicht schlecht und das Problem mit Eclipse kann gelöst werden.
Aber der Reihe nach.

Das erste was bei Netbeans auffällt, ist das SVN-Plugin. Man kann es benutzen, aber es ist nicht sonderlich überragend. Insebsondere fehlt mir der Repository-Explorer, wie ich ihn von Eclipse kenne. Vor allem wenn man mehrere Projekte auschecken will, artet das bei Netbeans schnell in eine ziemliche klick-und-tipp-Orgie aus. Man muss sich für jedes Projekt wieder neu zum Repository verbinden und - je nach Struktur - den Projektnamen noch mal eintippen, weil Netbeans sonst immer nur in das Verzeichnis "trunk" auschecken will. Aber es funktioniert und so oft checke ich persönlich auch keine Projekte aus.

Kommen wir zur Integration von Maven. Das Plugin dafür ist schon dabei und nach dem auschecken fällt auf, dass die Projekte automatisch als Maven-Projekte angezeigt werden. Auch funktioniert das Maven-Plugin mehr wie ein Wrapper (also ähnlich wie die m2eclipse Versionen bis 0.12.1) und funktioniert damit out of the box (ohne das ich erst Plugins für das Plugin downloaden muss, oder Probleme mit lifecycle-mappings bekomme, wie bei den aktuellen m2e Versionen).
Leider fehlen mir ein paar der Features die m2e vorzuweisen hat. Zum Beispiel die Dependency Hierarchy über die man direkt Abhängigkeiten ausschließen kann.

Das Programmieren selber geht in Netbeans gut von der Hand. Das Syntax-Highlighting lässt sich inzwischen überaus filigran einstellen und auch Content-Assist und Compare-View sind überaus gelungen und machen Spaß zu benutzen!

Einen Dämpfer erlebt man jedoch, wenn es ans Testen geht. Netbeans wickelt die Tests auch über Maven ab, sodass man nur die Ausgabe in der eingebauten Konsole hat. Schon alleine in dieser Hinsicht ist die Integration von JUnit in Eclipse um einiges besser. Noch umständlicher wird es allerdings wenn es ans Thema Testcoverage geht. Bei Eclipse muss ich dafür nur das Plugin ECLemma installieren. Bei Netbeans muss ich jedoch die pom.xml meines Projektes erweitern, und dort ein Mavenplugin wie bspw. JaCoCo einbinden. Auch wenn ich den nicht invasiven Weg von Eclipse deutlich bevorzuge, wäre das noch nicht ganz so schlimm - wenn es denn wenigstens funktionieren würde. Tut es aber - zumindest bei den Projekten die ich habe - nicht! Es könnte daran liegen, dass besagtes Plugin Probleme mit multi-module-poms hat - aber dem bin ich dann nicht weiter nachgegangen.

Zusammenfassend kann ich sagen: Netbeans ist wirklich nicht schlecht! Es ist schnell, hat ein paar wirklich nette Features, die einem Arbeit abnehmen und ist einfach zu Bedienen. Die erwähnten Drawbacks haben mich allerdings dennoch wieder zurück zu Eclipse getrieben.

Und damit komme ich wieder zurück zum Anfang. Es stellte sich nämlich heraus, dass die Probleme mit der non-responsive GUI von Eclipse Juno, nur auftreten, wenn man sich das Package "J2EE" (Eclipse IDE for Java EE Developers) gedownloaded hat! Die Classic-version (Eclipse Classic 4.2.1) hat diese Issues nicht, und reagiert gewohnt schnell!