Posts in category code

Lockless Algorithms

Schon lange habe ich keine so passende Visualisierung mehr für etwas gesehen:

Lockless Algorithms / Schlosslose Algorithmen:

http://i.imgur.com/0MfvQb3.gif

 via devopsreactions

Unsere Obsession mit der Shell

Grade dachte ich mir ich mache mal wieder zum Spaß etwas an einem Open Source Projekt, checke es aus und will es mal bauen um etwas im Code herum zu lesen - und das erste was mir passiert ist das der Build-Prozess einfach irgendwann verstirbt.

Nach einiger Debugging Zeit stellt sich dann heraus das das selbstgestrickte build-system (eine Sammlung von Shell-Scripten) halt nicht mit spaces im Pfad zu den Sourcen klarkommt.

Gnarf! Wieso macht man sowas in Shell? Muss das sein?

Ok, aber es gibt ja workarounds, und die kenne ich ja auch und die lassen sich ja auch relativ einfach einbauen. ABER: Nicht mal die Abhängigkeiten ([ pkg-config] in diesem Fall) baut mit spaces im Pfad.

ARGH!

Und natürlich haben die ein [ Autohell] buildsystem an das ich nun wirklich nicht ranfassen will. :-(

Wie sollen wir als Softwareentwickler eigentlich irgendwann zu dem Punkt kommen wo unsere Software auch nur so etwas einfaches wie Pfade korrekt verarbeitet - also mit Leezreichen, Umlauten und Sonderzeichen (jep '/' ist auch gemeint) - wenn nicht mal unsere Eigenen Build-Tools und Shell-Sprachen damit verlässlich klar kommen? Also die Basis auf die einfach immer wieder zurückgegriffen wird?

Unglaublich.

Objective-C Metaprogrammierung: Blöcke zu Methoden

Die Ruby Welt verwendet Blöcke (Closures) liebend gerne für alles mögliche. Zum Beispiel als Builder-Methapher um Baumstrukturen (XML, GUI's, HTML, Tests) in der Sprache hinzuschreiben und dann nur noch in die Target-Sprache zu rendern.

Das sieht in Tests zum Beispiel so aus:

describe "something" do

  it "should do fnord" do
    someObject.should be_fnordy
  end

end

Der Trick dabei ist das alles von do bis end jeweils ein Block ist der von der Methode describe oder it dann in eine UnitTest Klassenstruktur eingehängt wird um dann später als 'ganz normale' unit tests ausgeführt zu werden.

Jetzt wo Objective-C auch Blöcke unterstützt (ok, die können natürlich weniger als das Ruby Equivalent) müsste das eigenltich auch gehen - und siehe da mit  Cedar gibt es auch schon einen ersten Versuch  RSpec in Objective-C nachzubauen.

Well und daher habe ich mir mal angeschaut wie weit man denn kommt wenn man in Objective-C einen Block in eine Instanz-Methode umwandeln will.

Gleich vorneweg - das Typ-System von Objective-C macht mir hier einen kleinen Strich durch die Rechnung - ich habe es nicht geschafft einen Block nicht direkt als Funktions-pointer verwenden.

Aber mit etwas Umweg geht es doch.

Der Trick ist das Blöcke auch id's sein können, d.h. man kann sie bequem in ein NSMutableDictionary packen.

Also brauche ich auf meiner Klasse nur ein Dictionary, speichere die Blöcke darin mit dem Namen der Methode ab und baue mir einen generischen Dispatcher-IMP der den Selector (zweites unsichtbares Argument jeder Objective-C Methode) verwendet um den Block aus aus dem Dictionary zu ziehen und führe ihn dann einfach aus.

So sieht dass dann aus

jQuery editInPlace

Well, I just finished some major reworking of that jQuery plugin, so now it has a real testsuite and conforms to the  jQuery Plugin Guidelines and doesn't pollute the core prototypes (of String) anymore.

There are a few new features, most prominent the ability to define a class to apply for the hover effect (so you can style the hover in css instead of having to hand in the colors directly and more control over the way errors are presented so it is easier to embed into bigger applications.

So enjoy  the demo and  the download while they are hot, and keep a bookmark to  the project homepage. :)

Stuff I'd like to note:

  •  JSpec rocks, writing tests with it is a breeze. The DOM Testrunner they have could use some work though to become even more usefull
  • Writing the tests with no dom insertion is a _great_ technique to get a fast testsuite where you can almost guarantee that it has no test-ordering issues.
  • jQuery allows you to almost completely drive the interaction with the editor as a user would, making it almost like an acceptance test (and with very little dependency on the internal working of the editor.
  • Refactoring JavaScript Code is hard if you don't have a testsuite. My Advice: Break it down into smaller bits. I found it incredibly hard to refactor larger pieces of the code, as not having a testsuite means there's no way you know what still works. :/

Python Saug Punkte contd.: x += y ist nicht x = x + y

a = b = list()
a = a + ['foo']
print b # => []

a = b = list()
a += ['foo']
print b # => ['foo']

Doh. Wie kann das sein? Kommt man von C ist das erst mal sehr verblüffend - und auch die meisten anderen Programmiersprachen die ich kenne verwenden a += b als equivalent für a = a + b.

Well, nicht so Python. Weil  da gab es offenbar mal Programmierer die fanden dass man Code der mit Matrizen rechnet lieber mit Operatoren schreiben möchte weil sich das besser ließt. Natürlich nicht mit den normalen operatoren wie */+-, weil, da kann man ja den empfänger nicht in place modifizieren, und wie jeder weiß sind Matrizen ja so groß dass die dann nicht mehr in den Ram passen.

Also haben sie die <op>= operatoren in Python so spezifiziert, dass sie ihre left-hand-variable in place modifizieren wenn diese mutable sind.

:-(

Python Saugpunkte: Klassenobjekte

Klassenobjekte sind special - daher hat man im boddy einer klasse keinen Zugriff auf das klassenobjekt.

Weil, self ist ja auch nicht automatisch und man muss es in Methoden immer als explizites Argument hinschreiben, und so etwas gibt es ja bei Klassen nicht, denn das sind ja keine Methoden und daher kann man halt das Klassenobjekt nicht referenzieren im body.

Doh.

Und das nervt natürlich total bei der meta-programmierung.

Hier mal ein Beispiel von etwas SQL-Alchemy Code wo mir das wieder aufgefallen ist:

class Poll(Base):
    proposal = relation(proposal.Proposal, backref=backref('polls', cascade='all',
                           lazy=False, order_by=Poll.begin_time.desc()))

Das geht nicht, weil ich auf Poll nicht zugreifen kann und damit nicht auf andere attribute der Klasse. Der Workaround den SQLAlchemy dafür macht ist das man einen String hineinreicht und die den dann aufwendig parsen. Total gar nicht toll.

:-(

  • Posted: 2010-01-13 19:00 (Updated: 2010-01-13 19:01)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

Grand Unified Theory of Programming?

Das höchste Ziel in der Physik ist alle Kräfte durch eine Formel auszudrücken bzw. sie in Beziehung zueinander zu setzen. Maxwell zum Beispiel gelang das für elektrische und magnetische Felder - und dafür ist er noch heute berühmt.

In der Software-Entwicklung gibt es so etwas bisher nicht. Klar, es gibt Daumenregeln, so wie:

Aber, und das ist der wichtige Teil: diese Daumenregeln sind keine Unifikation die die verschiedenen Probleme beim Programmieren abwägen und in Beziehung setzen.

Daher finde ich  Jim Weirichs Vortrag  The Building Blocks of Modularity sehr spannend - denn da stellt er den Ansatz der  Connascence vor (ab Folie 35).

Das ist letztlich eine Klassifizierung welche Art von Abhängigkeit man sich durch welche Programmiertechnik einfängt - und damit kann man 'normales' Refactoring anwenden um von problematischeren Connascence's (?) zu weniger problematischen zu kommen.

Ach ja, ursprünglich kommt das aus dem Buch  What every Programmer should know about Object Oriented Design. Davon kann man aber Getrost nur noch den dritten Teil lesen (über Connascence) - der rest ist nach 15 Jahren einfach veraltet. :)

** Niemand sagt das so gut wie Kent Beck: "Lots of little pieces - Good code invariably has small methods and small objects. Only by factoring the system into many small pieces of state and function can you hope to satisfy the “once and only once” rule. I get lots of resistance to this idea, especially from experienced developers, but no one thing I do to systems provides as much help as breaking it into more pieces."

Python Saug Punkte contd.

Eine Sache die mich bei Python immer wieder ärgert ist die Tatsache dass Standardwerte für Methodenargumente zur Parsezeit festgelegt werden anstatt zur Aufrufzeit.

Das ist total doof, denn dadurch teilen sich alle aufrufe der Funktion den gleichen default-wert - was zwar schön schnell sein mag, aber trotzdem in fast allen Fällen nur bei nicht veränderbaren Objekten (so wie Integer und Strings) Sinn ergibt.

So führt das dazu dass man in Python eine ganze Menge Workarounds braucht um mit default-argumenten zu arbeiten.

Das wichtigste dabei ist der default typ None. Das ist der workaround für alle mutable-objekte, da man die in fast keinem Fall zwischen verschiedenen Methodenaufrufen teilen möchte. So sieht das aus:

def end_poll(self, end_time=None):
    if end_time is None:
        end_time = datetime.utcnow()
    # work with end_time...

Der Punkt hier ist dass man datetime.utcnow() nicht in das standard Argument hineinschreiben kann, da man sonst bei jedem Aufruf der Methode den gleichen Wert hätte: Die Parsezeit. Nicht sonderlich nützlich.

Das hat zur Folge dass man:

  • aus der Signatur nicht sehen was das Standardargument ist (utc/gmt oder vielleicht ewas ganz anderes?). Immerhin gibt es inzwischen immer mehr IDEs die diese Signatur beim aufrufen für autocompletion nutzen oder sie wenigstens anzeigen können.
  • Man beim verwenden von Standardargumenten immer überlegen muss ob man dieses Argument jetzt in die Methodendefinition oder in den Body aufnehmen muss.
  • für jedes Standardargument noch mal zwei extra Zeilen braucht. Das nervt vor allem deswegen weil man sich mit den standard Argumenten ja Zeilen sparen möchte. Das heißt die Kosten für Standard-Argumente steigen und man benutzt sie seltener.
  • die default argumente noch mal separat dokumentieren muss, da ein dokumentations-extraktions-Werkzeug ja den Code nicht sieht, der das tatsächliche Standardargument setzt. Und natürlich hat man dann noch mal DRY verletzt da die Information jetzt zwei mal da steht.
  • richtig fiese Bugs kriegt, weil viele Leute diese Probleme nicht kennen oder sie ab und an vergessen und mal ein list(), dict() oder set() als Standardwert nehmen was dan für viel Freude beim Debuggen sorgt.

Also, know your Python und vorsicht mit Standardargumenten!

Vielleicht kriegen wir ja irgendwann von unserem BDFL ein from __future__ import runtime_standard_argument_evaluation.

  • Posted: 2010-01-11 17:03 (Updated: 2010-01-11 17:07)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

SOLID object oriented design

Ein Vortrag von der  GORUCO - sehr zu empfehlen.

Besonders gefallen hat mir ihr Fazit dass man mehr als nur  DRY als Prinzip beim Refactoring anwenden soll um bei gutem Code anzukommen.

Sandy Metz empfiehlt das man sich an den 'Grünen' Stellen des Red/Green/Refactor Zyklus für jedes Objekt diese Fragen stellt:

  • Is it DRY?
  • Does it have one responsibility?
  • Does everything in it change at the same time?
  • Does it depend (only) on things that change less often than it does?

Und bringt das auch an einem ordentlichen Beispiel auf den Punkt.

Alles in allem: Ein Vortrag der zum Nachdenken über den eigenen Code-Stil einlädt. Empfehlenswert!

Method argument naming confusion

Schon seit einigen Wochen bin ich am grübeln, nach welcher Regel ich in Python meine variablen für Methoden-Argumente benennen soll. Das ist erstaunlicherweise gar nicht so klar.

Hier mal das Problem: In Objective-C ist alles sehr klar und einfach (von Smalltalk kommend). Jede Methoden-Deklaration besteht abwechselnd aus einem Teil Methodennamen und dann einer Variablen. Hier mal ein Beispiel:

- (void) setValue:(id)aValue forKey:(id)aKey

Das hat den großen Vorteil dass man den Methodennamen benutzen kann um Stück für Stück die Argumente zu dokumentieren. Verwendet wird das so, dass das Stück Methodennamen das vor einem Argument kommt die Rolle beschreibt die das Argument spielen wird, während der Name der Variablen eher generisch ist und sich eher am Typ orientiert. Dazu kommt natürlich das man die Typen auch explizit auszeichnen kann, was die notwendigkeit für die Typ-Annotation im Namen der Variablen im vergleich zu Smalltalk oder Python noch mal vermindert und man kann ihn ganz der Rolle hingeben die die Variable in der Methode spielen wird - versehen mit dem a/an/some/etc. prefix der Argumente (als generische Instanzen von etwas) von den lokalen und instanz-variablen unterscheidet.

In Python geht das so nicht. Man kann versuchen das auf zwei wegen anzunähern:

def set_value_for_key(a_value, a_key): pass
# benutze als: some_dict.set_value_for_key('value', 'key')

Das hat den Vorteil das man die Argumente mehr oder weniger benennen kann wie man möchte, aber den Nachteil das die Dokumentation der argumente nicht mit diesen zusammen ist. Das hat schon mal den unangenehmen seiteneffekt das es sehr viel schlechter auf mehrere Argumente skaliert und damit sehr fix mehr extra-dokumentation nötig macht.

Der andere Weg wäre so:

def set(value, for_key): pass
#benutze als: some_dict.set(value='value', for_key='key')

Das hat den Vorteil dass der MethodenNamen von der Dokumentationshürde befreit ist - und damit Kurz wird. Auf der anderen Seite sind die Argument-Namen jetzt effektiv teil des Methoden-Namens und damit kann man sie nicht mehr so gut benutzen um den Typ der Argumente zu dokumentieren.

:-(

Das ist der Grund wieso ich die Objective-C / Smalltalk Syntax so gerne mag, weil es darin so einfach ist selbstdokumentierenden Code von hoher qualität zu schreiben.

Python Saug-Punkte

Viele standard-funktionen und module in python haben zu kurze namen.

Das ist deshalb ein Problem weil, man diese Namen nicht für lokale Variablen verwenden kann bzw. ungewollt eine Standardfunktion überschreibt.

id zum Beispiel. Oder dict, list.

Module sind dabei aber auch Problemkandidaten - vor allem wenn man sie häufig wie ein Objekt benutzt. Das json Modul macht mir immer wieder probleme, weil ich eine lokale Variable die json enthält nun mal gerne json nennen würde. json_serialization wäre vielleicht ein besserer Name für das Modul.

Die Standard-Bibliothek ist leider voll von solchen Beispielen und der Include-Mechanismus von Python der die Module quasi als Objekt im namespace des Empfängers verfügbar macht hilft da nicht wirklich weiter. Das ist zwar IMO eine bessere Idee als der C-Präprozessor #include (was Ruby ja zum Beispiel nachbaut) aber gerade bei so kurzen Namen kann das wirklich nerven.

Wenn man aus einem Modul ein Objekt importiert ist das interessanterweise kein Problem, da Objekte in Python (wenn sie sich an die Namenskonvention halten - leider auch oft nicht der Fall in der Standardbibliothek) immer mit einem Großbuchstaben anfangen und dadurch diese Namenskollision nicht auftritt.

Für mich ist da das Problem dass die Python Programmierer leider so eine Obsession damit haben alles möglichst kurz machen zu wollen - und dabei aber dem Programmierer der mit der (Standard-) Bibliothek arbeiten möchte gerade wieder Steine in den Weg legen dass kurz zu machen was für Ihn am meisten sinn macht - lokale Variablen.

Das ist leider Premature Optimisation in Reinstkultur - und es stört mich beim Entwickeln von meiner Software. :-(

How to crash IE 7 with javascript

Well, it's all too easy apparently. We stumbled upon the problem when suddenly our web application crashed left and right on us in IE 7.

I've since reduced the code involved and created a plugin to jQuery to make it easier to reproduce this.

Well, maybe perhaps sometimes somebody even discovers a use for the crashIE7 jQuery plugin. :)

In any event - it was fun creating this. :)

See the blog post page for the attached source and how to use this.

  • Posted: 2009-12-12 09:55 (Updated: 2009-12-12 09:56)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

Python distributions

Endlich mal hab ich einen überblick gefunden wo die Entwicklung momentan hingeht.

 http://mail.python.org/pipermail/python-dev/2009-October/092754.html

Kurz zusammengefasst:

  • easy_install, ez_setup und Konsorten wird sterben.  distribute ist die Zukunft
  • easy_install wird sterben ->  pip ist die Zukunft
  • Metadata wird aus dem setup.py script herauswandern und stattdessen in einem ini file zur Verfügung gestellt (Da ist mir noch nicht ganz klar wie das funktionieren soll - aber gut).
  • Deinstallieren wird auch mit den distutils gehen - man braucht also nicht mehr pip um das zu machen

Und noch mehr. Aber das find ich schon mal am wichtigsten. :)

Wieso Blöcke keine echten Funktionen sein sollten

Zu  "Ergie" von Rainer von Vielen

In meiner Freizeit beschäftige ich mich gerade viel mit Ruby - sowohl  The Ruby Way, als auch  The Ruby Programming Language sind dafür gute Bücher.

Und ich muss sagen, das Ruby ist nicht unspannend. Zwar gibt es auch einiges dass ich ganz schön eklig finde (z.B. das Flip-Flop Statement, oder dass so viel mit globalen Variablen gearbeitet wird, oder dass viele Sachen so komplex sind.

Aber darum gehts hier gar nicht. Mir geht es hier um die Erkenntnis wieso und unter welchen umständen man Blöcke als etwas anderes sehen möchte als anonyme Funktionen.

Das dauert nämlich bis man das merkt.

Zuerst einmal die Konfusion: Ruby hat eine Syntax für Methoden

def method(positional_argument, *all_other_arguments)
  # some body
end

und eine für Blöcke

10.times do |positonal_argument, *all_other_arguments|
  # some body
end

Wieso der Unterschied? Wieso macht man Blöcke nicht einfach zu normalen Funktionen die man dann auch gleich mit () aufrufen kann anstatt immer ein a_block.call() verwenden zu müssen?

Echte Lambdas gibt es ja noch zusätzlich in Ruby.

Well, den Unterschied in der Syntax verstehe ich immer noch nicht. Aber dahinter steht der Grund dass Blöcke eine andere Aufgabe haben als Methoden - der Punkt ist nämlich dass man sie gerne als Teil der sie lexikalisch umgebenden Methode betrachten möchte damit man sie nutzen kann um mit ihnen Kontrollstrukturen zu implementieren. Hier mal ein Beispiel:

def find(needle, haystack)
  haystack.each.with_index do |index, element|
    if element == needle
      return index
    end
  end
  return nil
end

(Also als Python Programmierer muss ich ja sagen dass die end statements ganz schön auf die Nerven gehen. Doppelte Zeilenanzahl für null zusätzliche Information oder Nützlichkeit. But I digress.)

Das spannende daran ist die Zeile return index. Seht ihr was daran besonders ist? Ich Puzzle es mal auseinander als wäre der Block eine funktion, dann wird es klar.

find ruft einen iterator auf dem haystack auf, d.h. übergibt ihm eine Funktion die das richtige Element findet. Diese Funktion erhällt ein Element aus dem haystack und einen index und gibt diesen index zurück wenn das element das gesuchte ist.

Und da ist das Problem: Damit find funktioniert muss return index find verlassen und nicht nur die iterator-funktion.

Das ist der Grund wieso man Blöcke als etwas anderes als Funktionen/Methoden betrachten muss wenn man sie nutzen will um damit Kontrollstrukturen implementieren zu können und ihre volle Nützlichkeit für Abstraktionen verwenden zu können.

Playing with JavaScript variable lookup

I am always amazed at the very nice features of JavaScript on the one hand and the very, very bad features of it on the other side.

Here's something I learned the other week that I find quite interesting: eval vs. new Function. Here's what I wanted to achieve: I was looking for a way to do some meta-programming with JavaScrip, specifically prevent the problem that any variable you assign to but don't declare ends up as a member of the global object.

The problem is that name lookup in JavaScript is quite peculiar. First it consults the current function activation for local variables and then it goes straight back to the window object and just prepends all it's contents to the local namespace.

Doh. This has the very bad consequence that every variable you forget to declare via the var someVariableName syntax becomes part of the global object - and therefore itself global.

Now you can change this lookup by inserting some of your objects in this lookup chain by using the  somewhat controversial with statement like so:

var namespace = {foo:'bar'};
with (namespace) {
    // this scope now has a local variable foo with the value bar
}

This is considered a bad feature, as it means that if you assign to foo that will assign a new value to namespace but if you assign to something else or mistype, that will still end up on the global object. Not very nice - and therefore most JavaScript programmers don't use with ever.

Still with some working and eval it can be used to re-map free functions transparently, so that you can do something like this:

var namespace = {
    equals: function(actual, expected){
        if (actual !== expected)
            console.log('actual', actual, 'does not equal expected', expected);
    }
}

function test(aDescription, aTestFunction) {
    useNamespaceForFunction(namespace , aTestFunction)();
}

test("that I can call equals as a free function", function(){
    equals(1,1);
});

Which would allow you to not pollute the global namespace with all the testing equality functions but still call them in a convenient way (that is without needing to go through an object.

All in all, quite nice really - even though I haven't really found a use for this yet. :-)

Implementing the useNamespaceForFunction however isn't quite as straightforward as I had thought - here's my first go at it:

function useNamespaceForFunction(aNamespace, aFunction) {
    with (aNamespace) {
        return  eval('(' + aFunction + ')');
    }
}

I have since learned that actually using new Function to do the eval might be quite a good idea as the whole point of it is that it ignores the namespace around it, so here's my version two:

function useNamespaceForFunction(aNamespace, aFunction) {
    var namespacingCode = "with (aNamespace) { return (" + aFunction + "); }";
    // using new Function instead of eval to prevent the current namespace leaking into the eval<
    return new Function("aNamespace", namespacingCode)(aNamespace);
}

So, lets see what uses come up for this technique - I've seen some js code that does an Interpreter of sorts with this trick - but thats about it.

So - I didn't achieve my initial goal - but I did come nearer to it. So I'll call it a success here for now.

If you find any use for this technique, please let me know!

Get the source!

  • Posted: 2009-10-17 18:11 (Updated: 2009-10-24 16:02)
  • Author: dwt
  • Categories: code
  • Comments (0)

Simple super

Python is a wonderfull language - except when it's not.

For example calling a super-method is really, really hard. Here's an example:

class Super(object):
    def method(self):
        pass

class Sub(Super):
    def method(self):
        super(Sub, self).method()

This is bad because of several reasons:

If your class names become longer this becomes more and more unreadable, consider this Acceptance-Test

class CanEnterUsernameAndPasswordOnLoginForm(TestCase):
    def setUp(self):
        super(CanEnterUsernameAndPasswordOnLoginForm, self).setUp()
        # more

You need to repeat the class name in each method that calls super. This is especially bad if you rename your class as you need to repeat the name in so many places - also there might be situations where having the wrong name doesn't bomb but just calls the wrong code. Also if you move methods up and down the class-inheritance-chain this becomes more and more annoying.

Well, so I looked at what you can do with some meta-programming - and lo and behold there's a lot you can do.

Here's an example what I ended up with:

class Super(object):
    super = SuperProxy()
    def method(self):
        pass

class Sub(Super):
    def method(self):
        self.super()

Yeah! Now that's simpler. to call super you can use several syntaxes:

  • self.super() just calls the super method of the same name and hands it all arguments that the current method got
  • self.super('foo') this way you can hand specific methods to the super-class and prevent the automatic gathering of arguments. If you prefer explicit - this is it.
  • self.super.some_method() self.super is exactly the same as what the super-method returns (so it's the same as super(ThisClass, self)) so you can use it to call any method on the superclass and hand it any arguments you want.

Well, so I consider this a boon for any bigger python project as it considerably eases the pain of working with class-hierarchies, and best of all you can import it into your project one superclass at a time.

Oh, and please tell me if you use it and like it. :-)

So here's the code!

  • Posted: 2009-10-14 20:49 (Updated: 2009-10-14 20:50)
  • Author: dwt
  • Categories: code
  • Comments (0)

Blocks in Objective C

I've long had a fascination with SmallTalk style blocks in Objective-C. So much so, that I learned a lot about how C and GCC work when I implemented them on the primitive of GCCs nested functions myself.

Heres the Source

Of course, just as I had it working, Apple deprecated GCCs nested functions, as they where implemented using a trampoline on the stack. And of course, a trampoline being executable code they where out when the non executable stack came in.

Ah well.

BUT, Apple just released with Snow-Leopard a new compiler feature [Blocks]!

Yay, closures in C!

So here's how it looks if you implement the Smalltalk collection iteration protocoll in ObjC. (Note: this of course are not propper ObjC-Names, but each Smalltalker will none the less get a tear in their eye when they see this)

#import <Foundation/Foundation.h>

@implementation NSArray (BlocksTest)

- (void) do:(void (^)(id))aBlock;
{
    // Take care, -enumerateObjectsUsingBlock: wraps an auto-release pool around the iteration
    [self enumerateObjectsUsingBlock:
        ^(id obj, NSUInteger idx, BOOL *stop) {
            aBlock(obj);
        }];
}

- (NSArray *) collect:(id (^)(id))aBlock;
{
    id collectedItems = [NSMutableArray arrayWithCapacity:[self count]];
    [self do:^(id each) {
        [collectedItems addObject:aBlock(each)];
    }];
    return [collectedItems copy]; // REFACT: consider to drop copy
}

- (id) detect:(BOOL (^)(id))aBlock;
{
    // Take care, -enumerateObjectsUsingBlock: wraps an auto-release pool around the iteration
    __block id resultObject = nil;
    [self enumerateObjectsUsingBlock:
        ^(id obj, NSUInteger idx, BOOL *stop) {
            if (aBlock(obj)) {
                resultObject = obj;
                *stop = YES;
            }
        }];
    return resultObject;
}

- (id) detect:(BOOL (^)(id))aBlock ifNone:(id (^)())errorBlock;
{
    id foundElement  = [self detect:aBlock];
    if (foundElement)
        return foundElement;
    else
        return errorBlock();
}

- (id) inject:(id)aValue into:(id (^)(id, id))aBlock;
{
    // Need to take care with retain here, because apple wraps an auto-release pool around the block iterator. :/
    __block id collected = [aValue retain];
    [self do:^(id each){
        collected = [aBlock([collected autorelease], each) retain];
    }];
    return [collected autorelease];
}

- (NSArray *) reject:(BOOL (^)(id))aBlock;
{
    id selectedObjects = [NSMutableArray arrayWithCapacity:[self count]];
    [self do:^(id each){
        if (aBlock(each))
            return;
        [selectedObjects addObject:each];
    }];
    return [selectedObjects copy]; // REFACT: consider to drop copy
}

- (NSArray *) select:(BOOL (^)(id))aBlock;
{
    return [self reject:^(id each){ return (BOOL) ! aBlock(each); }];
}
@end


#define log(objcObject) fprintf(stdout, "%s\n", [[objcObject description] UTF8String])

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    id array = [NSArray arrayWithObjects:@"first", @"second", @"third", nil];
    
    log(@"\ndo:");
    [array do:^(id each){
        log(each);
    }];
    
    log(@"\ncollect:");
    log([array collect:^id(id each){
        return [each uppercaseString];
    }]);
    
    log(@"\ndetect:");
    log([array detect:^(id each){
        return [each isEqual:@"second"];
    }]);
    
    log(@"\ndetect:ifNone:");
    log([array detect:^(id each){ return NO; } 
                        ifNone:(id)^{ return @"Yeehaw!"; }]);
    
    log(@"\ninject:into:");
    log([array inject:@"" into: ^ id (id concatenation, id element){
        return [concatenation stringByAppendingString:element];
    }]);
    
    log(@"\nreject:");
    log([array reject:^(id each){
        return [each hasSuffix:@"nd"];
    }]);

    log(@"\nselect:");
    log([array select:^(id each){
        return [each hasSuffix:@"d"];
    }]);
    
    [pool drain];
    return 0;
}

Ain't that pretty?

Here's the current version!

  • Posted: 2009-09-26 23:14 (Updated: 2009-09-27 09:43)
  • Author: dwt
  • Categories: code
  • Comments (0)

$this->doSomethingWith($someNonExistingVariable)

PHP ist wirklich eine Drecksprache. Selbst JavaScript kann das besser.

Man mache irgend etwas mit einer Variablen bei deren Namen man sich verschrieben hat. Dann würde man erwarten dass einen der Interpreter in irgend einer Form darauf hinweist. Wenigstens ein bisschen, mit einer Log-Meldung vielleicht. Sowas ist ja nicht schwer zu machen.

Aber, nicht so bei PHP. Vermutlich wieder aus den ominösen Performance-Gründen.

Vielleicht gibt es sogar irgendwo eine Option mit der man solche warnings zuschalten kann.

Aber alleine die Tatsache das man Gehirnzellen darauf verbrennen muss...

*grumpf*

Ok, der workaround ist dass sich jeder Entwickler in seiner php.ini diese Zeilen aufnehmen muss:

display_errors = On
error_reporting = E_STRICT

Der Witz daran E_STRICT warnt natürlich bei noch mehr Sachen als E_ALL. Sogar der MediaWiki Source des aktuellen Releases wirft da noch mit Warnings um sich.

Unglaublich.

Kent Becks keynote auf der Railsconf 08

Und er erzählt dabei ein paar Geschichten. Ohne einen Punkt.

Das ist ein ganz interessanter Rückblick auf seine Vergangenheit, seine Meinung und Art wie er spricht.

Das Beste ist aber die Fragerunde am Schluss - in der wird es nämlich auf einmal richtig Politisch:

How is it, that the thing that I could do could contribute the most possible. I'ts gonna take getting involved with the people whose lives are affected by the programs that I write. Whether they're dockworkers, or policemen or whoever. They have to gain power in that process.

One of the Principloes that I've used always, always, is the principle of mutual benefit

I try to finde a way where everybody can do better out of the activities that I'm involved in. I fall short of that at times, but I'm always looking for that.

And I think software as an industry falls short of that. There are winners and losers in software development. And I think thats really too bad, because I think it's mostly a choice.

And I'd love to find a way to get past that.

Schaut euch den Kontext um dieses Statement an - es lohnt sich!

 Hier gibt's den Vortrag

"What Killed Smalltalk Could Kill Ruby, Too"

Mit dieser These ist Robert Martin auf der RailsConf 09 aufgetreten - und hat ganz ohne Powerpoint in einer hoch spannenden Stunde diese These erklärt und hervorragend vertreten.

Ganz nebenbei liefert er dabei noch die beste Motivation für  Test Driven Development die ich bisher gesehen habe.

 unbedingt anschauen!

PHP

Bäh! = Ich hab mir heute mal mit einigen bekannten die doch etwas besser PHP können angeschaut wie man in die Wikipedia ein plugin einbaut.

Und boah, das ist echt superecklig.

Das geht schon damit los dass man an einen Array nicht vernünftig etwas anhängen kann. Denn, das muss man sich mal auf der Zunge zergehen lassen, arrays sind natürlich keine Objekte - dafür aber auch noch gleich dictionaries.

Und etwas anhängen an einen Array ist daher eine total schwierig. Es gibt zwar eine (globale!) Funktion - so in der art von array_push_value() oder so, aber das will niemand schrieben, desshalb gibt es dafür eine extra Syntax (!!!):

$someArray[] = "fnord";

Geil oder?

Und natürlich ist Wikipedia auch ein total gewachsenes Projekt, daher gibt es tausende von globalen Variablen die man manchmal überschreiben, manchmal anhängen und manchmal irgendwas machen muss.

Auf meine Frage wieso man so etwas so macht, fiel auch meinen Bekannten auch nur ein "das ist halt vermutlich so schneller" ein. Denn, der ganze Code muss ja für jeden Seitenabruf neu geparst werden.

Wie krass!

Und es ist nicht so das es dann für das Plugin registrieren eine Funktion gibt, in der man vielleicht noch ein Verzeichnis übergibt, wo dann nach einem standard-layout die ganzen Files drin sind. Nein! man muss x verschiedene globale variablen von hand anfassen.

*kotz*

 Wers nicht glaubt, schaut selber nach!

Core War

Ich hab mich schon länger mit dem Gedanken herumgetragen dass es eigentlich mal einen schönen aktuellen  CoreWar clienten für den Mac geben müsste.

Und, irgendwie kam es bisher nicht dazu - aus irgend einem Grund hat niemand einen geschrieben.

Well, jetzt hab ich mal einen minimalen Anfang gemacht um herauszufinden wie man so etwas überhaupt programmieren müsste.

Hier ist das vorläufige Ergebnis.

WARNING: Work In Progress!

Das ist natürlich völlig unfertig - Es läuft genau nur ein Krieger ( IMP) - und der auch nur weil er alleine ist. :-)

Aber, ich habe eine Abstraktion für die VM, ein View was das Ergebnis anzeigt, und ein paar Tests die auf der VM herumrödeln und schauen dass sie prinzipiell das richtige tut.

Kleinigkeiten die noch fehlen wären zum Beispiel ein Parser für Redcode, Einstellungen um die Applikation an die verschiedenen Regelwerke anpassen zu können und natürlich eine Implementierung aller Redcode Instruktionen...

Also ein Anfang. Und man sieht schon etwas. :)

Ach ja, hier noch was für  echte Core War fans...

Cappucino != Cocoa

Tja, also nach längerer Neugierde habe ich mich mal mit  Cappucino auseinander gesetzt und ein wenig damit Programmiert.

Und das hat für mich als Cocoa Programmierer eine Menge Spaß gemacht.

Erst mal ein paar Kleinigkeiten:

  • Der Java-Script Parser von Safari saugt. Er sagt einem nie wo der Fehler jetzt genau aufgetreten ist. :-( Unbedingt mit Firefox und Firebug entwickeln!
  • Wenn man sich den git tip of the tree auscheckt kriegt man weit mehr Klassen als in der offiziellen Dokumentation erwähnt sind. Insbesondere funktioniert das nib2cib tool auch ein wenig besser - und das spart unmengen von Zeit.
    • Allerdings funktioniert nib2cib auch nicht soo gut. Viele Klassen die es können könnte gehen damit nicht. NSScrollView zum Beispiel.
  • Wenn man sein eigenes Objective-J kompilieren will darf man keine spaces im Pfad haben. (arrrrghhh!!!!!)
  • Man kann (noch) nicht den Code so ohne weiteres in Unterverzeichnisse auslagern. Das macht die Projekte unübersichtlich - aber angeblich soll das bald gefixt sein.
  • nib2cib darf man nie mit mehr als einem Filenamen aufrufen. Sonst schreibt es nämlich einfach in das zweite Argument den output hinein.
    • Wo ich gerade darüber nachdenke: das geht eigentlich mit kaum einem objective-j tool ojtest zum beispiel... Dabei ist der Startup von Rhino immer das langsamste daran. :-(
  • Und man muss einiges an Umgebungsvariablen setzen. Am besten macht man sich ein ein shell script was diese dinger setzt und dann wieder eine shell exec'd. Damit hat man dann gleich eine Arbeitsumgebung für Cappucino ohne alles irgendwohin in standard-Betriebsystem-Locations installieren zu müssen.
#!/bin/sh

PROJECT_DIR="/path/to/the/cappucino/sources/but/without/spaces/cappuccino"

# you can move this elsewhere to keep things separate
export STEAM_BUILD="${PROJECT_DIR}/build"
TOOLS_DIR="${STEAM_BUILD}/Cappuccino/Tools/objj"
NIBTOOLS_DIR="${PROJECT_DIR}/Tools/objj"

export OBJJ_HOME="${TOOLS_DIR}"
export PATH="${PATH}:${TOOLS_DIR}/bin:${NIBTOOLS_DIR}/bin"
"${SHELL}"

Damit kann man immerhin die ganzen objj tools (ojtest, nib2cib, etc) direkt benutzen.

Cappucino selber benutzt sich tatsächlich ziemlich wie eine recht alte Version von Cocoa. Erstmal keine Bindings und entsprechende Controller und !CPBundle ist reichlich underpowered - Lokalisierter Resourcen-Lookup ist damit erst mal noch nicht drin.

Die Tools sind natürlich nicht so toll. Insebesondere fehlt ein vernünftiger Ersatz für Interface Builder.

Aber ansonsten rockt  Cappuccino schon gewaltig. Jedem Cocoa-Programmierer sei also eine Beschäftigung damit schwer ans Herz gelegt.

Xcode mit svn 1.5

Eigentlich dachte ich ja ich hätte das schon gebloggt - aber ich habs nicht wiedergefunden. (Vielleicht sollte ich nochmal mit google suchen?)

Ah well.

Dieses Script bringt Xcode mit svn 1.5 via  Fink installiert zum laufen.

#!/bin/sh

svn_libs=/sw/lib/svn15

cd /Developer/Library/Xcode/Plug-ins/XcodeSubversionPlugin.xcplugin/Contents/MacOS || exit 1
ditto XcodeSubversionPlugin XcodeSubversionPlugin.new

libs=`otool -L XcodeSubversionPlugin.new | grep libsvn | awk '{ print $1 }'`
 
for lib in $libs ; do
  new_lib=`echo $lib | sed "s,/usr/lib,$svn_libs,g"`
  echo "install_name_tool -change $lib $new_lib XcodeSubversionPlugin.new"
  install_name_tool -change $lib $new_lib XcodeSubversionPlugin.new
done

install_name_tool -change /usr/lib/libapr-1.0.dylib /sw/lib/libapr.0.dylib XcodeSubversionPlugin.new
install_name_tool -change /usr/lib/libaprutil-1.0.dylib /sw/lib/libaprutil.0.dylib XcodeSubversionPlugin.new

echo otool -L XcodeSubversionPlugin.new
otool -L XcodeSubversionPlugin.new

echo
otool -L XcodeSubversionPlugin.new | grep svn | awk '{ print $1 }' | xargs -n 1 ls -la

exit 0

Ach ja, am Schluss muss man noch von Hand in /Developer/Library/Xcode/Plug-ins/XcodeSubversionPlugin.xcplugin/Contents/MacOS das original zur Seite bewegen und bei dem neuen das ".new" streichen.

Andere kochen auch nur mit Wasser

Apple in dem Fall mit Apple Mail.

Die Backtrace library will ich auch haben. :)

  • Posted: 2009-02-07 21:49 (Updated: 2009-10-24 15:55)
  • Author: dwt
  • Categories: code
  • Comments (0)

Holperdinger auf dem Marsflug

Tja, was soll ich sagen.  Squeak mit  Mars ist spannend.

Der Smalltalk Source Code für die Menü-Definitionen gefällt mir zum Beispiel wirklich gut.

menuFile
	^ (MarsMenuItem label: 'File')
		add: (MarsMenuItem label: 'File Out...' keyStroke: $o command: #fileOut);
		addSeparator;
		add: (MarsMenuItem label: 'Save' keyStroke: $S command: #saveImage);
		add: (MarsMenuItem label: 'Save As...' command: #saveImageAs);
		addSeparator;
		add: (MarsMenuItem label: 'Quit (Development)' keyStroke: $Q command: #developmentQuit);
		add: (MarsMenuItem label: 'Quit' keyStroke:$q command: #quit);
		yourself

Das ist knackig.

Auf der anderen Seite... Das  Versionskontrollsystem hat mal eben so vergessen das Changeset mit auf den Server zu schieben auf dem das was ich hochgeladen / comitted habe beruhte.

Äh, wiebidde? Das ließ sich dann zwar durch vereinte Anstrengungen von 3 Squeakern irgendwann fixen. Aber...

Und überhaupt, die GUI von Squeak ist... gewöhnungsbedürftig. Der größte Teil der Funktionalität liegt in Kontextmenüs versteckt - die man einfach nicht findet wenn man nicht weiß dass es sie gibt. Oder die nur kommen wenn man etwas bestimmtes nicht ausgewählt hat.

*Grusel*

Naja, mixed feelings.

OD Completion Dictionary works with Xcode 3

Hrm, das ist mir doch ganz entgangen bisher.

Aber:  here it is!

Was es tut: Beliebige Textbausteine in Xcode schnell einsetzen. Das geht zwar auch ohne ODCompletionDictionary, aber, mit kann man es auch in Xcode live bearbeiten. Und das macht das Feature eigentlich erst richtig nützlich.

Writing Code that doesn't suck

 Und noch mal von den Confreaks.

Ausgezeichneter Vortrag von Yehuda Katz darüber was er für gute Tests hält und was nicht. Sein Argument: Am Ende will man Regression-Tests haben, da nur die wirklich nützlich sind beim Refactorieren. Nur die sind nützlich, weil dass die Tests sind die man behalten kann während man den Code neu faktoriert.

Damit das geht muss der Code aber schon eine gute Faktorierung haben die man (ausschließlich?) über öffentliche Interfaces testen kann.

Wohlgemerkt in dem Talk geht es nicht darum ob TDD gut oder schlecht ist - der Zweck ist einfach ein anderer. Ihm geht es um API-Stabilität und die Frage wie man Interfaces (nicht Implementierungen) über eine lange Entwicklung stabil hält.

Spannend. Mir ist dabei eingefallen dass ich damals im ersten Buch über Test Driven Development einen Absatz gelesen habe in dem Stand dass man zuerst seine Tests schreiben soll um dann an diesen seinen Code zu schreiben und zu refaktorisieren. Und dass man danach den Code als Tests für die Tests verwenden kann um wiederum diese zu refaktorisieren.

Gaudio. Ob das schon das ist was Yehuda meint? Ich vermute noch nicht ganz - aber es hört sich für mich nach etwas an was ich direkt tun kann - ich kann meine Tests betrachten und mir anschauen ob sie tatsächlich das testen was mich interessiert (das interface) und wenn dem nicht so ist, dann kann ich mir das Interface betrachten und die Tests tatsächlich refaktorieren.

Yay, mehr Arbeit. :)

Code Review mal praktisch erklärt

Und zwar mit grandiosen schauspielerischen Leistungen. :)

Auf der Ruby-Conference 2008.

 The Ruby Code Review. A Play in Three Acts

Überhaupt finde ich Confreaks sehr cool. :)

Landing on Mars (with Squeak)

I just started playing around with  Mars which is a bridge from Squeak to Cocoa, which allows Squeak Programms to have a Cocoa GUI.

Pretty nice - however not quite as easy to get installed if you are a SmallTalk newbie like me.

 There are installation instructions on the Mars homepage however I couldn't quite follow them, so I enhanced them a little and give you the rundown here:

Instructions

  • I downloaded the basic Image from  http://ftp.squeak.org/3.10/Squeak3.10.2-7179-basic.zip
  • Got the Virtual Machine from  ftp://ftp.smalltalkconsulting.com/experimental//Squeak%203.8.21beta1U.app.zip
  • Installed basic developer tools by executing <HTTPSocket httpFileIn: 'installer.pbwiki.com/f/LPF.st'.> in a workspace window (select it and hit command-d)
  • Then I ran
    Installer universe 
    	addPackage: 'OmniBrowser';
    	addPackage: 'OmniBrowser-Morphic';
    	addPackage: 'OmniBrowser-Standard';
        	addPackage: 'OmniBrowser-Refactory';
    	install.
    
    to get the basics and
  • installed Mars by executing
    Installer wiresong project: 'ob'; install: 'OB-Monticello'.
    Installer lukas project: 'omnibrowser'; install: 'OB-Tools'.
    
    Installer ss project: 'Mars';
    	install: 'Mars';
    	install: 'OB-Mars'.
    

After that OBMarsWorld execute. replaces the menubar of Squeak with the Cocoa stuff.

:) Have fun!

Oh, and by the way, I just tried this with the dev image available from Damien Cassou at  http://damiencassou.seasidehosting.st/Smalltalk/squeak-dev

This image already has most stuff preloaded, so you can just run

Installer wiresong project: 'ob'; install: 'OB-Monticello'.
Installer lukas project: 'omnibrowser'; install: 'OB-Tools'.

Installer ss project: 'Mars';
	install: 'Mars';
	install: 'OB-Mars'.

to get Mars installed and then start it by running: OBMarsWorld execute.

Scriptdir

Ich habe immer wieder das Problem, dass ich in Shell-Scripten den absoluten Pfad zum eigenen Script, oder zumindest den Ordner in dem das Script liegt benötige.

Dieses Problem hat viele Lösungen - die allermeisten kommen aber entweder nicht mit Leerzeichen in diesem Pfad klar, oder versagen wenn man das Script über verschiedene Kombinationen von absoluten und relativen Pfaden aufruft.

Meine gegenwärtige Lösung ist das hier:

SCRIPTDIR="$(cd "$(dirname "$0")" && pwd)"
echo $SCRIPTDIR

Damit bin ich zwar auch überhaupt nicht glücklich, aber immerhin scheint es zu funktionieren. :/

Insbesondere ist das Quoting extra funky. Unter MacOS X scheint es aber zu gehen (d.h. die Bash frisst das korrekt)

Hat da vielleicht jemand eine bessere Idee?

Update: Florian hatte die großartige Idee doch einfach mal in gut benutzte Shell-Programme wie den Wrapper für OpenOffice zu schauen. Und siehe da:

# cat /usr/lib64/openoffice/program/swriter
#!/bin/sh

cmd=`dirname "$0"`/soffice
exec "$cmd" -writer "$@"

funktioniert großartig. Man kriegt zwar nur den relativen Pfad - aber das reicht ja in aller Regel.

  • Posted: 2009-01-29 14:07 (Updated: 2009-02-02 22:16)
  • Author: dwt
  • Categories: code
  • Comments (0)

SVN Revision Number in der About Box verfügbar machen

Das wollte ich tun, aber  die bestehenden Lösungen fand ich alle nicht so toll.

Well, also noch mal.

Immerhin bietet Python ja eine menge mit seinen Batteries Included. Das heißt ich muss nicht aufwendig mit RegExen die plist dateien auseinander nehmen. Schließlich gibt es die  plistlib.

Hier das Resultat:

#!/usr/bin/env python

# Usage:
# Either copy into a shell script build phase 
# (don't forget to change the interpreter to "/usr/bin/env python")
# Or just put it into a shell script and call it form there.
#
# Discussion:
# There are really two info plist keys that could be used for the source revision:
# The CFBundleShortVersionString and CFBundleVersion
# Apple recommends to use CFBundleVersion, because it is not shown in the finders info dialog,
# but is shown in the standard about dialog of a cocoa application where the CFBundleVersion
# is shown in parantheses behind the CFBundleShortVersionString.
#
# Inspired by Daniel Jalkut at http://www.red-sweater.com/blog/23/automatic-build-sub-versioning-in-xcode
# License: Creative-Commons Public Domain http://creativecommons.org/licenses/publicdomain/

import os, re, plistlib

# Test if run from xcode
is_run_from_xcode = os.environ.has_key("BUILT_PRODUCTS_DIR")
if not is_run_from_xcode:
    exit("Needs to be run from a Xcode shell scribt build phase")

# We take the one from the built products dir to keep revision numbers out of the repository
info_plist_path = os.path.join(os.environ["BUILT_PRODUCTS_DIR"], \
                               os.environ["INFOPLIST_PATH"])

# get latest svn revision
def output_of_command(*command_and_arguments):
    import subprocess
    return subprocess.Popen(command_and_arguments, stdout=subprocess.PIPE).communicate()[0]

os.chdir(os.environ["PROJECT_DIR"])
version_range = output_of_command("svnversion", "-nc")
latest_commited_version = re.search(r"\d*\w*$", version_range).group(0)

# enter into Info.plist
info = plistlib.readPlist(info_plist_path)
info["CFBundleVersion"] = latest_commited_version
plistlib.writePlist(info, info_plist_path)

Pretty neat. :)

Die aktuelle Version des Scripts liegt wie immer im Repository.

  • Posted: 2009-01-24 20:12 (Updated: 2009-01-24 20:28)
  • Author: dwt
  • Categories: code
  • Comments (0)

Der neue Trend: Unfactoring

Unfactoring is nicht einfach. Man muss es immer wieder üben.

 Hier ein Schulungsvideo von der RubyConf08.

Highly advised! ;)

The first and second rule of optimization are

(according to  Michael A Jackson)

  • The First Rule of Program Optimization: Don't do it.
  • The Second Rule of Program Optimization – For experts only: Don't do it yet.

Bisher dachte ich immer dass es sich dabei nur um eine andere Formulierung des guten alten: "First make it work, then make it fast" handelt.

Bis es mir jetzt wie Schuppen von den Augen fiel, dass da noch viel mehr dahinter steckt. Denn natürlich lässt sich der Spruch auch auf das tatsächliche Optimieren selber anwenden.

Wie macht man etwas schneller? Am besten in dem man es nicht tut.

Und wenn das nicht geht: Mach es zu einer anderen Zeit!

Doh.

Warum ich C++ hasse

Weil es so irrsinnig schwer zu debuggen ist.

Jeder Programmierer verwendet ein anderes Subset irgendwelcher obskuren features, die man eigentlich nicht bräuchte und fürs debuggen muss man sie dann alle kennen.

Gnah.

Zum Beispiel das hier: Aus Performance-Gründen wird der Speicher für Objekte beim initialisieren nicht auf einen neutralen Wert (0/NULL/whatever) gesetzt, sondern (!!!) uninitialisiert gelassen. (Nein, das ist kein Opt-In für die stellen wo man die Performance wirklich braucht, sondern tatsächlich der Standard)

Das geile daran: Vergisst man im Konstruktor dann eine Variable zu initialisieren, dann gibt es auch keine Warning...

Also habe ich jetzt Tageweise hinter her debugged, weil an einer Stelle halt eine Variable nicht in der Konstruktor-Initializer-Liste aufgeführt - und dieser Wert dann an das System weitergereicht wurde. Tja, schade eigentlich. Dadurch wurde dann der TCP-Sendspace eben auf einen zufälligen Wert (18 Millionen) konfiguriert und verständlicherweise reichten dafür die Buffer nicht mehr aus...

Und das alles weil C++ so hardcore Performance-Orientiert ist. Wo doch heute jedes Kind lernt, das man etwas erst zum funktionieren und dann schnell kriegen muss.

Gnah.

Apple iPhone Tech-Talk

War ja ganz nett auf den  Apple iPhone TechTalks - alles zusammen allerdings etwas langweilig weil wenig in die Tiefe. :-(

Lustiges Detail am Rande: Zwar war die Veranstaltung mit Anmeldung und schon lange vorher ausgebucht - aber trotzdem kam man vor Ort noch gut rein - das nächste mal kann man es also auch ohne Anmeldung einfach mal versuchen.

Interessant auch, dass das ganze Event unter NDA stand, das heißt, obwohl ich dort war und nur Informationen verbreitet wurden die alle ohne NDA aus der API erhältlich waren, darf ich nicht darüber erzählen was es dort zu hören gab. Das fand ich so strange dass ich doch gleich einen der Vortragenden Apple-Mitarbeiter gefragt habe wie das zu verstehen ist: Dieser hat sich dann entschuldigt und bestätigt, dass natürlich alle Informationen die aus den APIs kommen frei bloggbar sind, aber eben nicht worüber auf dem Event darüber gesprochen wurde.

Schade eigentlich.

iPhone Applicaton Development

Hier einige Notizen zu der iPhone API die ich spannend finde:

iPhone Development Facts

  • iPhones in 70 Ländern mit 70+ millionen iPhones (plus unbekannt viele Touches)
  • 5500 Apps gibts im App Store
  • 200 Millionen Apps wurden in 121 Tagen vergekauft
  • Wie Funktionieren iPhone Applikationen:
    • Bedienung mit dem Finger und Gesten: Pinch, Flick
    • So wenig Input wie möglich machen (Weil Input anstrengend ist)
    • Wenig Platz auf dem Bildschirm: Jede Applikation macht eine Sache und nur die
    • GPS, Accelerometer werden benutzen
    • Wenn die Applikation beendet wird wird der Zustand gesichert
  • Kriterien für Erfolgreiche Apps:
    • Delightfull (Spaß, Intuitiv)
    • Innovative (Revolutionär, Innovativ, anspruchsvolle Technik)
    • Designed
      • Great Design -> kleine Teams damit gute Ideen auch eine Chance haben
      • viele Iterationen
      • ständiger Fokus auf Design [40% der Zeit! Nicht wie sonst]
      • Attention To Detail
      • "Sollutions Not Features"
      • "Saying no to 1000 things"
      • "Chose top 5 Features and amplify them"
      • "Flatten data, to make it easily digestible"
      • 30 Second usage Scenario: Man soll nach 30 Sekunden mit der Applikation schon wieder fertig sein.
    • Integratied (Use all available technology - don't reinvent)
    • Optimized (Performance, Stability, Battery Life is important), no PORT -> neu und spezifisch fürs iPhone bauen
    • Connected: Integrate data as much as possible, Connect to a Mac-Application
    • Localized (40% des Maktes sind nicht-Englisch) Applikation UND Store lokalisieren! (Sonst weiß kein User dass die Applikation lokalisiert ist)
    • Diese Checkliste immer wieder abarbeiten. :)

Dev Tools Overview

  • Platz sparen ist das wichtigste, da sonst der watchdog das Programm killt
  • Ein iPhone Programm sollte nur etwa 20-25 MB Ram benutzen (Insbesondere nicht so viel wie direkt nach dem Start des iPhones frei ist!)
  • Code: Alles statisch gelinkt -> Code wird man nicht los
  • Bilder: png! (evtl. nochmal speziell komprimieren)
  • Named-Images: Die sind immer im Speicher da gecached -> Durch Filename laden (wird dann nicht gecached)
  • Audio: Dolby Surround ist schlecht -> mp3, mp4
  • Caching darf man gerne selber implementieren - Flasch kann man so viel nutzen wie man will
  • Applikationen im App-Store können maximal 2 Gig groß sein.

User Interface Design

Eine Applikation zu erstellen kann man grob in 4 Phasen einteilen

  • Familiarize:
    • iPhone verstehen
    • HIG lesen
    • Je schnell man mit einer Applikation fertig ist, desto besser: Starbucks, während jemand vor einem was ordert! (<30 sek)
    • Schnell starten / laden, schnell etwas rausfinden, schnell wieder beenden
    • Andere Input-Art: Finger. Weniger Input ist mehr!
      • Nach Möglichkeiten suchen Tippen zu vermeiden - weil schwer
      • Tippfehler akzeptieren / korrigieren
    • Weniger Platz am Bildschirm: Das wichtigste aussuchen, das Verstärken
      • "Reduce and Amplify!"
      • "You want to strip this app to the bone!"
  • Conceptualize
    • Fokus auf Lösungen!
    • "Refine, Refine, Refine"
    • Das Mentale Modell der User ergründen
      • Eine Frau die Kinder, Hund, Mann pflegt
      • Sie schaut auf ihre Einkaufsliste, wie viel sie auf dem Konto hat
    • Benutzer entscheiden in 20 Sekunden ob sie eine Applikation behalten. Wenn sie 15 davon zum Starten benötigt...
      • Nur Spiele die man eh länger (10-30 Minuten) spielt dürfen länger brauchen (manche Utilities auch)
    • Immer mit einer Idee anfangen:
      • "Vielleicht inneneinrichtung machen?" -> Was könnte man machen? Viel.
      • "Vielleicht  FoneLink auf das iPhone bringen?" -> Macht schweine viel....
      • Was ist die Lösung die man anbietet?
    • Define your solution:
      • Application Definition statement: Worum geht's wirklich? Was und für Wen ("iPhoto ist ein Programm zum sortieren, betrachten und teilen von Photos für Home-User und Amateure.")
      • Das ist das Sieb mit dem man Features aussiebt
    • Refine Features
      • Was ist das wenigste mit dem man durchkommt?
        • Wegschmeißen! Selber belohnen wenn man etwas rauslässt! :)
        • Sieben mit dem "Application Definition Statement"
      • Was ist Neu, Am häufigsten Benutzt, Von den meisten usern (80%), Passt für unterwegs Benutzung
      • Was passiert wenn man sich ver"klickt"?
      • Was wenn die Beleuchtung schlecht ist?
      • Weniger Information zu jedem Zeitpunkt anzeigen, dafür "drill down"
    • Welches Mentale Modell haben die Benutzer? Wie sieht er die Applikation?
      • Identify: Objects, Tasks, Roles, Relationships
  • Realize
    • Welche Art Programm baut man? (Welcher Typ?)
    • Produktivität: Lang benutzung (Mail), Wenig Graphik, Viele daten (Drilldown)
    • Utility: Ein Blick auf etwas, dann fertig
    • Immersive: Games, eigene Oberfläche, viel Graphik
    • Das kann man plotten: Von oben (Serious) nach unten (Fun), von links (Entertainment) nach rechts (Tool)
      • Oben Rechts (Serious Tool) Mail: Viel Text, Hierarchien, wenig Graphik
      • Unten Rechts: (Fun Tool) Social, Twitterific: Graphisch, wenig Text, Flache Hierarchien
      • Unten Links: (Fun Entertainment) Games: Graphisch, alles eigenes Interface
      • Oben Links: (Serious Entertainment) News: Text, wenig Graphik, Hierarchien (drill down)
      • Mitte: Utilities (Rest?) Wetter, Aktienkurse, Uhrzeit / Wecker
    • Objekthierarchie in der UI wiederspiegeln (Das hat mich überrascht, da man das auf dem Desktop ja eigentlich eher nicht machen will (Weil das Mentale Modell der user, nach dem man das Interface baut, nicht unbedingt der beste Implementierungsweg ist)
    • Mit Papier Iterieren (Welcher Screen kommt wo und wann?) immer wieder...
  • Finalize
    • Verbessern mit Photos und Bildern
    • Animation für Begeisterung und Feedback!
    • Refine, Optimize, Enhance

Zusammengefasst:

  • "Application Definition Statement" erstellen
  • Iterieren "early and often" (Auf Papier?)
  • Bis zum Release wiederholen - dann wieder von vorn. :)

UIKit Development

  • View Controllers stehen immer im Zentrum: "The C in MVC" :)
    • Einer für jeden Bildschirm / Screen in der App
    • Zwei Sorten: Custom Subclasses (Typischerweise 1 Screen), Apples Subklassen (machen mehrere Screens!)
    • "view" property zum ablegen des UIViews
    • wichtige delegate methoden: loadView: viewDidLoad: viewWillAppear: viewDidAppear:
    • initWithNibName:bundle: -> Designated initializer
    • Releasen wenn man fertig ist mit anzeigen -> Speicher sparen
    • Navigation controller: "Drill Down" interfaces
      • Managt andere ViewController!
      • wichtige Delegate Methoden: pushViewController:animated: popViewController:animated:
      • Wechselt Controller, Show/Hide NavigationController (für die Rückwärtsnavigation und wenige Aktionen)
    • UITabBarController: Verschiedene Ansichten auf die gleichen Daten (iTunes)
      • Managt NSViewControllers
      • More und Konfiguration (fast wie NSToolbar!)
      • Icons schwarz-weiß Outlines mit Alpha!
    • Toolbars: Tools und Aktionen (nur ein Icon)
      • Kein UIViewController!
      • OK: Modales-Sheet als Aktion bringen (Slide up from the bottom!)
      • Slide up meint: Etwas worauf man reagieren muss (Slide across meint navigating)
      • Nie im Tab-Switcher aktionen auslösen!
      • Bookmarks: Sheet dass den ganzen Bildschirm abdeckt
    • UITableViewController: Tabellen...
      • Plain (Adressbuch), Grouped (Prefs)
      • Header, Footer, Sektionen (wieder mit Header & Footer)
      • Jede menge delegate calls um es zu befüllen
      • UITableCells:
        • Kann: Bild, Text, Disclosure / Accessory (evtl. auch checkbox?)
        • Kann einfach Subviews hinzufügen (Yay, nie mehr SubviewTableViewController)
          • Speicher sparen: (Speziell beim scrollen wichtig, damit es flüssig ist) "Cellen" mit gleicher Klasse / Struktur sollten gleiche ID haben, dann kann man mit [tableView dequeReusableCellWithIdentifier:someID] die die oben rausgescrollt sind wieder verwenden statt jedesmal eine Neue zu machen
    • View Controller Tips
      • Immer festlegen ob man das View drehen kann oder nicht (Telefon verkehrt herum macht keinen Sinn - Spiele könnten aber auch in beiden Landscapes prima funktionieren)
      • Bildschirmgröße kann unterschiedlich sein: Beim Telefonieren ist die Statusbar doppelt so hoch!
      • Memory
        • Jeder UIViewController kriegt Memory-Warnings
        • applicationDidFinishLaunching -> leicht halten (für schnellen Startup)
        • Jedes UIKit View ist / hat einen core-animation Layer -> Macht spaß
        • Während der Animation (asynchron) die Applikation blockieren. Wenn der User irgendwo klickt und einen view-wechsel auslöst, der die Animation sinnlos macht...
  • Text Display:
    • UILabel, UIWebView (html, pdf, word)
    • NSString (UIStringDrawing)
    • UITextField (so wenig wie möglich, immer defaults anbieten!)
    • Keyboard Code: (UITextInputTraits) oder IB
      • shown in becomesFirstResponder hidden in resignFirstResponder
      • Cancel bedenken!
  • UITouch (ein Finger!)
    • multiTouch muss man per view einschalten!
    • Gesten muss man selber erkennen
    • touchesBegan:withEvent: touchesMoved:withEvent: touchesended:withEvent:
  • Accelerometer: Reichtung
    • deviceOrientationChanged: delegate
    • Oder direkt -[UIAccelerometer sharedAccelerometer]
      • möglichst wenig (baterie) grob: 10/sec, spiel: 30/sec
      • filtern! low pass (konstante bewegungen)-> Bubble-Level Sample, high-pass für plötzliche bewegungen
      • Kriegt keine konstante Bewegung oder Richtung mit
  • MediaPlayer: Full screen movies
  • Wichtig man kann zu jedem Zeitpunkt unterbrochen werden. Telefon, SMS, Termin (Telefonieren ist aber das wichtigste!)
    • Dann: Adio anhalten, Animation weg, Pausen-Funktion
    • Bei einem Telefonanruf: Erst wird eine Pause ausgelöst, und eine Dialogbox die dem User erlaubt das Telefonat anzunehmen oder abzuweisen, Wenn er annimmt, wird die Applikation terminiert, sonst "resumed"
  • CoreLocation
    • Filter setzen damit weniger callbacks notwendig sind: distanceFilter = xxx; desiredAccuracy = kConstant;
    • setzt man die filter "niedrig" genug wird möglicherweise GPS gar nicht angeworfen -> weniger Strom, schneller
    • Authorization Dialog: durch startUpdatingLocation -> lehnt der User ab muss man das behandeln (Muss man das jedes mal authorisieren?)

Optimization with Instruments

  • Wenig über Instruments und Shark. Mehr allgemeines Zeug
  • Man muss sich klarmachen was man wo an informationen kriegen kann:
    • Simulator: Mac: 2 ghz, 2 gig ram, 500 gb platte, 2mb l2 cache, ethernet, wifi
    • iPhone: 400mhz, 128 mb ram, 8-32 gb flash, kein cache, wifi, 3g, edge
  • Was messen: Speicher, Malen und Scrollen, Startzeit, Dateizugriff, Stromverbrauch
  • VM gibt's, aber kein swap, read only pages (memmap) werden aber geschossen und neu geladen
  • Keine Garbage Collector (man könnte sich aber mal an dem Boehm versuchen :)

Tips:

  • Static Memory minimieren
    • Code Size
    • Compiler flags (schon standardmäßig): -mthumb -> benutzt nur ein 16 Bit Subset und macht kleineren -> Floating point Code wird aber wesentlich größer -> Abwägen
    • "Dead Code Stripping" -> hilft aber nur bei C/C++ Code
    • Möglichst abschalten was man nicht braucht (C++ RTTI, Exceptions)
    • PNGs statt BMP/TIFF
    • Plists als binary Variante (macht Xcode automatisch beim Resourcen Kopieren)
  • Dynamic Memory
    • NSAutoreleasePools eher vermeiden sonst so nutzen wie mans machen soll -> computation intensive schleife -> alle 100/1000 mal einen neuen nehmen
    • "nonatomic" properties sind bis zu 10 mal schneller
    • leaks vermeiden....
  • Memory warnings
    • Unbedingt reagieren -> sonst wird die Applikation abgeschossen
    • Werden u.a. via den UIViewControllern geliefert
    • Dem Benutzer sagen dass die App gleich crashen könnte ist KEINE Lösung
  • Custom Drawing:
    • Views undurchsichtig (opaque) machen -> schneller
    • optimiert Zeichnen: -setNeedsDisplayInRect:, dann nur das in -drawRect: malen
    • Cachen was sich nicht verändert
    • Möglichst views nicht beim Scrollen allokieren -> langsam
      • "reusable cells" machen mehr sinn (siehe dequeue...)
    • Cell layer hierarchies zusammenfallen lassen? (Unklar was das genau sein soll
    • "Design for fast launch, short use"
    • "Optimize startup, Optimize quit"
    • Rest per lazy initializing
  • Große Datensets:
    • -[NSData initWithContentsOfMappedFile:]
    • SQLite für random access in großen Datensets
  • So wenig wie möglich schreiben
  • Sich schnell / langsam ändernde Daten trennen
  • Möglichst wenig Batterie verbrauchen
  • Möglichst wenig Netzwerktraffic
    • wenn doch: Viel auf einmal, wenig "chatty protokolls"
  • Location:
    • Holen, dann callbacks ausschalten
    • Genauigkeit nur so weit einschränken wie man es tatsächlich braucht
  • Posted: 2008-11-10 21:06 (Updated: 2008-11-10 21:12)
  • Author: dwt
  • Categories: code
  • Comments (0)

Delegation sucks

Well, it doesn't really. But if way overused it becomes a pain in the a**.

This is not so much a problem in on itself, but the code sprouts dependencies that nobody is able to follow from just reading a class but which become crucial to the operation of the application.

Well, out of frustration I started thinking about the saying that Design-Patterns are just for languages that are not powerful enough to implement something in the language.

So lets see, what can we do to make this simpler:

if ([someObject respondsToSelector:@selector(someDelegateMethod:)]) {
    [someObject someDelegateMethod:self];
}

The key here is to only call the messagte -someDelegateMethod: if it is actually supported on the target. A perfect application of my invocation? catching code?.

[[NMDelegateCaller callerForDelegate:object] someDelegateMethod:self];

In real life of course you would hide the delegator by just wrapping your delegate in the -setDelegate: method.

Interestingly implementing this proved rather more tricky than I anticipated. This is because of the way the objc-runtime creates NSInvocations out of c-method calls. The trick is, that really at runtime nothing is known about how arguments have to be scraped out of the stack because in C you have no clue what arguments actually are on the stack.

So objc takes a detour and stores this information alongside each method in the object that the method belongs to. This however has the unfortunate side effect that it should make just the delegator I wanted to build impossible to build. That is, because the runtime cannot build an NSInvocation out of a method call that the destination does not support - simply because it doesn't know what arguments are on the stack and what to return.

This is where my little evil hack comes in: I defined a method on NSObject that represents an identity for this situation - that is, it returns nothing and takes no argument - therefore the runtime won't crash anything by using it's description to build an NSInvocation. This is my marker.

And with this the problem becomes solvable - in -methodSignatureForSelector: I ask the target for a signature if it has one, and return my marker signature and let the runtime build an NSInvocation out of whatever method call was made (what exactly it was doesn't interest me, only that it's not implemented on the delegate). Then in -forwardInvocation: I can test if the signature is that marker and just discard the method if it is.

Voilá.

So it is proven - objc is powerful enough to implement this design pattern in itself - no need for all those if statements - and I learned a lot about the internals of NSInvocations.

Still I'm not sure if I really want to use that code. Yes it's shorter, but it's also a lot more complicated and it makes something very convenient which in essence I don't really want to be so convenient, because if overused it becomes a maintenance nightmare.

Also it lacks the simple elegance of just stating exactly what you want to achieve and returning good default values (my solution can just return NULL/NO/0 if the method is not implemented).

Damn. Alles für die Katz.

Still very interseting though. Maybe I'l find a different application that suits me more.

Syntax matters

Gerade habe ich doch ein Stück Code hochgeladen? mit dem man jeden beliebigen Methodenaufruf in eine Invocation umwandeln kann.

Nun, das hat mir nicht gereicht. Jetzt hab ich mich noch mal hingesetzt und mich an einer anderen Syntax versucht:

// anstatt
id invocation = NMCatchInvocation(@"fnord", stringByAppendingString:@"42");

// jetzt
id invocation = [NMRecordingInvocation invocation];
[invocation recordWithTarget:@"fnord"] stringByAppendingString:@"42"];

// oder
id invocation = [NMRecordingInvocation invocationWithTarget:@"fnord"];
[invocation record] stringByAppendingString:@"42"];

