Troyes
Meine neue Heimat ist Troyes, eine kleinere Stadt in Frankreich, etwa eine Stunde entfernt von Paris.
Diesmal ging der Umzug vonstatten, ohne dass meine gesamte IT-Infrastruktur kaputt ging. Wäre auch schlimm, wenn das zur Tradition würde. Was dafür wie immer nicht perfekt lief ist das Internet - diesmal habe wenigstens welches, aber es ist nur ein UMTS-Stick, bis das (frisch gebaute) Haus ans Netz angeschlossen wird. Am Anfang in einem fremden Land, dessen Sprache ich nur ein bisschen spreche, ist das nicht gerade optimal.
Ich bin hierhergezogen, weil mein neuer Job hier ist. Es ist eine Doktorandenstelle im Bereich HCI (eher CSCW) an der UTT, einer technischen Universität, wobei das hier etwas anderes bedeutet als in Deutschland (die Details sind mir nicht ganz klar).
Vielleicht ist uberspace die Lösung des Mailproblems
Dezentralisierung und Verschlüsselung ist der Grundsatzartikel, in dem ich beschrieb, dass und wie ich meine Daten unter meine Kontrolle bekommen will. Oder sie zumindest nicht mehr so direkt wie zuvor an die Geheimdienste fließen sollen.
Es folgte einige Versuche mit Verschlüsselung, wodurch im Ergebnis der Datenfluss zu Dropbox reduziert wurde, Emails unlesbar gemacht werden können, wenn der Kommunikationspartner mitspielt und der PC etwas sicherer ist, sollte hier mal die Polizei vor der Tür stehen.
Was fehlte war die Trennung von Google. Die ist schwer, denn ich bin sehr an deren Dienste gewöhnt, und finde diese noch dazu ziemlich gut. Immerhin war ich nie bei Google+, und dass sie den Reader von sich aus abgestellt haben, macht mir die Trennung wesentlich einfacher - ich passe wohl sowieso nicht mehr in deren Profil.
Aber da war noch die Gmail-Adresse. onli89@gmail.com ist das Zentrum meiner Internet-Identität. Hier läuft alles zusammen, hierüber bin ich erreichbar, diese Adresse gebe ich an, wenn ich nach einer Email-Adresse gefragt werde (und es nicht hochoffiziell ist). Das ändert sich jetzt.
Meine neue Adresse ist onli@paskuda.biz (die alte bleibt als Weiterleitung bestehen). Die Domain liegt bei hosteurope (oh, ich seh gerade, die Preise sind für manche Namen deutlich teurer) und hatte ich sowieso schon. Der Mailserver wird gestellt von uberspace.
Von uberspace wusste ich, dass einige aus der s9y-Ecke den Hoster ziemlich toll fanden. Dann hat auch noch Dirk ihn empfohlen. Mich hat es schließlich überzeugt, dass ich in einem kostenlosen Probemonat alles in Ruhe austesten konnte (der läuft sogar noch). Den Preis danach darf man selbst festlegen, das Minimum ist 1€ im Monat. Das ist ziemlich nett - ich habe keine Ahnung, was meine paar Mails am Tag an Kosten verursachen, aber die dedizierten Emailanbieter waren nicht günstiger. Und geben nicht die Möglichkeit, noch andere Dinge mit dem Server anzustellen.
Das ist aber erstmal gar nicht nötig, denn das Standardsetup von uberspace für Emails ist ordentlich. Mit einem Konsolenbefehl nach SSH-Login kann die eigene Domain als Endung hinzugefügt werden (das klappte bei mir erst nicht ordentlich, nach Wechsel auf den vmailmgr per Mausklick dann aber überraschend doch). Zusätzliche Adressen können in einem übersichtlichen Webinterface angelegt werden. Zum Abholen gibt es IMAP, natürlich verschlüsselt, und alternativ ein https-gesichertes Webinterface. Das ist ein gewöhnliches roundcube, und roundcube ist nicht so toll wie Gmail, aber da es zumindest deutlich schneller als auf meinem Heimserver ist, reicht es dann doch erstmal.
Vielleicht tue ich dem Webinterface sogar unrecht, wenn ich es als übersichtlich bezeichne. Vielmehr finde ich es bisher sehr sehr gut. Es listet einfach ein paar Tabs auf mit den wesentlichen Punkten, dann sind darin Beschreibungstexte mit Links, die Buttons sein sollten, weil sie Dinge machen, und dazu kommen ein paar Widgets, mit denen man Zeug einstellen kann, z.B. Emailadressen generieren und ihre Weiterleitungen einrichten. Für alles kompliziertere gibt es ssh. Man vergleiche das mit dem KIS von hosteurope…
Meine Grundziele sind damit auch erreicht.
Verschlüsselung ist gegeben: GPG für Mails geht immer noch und die Wege zum Mailserver sind mit SSL/HTTPS gesichert. Auf dem Server sind sie ohne GPG natürlich nicht sicher, aber das wären sie nirgends. Und in der Hinsicht hat uberspace einen weiteren Vorteil: Sie erheben keine Kundendaten. Was wirklich sehr sympathisch ist.
Dezentralität ist erfüllt, weil uberspace auswechselbar ist, da die Domain nicht dort liegt. Sollte irgendwas sehr schief gehen, kann ich die einfach umbiegen. Vielleicht dann zu einem selbstgehosteten Mailserver. Außerdem ist es ein weiterer Anbieter, der nicht Google, nicht Yahoo und auch nicht Microsoft ist - was meine Daten etwas vom Zentrum der NSA-Spionage entfernt.
Uberspace macht einen guten Eindruck und sollte von jedem geprüft werden, der seine Emailadresse von den Freemailanbietern wegbewegen will.
Litecoins minen
Bitcoin steht bei 500, und ich habs verpasst. Und es ist nicht so, als ob ich nicht den Anfang der Kryptowährung voll mitbekommen hätte und für wenig Geld einige Bitcoins hätte kaufen können. Damals konnte man sogar noch mit gewöhnlichen Grafikkarten welche minen, was ich sogar mal versuchte, aber mit meiner damaligen etwas exotischen Hardware nicht zum Laufen bekam. Zum in den Arsch beißen.
Bitcoins kann man heute nicht mehr mit normaler Hardware vernünftig minen, aber Litecoin, die zweitverbreiteste Kryptowährung (1 LC sind momenten 8$), ist dafür ausgelegt. Ich werde beschreiben, wie man das unter Linux einrichten kann (mein Testsystem ist ein Ubuntu 12.04). Diese Beschreibung ist wahrscheinlich nicht optimal, denn mangels vernünftigem Internet ist meine Recherchefähigkeit derzeit massiv eingeschränkt. Wer ihr trotzdem folgt, und sei es um ein eigenes zukünftiges Bitcoin-Debakel zu verhindern, wird daher hiermit höflichst gebeten, Korrekturen im Kommentarbereich vorzutragen.
0. Grundlagen
Die Anleitung sollte für alle passen, die eine AMD-Grafikkarte besitzen. Grundlegende Erfahrung im Umgang mit dem Terminal und der Kompilierung von Zeug ist empfohlen. Ich habe mir einen Ordner ~/litecoin/ angelegt, in den alles wichtige reinkopiert wurde, und werde die Anleitung der Einfachheit halber entsprechend schreiben.
Es ist nicht zwingend nötig, den PC beim Minen in Ruhe zu lassen - zumindest mir der für meine Grafikkarte bis jetzt optimal erscheinenden Konfiguration ist das System problemlos parallel nutzbar.
1. Litecoin-qt installieren
Litecoin-qt ist die wohl vom Bitcoin-Client geforkte Software, mit der Wallets und Adressen erstellt und Transaktionen durchgeführt werden können. Eine Adresse brauchen wir, um dort die produzierten Litecoins hinzusenden. Es ist ein P2P-Programm und muss nach der Installation erstmal die aktuelle Transaktionsliste durcharbeiten, das kann etwas dauern. Darauf muss aber nicht gewartet werden, alles für uns relevante funktioniert direkt.
Wer den Bitmessage-Client kennt, wird hiermit schnell zurechtkommen.
Der Client findet sich auf litecoin.org. Das tar.xz für Linux ist vorkompiliert und muss nur noch nach ~/litecoin/ entpackt werden. Ein ~/litecoin/litecoin-0.8.5.1-linux/bin/32/litecoin-qt
startet ihn, s/32/64/ wenn ein 64-Bit-Kernel genutzt wird. Im Empfangen-Tab sollte direkt eine Adresse vorhanden sein, diese kann per Knopfdruck ins Clipboard kopiert werden.
Ich habe bei der Gelegenheit meinen Wallet direkt verschlüsselt.
2. Pool auswählen
Ich habe mich für mine-litecoin entschieden. Ich fand die Webseite gut durchschaubar und die dort präsentierte Anleitung sehr hilfreich. Ich baue hier teilweise auf ihr auf. Auf der Webseite (oder einem alternativen Pool) sollte man sich nun registrieren und einen Worker (die Repräsentation und die Zieladresse des PCs) anlegen.
Meinem Verständnis nach kann man auch ohne Pool minen, es ist aber nicht empfehlenswert, weil man alleine nur selten einen Block lösen wird (siehe auch hier).
3. cgminer installieren
cgminer ist nun das Programm, mit der auf der Grafikkarte die Litecoins errechnet werden können. Der Fokus des Entwicklers Con Kolivas (ich wusste gar nicht, dass der wieder aktiv ist) liegt auf Bitcoins. Da diese mit Grafikkarten nicht mehr effizient produziert werden können, hat er aus neuen Versionen die Unterstützung für Grafikkarten entfernt. Die letzte dafür geeignete ist 3.7.2. Diese sollte heruntergeladen und schonmal entpackt werden.
64-Bit-Nutzer können jetzt vielleicht die vorkompilierte Version direkt nutzen, das habe ich nicht getestet.
3.1 Pakete
Zur Kompilierung benötig es ein paar Pakete. In der Readme gelistet werden:
libcurl4-openssl-dev pkg-config libtool
Bei mir war schon alles da.
3.2 ADL SDK
Das ADL-SDK gibt es momentan in Version 6 auf dieser AMD-Seite. Es wird von cgminer benutzt, um den Taktung und die Temperatur der Grafikkarte zu überwachen, ist also unabdingbar.
Das zip herunterladen und entpacken (Hinweis: Es hat keinen Hauptordner, den also vorher anlegen). In dem Archiv befindet sich ein Ordner namens include, dessen Inhalte müssen im entpackten cgminer-Verzeichnis in das Verzeichnis ADL_SDK, also nach ~/litecoin/cgminer-3.7.2/ADL_SDK/.
3.3 AMD APP SDK
Hier wird es ein bisschen unschön. In jeder von mir gefundenen Anleitung, inklusive der Readme von cgminer, steht, dass dieses SDK unbedingt installiert werden muss. Es werde benötigt, um OpenCL zu nutzen, mit dem die Berechnungen auf der GPU durchgeführt werden. In der Bescheibung der aktuellen Version des SDK steht jedoch, dass diese Funktion inzwischen in den Catalyst-Treiber integriert wurde, und das SDK nur noch für Berechnungen auf Prozessoren notwendig sei.
Das sah ich aber erst nach der Installation. Es ist also möglich, dass dieses Accelerated Parallel Programming SDK nicht mehr nötig ist, wenn der fglrx-Treiber in einer aktuellen Version vorliegt. Das wäre momentan fglrx-amdcccle-experimental-13. Und das würde ebenso bedeuten, dass für altere fglrx-Treiber eine Version des SDKs vor 2.9 installiert werden muss.
Meine Empfehlung ist, fglrx-amdcccle-experimental-13 zu installieren und die Kompilierung zu starten. Wenn sie nicht funktioniert, weil das APP-SDK nicht gefunden werden kann, dann kann das SDK immer noch installiert werden.
Das ginge so: Das Archiv herunterladen, entpacken (Hinweis: Es hat wieder keinen Hauptordner) und ein
sudo ./Install-AMD-APP.sh
ausführen. Das SDK wird dadurch nach /opt/AMDAPP/ installiert.
3.4 Kompilierung
Jetzt sollte alles zusammen sein. Wir müssen nur noch in das cgminer-Verzeichnis wechseln und dort das Kompilieren starten:
CFLAGS="-O3 -Wall -march=native" ./configure --enable-opencl --enable-scrypt make
Wenn es gelingt, sollte eine ~/litecoin/cgminer-3.7.2/cgminer erstellt werden. Ich habe sie dort einfach liegen lassen, alternativ würde ein checkinstall
oder ein make install
sie ins System kopieren.
4. Das Minen starten
Die Litecoin-Adresse ist generiert, der Pool kennt sie und ist eingerichtet, cgminer kompiliert: Es kann losgehen. Dafür starten wir die ~/litecoin/cgminer-3.7.2/cgminer mit dem Befehl, den der Pool sicher empfehlen wird. Bei mine-litecoin steht das wieder auf der Startanleitung. Er lautet:
./cgminer --scrypt -o stratum+tcp://europe.mine-litecoin.com -u YourUsername.WorkerName -p x
Jetzt sollte cgminer nach einer kurzen Verzögerung seine Oberfläche zeichnen und anfangen zu rechnen.
Mit diesem Startbefehl arbeitet cgminer mit minimalen Ressourcenaufwand, es ist nicht die optimale Konfiguration. Hinweise zu guten Konfigurationen stehen auf der Hardware-Vergleichsseite im Litecoin-Wiki. Bei mir trafen sie aber prompt nicht zu, deshalb erkläre ich kurz das Ziel.
Die beiden wichtigen Ausgaben habe ich rot markiert. Beide sollten so hoch wie möglich sein. Die erste ist die Rate der errechneten Hashs (Kh/s). Die zweite ist die Rate der akzeptierten Shares (Work Utility). Ich fand irgendwo die Aussage, dass die beim Litecoin-Minen etwa bei 0.9 * Kh/s
liegen sollte. Und genau das funktionierte mit der hohen Intensität der empfohlenen Konfiguration nicht: Denn während meine Karte locker 300 Kh/s erreichen kann, blieb die WU bei irgendwas um die 110. Wahrscheinlich produzierte sie zu viele Fehler.
Damit kann natürlich wunderbar experimentiert werden. Meine 210 Kh/s sind für die Karte beispielsweise doch verdammt wenig, ich werde sicher bald neue Parameter ausprobieren. Die stärkste Stellschraube scheint aber -I
, also schlicht die Intensität zu sein.
Als Startpunkt zeige ich mein Startskript für meine HD 7850:
#!/bin/sh export GPU_MAX_ALLOC_PERCENT=100 export GPU_USE_SYNC_OBJECTS=1 ~/litecoin/cgminer-3.7.2/cgminer --scrypt -o stratum+tcp://europe.mine-litecoin.com -u Nutzer.Worker -p x --thread-concurrency 4196 -I 12 -g 1
Auch dazu nehme ich natürlich gerne Hinweise entgegen. Bei mir läuft eine wohl etwas gestutzte Version von Club3D, was den Wert der Thread-Concurrency erklären würde, denn der sollte bei dieser Karte eigentlich doppelt so groß sein dürfen.
5. Fertig
Das ist alles. Litecoin-qt herunterladen und starten, einem Pool beitreten, und cgminer zum laufen bringen, was mangels verfügbarem .deb sicher der schwierigste Part ist und am Ende noch etwas Konfigurationsarbeit erfordert. Aber jetzt sollte alles eingerichtet sein und cgminer fröhlich vor sich hin werkeln.
Unter Windows ist es übrigens genau der gleiche Ablauf, nur dass cgminer dort nicht kompiliert werden muss, und das APP-SDK mit einem typischen Windows-Installer kommt - ausnahmsweise eine gute Sache.
Sollte hiermit jemand überraschend reich werden: Meine Litecoin-Adresse ist LcinsJSQ2bfdyFGwjrTsqAHdpYFC2CXeLg
Das URL-Input-Element verbessern: http:// voranstellen
HTML5 ist mit all den verschiedenen type-Attributen für das Input-Element auf einem guten Weg. So toll es aber auch ist, Client-Validierung für Email/Telefon/URL-Eingaben zu haben, so ärgerlich ist es, wenn dies eher zur Hürde wird. Beim s9y-Alphatest passierte dies - die URL werde als nicht valid angemeckert, obwohl sie richtig sei, war eine berechtigte Beschwerde. Das lag am fehlenden http://. Wenn man aus Chrome und FF eine URL kopiert, wird das zwar vorgestellt. Aber angezeigt wird es nicht mehr. In den Köpfen einiger Nutzer - und auch ich selbst bin darüber schon gestolpert - ist dieses http:// nicht mehr präsent. Was ja auch richtig ist, muss man es doch zurecht nirgends mehr eingeben, ist es doch immer impliziert. Außer beim URL-Input-Element von HTML5.
Leider gibt es dafür wohl auch keine integrierte Lösung. Es muss wieder Javascript herhalten, um das http:// voranzustellen, wenn es vom Nutzer vergessen wurde. Dafür eignet sich das change-Event:
if (document.getElementById('urlInput') != null) { var urlInput = document.getElementById('urlInput'); urlInput.addEventListener('change', function() { if (urlInput.value != "" && ! (urlInput.value.substr(0,7) == "http://" || urlInput.value.substr(0,8) == "https://")) { urlInput.value = "http://" + urlInput.value; } }); }
HTML5: Autovervollständigung für Texteingabefelder mit simuliertem multiple
Browser können schon länger einmal in Dateifelder eingegebene Daten speichern, z.B. den Loginnamen und Passwort, und das kann im HTML mit autocomplete
gesteuert werden. Dies ist aber nicht, was ich im Folgenden mit Autovervollständigung meine. Ich wollte eine Tageingabe bauen und dafür im System vorhandene Tags autovervollständigen. Das kann HTML5, dafür gibt es das datalist-Element. Das sollte in modernen Browsern auch durchaus funktionieren:
Nur: Wie kann so eine kommaseparierte Liste vervollständigt werden? Wie kann mehr als ein einzelnes Wort vervollständigt werden? Zwar gibt es das Attribut multiple
, doch ist das für type="text"
nicht gültig. Mit Hausmitteln kann HTML5 das also nicht.
Aber: Mit ein bisschen Javascript kann das nachgerüstet werden. Die Idee samt Demo fand ich hier: Sobald ein Wort eingegeben wurde, bzw immer wenn ein Separator getippt wurde, muss nur die Datalist mit der bisherigen Eingabe ergänzt werden.
Also: gegeben sei ein Eingabefeld und eine Dataliste<input class="entryTagInput" type="text" list="tags" name="tags" placeholder="tag1, tag2, …"></input> <datalist id="tags> <option value="tag1"> <option value="tag2"> <option value="tag3"> </datalist>Dann braucht es nur noch diesen Brocken Javascript, um eine kommaseparierte Tagliste autozuvervollständigen
var tagInput = document.querySelector('.entryTagInput'); var rawTags = document.querySelector('#tags').cloneNode(); var tags = document.querySelector('#tags'); var oldTagInputValue = ""; tagInput.addEventListener('input', function(evt) { var autocompleteOccured = false; if (tagInput.value.length - oldTagInputValue.length > 1) { autocompleteOccured = true; tagInput.value += ", "; } if (tagInput.value.substr(-1) == ',' || autocompleteOccured) { tagOptions = []; for (var i=0;i < rawTags.options.length;i++) { var newTag = document.createElement('option'); if (autocompleteOccured) { newTag.value = tagInput.value + rawTags.options[i].value; } else { newTag.value = tagInput.value + " " + rawTags.options[i].value; } tagOptions[i] = newTag; } while (tags.hasChildNodes()) { tags.removeChild(tags.lastChild); } for (var i=0;i < tagOptions.length;i++) { tags.appendChild(tagOptions[i]); } } if (tagInput.value == "") { // user has deleted all tags tags.parentNode.appendChild(rawTags); tags.parentNode.removeChild(tags); tags = document.querySelector('#tags'); rawTags = document.querySelector('#tags').cloneNode(); } oldTagInputValue = tagInput.value; });In der
rawlist
-Datalist bleiben die originalen Tags, in tags
werden die neuen Permutationen gespeichert, was im Input-Event der Tageingabe immer dann geschieht, wenn ein Komma eingegeben oder die Autovervollständigung genutzt wurde. Dass sie genutzt wurde kann leider nur festgestellt werden, indem geschaut wird, wieviele Zeichen auf einmal eingegeben wurde. Da gibt es leider scheinbar kein eigenes Event für.
Ajax Datei-Upload mit HTML5 (und Sinatra)
Bilder ohne Formular hochladen, ohne auf Flash zurückzugreifen, am besten mehrere auf einmal. Das geht mit HTML 5 und einer Prise Javascript recht einfach.
Die Grundidee
Das Input-Element von HTML 5 kann mehrere Dateien hochladen. Daher wird das genutzt, aber versteckt und aktiviert, indem ein anderes Element angeklickt und dann dieses den click
-Event des File-Input-Elements auslöst. Nach der Dateiauswahl wird den change
-Event ausgelöst, an der Stelle können die Daten der Dateien abgefangen, einem FileReader
übergeben und dann an den Server gesendet werden.
Anleitung
Zuerst braucht es ein Datei-Input-Element. Da das nur mit aktiviertem Javascript funktionieren wird, füge ich das auch per JS ein:
var imgButtonInput = document.createElement("input"); imgButtonInput.className = "imgButtonInput"; imgButtonInput.type = "file"; imgButtonInput.multiple = "multiple"; imgButtonInput.names = "images"; imgButtonInput.accept = "image"; body.appendChild(imgButtonInput);
Das Input-Element als Formularelement ist aber zur Darstellung hier relativ ungeeignet. Deshalb wird das versteckt und ein alternatives Element hinzugefügt:
.imgButtonInput { position: absolute; left: -9999em }
var imgButton = document.createElement("span"); imgButton.innerHTML = "IMG"; body.appendChild(imgButton);
Ein Klick darauf soll das File-Input-Element auslösen:
document.querySelector('.imgButton').addEventListener('click', function(evt) imgButtonInput.click(); };
Dessen change-Event muss dann die Dateien dem Server übergeben:
imgButtonInput.addEventListener('change', function() { for (var i = 0; i < files.length; i++) { var f = files[i]; (function(f) { var reader = new FileReader(); reader.addEventListener("load", function(event) { object = {}; object.filename = f.name; object.data = event.target.result; var options = { method: 'post', url: '/file', data: object } snack.request(options, function (err, res) { if (err) { alert("error uploading file: " + err); } alert("file uploaded"); }); }); reader.readAsDataURL(f); })(f); } });
Ich benutze hier snack, um den Ajax-Request etwas zu verschönern, das geht mit jQuery fast äquivalent und der Code ohne Library sollte auch keine Problem sein.
Auf Serverseite, bei mir Ruby/Sinatra, müssen die Dateien dann nur noch entgegengenommen und gespeichert werden:
post '/file' do protected! data = params[:data] filename = params[:filename].gsub("..", "") data_index = data.index('base64') + 7 filedata = data.slice(data_index, data.length) decoded_image = Base64.decode64(filedata) target = File.join(settings.public_folder, filename) until ! File.exists?(target) if target.scan(".").size > 1 # assume the filename is a classical xy.abc target = target.reverse.sub('.','._').reverse else target = target + "_" end end file = File.new(target, "w+") file.write(decoded_image) target.gsub(settings.public_folder, "") end
Quellen:
- http://www.skuunk.com/2011/04/reading-ajax-xhr-file-uploads-in.html
- http://www.nickdesteffen.com/blog/file-uploading-over-ajax-using-html5
- https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
Alternativ kann man natürlich auch eine vorgefertigte Lösung wie den fine-uploader benutzen. Und sich damit rumärgern, von der Githubseite für die Installationsanweisung zur Webseite verwiesen zu werden, die wiederum auf die Downloadseite verweist, wo es aber nur die Bezahlversion gibt. Um dann das Github-Repo zu ziehen und festzustellen, dass das bescheuerte Build-System unter Ubuntu nicht funktioniert.
Schlanke Controller und Fehlende Manager
Auf Wunsch beschreibe ich im Folgenden, was ich mit diesem Satz über dsnblog meinte:
Dazu kam der Versuch, eine bestimmte Form der objektorientierten Programmierung durchzuziehen: ohne Managerklassen, und mit einem kleineren Controller durch Initialisierung bei Objekterstellung in den Objekten selbst
Dazu muss ich ein bisschen ausholen, denn ohne Kenntnis des Hintergrunds ergibt das alles wahrscheinlich wenig Sinn.
Ich habe Informatik studiert, aber Programmieren schon vorher gelernt - auch wenn ich im Studium natürlich viel dazugelernt habe. Natürlich wurden wir im Studium mit Patterns konfrontiert, aber von denen war und bin ich nicht überzeugt. Klar, ein Singleton kann nützlich sein, und das benutze ich auch. Aber Dinge wie eine Factory oder eine Factory-Factory bringen mich dazu, schreiend davonzurennen. Vielleicht ist das gar nicht so unüblich für Menschen, die Programmieren größtenteils mit Bash gelernt haben…
Ich bin überzeugt, dass viele der Patterns - so wie man sie an der Uni lernt zumindest - dazu führen, dass der produzierte Code tausendmal schlechter ist, als wenn ein halbwegs ordentlicher Programmierer sich auf seine Intuition verlässt. Ich muss wohl hinzufügen, dass ich mich für einen nicht besonders guten Programmierer halte, daher schon länger mir Hilfen zusammensuche, um meine Intuition zu ergänzen.
Meine Ablehnung von Pattern schließt das MVC-Pattern ein, aber nur teilweise. Denn ich finde die Grundidee gut: Die Präsentation von der Logik trennen, das macht meistens Sinn. Aber die konkrete Ausgestaltung des Patterns variiert in jeder Implementation, es ist meiner Wahrnehmung nach das meistbenutzte Pattern überhaupt, das keine zwei Programmierer im Universum gleich verstehen. Bedeutet für mich: Wenn ich meinen Code für meine Projekte halbwegs ordentlich strukturieren will, muss ich meine eigene Architektur schaffen, die ich für gut erachte - denn am MVC kann ich mich nur an der Grundidee grob orientieren.
Das ist die Ausgangslage: Gute Architektur durch Trennung von Datenbank-Ebene, Code-Ebene und Präsentation, dann ein klar definiertes Kontrollschema, und das ganze so objektorientiert wie sinnvoll, das ist das Ziel.
Fehlende Managerklassen
Einer dieser Grundpfeiler meiner eigenen Architektur ist die Database-Klasse, bei meinen Ruby-Projekten in der database.rb. Meistens benutze ich sqlite, aber ich möchte meinen Code schon so schreiben, dass ich die Datenbank leicht auswechseln kann. Deshalb beinhaltet die Klasse eine Reihe von Funktionen, mit der auf die Daten zugegriffen wird - beispielsweise eine addEntry(entry)
. Dem Rest des Programms ist es dadurch egal, was genau die Datenbank da macht, und beinhaltet keinen einzigen Fitzel SQL.
Nur: Wer spricht mit der Datenbankklasse?
Die erste Version meines Hardwareempfehlers war ein Java-Programm. Auch dort gab es diese Database-Klasse, aber zusätzlich gab es eine zweite Zwischenebene: Die Warehouse-Klasse, eine Managerklasse, die beispielsweise die gesamte Hardware unter einem bestimmten Preis holte, sortierte und in passender Form für den Recommender aufbearbeitete. Ein Request eines Nutzers ging also erst an den Server, dann an den Recommender, dann an das Warehouse, dann an die Datenbank, dann konnte der Recommender arbeiten, und schließlich der Server die Seite rendern. Das gefiel mir nie so richtig, schon weil das Warehouse immer mehr an Funktionen in sich sammelte und noch dazu die Klassen der Parameter (Hardware, Cpu oder Gpu? Oder Hardware samt generic?) irritierend wurden.
Ich glaube, man sieht leicht: Das war weder schön noch simpel noch effektiv. Und das ist sogar noch unvollständig. Einfach keine gute Architektur.
Dann stolperte ich über diesen Ratschlag:
Don't make objects that end with 'er'.
Und ich sponn das weiter. Es geht mir nicht um die Benennung, und ich glaube, es geht auch den Autor des Artikels nicht um die Benennung. Es geht darum, Objekte zu bauen, die mehr sind als ein Datenbehälter mit vielleicht ein-zwei Funktionen, die von einer Managerklasse gesteuert werden. Es geht darum, komplette und lebensfähige (self-sufficient) Objekte zu bauen, die ihre Funktion soweit möglich komplett beinhalten.
Mit Ausnahme von Dingen wie der Trennung zwischen Anwendungsebene und Datenbank, natürlich.
Für den Hardwareempfehler fiel dadurch die Warehouse-Klasse weg, und der Recommender wäre das nächste Ziel, wollte ich konsequent sein. Das war zuviel Aufwand, aber für dsnblog konnte ich es von Anfang an beherzigen, daher ist das näher an dem Ideal.
Dementsprechend gibt es in dsnblog keine "EntryManager"-Klasse, die dafür sorgt, dass die Entry-Objekte mit den richtigen Daten initialisiert und geordnet werden und wann sie in der Datenbank gespeichert werden und wann nicht. Teilweise spielt da zwar die server.rb rein, der Controller, aber generell ist damit gemeint, dass natürlich die entry.rb eine Funktion sendTrackbacks(request)
hat, statt dass eine Trackbackmanagerklasse einen Entry - oder gar eine Liste von Links - bekommt und die Trackbacks dann absendet.
Das ist ein fortwährender Abwägungsprozess. Folgt man dem Motto zu arg, kann es passieren, dass Funktionalität doppelt geschrieben wird. Oder die Erweiterbarkeit nicht so einfach gegeben ist. Beispiel dafür ist meine Markup-Sprache: Momentan ist das auch im Entry, und man kann durchaus argumentieren, dass das auf die Template-Seite gehört, und natürlich muss das für Kommentare auch noch kopiert werden. Andererseits ist es doch desöfteren so, dass es da kleine Unterschiede gibt - Kommentare müssen z.B. keine Bilder einbinden können, sollten es sogar nicht können. Also ist es vielleicht gar nicht so schlecht, wenn sie direkt ihre eigene Formatierungsfunktion dafür bekommen. Wie gesagt, ständiger Abwägungsprozess.
Bei dsnblog war ich im Endeffekt sehr glücklich mit dem Ergebnis dieser Regel, weil es dazu führte, dass ich neben database.rb und server.rb nur die Kernklassen hatte: Eine Klasse Entry und eine Klasse Comments, dazu eine Klasse CommentAuthor (die ich im Nachhinein nicht so toll finde). Genau das, woraus ein Blog besteht, und alles, was in der Datenbank gespeichert und in der View abgebildet werden muss.
Schlanke Controller
Eine weitere Konstante meiner momentanen Architektur ist die server.rb. In Ruby/Sinatra ist die bei der klassischen Variante wohl generell üblich, aber ich hatte genau so eine Klasse auch schon im Java-Code. Für mich ist sie der Controller: Hier wird festgelegt, was bei Aufruf welcher URL passiert. Dazu kommen ein paar Hilfs-Funktionen und der Cache ist hier implementiert, aber URL->Funktionalität, das ist die Grundaufgabe.
Diesen Controller schlank zu halten ist eine stete Herausforderung. Denn es ist ziemlich verlockend, relativ viel Funktionalität einfach hier zu erledigen. Aber zum einen könnte man dann auf Objekte gleich verzichten, und man schafft einen Blob, eine viel zu große Sammlung an Code.
Stattdessen versuchte ich in dsnblog, alle Funktionalität auszulagern. Ein Beispiel:
post '/addEntry' do protected! entry = Entry.new(params, request) redirect "/#{entry.id}/#{URI.escape(entry.title)}" end
Das ist der gesamte Code des Controllers, um einen Artikel anzulegen. Es wird einfach ein Entry-Objekt erstellt, und das Objekt weiß selbst, was es zu tun hat. Das funktioniert natürlich besonders gut in Verbindung mit dem Verzicht auf Managerklassen von oben, aber auch mit Managerklassen wäre das möglich - die Daten könnten ja auch einer EntryFactoryManagerFactory übergeben werden.
Ruby macht das nicht unbedingt ganz einfach, denn die entry.rb soll darauf reagieren, wieviele Argumente es erhält. Bekommt es nur eins, weiß die Klasse, dass es eine ID bekommen hat, und kann sich mithilfe der ID aus der Datenbank initialisieren. Bekommt es zwei, weiß sie, dass es um einen neuen Eintrag geht. Aber Ruby kann keine Funktionen überladen, auch nicht den Konstruktor. Deshalb muss ich den so definieren:
def initialize(*args) case args.length when 1 initializeFromID(args[0]) when 2 # creating entry from params and save in database … end end
Muss man übrigens auch nicht schön finden, das über die Anzahl der Argumente zu steuern, aber alternativ könnte man ja auch den Inhalt des Arguments nehmen, wie das beispielsweise die Preview derzeit macht. Wobei die Ansätze zu mischen an sich unschön ist, aber ich brauchte einen Toggle, um das Abspeichern in der Datenbank und Absenden der Trackbacks zu verhindern und trotzdem den Konstruktor nutzen zu können.
Ich habe mich da übrigens nicht immer konsequent dran gehalten. Die Datei-Hochladenfunktion ist beispielsweise komplett in der server.rb implementiert, weil es dazu keine passende Klasse gibt, oder ich sie nicht sah, und der nötige Code so kurz ist.
Aber trotz der Inkonsequenz: Das sofortige Weiterreichen der Eingabedaten an die Konstuktoren der Objekte, statt leere Objekte zu konstruieren und dann im Controller zu befüllen und am besten dort noch zu managen (z.B. indem sie danach der Datenbank zum Speichern übergeben werden), führt dazu, dass die server.rb mit ihren 400 Zeilen vertretbar schlank ist.
Fazit
Ich kann das ganze auch kurz fassen: Ich suche immer noch nach einer guten Standardarchitektur für Webanwendungen, und fand es bei dsnblog sinnvoll, Logik statt in Managerklassen in die Objekte selbst zu legen und den Controller Daten einfach nur durchreichen zu lassen, sodass die Objekte sich auch noch selbst initialisieren müssen. Die so entstehende objektorientierte Architektur gefällt mir gut, insbesondere für einen Blog, dessen Einträge und Kommentare in dieser Form schön abbildbar sind.
Ich bin dabei wahrscheinlich beeinflusst durch eine Reihe von Artikeln, über die ich durch HN gestolpert bin, die ich aber nicht aufzählen kann. Wobei viele auf HN vorgestellte Projekte das sowieso völlig anders machen.
dsnblog
Die Suche behauptet, dass ich dsnblog hier noch nicht richtig vorgestellt habe. Das ist vielleicht auch ganz gut so, denn der Charakter dieser Software hat sich die letzten Tage ein bisschen geändert.
Ursprünglich war das ein kleines Blogsystem, das ich als Fingerübung mit Ruby/Sinatra bauen wollte. Dann benutzte ich es als Grundlage für eine Hausarbeit in einer Vorlesung und erweiterte es dafür um Funktionen eines sozialen Netzwerks (Freundeslisten z.B.). Das habe ich jetzt wieder herausgenommen, weil diesen Part stabil hinzubekommen würde ewig dauern, und dafür die Software weiter in Richtung normales Blogsystem entwickelt.
Also: Es ist ein kleines Blogsystem mit den grundlegenden Funktionen (Kommentare, Trackbacks und Pingbacks, Tags), aber auch mit ein paar Änderungen vom Standard, wie einer bis jetzt konsequenten Frontend-Administration, Mozillas Persona als Loginsystem oder integriertem Bayes-Spamfilter, automatischer Titelerkennung für Links, und Caching. Dazu SQLite als Datenbank und ein schlichtes Design.
Wird das die Welt verändern? Sicher nicht. Ich bin mir nichtmal sicher, ob das ein ernsthaftes Projekt ist - serendipity ist ja mehr als ausreichend für meinen eigenen und viele weitere Blogs. Es ist nur eines von unzähligen Blogsystemen, aber mir hat es Spaß gemacht, meine eigene Architektur aufzubauen und die Funktionen, die ich von s9y gewöhnt bin - oder die ich gerne mal ausprobieren wollte - umzusetzen. Dazu kam der Versuch, eine bestimmte Form der objektorientierten Programmierung durchzuziehen: ohne Managerklassen, und mit einem kleineren Controller durch Initialisierung bei Objekterstellung in den Objekten selbst. In meinen Augen hat das ganz gut funktioniert.
Vielleicht hat ja jemand Interesse, hiermit ein bisschen zu spielen.
Willkommen in der Zukunft
Man mag es angesichts meines zarten Alters kaum glauben, aber ich bin tatsächlich nicht erst seit ganz kurzer Zeit dabei. Sicher, ich kann keinen grauen Bart vorweisen und auch keine Geschichten darüber erzählen, wie ich damals Suse 1.3 mit 20 Disketten auf meinem Heimrechner mit 4 MHz und 2KB Ram installiert habe. Aber wenn ich mir mein Damals anschaue und dagegen das Jetzt setze, dann hat sich doch ein bisschen was getan.
Heute zum Beispiel ist eine meiner Zukunftsutopien wahr geworden. Als ich damals als Supporter im Spieleforum unterwegs war, waren die wenigen zuverlässig per Wine laufenden (eigentlich nur WoW) und einige bessere - aber doch zumeist obskure - nativ unter Linux laufende Spiele die Höhepunkte (eher obskur z.B. Regnum, aber es gab auch das ernsthaft hervorragende Freespace 2 mit verbesserter Grafik). Dazu noch eine ATI-Karte, mit der insbesondere Wine fast nie zurechtkam, und das Thema Spiele unter Linux war nach Durchspielen der guten Ausnahmen eigentlich schnell gegessen. Führte bei mir zum einen dazu, dass Windows im Dualboot beibehalten wurde, und bewirkte zudem, dass die ganze Computerspielsache etwas weniger interessant wurde, jedoch ohne je ganz zu verschwinden. Denn gleichzeitig malte ich mir wie so viele eine Zukunft aus, in der neue Spiele nicht nur für Windows, sondern auch für Linux veröffentlicht werden. In der man von einem neuen Spiel hört und die Frage, ob es unter Linux läuft, nicht absurd ist - sondern mit ja beantwortet wird.
Genau das ist mir gerade passiert. Ich stolperte über No More Room in Hell, google es, lese etwas von Linux, und tatsächlich, es ist per Steam installierbar. Wird gerade geladen.
Ja, es war ein Glückstreffer, weil es die Source-Engine benutzt, weil es kostenlos und ein Indie-Spiel ist (die seit letzter Prägung des Begriffs desöfteren unter Linux laufen) und trotzdem Schlagzeilen machte. Nur ein Zombie Spiel, dazu eines mit ethischen Bedenken. Und doch, für mich ein ganz kleines Stück mir sehr willkommener Zukunft.