Und ich finde das ist von der Syntax her deutlich eleganter als das Makro.

Allerdings:

  • Es ist etwa der doppelte Aufwand dass zu implementieren
  • Das Makro erzeugt viel schnelleren Code (viel weniger allokationen)

Was soll ich davon jetzt halten? Small is beautifull? Keep it simple stupid? Oder ist doch wichtiger dass der zweite Versuch eher dem Objc-Spirit entspricht?

Mir ist das auch nach einem Tag darüber nachdenken erheblich unklar.

NSInvocations zusammenbauen...

...ist deutlich schmerzhaft.

Man muss eine Menge Aufwand treiben um aus einem objc-call etwas zu machen dass man schön hin und her passen kann.

Zum Beispiel:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    id aString = @"fnord";
    
    id signature = [aString methodSignatureForSelector:@selector(stringByAppendingString:)];
    id invocation = [NSInvocation invocationWithMethodSignature:signature];
    
    [invocation setTarget:aString];
    [invocation setSelector:@selector(stringByAppendingString:)];
    id appender = @"23";
    [invocation setArgument:&appender atIndex:2];
    
    [invocation invoke];
    
    id returnValue = nil;
    [invocation getReturnValue:&returnValue];
    
    NSLog(@"Got: %@", returnValue);
    
    [pool drain];
    return 0;
}

Das nervt sobald man es öfters als einmal machen muss.

Darum hab ich mir nach langem nachdenken mal etwas zusammengebaut das das erleichtert. Es ist nicht perfekt und ich hab auch eine Menge Ideen wie man es noch schöner machen kann, aber es funktioniert. :)

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    id invocation = NMCatchInvocation(@"fnord", stringByAppendingString:@"23");

    [invocation invoke];

    id returnValue = nil;
    [invocation getReturnValue:&returnValue];
    
    NSLog(@"Got: %@", returnValue);
    
    [pool drain];
    return 0;
}

Und das sieht schon bedeutend besser aus.

  • Posted: 2008-10-25 07:42 (Updated: 2008-10-26 20:28)
  • Author: dwt
  • Categories: code
  • Comments (0)

C-Code für C und C++ verwendbar machen

Ich hab mich schon immer gefragt woher man eigentlich diese Defines nimmt um C-Code deklarationen so zu schützen, dass sie auch in C++ Code gut reingeholt werden können.

Ja klar, man kann das selber machen, etwas präprozessor bla und los gehts. Aber, das gibts ja vielleicht schon.

Enter #import <sys/cdefs.h>.

Und das ist in in Apple-Code praktischerweise immer schon includiert und enthält praktische Kleinigkeiten wie:

  • __BEGIN_DECLS - jetzt kommen C-Deklarationen
  • __END_DECLS - fettich
  • __dead2 - diese Funktion kehrt nicht zurück (über das 2 könnte ich mich total amüsieren)
  • __pure2 - diese Funktion hat keine Seiteneffekte
  • __unused
  • __printflike(formatArg, firstVarArg)
  • __scanflike(formatArg, firstVarArg)
  • __COPYRIGHT(string) - erzeugt eine globale variable
  • __PROJECT_VERSION(string) - erzeugt eine globale variable
  • __deprecated

Dann gibts natürlich noch <AvailabilityMacros.h> das auch noch ein paar Kleinigkeiten enthält - neben dem ganzen Zeug um APIs zu versionieren sind da auch noch mal Attribute definiert:

  • WEAK_IMPORT_ATTRIBUTE -- funktion ist vielleicht da
  • DEPRECATED_ATTRIBUTE -- funktion soll nicht mehr verwendet werden
  • UNAVAILABLE_ATTRIBUTE -- funktion ist nicht vorhanden (z.B. weil sie API ist die erst in einer späteren Version des Frameworks unterstützt wird)

Vor allem __BEGIN_DECLS, __END_DECLS und __deprecated werde ich mit Sicherheit sofort benutzen. Ob ich die anderen auch verwende, weiß ich noch nicht. Mal schaun. :)

Regexe updated

Hab ich mir doch gedacht, nur matching von regexen auf ganzen Strings ist doch langweilig - also hab ich noch containsRegex: hinzugefügt um etwas mehr komfort zu haben.

Da dachte ich ja zuerst dass ein Regex wie dieser hier eigentlich nicht funktionieren dürfte: .*^a angewandt auf "aaa" - aber interessanterweise funktioniert das ohne Probleme. Muss ich wohl noch etwas an meinen Regex-Verständnis feilen.

Ah well.

Code hier

Cocoa hat ja Regex Support

Überraschend.

Das der so vollständig ist, hätte ich nicht erwartet. Aber in der Tat,  ICU-Regexe in ihrer ganzen Pracht. Völlig ohne eine neue externe Dependency.

Laut der Dokumentation sollte das sogar in 10.4 schon funktionieren - mit dem einen Caveat, dass multiline Support dort wohl von Hand eingeschaltet werden muss (via (?m))

Sowas wird da möglich:

if ([@"aaa" matchesRegex:@"a{3}"])
    NSLog(@"gotcha");

// other examples
[@"argh fnord argh" matchesRegex:@".*\\bfnord\\b.*"];
[@"aaa" matchesRegex:@"\\w{3}"];

Und das mit extrem wenig Aufwand:

@interface NSString (SimpleRegexMatching)
- (BOOL) matchesRegex:(NSString *)aRegex;
@end

@implementation NSString (SimpleRegexMatching)
- (BOOL) matchesRegex:(NSString *)aRegex;
{
    id predicate = [NSPredicate predicateWithFormat:@"self matches %@", aRegex];
    return [predicate evaluateWithObject:self];
}
@end

Ich bin beeindruckt.

Code hier

  • Posted: 2008-10-09 20:43 (Updated: 2008-10-12 14:16)
  • Author: dwt
  • Categories: code
  • Comments (0)

Aus einem Variablen-Namen einen String machen

Braucht man manchmal für Bindings. Gleichzeitig will ich aber nicht überall die namen meiner Variablen als Strings hinterlegen - schon alleine damit ich beim Refactorn auch alle Namen erwische.

Also den Macro-Prozessor zur Rettung:

#define NSStringize(aVariableName) @#aVariableName
#
macht aus einer beliebigen expression einen String
@
und das @ davor macht aus einem constanten c-string einen ObjC-NSString.

Und das problem ist gelöst. :)

Creating NSNumbers from arbitrary values...

... without needing to care which constructor to use this time to get it right.

Well, I was offended by this just long enough, so I sat down and wrote a macro that allows this:

//  Use like this:
    id dict = [NSDictionary dictionary];
    NSNumber *ten = NMMakeNumber(10.0f);
    [dict setObject:ten forKey:@"numberOfFingers"];
    [dict setObject:NMMakeNumber(23) forKey:@"fnord"];
    [dict setObject:NMMakeNumber(YES) forKey:@"wantFries"];

I'ts almost the way  Autoboxing works in  Java or C#.

This quite eases the pain of creating NSNumbers correctly for me, because it means I don't have to repeat the type of what I am working with quite as often.

Pretty  DRY. :)

Here's the code to a NSNumber category and a makro that makes this happen.

  • Posted: 2008-10-04 12:20 (Updated: 2008-10-07 10:07)
  • Author: dwt
  • Categories: code
  • Comments (0)

Ein Tool sie zu finden und alle zu binden...

... auf die Shell zu ziehen und ins Highlighting zu ziehen. :) (Sorry, ich muss dringend mal wieder  Herr der Ringe anschauen)

Mir geht es um grep, der Urgroßvater aller Projektweiten Suchmöglichkeiten. Das kann viel - aber nicht alles.

Zum Beispiel ist es nicht besonders einfach in einem Projekt zu suchen dass SVN für die Versionskontrolle einsetzt. Grep besteht nämlich darauf dass es unbedingt bei -r auch in .svn Verzeichnissen suchen will. Und das beste: es gibt zwar eine Option --exclude - die funktioniert aber nur auf File-Basis. Das heißt man kann damit keine Verzeichnisse (wie z.B. .svn) ausschließen.

Doh.

Die Entwickler sagen dazu dass man doch lieber find . mumbojumbomagic | grep muster benutzen soll. Tja.

Enter  Ack. Die Lösung aller Probleme. :)

  • Das Tool sucht standardmäßig rekursiv - d.h. man muss nicht ständig -r angeben.
  • Es ignoriert standardmäßig .svn .hg .git etc. Verzeichnisse - daher muss man nicht ständig irrelevante Resultate ignorieren.
  • Man kann mit switches wie --python oder --objc nur in Dateien dieses Typs suchen.
  • Man hat vollständige Unterstützung für Perls Regular Expressions auf der Kommandozeile (das Tool ist in Perl geschrieben)
  • Man kann das Tool supereinfach in ~/.ackrc konfigurieren.

Hier meine .ackrc:

$ cat ~./ackrc
--type-set=nib=.nib
--type-add=objc=.mm
--ignore-dir=build

Schick ne?

 Hier zu finden.

2008/09/05/19.08

Python != Funktionales Programmieren

--- zumindest scheint es so.

Das hier zum Beispiel:

#!/usr/bin/env python
# encoding: utf-8

def counter_generator():
    current_value = 0
    def counter():
        current_value += 1
        return current_value
    return counter

import unittest
class ClosureTest(unittest.TestCase):
    def test_counter(self):
        self.assertEquals(1, counter_generator()())

if __name__ == '__main__':
    unittest.main()

Wenn man das ausführt dann kriegt man den wenig aussagekräftigen Fehler UnboundLocalError: local variable 'current_value' referenced before assignment. Oder mit anderen Worten: "Wiebidde?"

Kommentiert man current_value += 1 aus, funktioniert das ganze (ok, bis auf den dann fehlschlagenden Testcase).

Immerhin kommt man dem Problem dann auf die Spur (ich und Felix haben das leider erst herausgefunden nachdem wir auf #python nachgefragt haben...) Python kann nämlich seine Scopes nicht vernünftig. Das heißt, man kriegt keinen schreibenden Zugriff auf die Variablen eine Funktion weiter draußen - nur lesend. Nur dass die Fehlermeldung natürlich total mist ist.

Grah.

Interessanterweise geht diese Lösung auch nicht

#!/usr/bin/env python
# encoding: utf-8

def counter_generator():
    workaround = object()
    workaround.current_value = 0
    def counter():
        workaround.current_value += 1
        return workaround.current_value
    return counter

import unittest
class ClosureTest(unittest.TestCase):
    def test_counter(self):
        self.assertEquals(1, counter_generator()())

if __name__ == '__main__':
    unittest.main()

Für hinweise wieso bin ich dankbar.

Vielleicht kriegt Python dass ja irgendwann mal hin. In der Zwischenzeit hilft dieser Workaround:

#!/usr/bin/env python
# encoding: utf-8

class AttributableDict(dict):
    def __getattr__(self, attribute_name):
        return self[attribute_name]
    def __setattr__(self, attribute_name, value):
        self[attribute_name] = value

def counter_generator():
    workaround = AttributableDict()
    workaround.current_value = 0
    def counter():
        workaround.current_value += 1
        return workaround.current_value
    return counter

import unittest
class ClosureTest(unittest.TestCase):
    def test_counter(self):
        self.assertEquals(1, counter_generator()())

if __name__ == '__main__':
    unittest.main()
  • Posted: 2008-09-05 19:14 (Updated: 2008-09-05 19:14)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

Authenticating an OTR user in Adium

(With many thanks to Dan Levin for his help)

This is what I arrived at last week as an authentication GUI:

This GUI Design has several good and bad points - first the good ones:

  • It tells the user exactly what he should do
  • It gives help in how to achieve that
  • It provides additional help on how to achieve this

The bad ones though weigh very heavy:

  • Its a dialog.
  • Its super text heavy
  • it looks ugly (well not because of the design, but because it has so much text in it.)

The most pressing problem however is in the library support from OTR. It simply does not know what a question is.

This is a problem, because it makes it almost impossible to have the Question as a special entitiy in the GUI - and also that none of the synchronisation problems that this brings are adressed in the GUI. So what to do if both users start typing their own question? The other User wouldn't know - then how does the programm tell the user that his question was just overruled be the other persons and that he should provide the answer to that as the secret word?

There is more of course - but this is what I consider the most important.

Well, this is our current approach at solving these problems:

This is what I like about this:

  • No more dialog, it's all happening inline
  • It clearly states what goes where in clear statements
  • It tells the user in clear terms what he needs to do and how to do it
  • Only as much text as is absolutely needed
  • Provides more help should the user request it.

Especially this is the most simple GUI of all the approaches that I tried.

I don't have finished mockups of the other aproaches (most is pen'n'paper work) but I did try having the question and answer thing inline like so:

I especially like the fact that this gives lots of practical information to the user - even if it is still very wordy and does not solve the race condition of who will actually ask the question.

Therefore we decided to go with the first aproach pictured above.

There is of course more to this GUI than just the pretty pictures shown above. A few more things come to mind, that we think are very important:

  • The normal chat window can and should be used as a feedback and instruction window. (With help messages clearly marked as such of course.) We want to use this to show some initial help when the "Verify" button is clicked and the authentication process is started and also at least when the authentication process fails multiple times, to provide additional help and tips as to how to overcome this situation.
  • Whenever the user types the secret word in the normal chat we want to either clearly notify the user that he has just compromised his password exchange and needs to choose a new question and answer (also if it is in the recent history) or we'd like to prevent the sending of the message (perhaps with a dialog box that also allows to proceed if that is really wanted).

One unsolved problem is, that when the authentication fails multiple times, we need to find a good compromise between teaching the user about ways to prevent errors and making him aware of the possibility that there may be a man in the middle attack taking place.

Of course, just as the last time: Feedback welcome.

 Here's the rest of the work

  • Posted: 2008-09-03 17:50 (Updated: 2009-10-24 15:55)
  • Author: dwt
  • Categories: code
  • Comments (0)

Kryptographie saugt

Und zwar gewaltig.

Heute hab ich eine Menge Zeit darauf verbrannt unserem Online-Update-System auf den Zahn zu fühlen. Das arbeitet nämlich ganz hervorragend mit  Sparkle um für unsere Applikation via  Appcast herunter zu laden, wenn eine neue Version ansteht. Und damit das auch schön sicher ist, nutzen wir ein kleines öffentlich/privates Schlüsselpärchen um das abzusichern.

Nun ja, bis heute mittag - da hat es nämlich auf einmal aufgehört und konnte die heruntergeladenen Dateien nicht mehr validieren.

Jetzt hatte ich natürlich das Problem, das ich an sich recht wenig von Crypto verstehe und erst recht wie man das von der shell aus einsetzt. Das wiederum hatte zur Folge, dass ich das nicht sofort auf der Shell nachvollziehen konnte und wir schon anfingen zu fürchten das vielleicht unsere Schlüssel abgelaufen sind.

Gott sei dank war es aber doch was anderes - denn unser Proxy hatte fieser weise einfach eine alte Version des Appcasts gespeichert und stattdessen immer die ausgeliefert - was natürlich nicht funktionierte... Grumpf.

Note to self: Nächstes mal gleich den ganzen Vorgang von Hand reproduzieren um zu sehen wo es schief läuft - und zwar angefangen von da wo das Programm anfängt. (Auch wenn in diesem Fall die Fehlermeldung wirklich eher auf einen OpenSSL Fehler hindeutete).

Debuggen ist eben schwieriger als den Code zu schreiben. Ich vermute, weil man ihn dafür wirklich verstehen muss - inklusive aller Fehler die man in seinem eigenen Verständnis hat.

P.S.: So Signiert und Validiert man ein File mit OpenSSL von der Kommandozeile:

# base 64 encoding to ease textual transmission
openssl dgst -dss1 -sign dsa_private_key.pem Important.rtf | openssl enc -base64 > signature.sig
# and decode again
openssl enc -base64 -d < signature.sig > decoded.signature.sig
# and verify
openssl dgst -dss1 -verify dsa_public_key.pem -signature decoded.signature.sig Important.rtf

Einige Fallstricke: -dss1 und -sha1 erzeugen offenbar die gleichen Checksummen (man könnte also meinen das es der gleiche Algorithmus ist) die beiden digest modi werden aber in openssl offenbar unterschiedlich behandelt. Beim Signieren löst das verwenden von -sha1 auch schön eine Fehlermeldung aus - wenn man beim Überprüfen aber -sha1 zu verwenden versucht, dann erhält man nur den Hinweis dass die Verifikation gescheitert ist - an einem falschen public key. Seeehr hilfreich liebe Entwickler.

Wie man ein Schlüsselpaar erzeugt und vieles mehr  gibts hier.

  • Posted: 2008-09-01 21:44 (Updated: 2008-09-01 21:48)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

Kontrollieren welche Libraries ein Programm lädt

ist beim Entwickeln ein echtes Problem.

Frameworks unter Mac OS X werden schließlich so gelinkt, das jeder Framework den Pfad enthält, an dem er später einmal leben wird - und dieser Pfad wird dann in die Applikation eingetragen die ihn laden möchte.

Das ist blöd, wenn man Frameworks in /Library/Frameworks/ installiert, bei der Entwicklung dann aber mal eine andere Version ausprobieren will oder muss. Auch klassisch ist, dass ein Framework der von Apple kommt zu alt ist und man sicherstellen muss, das ein eigener Framework stattdessen verwendet wird, der Bugfixes enthält.

Schwierig.

Nunja, man macht das unter OS X mit dem Äquivalent des Linux Konstrukts LD_LIBRARY_PATH, also diversen Umgebungsvariablen, mit denen man das Verhalten des dynamischen linkers beeinflussen kann. Spaßigerweise hat Apple das verhalten des dynamischen Linkers unter Tiger (10.4) verändert, dass er sich jetzt genauso verhält wie unter Leopard (10.5) - zumindest sagt mein Gedächtnis andere Dinge als die 10.4 manpage von dyld.

Also sind jetzt eigentlich nur noch zwei Umgebungsvariablen interessant:

  • DYLD_FRAMEWORK_PATH wenn man etwas laden will, bevor die Pfade verwendet werden die in der Applikation für einen Framework stehen -> also um einen Framework zu überschreiben.
  • DYLD_FALLBACK_FRAMEWORK_PATH wenn man etwas nachladen will, nachdem es an den vorgegebenen Pfaden nicht gefunden wurde. Die Manpage ist mir da noch nicht 100% klar - es wirkt fast so, als könnte man dyld auch dafür verwenden eine neuere Version anzugeben, die nur genutzt wird, wenn in der alten ein bestimmtes Symbol nicht gefunden wurde - das konnte ich aber noch nicht testen (und kommt mir auch komisch vor).

Also: kennen sollte man die - das gibt viel flexibilität.

Obfuscated Objc - oder - wie macht man Konstanten?

Ist ja eigentlich nicht schwer, man braucht lediglich eine Definition wie diese

#define kIOHIDVendorIDKey @"VendorID"
const NSString *windowName = @"Morgens"

Einziges Problem - immer wieder hat man vom System konstante C-Strings, die man aber als Objc-Strings benötigt.

Interessanterweise hat der Compiler überhaupt kein Problem das hier zu machen:

#define kIOHIDVendorIDKey "VendorID"
// ...
[matchingDict setValue:[NSNumber numberWithInt:entry->Vendor_ID] 
       forKey: @kIOHIDVendorIDKey]; 

// same, but more readable than
[matchingDict setValue:[NSNumber numberWithInt:entry->Vendor_ID] 
       forKey: [NSString stringWithCString:kIOHIDVendorIDKey]];
// Also note that -stringWithCString is deprecated

Oder mit anderen Worten man kann problemlos aus jeder C-String-Konstante eine Objc-String-Konstante machen, indem man einfach ein @ davor schreibt.

Neat.

Debugging Magic in Cococa

Erstaunlich aber wahr - viele Programmierer scheinen die Debugging-Möglichkeiten der Plattform ihrer Wahl nicht zu kennen.

Cocoa zum Beispiel. Viele Programmierer wissen nicht wie man Speicherlöcher effektiv fängt.

Well, nicht Leaks - da kann man ja mit  ObjectAlloc draufhauen und dann das Programm fixen.

Nein, viel problematischer als das sind die doppelten Befreiungen.

id foo = [[NSString alloc] initWithFormat:@"foo"];
[foo release];
// do something else
NSLog(@"foo is %d", [foo length]); // almost certainly bombs

Die krachen nämlich fast immer - und dann sucht man sich tot was genau da eigentlich gekracht hat und woher das genau kommt. Tja.

Dabei kriegt man aus dem Programm eigentlich alle Informationen heraus die man braucht. Man muss nur jedes malloc mitzählen und aufschreiben wo es passiert ist, dann jedes objekt das freigegeben wird, nicht wirklich freigeben, sondern lediglich markieren und dann bei einem weiteren Zugriff auf so ein Objekt eine Exception werfen.

Nichts leichter als das. :)

Man setze diese Environment Variablen (am besten auf einem Duplikat der zu entwanzenden Applikation in Xcode)

MallocStackLogging YES
MallocStackLoggingNoCompact YES
NSZombieEnabled YES
NSDebugEnabled YES

Hat man dass macht die Runtime alles richtig. Jede allokation wird aufgezeichnet und jedes freigegebene Objekt durch einen Zombie ersetzt. Und der Meldet sich, sobald er für irgend etwas verwendet wird. Nämlich so:

2008-08-26 00:19:43.524 CrashTest[1663:817] *** -[CFString length]:
 	message sent to deallocated instance 0x105440

Hängt man dann im Debugger, kann man von dort aus direkt diese Information abgreifen.

(gdb) shell malloc_history 1663 0x105440

Call [2] [arg=16]: thread_a00ddfa0 |start | main | -[NSString initWithFormat:] |
 -[NSPlaceholderString initWithFormat:locale:arguments:] |
 _CFStringCreateWithFormatAndArgumentsAux | CFStringCreateCopy |
 __CFStringCreateImmutableFunnel3 | _CFRuntimeCreateInstance | malloc_zone_malloc 

Die Syntax für malloc_history ist dabei malloc_history pid address.

Jetzt weiß man mit was für einem Objekt man es zu tun hat und man weiß wo es her kommt.

Nice! (Mehr unter  Mac OS X Debugging Magic)

p.s.: Ohne etwas Schelte an Apple geht es aber nicht. Das man sich da von Hand die Umgebungsvariablen einstellen muss - böse. Zumal das noch an einer Custom Executable geschieht. Die sind nämlich nicht im Projektfile gespeichert, sondern in den Dateien die Xcode pro Benutzer anlegt. Das hat zur Folge, dass im Normalfall nicht ein Entwickler diese Dinge raussucht und dann alle anderen im Projekt dass als Tool nutzen können, sondern dass sich das jeder Selber einrichten und warten muss. Ach ja, und natürlich zeichnet malloc_history nur die allocs auf und nicht die frees - das wäre nämlich noch praktischer, dann könnte man nämlich direkt kucken wo es das erste mal freigegeben wurde. Aber nein....

NSToolbarItem whoes

Kleines Detail, dass man wissen sollte.

NSToolbarItems sind etwas völlig anderes als normale Controls. Sie sind nämlich keine. Sondern sie enthalten höchstens welche - aber nur als View. Das heißt, wenn eine Toolbar z.b. auf "nur Text" geschaltet ist, dann hat das Item überhaupt kein wissen darüber was in dem NSView eigentlich passiert - und daher versucht es auch überhaupt nicht etwas sinniges zu tun.

Unter Tiger zumindest. Da wird zwar alles schön angezeigt - aber machen tut das dann alles nix mehr. Unter Leopard ist es immerhin so, dass auf einen Klick die Toolbar wieder mit Bild angezeigt wird.

Tja und Lösen muss man das ganze dann so, dass man verstehen muss, das NSToolbarItems für die nur Text Darstellung ein NSMenuItem verwenden, dass man selbst anpassen muss. Steht nur in der Dokumentation lediglich als mini Absatz ohne Erklärung wieso man das braucht.

Hrmpf. Naja. Immerhin macht es Sinn wenn man versteht was sie als Design gewählt haben.

Aber wieso sie sich für dieses Design entschieden haben, verstehe ich absolut nicht. Sie hätten ja auch gleich ein NSControll als content nehmen können - und wenn das ein Target und eine Action hat dann einfach die wiederverwenden...

Aber neee....

  • Posted: 2008-08-21 20:20 (Updated: 2008-08-25 23:45)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

Usability und Programmierer

Ich habe ja schon lange den Verdacht, dass das allgemeine Vorurteil das Programmierer schlechte GUI-Designer sind vielleicht nicht ganz korrekt ist.

Jetzt habe ich endlich den Beweis: Gutes GUI Design ist wie gutes API-Design - die allermeisten leute können es nicht.

 Das hier zum Beispiel  gethostbyname() ist teil der libc und gibt einem zu einem Hostnamen die Daten aus einem DNS-Lookup zurück.

Well.

Nur das wir jetzt eben mal zwei Tage darauf verbrannt haben herauszufinden, dass dieses gethostbyname so dämlich ist, dass es niemals nicht von sich aus checkt ob sich vielleicht seit das Programm gestartet wurde, die Netzwerkeinstellungen irgendwie verändert haben könnten. Egal wie lange das Programm schon läuft...

Groooßartig.

Dafür muss man nämlich die Funktion  res_init() aufrufen. Ach ja, und wann man das tut rät man einfach. Weil einen Callback gibts dafür natürlich nicht. Ach ja, und in der Manpage von gethostbyname steht natürlich auch nix über res_init.

Gnah.

Das ist für mich ein Musterbeispiel für eine verkackte API - eine API deren Benutzerfreundlichkeit gegen null geht.

Ergo: Programmierer können Benutzeroberflächen machen - aber die meisten eben nicht. Und das müssen wir alle dann wieder an den APIs erleiden die sie designen.

Hab ich schon mal erwähnt das ich die libc immer weniger mag?

Adium + OTR

So I want to be able to chat to my buddies in private. This is still very hard - one has to have all sorts of knowledge about encryption to get it right and even then the process is painful and hard to get right. Well, it's time to do something about it.  Adium has supported  OTR for some time now, and OTR recently gained the ability to use the  Socialist Millionaire Protocol as a means for authenticating chat partners to each other. The problem is, It's just not yet implemented in Adium.

We'd like to change that.

(Disclaimer: Imagine in long and fearful words me saying that this is not final and is just part of my thinking process and stuff I want to get feedback on) The obvious first thing to do is to think about the GUI and what I want to achieve there. Adium is quite good, in that it's reasonably easy to set it up so that as much as possible is encrypted, or to start encryption by just a single mouse click. The most pressing problem lies in that it's very hard to verify that a buddy really is who he claims to be. This is because both users have to read and confirm a 20-something digit fingerprint to each other out of the band of im communication (preferably over the phone or in person).

The Socialist Millionaire Protocol offers an alluring alternative - in theory, authentication is now as simple as you asking your partner a question that only he can answer correctly. If he provides the correct answer, dual-sided, authenticated encryption can be established. However, as shown in a  usability study on the Pidgin implementation of OTR bad GUI-Design can still ruin this great idea.

So lets get think about how we can make this work as seamlessly as possible.

Here's the example user that guides my thinking: Andrea, a history student wants to chat with her buddy Bert. She has no deeper understanding of computer science and cryptography, nor is she especially computer savvy. Even so she should be able to effectively secure her privacy with a few clicks and without any studying of handbooks or confusion on her part.

Two parts are needed for this to work:

1) Adium needs to inform her of the 'security status' of her connection without any doubt. This includes that it needs to show her in unambigous ways what she has to do next if she wants to get to a secure chat-connection. 2) Adium needs to allow her to run the Social Millionaire Protocol.

For 1) I have some nice ideas already. (linked to at the end of the article. For 2) I'm still not very far. The obvious way would be to just open another window - but thats merely ok GUI-Design as it completely disrupts whatever the user wanted to do. Another alternative could be to do it inline in the chat window - but this leads to all sorts of confusion, as the chat window suddenly becomes the interface to something completely different than chatting - something which it wasn't meant to do.

So, more drawing board needed there.

On to some mockups. :)

It's all basically the same idea. There are two ways that I see this working: either by showing the status of the chat inline in the message view - or not.

Inline has several advantages - for one, it's a lot easier to program. However, it has severe usability disadvantages. It is hard to see for example, because the font is small and who reads those system messages anyway? Also running the Socialist Millionaire Protocol requires an action by the user, so that leaves me with either adding a hyperlink to the message field to start this (which is unexpected by the user) or just explaining him what to do (which is bad, if I have to explain to him, why not just let him do it from right there?)

So I discarded that idea.

What I really like is Firefox in this regard. Their way of showing that they just suppressed a popup or a cookie is just a perfect example of the kind of interface I have in mind. Easy to see, enough space to put a button and a nice way to tell the user some status that doesn't disturb his workflow if he doesn't bother / can't do anything about it right now.

So I have something like this in mind:

This fits my requirements pretty good (more Ideas I toyed with at the end of the article). It clearly states what is happening, and gives the user an immediate means to do something about it. No confusion possible.

Well, to be frank, wording is still a problem. "Verify his identity" is very long, and will be much longer in german. Same for the first sentence. Some users may also react with a heartfelt: "Now you tell me, when I tried to turn encryption on?". Also what if the user just doesn't care?

Well, I'l ponder that some more.

Next stop: Actually doing the Socialist Millionaire Protocol.

After the user clicks the darned button, there be dragons. As I said above, I briefly toyed with the idea of doing everything inline the message view, however decided against that. My current working thesis is this:

This dialog has some merits: It clearly states what the user is supposed to do. It gives hints on how to do it and what to avoid, and it is mostly text.

Well.... I'm not very content with it yet. Also I haven't had a good look at how  Pidgin actually implements this feature (I wanted to build my own ideas unobstructed first).

Well, onto that later.

Here's the rest of my WIPs:

Note the lock in the menu-bar that also tells me the status of the conversation is private (to make the easy to miss lock in the toolbar more prominent.

I toyed with placing more locks, here's some results:

Just repeating something that doesn't work seems, to force it to work seems foolish though, so I thought about more radical approaches, of which I like this the most:

The problem is to communicate to the user that he is in a completely different status now, and make it accessible at a glance. Well, why dont just change the background color? Again Firefox is an example here, when surfing to a secure site, the status bar is rendered completely different to give the user good feedback. Why not apply that to Adium?

Oh and the inline SMP implementation:

Well.... It could be made to work... from a technological standpoint. :) Maybe it sparks some new ideas.

Want to read more?

  • Posted: 2008-08-19 21:25 (Updated: 2009-10-24 15:57)
  • Author: dwt
  • Categories: code links
  • Comments (0)

Template engines in Python

Sind fast alle XML-Basiert, oder haben eine hässliche special case Syntax.

Well, zumindest finde ich das.

Jetzt bin ich über  Jinja gestolpert und war auf den ersten Eindruck erst mal ganz überrascht. Übersichtliche Syntax, kein xml Zwang, und template inheritance.

Muss ich mal genauer anschauen. Immerhin finde ich Xpath wie es in Genshi implementiert ist auch reichlich nice - damit kann man sich wunderschön in bestehende Templates reinhacken - auch wenn die gar nicht dafür gemacht sind wiederverwendet zu werden.

Das ist natürlich gut und schlecht zugleich, daher bin ich mir nicht sicher dass das die beste aller Ideen ist. Also mal schaun.

bzr vs. hg vs. git

Ich fang ja nur langsam an zu verstehen wie sich die verschiedenen Konstrukte unterscheiden.

Da wäre zum Beispiel das immer wieder auftretende Problem das man an Sourcen arbeitet, auf die man nicht comiten kann oder will.

Also zum Beispiel an Bugfixes für ein Projekt, von dem man zwar die Sourcen hat, das aber dooferweise einer Firma gehört die keine comits zulässt. Ah well.

In  Git löst man das, indem man das einfach brancht und bei einem neuen Release mit rebase seine Changes auf die neue Plattform hebt.  Mercurial und  Bazaar lösen das anders, dort gibt es spezielle Plugins  Mercurial Patch-Queues und  Bazaar Looms die ein  Quilt ähnliches Interface bieten um das Problem zu lösen.

Tja, was macht den Unterschied aus?

Eine Sache die mir allmählich aufgeht ist das Teilen dieser Änderungen. Macht man einen Branch und rebased dann immer wieder auf die originalen Sourcen. Das ist super, weil es kein neues Konzept braucht und somit relativ simpel. Aber man kann diesen Branch so gut wie nicht mit anderen leuten Teilen - schließlich verändert man ständig die History. Also doof für die Zusammenarbeit an so einem branch.

Mercurial Patch-Queues sind da besser geeignet - mit dem unterschied das sie extra sind und nicht versioniert werden. Damit kann man sie aber auch nicht teilen.... :) - Das wiederum lässt sich hacken, indem man einfach in dem repository in dem ordner in dem die patch-queue liegt wiederum ein eigenes hg-repository eröffnet. Ein Hack - aber er löst das Problem auf elegante Art und Weise.

Bazaar scheint das mit dem looms am besten gelöst zu haben - die lassen sich nämlich ganz normal im repository pushen - sind aber eben nicht teil der history und führen daher nicht zu den üblen merge-conflikten die man in git erhält.

Auf mich wirkt das so, als zeigen sich die verschiedenen Ideologien der Entwicklungsteams in diesen Entscheidungen: GIT:: KISS - kein neues Konzept (Quilt) ohne das es absolut sein muss, gleichzeitig aber die Linux-Poweruser Mentalität: Wenn du deine History-Verändern willst, dann mach doch, wirst schon wissen was du willst. HG:: Gutes Design, history verändern ist evil - daher musste es was anderes sein. Gleichzeitig ist man aber so stolz auf die Binary- und Wire-Formate, dass man sie nicht verändern darf - daher bleibt die Information aus dem Repository ausgeschlossen. BZR:: Hier wird vor allem auf die Qualität der Code-Basis geachtet, Veränderbarkeit, Usability und schöne API sind dort am wichtigsten - daher ist es überhaupt kein Problem, das die looms natürlich im repository und damit push-bar sind.

Mir ist unklar wie ich das gegeneinander abwägen kann - jede der Mentalitäten hat ihre eigenen Vor- und Nachteile. Zum Beispiel ist mir BZR immer noch zu Langsam um es im täglichen gebrauch einzusetzen - es macht schlicht keinen Spaß.

Andererseits erinnert es mich stark an das alte Mantra: "First make it work, then make it fast!". Also vielleicht wird es doch noch?

p.s.: Ich weiß natürlich das es  Stacked-GIT gibt - aber soweit ich das feststellen konnte, ist das nicht der "recommended way of doing things" in der GIT community.

  • Posted: 2008-08-02 17:51 (Updated: 2008-08-02 17:53)
  • Author: dwt
  • Categories: code links
  • Comments (0)

Wie lernt man am besten Programmieren?

Hab ich mal für Python gegoogelt.

Und bin erstaunlicherweise auf eine extrem coole idee gestoßen:

Einfach indem man dem Lernenden Rätsel aufgibt, die ihn stück für stück an neue Konzepte aus der Python Standardbibliothek heran bringen.

Definitiv die beste Idee für Übungsaufgaben die ich bisher gesehen habe - und wunderschön über eine Webseite machbar.

 Hier gibts die Rätsel

By the way:  Auf dieses Coole Programm zum Python lernen bin auch auch gestoßen Einfach ein Webbrowser mit einem Manual links, und rechts ein Terminal um das alles auszuprobieren.

Nice!

Wunderschöne APIs

Dokumentation.

Den vergleich zwischen Dokumentation und dem Austausch von Körperflüssigkeiten spare ich mir jetzt. Wahr ist er aber trotzdem.

Vergleicht man die  Python und die  Cocoa Dokumentation dann fällt bei Cocoa recht schnell auf das es keine klare Trennung gibt in High level Dokumentation und Klassenspezifische Dokumentation während das bei Python gemischt ist.

Dazu kommt das bei Python oft die high level Beispiele fehlen und es von der Referenz-Dokumentation keinen Link auf die komplette Listung der Methoden eines Objekts gibt.

Zwar kann man dann in Python oft auf den interpreter zurückgreifen und sich von dort aus noch mehr informationen über ein Objekt holen - das ist aber ganz schön aufwendig.

High Level Dokumentation: Cocoa++, Python--

Wenn es um die Klassenbasierte Doku geht hat Cocoa auch eindeutig die Nase vorne. Alle Methoden sind immer nach Topics geordnet.  NSArray zum Beispiel startet mit einer Liste der Fakten über die Klasse (in welchem file definiert, ab welcher Version des Frameworks verfügbar, welche High-Level Guides gehören dazu) und fährt dann erst einmal mit einem Überblick über den Zweck der Klasse fort der auch gleichzeitig die wichtigsten Methoden der Klasse erklärt.

Danach kommt immer der Guide was man beachten muss wenn man eine Subklasse davon anlegen will. (Was erfordern kann dass man ein bestimmtes set von Methoden immer gleichzeitig überschreiben muss, da in Cococa gerne  Class Clusters eingesetzt werden - ein Konzept das ich in Python leider noch gar nicht gefunden habe.

Dann kommt der für die tägliche Arbeit für mich wichtigste Teil der Dokumentation:  Tasks. Hier sind alle Methoden des Objekts nach den Aufgaben sortiert die man mit ihnen erledigt.

Creating an Array, Initializing an Array, Querying an Array, Comparing, Sorting, ...

So findet man schnellstens heraus was das Objekt anbietet für das was man tun möchte.

Ergo: Dokumentation in Python ist ganz schön mies - in Trac fehlt sie sogar ganz :/

Kunst und Programmierung: Was macht APIs schön?

Und wieder mal heißt es: Python/Trac oder Cocoa? Wer wird gewinnen?

Erkundbarkeit der Frameworks ist mein nächstes Thema: Die Klassen die bestimmte Aufgaben haben sollen leicht zu finden sein.

Das funktioniert bei Trac in der Regel sehr gut, da man in Python sehr schön über Packages hierarchische Namensräume definieren kann - ähnlich wie bei Java eben auch.

Nur dass es dort etwas Taktvoller eingesetzt wurde.

Das heißt es gibt trac.wiki trac.tickets trac.timeline etc. Wenn man den Code zu einem Feature sucht, kann man ihn auch schnell finden.

Ganz im Gegensatz zur Standard Library von Python - da ist alles Kraut und Rüben - wenn man mal eben versucht alle Klassen die was mit String-Verarbeitung oder mit ein und Ausgabe zu tun haben, dann kann man ganz schön suchen - es gibt nicht einen Namensraum der überschaubar wäre und alles enthält.

Cocoa ist da auch nicht vorbildlich - da gibt es gar keine Namensräume. Nur eine grobe unterteilung in Foundation für alles was keine GUI braucht und AppKit für alles was mit einer GUI zusammenhängt.

Na immerhin etwas. Dazu kommt dass Apple fast alle neuen größeren Features als Framework verpackt die einen Aufgabenbereich verkapseln. CoreAudio für alles was mit Audio zu tun hat. CoreImage für ... Bilder, CoreVideo.... etc. Immerhin. Aber. Da alle Klassen zur Laufzeit dann doch in einem einzelnen Namensraum liegen, heißt dass das dann doch wieder jede Klasse einen Präfix bekommt, damit sie nicht mit Klassen von irgend jemand anderem zusammenstößt. Bäh. Man hat also NSArray statt Array und NSApplication statt Application und NMDeviceDatabaseEntry (NM für  NovaMedia). Also: Namensräume rocken - und Cocoa kann da deutlich weniger als Python.

Hierarchische Namensräume sind gut, weil sie Übersicht schaffen - wenn man sie nicht nur zum Spaß einsetzt sondern tatsächlich verschiedene Bereiche getrennt bekommt.

Kunst und Programmierung: Was macht APIs schön?

Neulich beim Diskutieren mit  Felix kam wieder die Frage auf, die schon viele Flamewars ausgelöst hat.

Was macht APIs schon?

Das lässt sich natürlich nicht beantworten - aber an Beispiele kann man dass sehr konkret besprechen:

Das fängt ganz vorne an: Namensgebung der Klassen. In Python ist es guter Stil, das man alle Namen so kurz wie irgend möglich macht.

Das hat zur Folge, dass leider aus den Funktionsnamen so gut wie nichts über die Anzahl und die Typen der Argumente abzuleiten ist.

Und das heißt, das man entweder in der API-Doku nachsieht, oder es halt wissen muss.

Gar nicht schön.

Cocoa dagegen funktioniert so, dass vor jedem Argument ein Stück Methodennamen kommt. Und es ist Usus, dass man dieses Stück Methodennamen verwendet die Argumente zu erklären, Beschreiben oder zumindest deren Typ anzugeben.

Damit kann die billigste Autocompletion fast jede API-Doc ersetzen. Zusammen mit den strikten Regeln wie Methoden zu bennen sind (und z.B. [welche Abkürzungen in Namen verwendet werden dürfen]) kann man damit fast alle Methodennamen hervorragend raten und braucht noch weniger Trips in die Dokumentation.

Hier mal ein paar Beispiele:

[NSException raise:NSInternalInconsistencyException 
            format:@"'format' allways means that there %@ %@", 
                       @"is format string support", @"very handy. :)"];

[aDictionary setObject:@"anObject" forKey:@"aKey"];
// No more confusion which is first, key or value

// or if you do GUIs and want to ingegrate with other applications
[[NSWorkspace sharedWorkspace] openFile:aPath
    fromImage:anImage at:theLocationInWindow inView:aView];

Keine Konfusion an dieser Stelle. Kommentare werden viel seltener gebraucht, da sich der Code ausgezeichnet selbst dokumentiert - in jedem fall viel besser als ähnlich guter Python Code.

So. Morgen gehts weiter mit der anderen Seite - was mir an Python besser gefällt als an Obj-C.

Xcode Tips: Tipp deine [ ] doch selber!

Dachte ich mir zum letzten mal.

Denn das kann XCode ja schließlich auch automatisieren, wenn man ein wenig in die Makros von Xcode reingreift.

Schritt eins, /Developer/Applications/Xcode.app/Contents/PlugIns/TextMacros.xctxtmacro/Contents/Resources/ObjectiveC.xctxtmacro im Texteditor der Wahl öffnen und das Macro finden das Apple als Hilfe für solche Fälle vorgesehen hat. (Bei mir ist das auf Zeile 68)

        {
            Identifier = objc.bracket;
            BasedOn = objc;
            IsMenuItem = YES;
            Name = "Bracket Expression";
            TextString = "[<#!expression!#> ]";
        },

Das habe ich mir dann durch zwei Macro-Definitionen ersetzt die für mich wesentlich nützlicher sind:

        {
            Identifier = objc.bracket;
            BasedOn = objc;
            IsMenuItem = YES;
            Name = "Bracket Expression";
            TextString = "[<#!expression!#>]";
        },

        {
            Identifier = objc.bracket.withSemicolon;
            BasedOn = objc;
            IsMenuItem = YES;
            Name = "Bracket Expression with Semicolon";
            TextString = "[<#!expression!#>];";
        },

Das war Schritt eins, um dass jetzt noch richtig Nützlich zu machen, habe ich mir diese Befehle (die jetzt beide im Menü auftauchen) noch mit einem Shortcut belegt - also Menü->Xcode->Preferences->Key Bindings, dort eine eigene Konfiguration anlegen und bei den "Menu Key Bindings" die Menü-Eintrag unter Edit->Insert Text Macro->Objective C finden.

Ich hab mir den ersten (ohne Semicolon) auf Command-1 und den zweiten (mit Semicolon) auf Command-2 gelegt.

Nie wieder Semicolons Tippen. Einfach Text-Auswählen und Command-1, dann hab ich eckige Klammern drumherum.

Ahhh....

Lediglich eines hätte ich noch gerne, dass der Cursor danach vor der schließenden eckigen Klammer ist.. aber das finde ich auch noch raus.

  • Posted: 2008-07-14 12:10 (Updated: 2008-10-08 09:09)
  • Author: dwt
  • Categories: code
  • Comments (0)

Hacking !TracBlog

Well, wir sind weiter gekommen. Man kann sogar schon posten.

Vor allem aber, haben wir eine recht vollständige Testsuite. Und dazu gehören auch funktionale Tests  mit Twill.

Die finde ich vom Ablauf her noch reichlich krude und aufwendig, aber ein Anfang ist gemacht.

 Wer schon mal einen Blick werfen will, darf das gerne tun. :-)

Warum kann man…

… eine Wurzel berechnen indem man einen float einfach um ein Bit nach rechts schiftet?

float InvSqrt(float x)
{
    float xhalf = 0.5f*x; 
    int i = *(int*)&x; // get bits for floating value
    i = 0x5f3759df - (i>>1); // gives initial guess y0
    x = *(float*)&i; // convert bits back to float
    x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy
    return x;
}

 Chris Lomont sich das mal angesehen und seine Erklärung hab ich verstanden.

Was diese Stelle tut:

    int i = *(int*)&x; // get bits for floating value
    i = 0x5f3759df - (i>>1); // gives initial guess y0
    x = *(float*)&i; // convert bits back to float

Ist total cool. das Bitmuster des floats wird einfach als long interpretiert - was nichts weiter bedeutet, als das die Teile des Floats, Vorzeichen, Mantisse und Exponent jetzt halt nebeneinander in einem Bitfeld liegen. Wenn man dieses Bitfeld jetzt nach rechts schiftet dann hat man das effektiv eben auch mit jedem der Bitfelder gemacht. Wenn man dann dieses Bitmuster noch etwas manipuliert, indem man es mit einer Korrekturkonstante vermurkst die die Fehler durch das hereinschiften von werten aus der Mantisse in den Exponent minimieren, dann hat man eine tolle Annäherung.

Und diese Annäherung erlaubt es mit nur einem Schritt nach  Newton zu einer sehr guten Näherung an die Quadratwurzel zu kommen.

Rockig.

Vor allem finde ich es cool, wie die Hacker hinter diesem Algorithmus ihn gefunden haben. Das konnten sie nämlich nur machen, weil sie genau wussten wie ein float tatsächlich implementiert ist - einem Wissen das die meisten Hacker heute nur noch theoretisch haben - aber nicht so genau das sie dieses Format auch mal so ohne weiteres für so einen fiesen Trick missbrauchen können.

In diesem Sinne: Aus der Geschichte lässt sich viel lernen. :-)

Was macht dieser Code

Aus dem  Q3 Source Code:

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;  // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 ); // what the fuck?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

  #ifndef Q3_VM
  #ifdef __linux__
    assert( !isnan(y) ); // bk010122 - FPE?
  #endif
  #endif
  return y;
}

Was macht das und wieso funktioniert es?

(Für all zu ungeduldige:  Auflösung)

Was ich besonders spannend finde, ist  die Geschichte mit der es entstanden ist. ( Und die Fortsetzung)

Total cool.

(via:  Einen Podcast von Ryan 'icculus' Gordon [etwa ab min. 30])

Spaces in the path

are not allowed on UNIX.

Damn.

Um das klarer zu machen: Ich will ein Skript schreiben, bei dem oben im  shebang hartcodiert ein Pfad zum interpreter steht.

So weit so gut. ABER. Dieser Interpreter liegt in einem Verzeichnis - und dieses Verzeichnis hat ein Leerzeichen im Namen.

Doh.

Die Regeln sagen nämlich:

The initial line of a script file must begin with #! as the
first two bytes, followed by zero or more spaces, followed by
interpreter or interpreter argument.  One or more spaces or tabs
must separate interpreter and argument.  The first line should
end with either a new-line or null character.

Und da hält sich auch der Mac OS X Kernel genau daran.

 Ich bin auch nicht der erste der darüber gestolpert ist. Hallo Felix. :-)

Das Blöde daran: Die Implementierungen erlauben weder ein Escapen, noch ein "In Anführungszeichen Setzen" des Interpreters.

Hrmpf.

Nach einigem Nachdenken sind mir letztlich nur zwei Lösungsmöglichkeiten aufgefallen:

1) Das Programm in zwei Dateien aufsplitten - die erste startet mit /bin/sh im shebang und dann ganz normal im 'body' den normalen Interpreter mit den normalen Mitteln der Shell (die auch quotes und escapen erlauben).

2) Wenn das Programm ein File bleiben soll / muss, kann ein  Polyglot das Problem lösen.

Das sah bei mir dann so aus:

#!/bin/sh
# Solves the problem that shebang lines can't contain spaces 
# (even escaped or quoted) in the interpreter path via a polyglot
"""false"
exec /Users/dwt/Archiv/Code/Aktive\ Projekte/Trac/env-trac-0.11/bin/python $0 $@
"""

import sys
print sys.argv

Das funktioniert so:

  • wird die Datei gestartet, wird sie mit der Bourne Shell (oder einer der Nachkommen) ausgeführt
  • in der Zeile """false" maskiere ich mit Hilfe des Kommandos "false" den Beginn eines Python Doc-Strings / Kommentars / Multiline-Strings.
  • Danach wird mit "exec" die Datei nochmal ausgeführt - diesmal mit dem richtigen Interpreter.
  • Der überspringt diesen Teil dann weil für ihn der Start komplett im Kommentar / ... / ... verschwindet.

Einerseits finde ich es cool das ich diesen Workaround gefunden habe - andererseits ist das natürlich ein totaler Hack. Insofern bin ich eher dagegen.

Was ich jetzt damit Tue weiß ich jedenfalls noch nicht.

Debugging Unit Tests in Xcode 3.1

Since I haven't found this info anywhere in the Blogosphere and had to painfully gather it through a long debugging stare, I'm posting it here:

If you want do debug Unit Tests in Xcode 3.1 you have to do the usual, that is:

  • Make a custom Executable
  • Set it's argumet to -SenTest All
  • Set some Environment Variables
    • DYLD_INSERT_LIBRARIES to ${DEVELOPER_LIBRARY_DIR}/PrivateFrameworks/DevToolsBundleInjection.framework/DevToolsBundleInjection
    • DYLD_FALLBACK_FRAMEWORK_PATH to $(DEVELOPER_LIBRARY_DIR)/Frameworks
    • XCInjectBundle to $(BUILT_PRODUCTS_DIR)/InvocationBuilder Tests.octest This is of course where you have enter the name of your test bundle

So far nothing new.

What is new though is that you also set the variable XCInjectBundleInto to point to the executable that is going to load the tests (probably to avoid the 3.0 Bug that would load the tests into the shell that GDB used to start the Application). This should be the same as the $(TEST_HOST) variable that you have to set for the Unit Test bundle. In my case this is $(BUILT_PRODUCTS_DIR)/InvocationBuilder.app/Contents/MacOS/InvocationBuilder.

This is quite interesting, since it means that otest is not used anymore to start the tests, but instead RunTargetUnitTests executes the tests directly.

Well, now that I can again debug my tests maybe I'l even get it to work with debugging Unit Tests which use Mock-Objects sometime. :-)

  • Posted: 2008-05-25 09:45 (Updated: 2008-05-25 09:46)
  • Author: dwt
  • Categories: code
  • Comments (0)

Wie man über Objektorientierung nachdenken kann

Eines meiner Lieblingszitate über Objektorientierung kommt von  Ward Cunningham: "Jedes Objekt ist eine kleine Sprache".

Ich verstehe das so das man mit jedem Objekt das man entwirft eine kleine Sprache schreibt. Eine Sprache die es einfach macht die lösung für ein spezifisches Problem darzustellen.

Fast noch wichtiger finde ich aber jetzt gerade (wo ich mich  auf Arbeit mit vielen globalen Variablen herumschlage) das jedes Objekt eben auch einen eigenen Namensraum darstellt - der optimaler weise auch verschachtelt werden kann.

Wunderschön finde ich das zum Beispiel bei Ruby sichtbar: Wenn man in eine Datei direkt Code schreibt, dann ist dieser Code teil eines top-level objekts. Schreibt mann in einer Datei dann weitere Objekte, sind diese Objekte in diesem top-level objekt eingebettet.

Das Prinzip erweitert sich wunderbar zu paketen und paketen von paketen - öffentlichen und privaten klassen (fals man das braucht) etc.

Und das alles ohne ein weiteres Konzept einzuführen - nur die Objektabstraktion. (*schnief* Java?)

Warum nur machen das so wenige Programmiersprachen so? Objective-C jedenfalls hat noch nix in die Richtung. In C++ hat man sowieso verloren - C hat gar keine Objekte - aber man kann dafür jede Bibliothek als Objekt sehen (so man will).

Stellt sich die Frage: Übersehe ich irgend ein total geiles Feature das man erhält wenn man Pakete als ein eigenes Feature in eine Sprache einbaut?

Hmpf.

Genshi

Heute habe ich mir mit  Felix den Template Teil des Blogs, an dem wir gerade schrauben, angeschaut. Ziel war es eine Blog-Posting Seite zu bauen die möglichst einfach zu warten ist und gleichzeitig möglichst alle Features bietet die auch die  Standard Wiki-Editier-Seite von  Trac bietet.

Das geht überraschenderweise sogar ganz gut. Denn: Trac ab 0.11 verwendet Genshi als Template-Sprache und Genshi wiederum ist XML mit  XPath allüren.

Will heißen wir konnten uns das Template für die normale Wiki-Eingabe nehmen und dann sagen: Aber die Überschrift oben "Editing XXXXX" soll bitte weg und dafür soll da stehen "Create Blog Entry" - ein Feature das die meisten Template-Engines schon mal nicht bieten.

Und das ist natürlich geil, weil wir nicht den kompletten Template-Source kopieren müssen um das gleiche look und feel zu kriegen.

Nur das XPath natürlich wie alles bei XML total kompliziert macht. Das heißt zum Beispiel das man damit nicht sagen kann, nimm dass und ersetze darin das gegen dies. Neeeiiiin. Natürlich ist alles andersherum.

Man sagt also: Meine Welt ist so definiert, das dies immer wenn es auftritt durch das ersetzt wird und jetzt hole ich mal das original in meine Welt hinein und magisch wird darin alles ersetzt.

Großartig.

Klar das geht - aber das hätte man auch einfacher haben können.

p.s.: Weiß jemand wie man mit XPath aus dieser Struktur

<fieldset id="changeinfo">
 <legend>Change information</legend>
 
 <div class="field">
    <label for="tags">Tag under: (<a href="http://xn--hcker-gra.net/cgi-bin/trac.cgi/tags">
                            view all tags</a>)</label><BR/>
    <input title="Comma separated list of tags" type="text" id='tags' 
                name="tags" size="30" value="blog">
 </div>
 <div class="field">
  <label>Comment about this change (optional):<br />
  <input id="comment" type="text" name="comment" size="60" value="" /></label>
 </div><br />
 
  <div class="options">
   <label><input type="checkbox" name="readonly" id="readonly" />
   Page is read-only</label>
  </div>
 
</fieldset>

nur das div mit dem label und input für den Kommentar herausmatchen kann?

Unsere Versuche sind erst einmal bei diesem XPath query stehen geblieben div[@id='changeinfo1']/div[@class='field'].

Experimentalphysik und Programmieren

Das sind an sich identische Tätigkeiten - hat mir mein Vater heute klargemacht.

Klar, er ist ein alter Physiker und trauert immer noch ein wenig der Tatsache nach das ich keiner geworden bin.

Oder auch nicht, denn: Ein guter Experimentalphysiker arbeitet so, dass er in einer Versuchsreihe in jedem Schritt immer nur eine Variable verändert um methodisch vorzugehen. Entweder nimmt er eine (wirklich nur eine) Änderung an seinen Messinstrumenten, oder an dem Experiment vor.

Dazu dann natürlich noch einiges an Intuition welche Veränderung sinnvoll ist - experimentelle Physik ist eben auch eine Kunst, genau wie das Programmieren.

Ach ja, und natürlich schreibt er ein Protokoll um hinterher nachvollziehen zu können, was er eigentlich getan hat.

Beim Programmieren kann man diese Techniken genau so wiederfinden. Die Messinstrumente sind in der Regel  UnitTests oder etwas äquivalentes (wobei UnitTests besser geeignet sind, da sie exakter messen können) und damit wird eine API dann abgeklopft.

Jetzt kommt bei uns Programmierern aber noch eine komplette Dimension dazu. Denn während ein Experimentalphysiker die Welt um ihn herum so hinnehmen muss wie sie ist, können wir Programmierer den Gegenstand unserer Tests beliebig verändern. Nur unser Phantasie schränkt uns ein.

Aber es läuft genauso: Ein Messinstrument, ein Stück Weltveränderung, ad infinitum.

Das letzte fehlende Detail ist das Logbuch: Das sind die schon geschriebenen Tests, die dokumentieren was wir schon herausgefunden haben.

Ich war wirklich überrascht wie weit die analogie trägt. Und auch die Implikation gefällt mir als UnitTest-Fan natürlich sehr. Und natürlich auch der Fakt das wir als Informatiker 'wieder mal' mehr können als die Physiker.

:-)

Wann darf man eigentlich…

…die Syntax einer Sprache modifizieren?

In C natürlich. Das Problem ist klar: Makros können ja beliebige Ersetzungen im Text vornehmen und dann mit vielen Makros aus C Pascal zu machen geht auch - aber das will man ja nicht, denn sonst hat man das gleiche Problem wie die  Bourne- Shell die irgendwann niemand mehr weiterentwickeln konnte.

Klar.

Andererseits nervt es wirklich unter Cocoa z.B. für jede Iteration über einen Array das hier zu schreiben:

NSArray *someStrings = [NSArray arrayWithObjects:@"first", @"second", @"third", @"fourth", nil];

NSString *eachString = nil;
NSEnumerator *enumerator = [someStrings objectEnumerator];

while (eachString = [enumerator nextObject])
{
    NSLog(@"eachString is %@", eachString);
}

Das heißt, für eine Schleife (zwei Zeilen) ha man noch mal mindestens zwei weitere Zeilen nur für das Setup.

Das nervt so sehr, das viele Leute das etwas kürzen:

NSArray *someStrings = [NSArray arrayWithObjects:@"first", @"second", @"third", @"fourth", nil];

id eachString, enumerator = [someStrings objectEnumerator];
while (eachString = [enumerator nextObject])
{
    NSLog(@"eachString is %@", eachString);
}

Schon besser, aber man verliert die Typchecks (falls man die haben will) und man muss trotzdem noch eine langweilige Zeile schreiben, die immer gleich ist und sich nicht wirklich kürzen lässt.

Jetzt kann man natürlich unter C schön ein Makro bauen, was das schöner macht - aber, siehe  Bourne Shell...

Aber, mit  ObjC 2.0 gibt es das for(String *each in someStrings) konstrukt, das schon deutlich kürzer (und auch schneller - aber aus anderen Gründen) ist.

Also, doch mal ein Makro. Nach reiflichem (2 Minuten) überlegen hab ich mich entschieden es erst einmal mit einer for Schleife zu probieren, da das bedeutet das der Scope der Variablen schön beschränkt ist.

Ziel wäre es also, das hier schreiben zu können:

NSArray *someStrings = [NSArray arrayWithObjects:@"first", @"second", @"third", @"fourth", nil];

FOREACH(eachString, someStrings)
{
    NSLog(@"eachString is %@", eachString);
}

Und das sähe dann so aus:

#define FOREACH(each, collection) \
    for(id __enumerator = [collection objectEnumerator], each = [__enumerator nextObject]; \
        each; \
        each = [__enumerator nextObject])

In der Praxis hab ich es auf Wunsch meiner Kollegen noch etwas modifiziert, damit man den Typ der Variablen noch einstellen kann (auch wenn er bisher noch nicht verwendet wird - immerhin kann man ihn aber als Startpunkt für Dokumentations-Lookup verwenden).

/// Use like this:
// FOREACH(NSString *, each, arrayOfStrings)
// {
//      if ([each hasPrefix:@"foo"])
//          [self doSomething];
// }
// This macro is in preparation of the ObjC 2.0 Fast enumeration protocol
#define FOREACH(typeOfEach, each, collection) \
    for(id __enumerator = [collection objectEnumerator], each = [__enumerator nextObject]; \
        each; \
        each = [__enumerator nextObject])

Bei Verwendung, bitte e-mail. :-)

Warum jeder überall das gleiche Passwort verwendet …

… wo doch jeder weiß wie unsicher das ist?

Dabei ist dass Problem so klar: Niemand kann sich für jeden Account den man heute im Social Web anlegen muss eine neue Kombination aus Benutzername und Passwort merken.

Und es gibt mehrere Lösungen:

Microsoft (aber auch viele Andere) favorisiert Single-Sign-On - ein Server von Microsoft verwaltet die Benutzerdaten für alle Webseiten. Man meldet sich nur noch bei Microsoft an und alle anderen Sprechen nicht mehr mit einem selber, sondern nur noch mit Microsoft um festzustellen ob der der da rein will echt ist.

Super, nur noch ein Passwort - aber auch ein Punkt an dem alles schief gehen kann. Denn man muss dem Anbieter (Microsoft) vertrauen.

Apple hat eine Andere Lösung:  Keychain. Ganz Apple Typisch komplett in das System integriert und nicht nur eine Lösung für Webseite, sondern für jedes Authentifizierungsproblem.

Super, auch nur noch ein Passwort und keine zentrale Location der man Vertrauen muss (abgesehen von der Implementierung des Dienstes). Aber, wenn man mehrere Computer verwendet oder auch mal aus der Ferne auf eine Webseite zugreifen muss / möchte, hat man erst einmal verloren.

Und da habe ich heute die perfekte Lösung gefunden: Ein Javascript Bookmarklet das folgendes tut: Es verbindet ein konstantes Geheimnis (Master Password) mit einem Datum das für jede Webseite variiert (die URL oder den Namen der Seite) und jagt das Ergebnis durch eine  Kryptographische Hash-Funktion.

Das Ergebnis ist damit ein Passwort das für jede Webseite anders ist - aber das man sich nicht merken muss.

Das  Bookmarklet gibt es hier.  Und natürlich gibt es dafür auch eine Greasemonkey verschönerte Version für Firefox Benutzer. (Die Angenehmerweise auch auf dem iPhone funktioniert)

Ich verwende auf dem iPhone dieses Bookmarklet ( Als Backup gespeichert)

javascript:(function(){function%20hex_md5(s){%20return%20binl2hex(core_md5(str2binl(s),%20s.length%20*%208));}function%20core_md5(x,%20len){x[len%20%3E%3E%205]|=%200x80%20%3C%3C%20((len)%20%25%2032);x[(((len%20+%2064)%20%3E%3E%3E%209)%20%3C%3C%204)%20+%2014]%20=%20len;var%20a%20=%20%201732584193;var%20b%20=%20-271733879;var%20c%20=%20-1732584194;var%20d%20=%20%20271733878;for(var%20i%20=%200;i%20%3C%20x.length;i%20+=%2016){var%20olda%20=%20a;var%20oldb%20=%20b;var%20oldc%20=%20c;var%20oldd%20=%20d;a%20=%20md5_ff(a,%20b,%20c,%20d,%20x[i+%200],%207%20,%20-680876936);d%20=%20md5_ff(d,%20a,%20b,%20c,%20x[i+%201],%2012,%20-389564586);c%20=%20md5_ff(c,%20d,%20a,%20b,%20x[i+%202],%2017,%20%20606105819);b%20=%20md5_ff(b,%20c,%20d,%20a,%20x[i+%203],%2022,%20-1044525330);a%20=%20md5_ff(a,%20b,%20c,%20d,%20x[i+%204],%207%20,%20-176418897);d%20=%20md5_ff(d,%20a,%20b,%20c,%20x[i+%205],%2012,%20%201200080426);c%20=%20md5_ff(c,%20d,%20a,%20b,%20x[i+%206],%2017,%20-1473231341);b%20=%20md5_ff(b,%20c,%20d,%20a,%20x[i+%207],%2022,%20-45705983);a%20=%20md5_ff(a,%20b,%20c,%20d,%20x[i+%208],%207%20,%20%201770035416);d%20=%20md5_ff(d,%20a,%20b,%20c,%20x[i+%209],%2012,%20-1958414417);c%20=%20md5_ff(c,%20d,%20a,%20b,%20x[i+10],%2017,%20-42063);b%20=%20md5_ff(b,%20c,%20d,%20a,%20x[i+11],%2022,%20-1990404162);a%20=%20md5_ff(a,%20b,%20c,%20d,%20x[i+12],%207%20,%20%201804603682);d%20=%20md5_ff(d,%20a,%20b,%20c,%20x[i+13],%2012,%20-40341101);c%20=%20md5_ff(c,%20d,%20a,%20b,%20x[i+14],%2017,%20-1502002290);b%20=%20md5_ff(b,%20c,%20d,%20a,%20x[i+15],%2022,%20%201236535329);a%20=%20md5_gg(a,%20b,%20c,%20d,%20x[i+%201],%205%20,%20-165796510);d%20=%20md5_gg(d,%20a,%20b,%20c,%20x[i+%206],%209%20,%20-1069501632);c%20=%20md5_gg(c,%20d,%20a,%20b,%20x[i+11],%2014,%20%20643717713);b%20=%20md5_gg(b,%20c,%20d,%20a,%20x[i+%200],%2020,%20-373897302);a%20=%20md5_gg(a,%20b,%20c,%20d,%20x[i+%205],%205%20,%20-701558691);d%20=%20md5_gg(d,%20a,%20b,%20c,%20x[i+10],%209%20,%20%2038016083);c%20=%20md5_gg(c,%20d,%20a,%20b,%20x[i+15],%2014,%20-660478335);b%20=%20md5_gg(b,%20c,%20d,%20a,%20x[i+%204],%2020,%20-405537848);a%20=%20md5_gg(a,%20b,%20c,%20d,%20x[i+%209],%205%20,%20%20568446438);d%20=%20md5_gg(d,%20a,%20b,%20c,%20x[i+14],%209%20,%20-1019803690);c%20=%20md5_gg(c,%20d,%20a,%20b,%20x[i+%203],%2014,%20-187363961);b%20=%20md5_gg(b,%20c,%20d,%20a,%20x[i+%208],%2020,%20%201163531501);a%20=%20md5_gg(a,%20b,%20c,%20d,%20x[i+13],%205%20,%20-1444681467);d%20=%20md5_gg(d,%20a,%20b,%20c,%20x[i+%202],%209%20,%20-51403784);c%20=%20md5_gg(c,%20d,%20a,%20b,%20x[i+%207],%2014,%20%201735328473);b%20=%20md5_gg(b,%20c,%20d,%20a,%20x[i+12],%2020,%20-1926607734);a%20=%20md5_hh(a,%20b,%20c,%20d,%20x[i+%205],%204%20,%20-378558);d%20=%20md5_hh(d,%20a,%20b,%20c,%20x[i+%208],%2011,%20-2022574463);c%20=%20md5_hh(c,%20d,%20a,%20b,%20x[i+11],%2016,%20%201839030562);b%20=%20md5_hh(b,%20c,%20d,%20a,%20x[i+14],%2023,%20-35309556);a%20=%20md5_hh(a,%20b,%20c,%20d,%20x[i+%201],%204%20,%20-1530992060);d%20=%20md5_hh(d,%20a,%20b,%20c,%20x[i+%204],%2011,%20%201272893353);c%20=%20md5_hh(c,%20d,%20a,%20b,%20x[i+%207],%2016,%20-155497632);b%20=%20md5_hh(b,%20c,%20d,%20a,%20x[i+10],%2023,%20-1094730640);a%20=%20md5_hh(a,%20b,%20c,%20d,%20x[i+13],%204%20,%20%20681279174);d%20=%20md5_hh(d,%20a,%20b,%20c,%20x[i+%200],%2011,%20-358537222);c%20=%20md5_hh(c,%20d,%20a,%20b,%20x[i+%203],%2016,%20-722521979);b%20=%20md5_hh(b,%20c,%20d,%20a,%20x[i+%206],%2023,%20%2076029189);a%20=%20md5_hh(a,%20b,%20c,%20d,%20x[i+%209],%204%20,%20-640364487);d%20=%20md5_hh(d,%20a,%20b,%20c,%20x[i+12],%2011,%20-421815835);c%20=%20md5_hh(c,%20d,%20a,%20b,%20x[i+15],%2016,%20%20530742520);b%20=%20md5_hh(b,%20c,%20d,%20a,%20x[i+%202],%2023,%20-995338651);a%20=%20md5_ii(a,%20b,%20c,%20d,%20x[i+%200],%206%20,%20-198630844);d%20=%20md5_ii(d,%20a,%20b,%20c,%20x[i+%207],%2010,%20%201126891415);c%20=%20md5_ii(c,%20d,%20a,%20b,%20x[i+14],%2015,%20-1416354905);b%20=%20md5_ii(b,%20c,%20d,%20a,%20x[i+%205],%2021,%20-57434055);a%20=%20md5_ii(a,%20b,%20c,%20d,%20x[i+12],%206%20,%20%201700485571);d%20=%20md5_ii(d,%20a,%20b,%20c,%20x[i+%203],%2010,%20-1894986606);c%20=%20md5_ii(c,%20d,%20a,%20b,%20x[i+10],%2015,%20-1051523);b%20=%20md5_ii(b,%20c,%20d,%20a,%20x[i+%201],%2021,%20-2054922799);a%20=%20md5_ii(a,%20b,%20c,%20d,%20x[i+%208],%206%20,%20%201873313359);d%20=%20md5_ii(d,%20a,%20b,%20c,%20x[i+15],%2010,%20-30611744);c%20=%20md5_ii(c,%20d,%20a,%20b,%20x[i+%206],%2015,%20-1560198380);b%20=%20md5_ii(b,%20c,%20d,%20a,%20x[i+13],%2021,%20%201309151649);a%20=%20md5_ii(a,%20b,%20c,%20d,%20x[i+%204],%206%20,%20-145523070);d%20=%20md5_ii(d,%20a,%20b,%20c,%20x[i+11],%2010,%20-1120210379);c%20=%20md5_ii(c,%20d,%20a,%20b,%20x[i+%202],%2015,%20%20718787259);b%20=%20md5_ii(b,%20c,%20d,%20a,%20x[i+%209],%2021,%20-343485551);a%20=%20safe_add(a,%20olda);b%20=%20safe_add(b,%20oldb);c%20=%20safe_add(c,%20oldc);d%20=%20safe_add(d,%20oldd);}return%20Array(a,%20b,%20c,%20d);}function%20md5_cmn(q,%20a,%20b,%20x,%20s,%20t){return%20safe_add(bit_rol(safe_add(safe_add(a,%20q),%20safe_add(x,%20t)),%20s),b);}function%20md5_ff(a,%20b,%20c,%20d,%20x,%20s,%20t){return%20md5_cmn((b%20&%20c)%20|%20((~b)%20&%20d),%20a,%20b,%20x,%20s,%20t);}function%20md5_gg(a,%20b,%20c,%20d,%20x,%20s,%20t){return%20md5_cmn((b%20&%20d)%20|%20(c%20&%20(~d)),%20a,%20b,%20x,%20s,%20t);}function%20md5_hh(a,%20b,%20c,%20d,%20x,%20s,%20t){return%20md5_cmn(b%20^%20c%20^%20d,%20a,%20b,%20x,%20s,%20t);}function%20md5_ii(a,%20b,%20c,%20d,%20x,%20s,%20t){return%20md5_cmn(c%20^%20(b%20|%20(~d)),%20a,%20b,%20x,%20s,%20t);}function%20safe_add(x,%20y){var%20lsw%20=%20(x%20&%200xFFFF)%20+%20(y%20&%200xFFFF);var%20msw%20=%20(x%20%3E%3E%2016)%20+%20(y%20%3E%3E%2016)%20+%20(lsw%20%3E%3E%2016);return%20(msw%20%3C%3C%2016)%20|%20(lsw%20&%200xFFFF);}function%20bit_rol(num,%20cnt){return%20(num%20%3C%3C%20cnt)%20|%20(num%20%3E%3E%3E%20(32%20-%20cnt));}function%20str2binl(str){var%20bin%20=%20Array();var%20mask%20=%20(1%20%3C%3C%208)%20-%201;for(var%20i%20=%200;%20i%20%3C%20str.length%20*%208;%20i%20+=%208)bin[i%3E%3E5]%20|=%20(str.charCodeAt(i%20/%208)%20&%20mask)%20%3C%3C%20(i%20%25%2032);return%20bin;}function%20binl2hex(binarray){var%20hex_tab%20=%20%270123456789abcdef%27;var%20str%20=%20%27%27;for(var%20i%20=%200;%20i%20%3C%20binarray.length%20*%204;%20i++){str%20+=%20hex_tab.charAt((binarray[i%3E%3E2]%20%3E%3E%20((i%254)*8+4))%20&%200xF)%20+%20hex_tab.charAt((binarray[i%3E%3E2]%20%3E%3E%20((i%254)*8))%20&%200xF);}return%20str;}function%20mpwd_doIt(){var%20master=document.getElementById(%27masterpwd%27).value;if%20(master%20!=%20%27%27%20&&%20master%20!=%20null)%20{re%20=%20new%20RegExp(%27https*://([^/]+)%27);host%20=%20document.location.href.match(re)[1];var%20i=0,%20j=0,%20p=hex_md5(master+%27:%27+host).substr(0,8),%20F=document.forms;for(i=0;i%3CF.length;i++){E=F[i].elements;for(j=0;j%3CE.length;j++){D=E[j];if(D.type==%27password%27){D.value=p;D.focus();}if(D.type==%27text%27){if(D.name.toUpperCase().indexOf(%27PASSWORD%27)!=-1||D.name.toUpperCase().indexOf(%27PASSWD%27)!=-1){D.value=p;D.focus();}}}}}document.getElementsByTagName(%27body%27)[0].removeChild(document.getElementById(%27mpwd_panel%27));};function%20getPwdFld()%20{var%20L%20=%20document.getElementsByTagName(%27input%27);for%20(var%20i%20in%20L)%20{var%20nm%20=%20L[i].getAttribute(%22name%22)%20||%20%22%22;var%20tp%20=%20L[i].getAttribute(%22type%22)%20||%20%22%22;if%20((tp%20==%20%22password%22)%20||(tp%20==%20%22text%22%20&&%20nm.toLowerCase().substring(0,5)%20==%20%22passw%22))%20{return%20L[i];}}return%20null;}function%20panel()%20{var%20pwdTop%20=%200;var%20pwdLeft%20=%200;try%20{var%20obj%20=%20getPwdFld();if%20(obj.offsetParent)%20{while%20(obj.offsetParent)%20{pwdTop%20+=%20obj.offsetTop;pwdLeft%20+=%20obj.offsetLeft;obj%20=%20obj.offsetParent;}}}%20catch%20(e)%20{pwdTop%20=%2010;pwdLeft%20=%2010;}var%20div%20=%20document.createElement(%27div%27);div.style.padding=%274px%27;div.style.backgroundColor=%27yellow%27;div.style.border=%272px%20dotted%20red%27;div.style.position=%27absolute%27;div.style.top%20=%20pwdTop%20+%20%27px%27;div.style.left%20=%20pwdLeft%20+%20%27px%27;div.style.opacity=%27.9%27;div.setAttribute(%27id%27,%20%27mpwd_panel%27);div.appendChild(document.createTextNode(%27Master%20password:%20%27));var%20frm%20=%20document.createElement(%27form%27);frm.action=%27javascript:void(0);%27;div.appendChild(frm);var%20pwd%20=%20document.createElement(%27input%27);pwd.setAttribute(%27type%27,%27password%27);pwd.setAttribute(%27id%27,%27masterpwd%27);frm.appendChild(pwd);var%20ok%20=%20document.createElement(%27input%27);ok.setAttribute(%27type%27,%27submit%27);ok.setAttribute(%27value%27,%27OK%27);ok.onclick%20=%20mpwd_doIt;frm.appendChild(ok);document.getElementsByTagName(%27body%27)[0].appendChild(div);setTimeout(%22document.getElementById(%27masterpwd%27).focus();%22,%20333);};panel();})();

hg <-> svn

Tja... leider nicht so einfach wie ich dachte.

Meine Versuche mit  BZR die verbindung herzustellen, haben leider nicht geklappt. Das  hg-plugin dort läuft mit der aktuellen Version leider nicht mehr - und hat auch keine Unit-Tests, sonst hätte ich mir ja schon mal etwas Zeit genommen um zu schauen woran es liegt.

Aber so...

Egal.

Was doch geklappt hat, ist mit  hgimportsvn einen svn checkout zu machen. Mit einem lokalen hg clone konnte ich dann Arbeiten und auf den svn-checkout zurückschieben und dann von dort comitten.

Vorteil:

  • Es geht überhaupt

Nachteil:

  • Alle commit Kommentare gehen verloren
  • Ich kann lokal so oft comitten wie ich will - im Subversion kommt nur ein commit an. :-/

Eher unbefriedigend. Aber immerhin kann man überhaupt an einem SVN-Checkout arbeiten.

BZR -> SVN

Puah. Nach viel Gefrickel hab ich es endlich zum laufen gebracht.

Jetzt kann ich mit bzr von svn auschecken, lokal arbeiten (mit voller historie) und auch wieder comitten.

Was mich beim letzten mal? noch aufgehalten hatte war ein Bug, der in der neuesten Version zwar gefixt ist - aber man musste zusätzlich noch die ~/.bazaar/subversion.conf wegschmeißen.

Ach ja, und einen neuen Branch vom Subversion machen, das  rebase Plugin installieren und mit cd $NEW_BRANCH_LOCATION && bzr replay $OLD_BRANCH_LOCATION -r $REVISIONS_TO_GET die alten Patches holen.

Eigentlich ganz einfach wenn man es mal raushat. Und dann kann man das auch von dort aus in den SVN comitten.

Yeah. (Danke an  Jelmer Vernooij für die Hilfe beim fixen der Probleme)

Das neue Fazit: BZR: gut GIT: gut HG: kann bisher noch nicht ins SVN comitten - aber immerhin  arbeitet da jetzt jemand dran.

Bleibt noch den bzr hg modus auszuprobieren ob ich damit ins subversion comitten kann.

  • Posted: 2008-04-27 14:43 (Updated: 2008-04-27 14:48)
  • Author: dwt
  • Categories: code links
  • Comments (0)

Function Call vs. Message Send

Oder: Wieso mich die Lisp Syntax so stört.

In meiner Freizeit beschäftige ich mich gerade mit dem Buch  Practical Common Lisp.

Das Buch gefällt mir auch ganz gut - vielleicht abgesehen davon das er 'loop etwas zu häufig verwendet - ohne es zu erklären - was den Code gerade am Anfang reichlich magisch macht.

Aber: Worum es mir eigentlich geht, ist die Syntax von Lips. Nicht die Klammern, nicht die Einrückung, nicht die Art wie man es schreibt, sondern schlicht die Reihenfolge.

Weil: in Lisp ist erst einmal alles auf Funktionen abgebildet. Und das hat Vorteile aber eben auch einen gravierenden Nachteil: Jede Funktion muss immer als erstes in einem Funktionsaufruf Tupel stehen.

(funktion argument-eins argument-zwei)

Das ist ein Problem, weil man bei verschachtelten Funktionsaufrufen (auch schon bei nur sehr wenigen Ebenen) sehr schnell den Überblick verliert welcher Funktionsaufruf zu welchem argument gehört.

(funktion (funktion-zwei argument-eins (funktion-drei argument-eins argument-zwei)) argument-zwei)

Das kann man jetzt natürlich auch als Feature sehen, weil man gezwungen wird seinen Code vernünftig einzurücken und keine zu langen Funktionen zu schreiben

(funktion (funktion-zwei argument-eins
                     (funktion-drei argument-eins argument-zwei)) 
        argument-zwei)

Das hilft, aber eben nur so weit. Vor allem weil man beim Lesen von Code immer eine interne herumdrehung machen muss um den Code korrekt zu lesen.

(defparameter a-list (list 1 2 3 4 5))

(car (cdr (cdr (cdr a-list))))

Soll (wenn (ich) (mich nicht verschrieben) habe) 5 ergeben.

Aber man muss es von innen nach aussen lesen, damit man es richtig versteht.

Und das erfordert jede menge Geistige Kapazität, die besser beim finden von guten Namen für Variablen und Funktionen aufgehoben ist. Viel besser.

Jetzt zum Message-Passing - das ist zwar fundamental "weniger mächtig" als der Funktionsaufruf, weil man zum Beispiel generische Funktionen nicht oder nicht so gut implementieren kann, ABER: man kann viel besser mit dem resultat eines vorherigen Aufrufes das nächste tun.

Weil die Reihenfolge in der die Sachen ausgeführt werden die gleiche ist in der die Sachen auf dem Bildschirm stehen.

Und das ist eine ganze Menge besser les- und versteh-bar als die Funktion es jemals sein kann.

Das war es was  Alan Kay meinte, als er sagt das das wichtigste an  Smalltalk nicht die Objektorientierung, sondern das  Message Passing gewesen ist.

Wuala vs. Tahoe

 Wuala und  Tahoe sind zwei technisch ähnliche Systeme mit dem gleichen Ziel: Sicher Online Dateien tauschen und speichern können.

Und das Verteilt, Verschlüsselt, Dateisystemsemantik. Als Modell haben sich beide ein Verschlüsselungs und Rechtesystem ausgedacht die ein Friendt-to-Friend Netzwerk möglich machen - also ein Netzwerk in dem man alles einstellen kann was man möchte, aber mit der Sicherheit, das nur derjenige dem man das erlaubt hat die Daten sehen kann.

Naja, eigentlich ein etwas unfaierer Vergleich.

Denn, hinter Wuala steht ein Startup, eine Firma also die jede Menge Geld in den Aufbau des Netzwerkes steckt (Interessanterweise handelt es sich dabei um eine  Ausgründung aus der ETH Zürich. Das erlaubt es dem Netzwerk jedem Benutzer erst einmal 1 Gigabyte Speicherplatz zu schenken (und ein weiteres für jeden Benutzer den man wirbt) und eben auch mittels zentraler Server, die Benutzung das Projekt vor allem am Anfang enorm zu beschleunigen und damit eine bessere User Experience zu bieten.

Auf der anderen Seite ist  Tahoe ein reinrassiges Open Source Projekt.  Buildserver,  Repositories ( Darcs), Trac... alles da.

Dafür ist das Programm noch deutlich unfertig. Immerhin gibt es am Anfang einen schönen Login Dialog - aber das war es auch erstmal.

Ah well, die Entwickler im IRC-Kanal #tahoe sind jedenfalls sehr hilfreich und nett - und der größte Teil des Projekts ist in Python geschrieben. Das ist doch schon mal was.

:-)

Kleine Graphik-Schmankerl

Ich habe mich über die letzten zwei Tage erneut  von Katrin inspirieren lassen.

Herausgekommen ist ein kleines Cocoa-Programm Download (Source) dass ein wenig mit solchen Grid-Animierten Ansätzen herumspielt und nebenbei noch ein wenig Sheets und Preferences verwendet.

Interessant ist dabei für mich der unterschiedliche Ansatz: Katrin war wichtig, das jeder Display Vorgang in konstanter Zeit abläuft und sie verwendet daher eine Liste der einzelnen Rechtecke. Ich hatte mir das auch überlegt, mich dann aber dagegen entschieden weil es komplexer zu Programmieren gewesen wäre.

Geschwindigkeit hat sich dann im Test überhaupt nicht als Problem herausgestellt - aber das grundsätzliche Problem bleibt. War es vernünftig den Code erst einmal einfach zu halten? Ich bin überzeugt das ja.

Ganz zufrieden bin ich aber noch nicht, weil das einzige Objekt ein View und gleichzeitig der Controller für die Preferences des Programms ist.

Aber - das Programm ist so auch kurz und Übersichtlich.

Hm.

Mal schauen was ich noch daraus mache. Vorschläge für interessante und auf dem modell ausdrückbare Algorithmen immer gerne an mich.

Hier noch ein paar Screenshots:

Screenshot Brownian Motion Algorithmus

Screenshot Random Cycle and Prefs

Vom Sprachdesign: Konstruktoren

Endlich ist mir wieder so ein Punkt klargeworden, der mich schon lange an Python stört, den ich aber bisher noch nicht so richtig spezifizieren konnte.

Zusammenfassen lässt es sich als: Konvention ist besser als eine eigene Syntax / Regel.

Konkret geht es um folgendes: Python besitzt eine eigene Syntax für Konstruktoren. Nämlich diese:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

point = Point(10, 15)

Der Konstruktor eines Objekts heißt immer __init__ (das mit den Unterstrichen finde ich sowieso hässlich, aber well, darüber ärgere ich mich wan anderes).

Das ist gut, weil man so den Konstruktor immer gleich findet.

ABER: Das ist total scheiße wenn man mehrere Wege braucht um ein Objekt zu initialisieren.

Denn genau wie in Java auch, kann man anhand den Argumenttypen ja keinen dispatch machen. Doh.

Also, wenn man das Beispiel erweitern will um auch Polarkoordinaten zu unterstützen, dann geht das nicht, denn bei

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __init__(self, theta, phi)
        self.x = ...
        self.y = ...

point = Point(10, 15)

kann die Runtime nicht mehr auflösen welchen Konstruktor mann denn jetzt genau gemeint hat.

Doh.

Jetzt hat man entweder die Wahl so etwas zu machen wie hier im  Trac-Projekt

class WikiPage(object):
    """Represents a wiki page (new or existing)."""

    realm = 'wiki'

    def __init__(self, env, name=None, version=None, db=None):
        self.env = env
        if isinstance(name, Resource):
            self.resource = name
            name = self.resource.id
        else:
            self.resource = Resource('wiki', name, version)
        self.name = name
# ....

Was aber total ekelig ist, denn jetzt hat der Konstruktor auf einmal einen Parameter name der überhaupt nicht mehr dass ist, was er mit seinem Namen dokumentiert.

Klasse. Jetzt muss man tatsächlich den Code des Konstruktors lesen um zu verstehen was für Argumente man benötigt um so ein Objekt zu erzeugen. Habt ihr wirklich gut gemacht!!!!11!!

Oder aber man scheißt auf das Konzept eines Konstruktors und schreibt Factory-Methoden, also im Beispiel etwa so:

class WikiPage(object):
    """Represents a wiki page (new or existing)."""

    realm = 'wiki'

    @classmethod()
    def create_from_resource(klass, env, resource, db=None):
        page = WikiPage(env, resource.id, resource.version, db)
        page.resource = resource
        return page

    def __init__(self, env, name=None, version=None, db=None):
        self.env = env
        self.resource = Resource('wiki', name, version)
        self.name = name
# ....

Das ist jetzt immerhin klarer - hat aber den Nachteil das der Leser jetzt wissen muss dass man Factory Methoden einsetzt und welche (nichtstandardisierte) Konvention man für diese einsetzt. Na Klasse.

Ergo: Die Sprachdesigner haben sich gedacht, das es ja eine Tolle Sache wäre, wenn sie eine eigene Syntax einführen würden, damit Konstruktoren etwas besonderes sein können und haben damit erreicht, dass man sich beim tatsächlichen Programmieren jetzt auf einmal ein Bein Brechen muss, damit man ganz alltägliche Probleme auf eine saubere Art und Weise lösen kann.

Ganz groß.

Hätte man doch von vornherein anerkannt, das ein Konstruktor auch nur eine Methode ist - mit der Konvention dass der Name mit "init" anfängt - dann existierten auf einmal diese Probleme nicht mehr. Denn dann kann man über den namen der Methode dokumentieren was sie soll.

class Point:

    def init_cartesian(self, x, y):
        self.x = x
        self.y = y
        return self
    
    def init_polar(self, theta, phi):
        # ...
        return self

cartesian = Point().init_cartesian(10, 15)
polar = Point().init_polar(0.23, 0.23)

Yucheh!

Dazu kommt, dass man dann auf einmal sinnige Dinge tun kann, wie zum Beispiel, das ein "Konstruktor" nicht immer die gleiche Klasse zurückgeben muss. Man könnte also zum Beispiel eine öffentliche Superklasse haben, die aber tatsächlich in mehreren privaten Subklassen implementiert ist - zum Beispiel ein String, der je nachdem wie er erzeugt wird anders aussieht: Stringkonstanten vielleicht eine Klasse die nicht/anders an der Garbage-Collection teilnimmt und dafür Uniqued (also garantiert nur einmal) existieren (für einen schnelleren Interpreter interessant), Strings aus einer Datei entweder normal, oder falls die Datei eine gewisse Größe überschreitet als Memory-Mapped File, Strings die Pfade repräsentieren so, dass sie mit den Eigenheiten von Dateisystemen besser klarkommen, Strings die keine Unicode enthalten noch mal anders, damit sie effizienter sein können.... etc.

Und das alles, weil man auf Sprachebene auf ein zusätzliches Konzept verzichtet.

Aber dass, scheint ja für Sprachdesigner das Schwerste zu sein.

Programmieren Lernen - mit Scheme?

Meine Freunden hat sich mal damit beschäftigt. Und da dachte ich, schauste doch mal was es da so gibt.

Klaro das große alte  SICP, der Großvater aller großartigen Programmierbücher mit Tiefgang. Gleichzeitig aber natürlich auch ziemlich Tief und für jemanden der sonst von Informatik nicht so viel Ahnung hat vielleicht zu viel.

Das sehen  andere  Leute auch so und daher gibt es inzwischen einen Nachfolger: Das  HTDP.

Well, die Haupt Kritik war eigentlich das das Buch zu viele Grundlagen in der Elektrotechnik verlange und zu tief in die Konstruktion von Computern hineingeht, dabei aber gleichzeitig zu wenig darüber erklärt wie man eigentlich ein größeres Programm aufzieht.

Und genau das wollten sie anders machen.

Gut fand ich dass Sie sich mühe geben jede ihrer Kapitel mit einem Rezept abzuschließen, in dem dann Dinge stehen wie "Mach dir erst Gedanken darüber was aus deinen Funktionen herauskommen muss bevor Du sie implementierst, damit Du Logikfehler besser finden kannst. - Schon mal ein guter Ansatz.

Dazu kommt durchweg eigentlich ein recht sauberer Programmierstil mit dem ich durchaus leben kann.

Was mir eher auf den Geist ging: Die ganze Zeit über predigen sie es, dass man zu jeder Funktion einen "Kontrakt" schreibt, der erklärt welche Typen man erhält und welche man zurückgibt.

Well, das ist ja ganz schön, aber entweder, soll man eine Sprache mit Typechecking nehmen, oder es lassen. Die Dokumentation ist zwar ganz nett - aber dann doch bitte so, dass man die Variablen so eindeutig benennt, das keine Typ-Unsicherheiten auftreten.

So sind die Kommentare (von denen sie pro Funktion mindestens drei bis fünf Zeilen haben wollen) nach der ersten Änderung einer Funktion nicht mehr aktuell.

:-(

Dazu kommt, dass der Klammernwald von Scheme, natürlich gerade für Anfänger schnell mal verwirrend wird und ohne Editor-Unterstützung gar nicht handhabbar ist. Andererseits: da gewöhnt man sich gleich daran kleine Funktionen zu schreiben...

Was ich auch merkwürdig finde ist, dass den studenten die wirkliche Freude eines  metazirkulären Interpreters vorenthalten wird. Da hat man sowas mit Scheme schon unter der Haube, und dann geht es irgendwann um den Interpreter und es werden lauter neue Datentypen eingeführt um etwas Scheme-Ähnliches darzustellen, dass dann nicht mal richtig interpretiert werden kann.

Habt ihr wirklich toll gemacht.

Generell hat das Buch weniger Tiefgang als das  http://www.htdp.org/SICP - vor allem weil es dazu natürlich die ganz hervorragenden  Video-Lectures gibt, die einfach nur begeisternd sind.

Fazit: Irgendwie hat das Buch schon Sinn - aber Begeistern kann es mich nicht. Mal schauen wie es meiner Freundin geht. :)

p.s.: Zum SICP gibt es noch einen Nachfolger,  SICM - Structure and Interpretion of Classical Mechanics. Das werd ich mir mal demnächst ansehen müssen. :)

Was ist an diesem Programm falsch?

So, jetzt kurz ganz intensiv hinschauen - so sagen wir mal für 10 Sekunden.

Funktionale

Und, schon gesehen was es ist?

Ok, nochmal 10 Sekunden.

Jetzt?

Na, will ich es mal auflösen. Das ist ein Programm das in der von mir schon beschriebenen Veranstaltung  Fortgeschrittene Techniken funktionaler Programmierung auf einer Folie erscheint.

Jep, als teil der Vorlesung für so sagen wir mal... 2 Minuten.

Aber jetzt zum Problem: Dieses Programm versteht man nicht! Schon gar nicht wenn man kein Opal kann - aber auch mit Opal-Kenntnissen hat man keine Chance.

Was für ein Schwachsinn diese ganzen Funktionssymbole sind.  Als würden Programmiersprachen heute noch mit einer eigenen Tastatur kommen. Vor allem aber haben sie ein Problem: Man muss den ganzen Kontext was sie Bedeuten komplett mitschleppen - was uns Menschen viel schwerer fällt als einfach ein Wort zu lesen das sagt worum es geht.

Jep.

Wer zweifelt, mag doch bitte das Programmfragment erklären.

q.e.d.

  • Posted: 2008-03-28 18:55 (Updated: 2009-10-24 15:58)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

Python saugt

Schon lange hab ich mich gefragt, wieso eigentlich mich dieser eine Punkt an Python so sehr stört:

Jede Methode eines Objektes erhält als ersten Parameter eine Referenz auf das aufrufende Objekt.

Bisher konnte ich den Finger nie darauf legen konnte, wieso mir das eigentlich so auf den Keks geht. Bisher dachte ich immer das es mich stört, weil man schwieriger aus normalen Funktionen Methoden zu machen, beim Refactorn, aber so wirklich passt das nicht. Also dachte ich, vielleicht gewöhne ich mich ja irgendwann daran. Dem war aber nicht so.

Bei den Vorbereitungen zum Java-Kurs ist mir jetzt durch einen Hinweis von  Robert der eigentliche Grund Aufgefallen.

Folgendes war der Auslöser: Wir haben uns gerade darüber unterhalten wie wir den Studenten im Java-Kurs erklären, wie sie this in Java verstehen sollen.

Naja, von der technischen Seite ist das klar - die Methoden sind im Klassenobjekt implementiert und die Methode braucht aber einen Weg um an die Daten zum gegenwärtigen Instanz-Objekt zu gelangen. Also wird dieses Instanz-Objekt als verstecktes erstes Argument übergeben, das immer den namen this trägt.

Soweit so klar.

Das Mentale Bild das man von diesem Systems hat, ist aber eigentlich anders: Darin gehören die Methoden zum Instanz-Objekt und "leben" auch darin - damit ist dann auch klar, wieso eine Instanz-Methode zugriff auf die Daten des Objekts hat und wieso zum Beispiel eine Klassen-Methode das nicht kann - sie lebt einfach im falschen Objekt.

Der Knackpunkt: this ist eigentlich ein Workaround (aber ein notwendiger) der zeigt wie diese Abstraktion implementiert wurde, der aber auch erlaubt eindeutig zu spezifizieren welchen Namensraum man jetzt meint. Jetzt möchte man aber die Implementierung möglichst von der Benutzung abstrahieren, damit man gut auf dieser Abstrakteren Ebene arbeiten kann und diese nicht ständig verlässt. Daher macht es viel Sinn diese Implementierung so transparent zu machen wie nur irgend möglich.

In Python dagegen ist das anders. Hier wird ganz Explizit immer self als erster Parameter geschrieben. Der Effekt? Ständig wird man daran erinnert das die Abstraktion eigentlich nicht so ist wie man sich das denkt und es tatsächlich alles anders implementiert ist in Python. Das Problem? Man erhält durch diesen Abstraktionsbruch keinen Vorteil - ja, alles ist expliziter, aber es bricht auch ständig mit der Abstraktionsschicht auf der man Arbeitet. Details der Implementierung "bluten" über dieses "Loch" in den ganzen Code den man schreibt.

Und saubere, klar getrennte Abstraktions-Ebenen sind einfach wichtiger als einfachste Implementierung - speziell für einen Sprachdesigner.

Fortgeschrittene Techniken Funktionaler Programmierung

(zu  Mother Earth von Within Temptation)

 Diese Folien habe ich gestern gefunden.

Das Thema finde ich schon spannend - immerhin wird doch einiges angesprochen, das ich mal wieder auffrischen könnte. Monaden, und effiziente implementierungen von Sequenzen und Maps würden mich vor allem interessieren.

Aber leider kranken die Folien unter einem ganz spezifischen Problem: Sie strotzen nur so vor Begeisterung über die tollen typographischen Möglichkeiten von  Tex.

:-(

Besonders bezeichnend finde ich  Vorlesung 3.2. Ich meine, es ist ja schön, das man mit Tech Programme auf solch mathematische Art und Weise schreiben kann.

Aber wer bitte soll das Lesen? Und wieso wohl hat sich APL nicht durchgesetzt?

Im GCC gibt es auch  eine Erweiterung die den tertiären Operator erweitert (test) ? ifTrueValue : ifFalseValue so dass man den mitleren Operanden weglassen kann um bei dieser Form zu landen aValue ?: ifValueEvaluatedToFalse . Das ist für sich gesehen nun jetzt erst einmal blöde - andersherum ergibt sich daraus die schöne Möglichkeit einen "Oder" Operator zu haben.

Das heißt: Muss man dieses Beispiel

void getImportantValue() {
    return somethingWhichGeneratesAStringOrNull();
}

erweitern, dass es nie null zurückgibt, sondern stattdessen immer mindestens einen leeren String, geht jetzt einfacher. Klassisch würde man das so machen müssen:

void getImportantValue() {
    char *value =  somethingWhichGeneratesAString();
    if (value) return value;
    else return "";
}

Oder eben kürzer mit der Erweiterung:

void getImportantValue() {
    return somethingWhichGeneratesAString() ?: "";
}

Ist diese Vereinfachung beim Schreiben die größere Komplexität beim Lesen wert? Immerhin wird fast kein C-Coder dieses Konstrukt kennen und daher zuerst einmal überrascht sein wird.

Vielleicht sollte man es dann also in ein Macro packen, das diese Funktion über den Namen dokumentiert, etwa

#define IF_NULL_USE ?:

Dann kann man aber eigentlich auch schon wieder ein Makro bauen das das explizit macht. So zum Beispiel:

#define IF_NULL_USE(testedValue, alternativeValue) (testedValue) ?: alternativeValue;

Oder man definiert sowas gleich als Funktion.

Und das ist genau das Problem dass diese Konstrukte, ob in Opal oder in C, immer haben. Es ist ja schön das man sowas machen kann - aber es dokumentiert sich selbst ganz beschissen. Und man schreibt den Code nun mal für andere Menschen - nicht für das Satzsystem. Also muss man doch eine Funktion oder ein Makro definieren, damit sich dieser Trick selbst dokumentiert - und dann kann man an der einen Stelle auch gleich ein if hinschreiben.

  • Posted: 2008-03-28 04:52 (Updated: 2008-03-28 04:56)
  • Author: dwt
  • Categories: code
  • Comments (0)

Wie erklärt man Objektorientierung (2)

Da hab ich doch tatsächlich meinen ersten Kommentar zu einem Blog-Eintrag erhalten. Per mail von  Felix!.

:)

Ja, ich muss noch an der Technik hier schrauben. Wird gemacht. Versprochen.

Hier zum Kommentar:

Richtig verstanden hat man erst wenn man es erklären kann. Das bedeutet aber auch, dass deine Erklärung nicht zu einem vollständigen Verständnis deiner Zuhörer_innen führen kann.

So hab ich diesen Satz noch gar nicht gesehen - aber das steckt da drinnen und ist absolut korrekt.

Allerdings...

Das bedeutet natürlich nicht, dass man sich weniger bemühen sollte auch schon bei der Erklärung eines Konzeptes so viel wie möglich erreichen zu wollen.

... muss man es trotzdem versuchen. Ein Kurs ist ja immer von ganz unterschiedlichen Menschen belegt, die jeder auf einem anderen Level sind.

Das Problem als Vortragender ist da natürlich das ich nicht individuell auf die einzelnen Zuhörer eingehen kann - um das Beispiel zu wählen das ihm am Meisten erklärt.

Das bedeutet aber natürlich auch, das damit jeder aus einem Vortrag etwas anderes mitnimmt. Ein perfekt strukturierter Vortrag gibt damit jedem der ihn hört genau das mit, was er benötigt um weiterzumachen und bietet bei erneutem hören weitere Inspiration.

Ok, das schafft man wohl nicht - aber das Ziel, das man das Material so präsentiert, das der Anfänger einen Einstieg hat, und der der etwas weiter ist, mehr mitnehmen kann - das hab ich schon.

Was ist Objektorientierung?

Es heißt ja, das man etwas erst wirklich verstanden hat, wenn man es einem anderen Menschen beigebracht hat.

Nun, mir stellt sich mal wieder die Frage, im Rahmen des  Javakurses der Freitagsrunde an der TU-Berlin:

Wie erklärt man Objektorientierung wirklich gut?

Verschiedene Kräfte zerren da an mir:

  • Der Vortrag muss in eine Stunde passen
  • Die Sprache anhand der es erklärt werden muss ist Java
  • Die Studenten haben noch nie eine Programmiersprache gesehen
    • oder vorher eine Funktionale Sprache gelernt, die sie nicht verstanden haben

Was mir am meisten am Herzen liegt sagt Kent Beck in  Smalltalk best Practice Patterns einfach wunderschön:

Objects model the world through behavior and state. Behavior is the dynamic, active, computational part of the model. State is what is left after behavior is done, how the model is represented before, after, and during a computation.

Of the two, behavior is the more important to get right. The primacy of behavior is one of the odd truths of objects, odd because it flies in the face of so much accumulated experience. Back in the bad old days you wanted to get the representation right as quickly as possible, because every change to the representation bred changes in many different computations.

Objects (done right) change all that. No longer is your system a slave of its representation. Because objects can hide their representation behind a wall of messages, you are free to change representation and only affect one object.

Nur leider können die Studenten mit dem Vergleich in die alten Tage nix anfangen, denn sie haben ja gerade keine Erfahrung warum die alten Tage schlecht waren.

Immerhin der erste Satz ist brauchbar - und ich werde ihn tunlichst einbringen. Damit ist das Problem doch schon mal zumindest kleiner geworden.

Denkfutter bis zum nächsten mal...

Warum entwickeln immer noch so viele Leute ohne !UnitTests?

Weiß ich nicht, aber ich weiß, was für Folgen es hat.

Wenn man ohne Tests entwickelt, dann hat man beim Schreiben von z.B. einer Klasse immer den vollen Zugriff auf das gesamte System - und es ist ständig verlockend, nur eben dieses andere Subsystem aufzurufen oder es in die klasse als Instanzvariable einzubinden. Da man kein Korrektiv hat, sind die Folgen dieser Handlungen fast unsichtbar. Die Abhängigkeiten im System nehmen immer weiter zu - und die Architetkur immer weiter ab. :/

Als Testgetriebener Entwickler muss man sich, um den Code in einer Testsuite überhaupt laufen lassen zu können maximal vom Rest des Systems abkapseln. Das bedeutet das man sich bei jeder Abhängigkeit die man dazu nimmt genau überlegt, ob man sie wirklich braucht, ob man sie erreichen kann, indem man einen Parameter übergibt, oder indem man ein Protokoll einführt das die Kommunikation dieser zweier Systembestandteile klärt. Immer macht man sich über die Grenzen der Komponente Gedanken und wie die Kommunikation darüber hinaus abläuft.

Man macht eben nicht einfach nur einen Konstruktorparameter über den dann als globale Variable alles zugänglich ist - den man im Test aber niemals instanziiert bekommt.

Diese Tatsache macht für mich einen der größten Unterschiede zwischen Testgetrieben entwickeltem Code und sonstigem Code aus.

Es ist unglaublich schwierig (auf anhieb) Code zu schreiben der Wiederbenutzbar ist und wenig Abhängigkeiten besitzt - wenn man Code testgetrieben entwickelt bekommt man aber unheimlich viel davon umsonst.

Alleine deswegen darf es heute eigentlich keine Ausrede mehr Geben ohne Tests zu entwickeln.

!UnitTests auf ein !TracPlugin...

... sind eine schöne Sache. Wenn man sie denn hinkriegt.

Da wäre zuerst der einfache Fall: Was macht man um eine Wiki-Seite zu erzeugen und zu laden?

Well, hier ist wie es geht:

#!/usr/bin/env python

import unittest

from trac.wiki.model import WikiPage
from trac.test import EnvironmentStub

class PluginTests(unittest.TestCase):

    def test_can_save_and_load_wiki_page(self):
        env = EnvironmentStub()
        page = WikiPage(env, "Foo", None)
        page.text = "barfoo"
        page.save("fnord", "bar", "localhost")
        
        self.assertEquals("barfoo", WikiPage(env, "Foo", None).text)

Soweit nicht soo schwer. Vor allem sehr schick, das das EnvironmentStub() einem die ganze Arbeit abnimmt um das Trac-Environment UnitTest fähig zu machen. Ein Call und alles ist da - inklusive einer in-memory Sqlite Datenbank.

Nice.

Der eigentliche Hammer ist dann aber, wenn man ein Plugin bauen will das von anderen Plugins abhängt. Das ist nämlich deutlich komplizierter.

Da ergibt sich nämlich das Problem, das man plötzlich das Component-System von Trac verstehen muss, damit man überhaupt auf die Klassen und ExtensionPoints eines anderen Plugins aufbauen kann.

Für uns um Beispiel das TracTags-Plugin. Dieser Blog basiert auf diesen Tags. Blog-Posts werden durch einen speziellen Tag ("blog") zu einem Blog-Post, Kategorien sind nur Tags und überhaupt funktioniert alles nur über Tags. Tags sind cool.

Aber, man muss das Plugin erst einmal schwer zur Zusammenarbeit überreden - denn, es benötigt ein Datenbankupgrade damit es überhaupt funktioniert.

Und überhaupt ist die Datenbank am Anfang total leer. :/

Um es gleich zu verraten: Die Lösung lag darin, das man dem Environment einerseits sagen muss, das man gerne Standard-Daten hätte und andererseits, welche Plugins es alles aktivieren soll. Nicht schwer. Nur nicht offensichtlich. :(

#!/usr/bin/env python

import unittest

from trac.wiki.model import WikiPage
from trac.test import Mock, EnvironmentStub, MockPerm

from tractags.api import TagSystem

from tractags.model import TagModelProvider

class PostFinderTest(unittest.TestCase):
    
    def setUp(self):
        self.env = EnvironmentStub(default_data=True, enable=["tractags.*", 'trac.*'])
        self.env.upgrade()        
        self.env.db.commit()

    
    def test_can_save_and_load_wiki_page(self):
        page = WikiPage(self.env, "Foo", None)
        page.text = "barfoo"
        page.save("fnord", "bar", "localhost")
        
        self.assertEquals("barfoo", WikiPage(self.env, "Foo", None).text)

                        
    def test_can_add_tags_and_retrieve_tagged_pages(self):
        from trac.web.href import Href
        page = WikiPage(self.env, "Foo", None)
        page.text = "barfoo"
        tag_system = TagSystem(self.env)
        req = Mock(perm=MockPerm(), args={}, href=Href('/'))
        tag_system.add_tags(req, page.resource, ["blog"])
        page.save("fnord", "bar", "localhost")
        
        self.assertEquals("barfoo", WikiPage(self.env, "Foo", None).text)
        
        blog_resources = [i for i in tag_system.query(req, "blog")]
        self.assertEquals(1, len(blog_resources))

Mei war das eine Arbeit die ganzen Objekte zusammen zu suchen die man für test_can_add_tags_and_retrieve_tagged_pages() benötigt.

Well, ein start ist gemacht, aus dem wir jetzt einen Page-Loader refactor-n (-ieren?) werden.

Yay!

Sauberes Programmieren

(zu  Return to Innocence von Enigma)

Es ist wirklich erstaunlich wie viel schwieriger es ist saubere Programme zu schreiben wenn mehr Leute an der Konstruktion beteiligt sind.

Nehmen wir zum Beispiel drei Leute. Einer hat vorher viel C++ progirammiert. Ganz selbstverständlich ist für ihn das Paradigma geworden, das man Accessoren -getVar und Mutatoren -setVar:aValue nennt.

Und schon mischt sich im System der Namens-Salat. Und das ist noch keine große Sache. Von sich aus besteht keinerlei Einheitlichkeit, wie wer seine Variablen und Funktionen nennt. Wie z.B. heißt eine Klassenmethode zum erstellen eines Objekts? -classname:instanceVarName with:anotherInstanceVarname oder -intentionWithNamedThing:aTypeDescription withAnotherNamedThing:aTypeDescription

Wie geht man mit Prefixen um? Was will man mit Instanzvariablen tun? Wann verwendet man (sinnvollerweise Accessoren und wann direkten Zugriff auf Variablen?

Die Summe dieser kleinen Entscheidungen macht so viel von der ( olfaktorischen?) Sauberheit eines Programms aus, das man sich (eigentlich) nicht leisten kann sie dem Zufall zu überlassen.

Aber der Overhead das alles vorher festzulegen oder auch nur zu kommunizieren ist gewaltig. Vor allem wenn man weder Refaktoring-Werkzeuge hat die einem die Änderungen erleichtern, noch Unit-Tests die einem die Änderungen absichern, noch Reviews / Pair-Programming die einen zu der Entscheidung helfen was geändert werden soll.

Was tun?

Erleichterung finde ich in der Tatsache, das tatsächlich viele kleinen Entscheidungen kaum oder keine Rolle spielen. Wo die Klammern gesetzt werden, oder wie weit eingerückt wird ist egal. Man überließt diese Unterschiede einfach.

Dazu kommt die Aussage aus dem  Big Ball of Mud Paper: Ein Dreckball mag besser sein als gar keine Software.

Ist Sauberkeit also doch überschätzt?

  • Posted: 2008-03-04 21:39 (Updated: 2009-10-24 16:04)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

if [ "z$?" != "z0" ] ; then ...

Aus dem Buildscript  imageFromFolder.sh des MOKit Releases.

Da ich so etwas schon oft gelesen habe, habe ich mich endlich mal dafür interessiert wieso zum Teufel so viele Programmierer das so machen. Könnte ja sein, hab ich mir gedacht, das es einfach so kranke sh implementierungen gibt, dass man das so machen muss damit das wirklich auf allen Systemen läuft.

Nun, ein guter Hacker im CCC den ich darauf mal ansprach antwortete darauf mit dem Klassiker: "Die machen das doch nur alle weil sie das Manual nie gelesen haben!" :-) Gut,  les ich mal nach...

Ok, also das Beispiel mal etwas detaillierter betrachtet:

hdiutil create # and then a lot of parameters

if [ "z$?" != "z0" ] ; then
    echo "Error creating image ($?)."
    exit 1
fi

Das hätte man doch in jedem fall auch so machen können:

hdiutil create # and then a lot of parameters

if [ 0 -ne $? ] ; then
    echo "Error creating image ($?)."
    exit 1
fi

Und das fängt den Sinn des Tests auch gleich viel besser ein.

Dann hätte man natürlich auch noch eine der Kurzformen wählen können, sowas wie

hdiutil create mumble mumble \
    || echo "Error creating image ($?)." ; exit 1

Das ich persönlich fast noch etwas klarer finde - erst recht wenn man es in eine Funktion error_exit oder etwas ähnlichem verpackt

error_exit ( )
{
    local exit_value=$?
    echo $1 "($exit_value)"
    exit $exit_value
}

hdiutil create mumble mumble \
    || error_exit "Error creating image." 

Überraschenderweise ist das nämlich gar kein Problem so etwas in der Shell zu machen. Macht sich nur offenbar kaum jemand die Mühe das "fine" manual mal zu lesen.

Oder zumindest so scheint es mir im Moment.

  • Posted: 2008-03-02 00:11 (Updated: 2009-10-24 16:00)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)

Trac als Homepage

(Zu  Amy Amy Amy)

Trac als Homepage hat sich für mich ja schon bewährt. Ein Wiki, eine Versionsverwaltung, Theoretisch auch noch Bug-Tracking (deaktiviert wg. Spammern) - was will man mehr.

Naja, einen Blog. Und da fängt das Problem dann auch schon an.

Traditionell sind Blogs alle in HTML oder mit merkwürdigen Wysiwyg-Editoren die nicht richtig funktionieren oder so beschränkt sind, das man darin gar nicht alles machen kann was man will (z.B. Source-Code highlighten. Noch dazu Objective-C!)

Ausserdem will ich das mein Blog sauber mit meinem Wiki Verlinkt ist und ich die normale Wiki-Syntax zum Bloggen verwenden kann.

Das mache ich bisher mit dem  TracBlogPlugin, nachdem ich früher mal mit dem  SimpleBlogPlugin eher... unzufrieden war.

Leider aber ist auch das TracBlogPlugin nicht perfekt. Zwar setzt es auf das  TracTags Plugin auf, das ganz excelent ist - aber leider bleiben einige schwächen.

  • History + Archiv ist mehr oder weniger nicht vorhanden
  • Kommentare gehen nicht.
    • Moderation derselben schon gleich gar nicht.
  • RSS-Feeds sind eher... nun ja, mit einigem Experimentieren kriegt man einiges hin, aber schön ist es nicht.
  • WeblogPings und Backlinks werden nicht unterstützt.
  • Blogrolls etc... alles von hand.
  • XMLRPC... Ah well.

Weiterentwickelt wurde das Plugin seit fast einem Jahr nicht mehr (ok, in  den letzten Tagen passiert wieder etwas, mal schauen wie das weiter geht)

Jetzt hatte ich mir schon überlegt [vielleicht selber Hand anzulegen], da stellt sich heraus, das schon jemand anderes deutlich entnervt war und das Plugin  FullBlog entwickelt hat.

Pro:

  • Feeds++
  • Kategorien (mit TracTags Unterstützung?)
  • Kommentare + Spamfilterung!
  • XMLRPC support

Cons:

  • Seiten sind nicht im normalen Wiki-Namespace (Aber man kann wohl hin und her linken)
  • Ich kann also nicht alle bestehenden Postings direkt übernehmen
  • Der Code malt (da er keine normalen Wiki-Seiten verwendet) direkt in der Datenbank herum. Gnah.

Mehr hab ich mir noch nicht angesehen. Aber eine vernünftige Blog-Integration für Trac wäre Gold wert. Will schließlich heute jeder haben, einen Blog. Ah well.

Eye in the Sky Projektideen:

  • Endlich mehr Blogging Features hier im Trac
  • Mehr Release Management Unterstützung
    • Upload von neuen Releases
    • App-Cast auto-generieren - mit Wiki-Seite für die Change-Notes
  • Buildserver wäre ein Traum - geht aber nicht solange ich auf Linux hoste und für den Mac Entwickle.

Diesen Freitag 17:00 Uhr treff ich mich mit  Felix und  Felix in der  Freitagsrunde und wir werden mal weiter beraten was wir mit Trac weiter machen. Wer Trac Hacken mag oder schon tut und dazukommen mag ist natürlich herzlich eingeladen. :)

LOGen von beliebig vielen Argumenten

Fehlt noch ein letzter Schritt - der erstaunlicherweise ganz einfach war und ausserdem noch ein ganz anderes Problem löste...

Zuerst die Implementierung:

#define LOG(what, variableArguments...) \
    [[Log sharedLog] logObjcType:@encode(typeof(what)) \
        arguments:(void *)what , ##variableArguments]

- logObjcType:(char *)typeString arguments:(void *) what, ...; {
    id formatString =  [self stringFromType:typeString inValue:what];
    
    va_list variadicArguments;
    va_start(variadicArguments, what);
    
    id logString = [[[NSString alloc] initWithFormat:formatString 
                    arguments:variadicArguments] autorelease];
    
    va_end(variadicArguments);
    
    return [self log:logString];
}

Eigentlich hatte ich hier mehr Probleme erwartet.

Ganz nebenbei löst das auch noch ein Problem von ganz zu Anfang:

LOG(@"%@", [NSString stringWithFormat:@"%s,%s", "fnord", "fnord"]);

Das macht jetzt keine Probleme mehr, da die zusätzlichen Komas vom Präprozessor (fälschlicherweise) als zusätzliche Argumente zur dem variadischen Makro aufgefasst werden. Hehe...

Jetzt fehlt nur noch ein Backend für den Logger und ich bin glücklich. Andererseits... da gibt es natürlich auch  Log4Cocoa das ich einfach erweitern könnte.

Hm. Mal schauen.

C-Typen in die Laufzeitumgebung heben

Wendet man @encode() jetzt auf ein paar Deklarationen an, erhält man folgende Ergebnisse:

#define LOG(what) [[Log sharedLog] logObjcType:@encode(typeof(what)) arguments:(void *)what]

LOG(@"fnord"); // @
LOG("foo"); // [3c]
char *string = "fnord";
LOG(string); // *

Ich parse das so:

#define RAISE_UNSUPPORTED_TYPE(encodedType) \
    [NSException raise:NSInvalidArgumentException \
        format:@" you are trying to log something that is not a string: %s", encodedType]

- stringFromType:(char *)encodedType inArray:(void *)what; {
    if ('[' != *encodedType) RAISE_UNSUPPORTED_TYPE(encodedType);
    
    encodedType++;
    unsigned length = strtol(encodedType, &encodedType, 10);
    if ('c' != *encodedType) RAISE_UNSUPPORTED_TYPE(encodedType);
    
    return [NSString stringWithCString:what length:length-1]; // \0 ignored
}

- stringFromType:(char *)encodedType inValue:(void *)what; {
    if ('@' == *encodedType) return [(id)what description];
    if ('*' == *encodedType) return [NSString stringWithCString:(char *)what];
    if ('[' == *encodedType) return [self stringFromType:encodedType inArray:what];
    RAISE_UNSUPPORTED_TYPE(encodedType);
    return nil; /* never reached */
}

(Vorschläge wie man das besser machen kann, bitte gerne an mich! ( z.B. via spamfaenger ät gmx de))

Fehlt nur noch die Erweiterung auf beliebig viele Argumente. Dazu aber morgen mehr. :)

  • Posted: 2008-02-15 22:18 (Updated: 2008-02-16 09:17)
  • Author: dwt
  • Categories: code
  • Comments (0)

Mit C Makros etwas tun, was mit ihnen nicht möglich ist

Das Problem ist also ein Makro zu schreiben, das typsicher und nach Typen überladen ist, eine variable Anzahl von Argumenten erhält und zudem einfach und verständlich ist.

Zugegeben, mir ist noch nicht klar wie ich das umsetzen kann.

Aber, wenn man das Problem nur ein klein wenig vereinfacht, und nur versucht eines oder mehrere Argumente zu unterstützen...

Also zum Beispiel ein LOG() Macro, dass als erstes Argument irgend etwas erhalten kann (für den Anfang, irgend etwas das sich in einen String umwandeln lässt) dann erhält man etwas das sich so verwenden lässt:

LOG(@"fnord");
LOG("fnord");
char *string = "fnord";
LOG(string);
LOG(@"%@", @"fnord");
LOG(@"%s", "fnord");
LOG(@"%d", 10);
LOG("%s", "fnord");
LOG(@"%@", [NSString stringWithFormat:@"%s,%s", "fnord", "fnord"]);

Die Implementierung ist allerdings etwas tricky.

Typinvariante Makros wie MAX(x, y) schreibt man üblicherweise so:

#define MAX(x, y) ({ \
    typeof(x) __x = (x); \
    typeof(y) __Y = (y); \
    (__x < __y) ? __x : __y; \
})

Das bedeutet durch die speziellen klammern ({ ... }) wird das Makro wie eine Funktion behandelt (eine GCC-Extension) und die übergebenen Ausdrücke nur einmal auswertet und in einer Variablen vom korrekten Typ speichert. int m = MAX(i++, ++j); ist damit also kein Problem. Schließlich ist der Wert der letzten Zeile der "Rückgabewert" der Makro-Funktion

Loggen ist aber schon noch ein Problem, da mit diesem Trick, der Typ noch nicht herauszufinden ist. Mein erster Ansatz mit  __builtin_types_compatible_p(typeof(aValue), char *) etc. brachten mich da nicht weiter. Es braucht noch die  @encode() Anweisung von Obj-C. Direkt im Makro mit if (@encode(char *) == @encode(typeof(aValue))) war es dann allerdings auch noch nicht obwohl ich gerne mit  __builtin_choose_expr( aConstant, expressionOne, expressionTwo ) zur Compilezeit alles erledigt hätte.

Nun ja, dann eben zur Laufzeit mit @encode().

#define LOG(aValue) [[Log sharedLog] logObjcType:@encode(typeof(aValue)) value:aValue]

Die erzeugt aus einem Typ nämlich einen C-String - der zur Laufzeit geparst werden kann um daraus herauszulesen wie der void-Pointer auf aValue interpretiert werden muss.

Wie ich das gemacht hab - morgen. :)

  • Posted: 2008-02-15 09:02 (Updated: 2008-02-15 21:19)
  • Author: dwt
  • Categories: code
  • Comments (0)

Macros in C

(Zu "Feuer Frei" von Rammstein)

Makros in C sind ja so eine Sache.

Zum einen kommt man in C an vielen Stellen gar nicht ohne aus, da sie die einzige Möglichkeit sind Ausdrücke OHNE sie zu evaluieren an mehreren stellen zu verwenden. Zum anderen Sind sie rein Textuell, "unhygienisch" und haben keine Ahnung was in ihnen eigentlich passiert.

Das macht vor allem in Objective-C mächtig Schwierigkeiten. Das hier zum Beispiel geht nicht.

#define DO(argument) argument

DO([NSString stringWithFormat:@"%s", "asdf"]);

Der Compiler meckert hier über zu viele Kommas und denkt das ich das Makro mit zwei Argumenten aufrufen will. Gnah.

Warum beschäftigt mich das eigentlich?

Ich möchte gerne Macro's haben, die je nach Typ des Arguments etwas anderes machen. Für Unit-Tests z.B. möchte ich sagen:

- (void) testOverloadedAssert
{
    ASSERT_EQUALS(3, 3);
    ASSERT_EQUALS(@"barf", @"barf");
    ASSERT_EQUALS("bar", "bar");

    // These should fail in a sensible manner
    ASSERT_EQUALS("bar", @"bar");
    ASSERT_EQUALS(3, 3.3);
}

Das ist aber schon ein großes Problem. In C++ könnte man den ersten teil ja noch über überladene Funktionen lösen - das geht in reinem Objective-C aber schon mal nicht. Dazu kommt ja noch der zweite Teil - da kriegt man vielleicht noch Compiler-Fehlermeldungen, wenn der Compiler nicht auto-conversion von Parametern anwirft. :-/

Dazu kommt, das ich C++ nicht mag und schon gar nicht in alle meine Tests eine zwingende Abhängigkeit auf C++ einfügen will.

Will man dann aber noch weiter gehen, wird es endgültig schwierig. Hier ein paar Beispiele:

- (void) testAdvancedAssert
{
    ASSERT_EQUALS(3.3, 3.3, 0.1 /* accuracy */);
    ASSERT_EQUALS(3.3, 3.3, @"important meta information");
    ASSERT_EQUALS(3, 3, @"info contains: %@ in %s:%d",
         @"other info", __PRETTY_FUNCTION__, __LINE__);
}

In reinem C hätte man hier gar keine Möglichkeiten mehr etwas zu machen.

Wie weit ich mit einer Lösung dennoch gekommen bin - morgen. :)

  • Posted: 2008-02-13 21:34 (Updated: 2008-02-14 07:47)
  • Author: dwt
  • Categories: code
  • Comments (0)

Vergessene Objective C-Möglichkeiten

Da gibt es doch tatsächlich in Objective C die Möglichkeit, jederzeit eine Klasse unter einem anderen Namen zu importieren.

Nice.

@compatibility_alias MyShortClassName MyVeryLongClassNameThatIDontWantToTypeEveryTime;

Von  Write a Screen Saver Part II

  • Posted: 2008-02-03 21:53 (Updated: 2008-02-03 22:10)
  • Author: dwt
  • Categories: code links
  • Comments (0)

Shell-Scripting aus Python

Ich wollte ja schon länger meine Shell-Scripte eigentlich gerne in Python schreiben - einfach weil mich bash so fürchterlich nervt.

Allerdings ist der call-out zu shell-scripten ziemlich ekelhaft. All die schönen / ekelhaften Dinge die man in der Shell so einfach machen kann, gehen nicht.

Naja, dachte ich. :)

Mit ein bisschen Googeln hab ich nämlich das hier gefunden.

Wunderschön einfach und simpel:

import shell

print shell.prefix
shell.prefix="/usr/bin"
print shell.prefix
shell.wait=False
print "calling true:", shell.true().returncode
shell.wait=True
print "calling true:", shell.true().returncode

shell.prefix="/bin"
print shell.ls("~/").stdout

Nice! Nie wieder Bash bashen.

:)

  • Posted: 2008-02-03 20:43 (Updated: 2008-02-03 20:47)
  • Author: dwt
  • Categories: code
  • Comments (0)

Andere machen auch Fehler...

Es ist doch immer wieder angenehm, wenn man feststellt das man nicht der einzige Mensch auf der Erde ist der Fehler macht.

Damit dieses Post besser via google zu finden ist, ab hier auf Englisch.

--- snip ---

For anyone trying to develop QT-based apps on leopard, take care. I couldn't find this bug documented anywhere, so I'm writing this up here.

The Leopard CDs Apple shipped with new computers right after leopard came out contain a very old QTKit Version in the 10.4u.sdk of Xcode 3.0.

This means (at least) that you get spurious warnings on building - and we have also found that it makes apps built on this machine (Leopard, Intel) behave very badly on PPC Tiger machines. That is, QuickTime Exports would fail silently. (This is what initally triggered us finding this)

To detect it, have a look at the file: </Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/QTKit.framework/Versions/A/Headers/QTMovie.h> and look till when the copyright goes. If it is till 2005 -> You have the old version. If it is till 2007 -> You most likely have the correct version.

I don't know if there are other problems with the CD Version of Leopard, so if you are using it, take care and consider installing something different!

Oh, and of course I filed a radar: 5699456

--- snap ---

Ah well. Mal schauen wo ich dieses Post unterbringen kann.

  • Posted: 2008-01-22 10:02 (Updated: 2009-10-24 16:22)
  • Author: dwt
  • Categories: fnord code
  • Comments (0)