Torstens Offizielle Cocoa Style Richtlinien

Ich kenne einige Leute die sich mit Cocoa beschäftigen, und davon sehr viele die gerade damit anfangen und noch nicht wissen, was guter Stil ist und was nicht. Um ihnen zu helfen dachte ich, ich schreibe mal ein paar der Regeln auf, denen ich immer folge, damit die mich zu ihrem Herrscher ernennen.

Wil Shiples ist Gott

Wil Shipley hat mit Cocoa entwickelt bevor es so hieß, und [seine Programme][deliciouslibrary] sind unter den Besten, die es auf Mac OS X gibt. Es gibt nicht viele Leute mit mehr oder wenigstens genauso viel Erfahrung, und praktischerweise teilt er seine Erfahrung gerne anderen mit. Man sollte dies ausnutzen.

Also lest http://www.wilshipley.com/blog/labels/code.html. Alles davon. Dann nochmal. So lange, bis ihr es verstanden habt. Druckt es aus. Verwendet es als Lesezeichen. Klebt es an den Kühlschrank. Hängt es in einem Bilderrahmen über die Jackenhaken wo man es garantiert sieht. Ja, ein einfacher Link ist etwas antiklimatisch für meine “magischen Regeln”, aber das ist wirklich der wichtigste Teil. Überspringt nicht dies und lest den Rest dieses Artikels, sondernd andersrum. Es ist wichtig.

Nun, zu meinen eigenen kleinen Empfehlungen. Vieles davon ist nur strikt “Was würde Wil Shipley tun?”, aber es macht mein Leben einfacher, also sind sie vielleicht auch für andere interessant.

Es gibt nur ein Mac OS X: 10.5

Viele Leute schreiben noch immer Code, der auch auf 10.4 und teilweise sogar 10.3 laufen soll, in der Hoffnung, auch für Nutzer dieser Systeme interessant zu sein. Ich halte das für sinnlos. Zuerst mal braucht man dafür noch eine Installation von 10.4 und evtl. 10.3, und jedes Release muss auf all diesen Systemen getestet werden. Außerdem verpasst man damit viele schöne Features wie Objective-C 2.0.

Natürlich bezieht sich dies nicht speziell auf 10.5, sondernd immer auf das jeweils aktuellste System. Der Rest dieser Liste bezieht sich aber speziell auf 10.5, sollte aber auch bei höheren Sachen noch zutreffen.

Verwende Properties.

Properties sind großartig. Wenn immer es ein setSomething: mit passendem (oder auch nur ein) -(id)something gibt, sollte es eine Property sein. Statt

@interface MyObject : NSObject
{
    [...]
}
- (void)setSomething:(id)newSomething;
- (id)something;

- (id)readonlySomething;
@end

sollte es also heißen

@interface MyObject : NSObject
{
    [...]
}
@property (retain) id something;
@property (readonly) id readonlySomething;

@end

Der Grund: Erst mal ist es geringfügig weniger Code im Falle der Read-Write-Property, aber insbesondere macht dies hier klar dass es sich eben um eine Methode ohne Seiteneffekte handelt, die nur für diese Property zuständig ist. Außerdem gibt es das verdammt coole @synthesize welches automatisch perfekte Zugriffsmethoden erzeugt, und das ganz ohne selbstgeschriebenen Code!

Natürlich solltet ihr bereits wissen, dass man @synthesize keineswegs verwenden muss, und dass man auch immer eine eigenen Implementation nehmen kann, egal was im Interface steht.

Verwende die Punkt-Syntax

Dies gehört relativ eng mit dem obigen Zusammen. Wenn immer man auf etwas zugreift sollte es mit myObject.property = ... oder ... = myObject.property anstelle von [myObject setProperty:...] oder ... = [myObject property] geschehen.

Das wichtige ist hier nicht wirklich der kürzere Code, sondernd die Überprüfung durch den Compiler. Objective-C verlagert erstaunlich viel zur Laufzeit, was sehr nett ist, aber dazu führt dass man viele Probleme mit dem Debugger bestimmen muss, die in anderen Sprachen vom Compiler abgefangen werden. Mit der .-Syntax führt ein Fehler dazu, dass man nicht starten kann, wie es auch sein sollte.

Das andere wichtige Ding ist Absicht. Wer sich mit Objective C auskennt, kann Standardmuster schnell erkennen. Aber mit dem Punkt muss man das nicht, was das Lesen von Code stark vereinfacht. Guter Code ist bekanntermaßen gut lesbarer Code.

Natürlich geht das nicht immer. Wichtig ist, dass es nicht bei ids geht: [[myDictionary objectForKey:@"key"] stringValue] lässt sich zwar mühsam (mittles eines Casts) mit dieser Syntax verwenden, dass ist aber nur hässlich und tatsächlich wird die eigentliche Absicht weniger klar.

Ein besonderer Pro-Tipp: Es gibt eine Reihe von Eigenschaften, deren Getter ein “is” davor hat, wie setFlipped: und isFlipped. Der Compiler erlaubt es nicht, dafür die Punkt-Syntax zu verwenden. Es reicht aber, eine Category zu definieren, die keine Implementation haben muss, wie folgt:

@interface NSImage (PropertyAdditions)
@property (assign,getter=isFlipped) BOOL flipped;
@end

Dadurch werden Sachen wie myImage.flipped = YES legal. Ja, es ist ein nerviger Hack, aber man braucht ihn relativ selten und ich denke, die Vorteile überwiegen die Probleme.

For-Each Aufzählung

Eine andere großartige Sache in 10.5 ist das for-each-Muster. Statt

for (NSUInteger i = 0; i < [array count]; i++)
{
    id object = [array objectAtIndex:i];
    [...]
}

oder dem grausigen, aber offiziell empfohlenen

NSEnumerator *enumerator = [array objectEnumerator];
id object;
while (object = [enumerator nextObject])
{
    [...]
}

Kann man jetzt einfach schreiben

for (id object in array)
{
    [...]
}

Weniger Code Hurrah! Außerdem ist klarer, was das Ganze denn soll. Es geht nicht immer, hauptsächlich nicht wenn man über zwei Arrays gleichzeitig iterieren will, aber wenn möglich verwende ich es immer.

Private Methoden in Klassenerweiterungen.

Wil Shipley empfiehlt, private Methoden in Kategorien zu tun. Ich denke, es ist sinnvoller sie in Klassenerweiterungen zu packen. Statt

@interface MyClass (Private)
- (void)_doSomethingSecret;
@end

@implementation MyClass
[...]
@end

@implementation MyClass (Private)
- (void)_doSomethingSecret;
{
    [...]
}
@end

habe ich lieber

@interface MyClass ()
- (void)_doSomethingSecret;
@end

@implementation MyClass
[...]
- (void)_doSomethingSecret;
{
    [...]
}
@end

Der Vorteil davon ist, dass man private Methoden nahe an öffentliche Methoden, die damit zusammenarbeiten, ran schreiben kann, was wieder einmal den Code besser lesbar machen kann.

Der Gedanke dahinter, die Methoden in eine Kategorie zu tun, ist, dass sie irgendwo offiziell deklariert sind (sonst mault der Compiler), und sie in eine Kategorie-Implementation zu tun hat den Vorteil, dass der Compiler mault wenn man vergisst, eine zu implementieren. Klassenerweiterungen liefern diese beiden Dienste aber schon von alleine.

Kenne dich mit manueller Speicherverwaltung aus; verwende Garbage Collection

Seit 10.5 kann man die Speicherverwaltung in Cocoa vergessen, weil ein Garbage Collector vorhanden ist. Andere Implementierungen der Cocoa-API, wie [GNUstep][gnustep], hatten dies schon deutlich länger, aber keiner interessiert sich dafür. Meine Empfehlung. Verwendet dieses Feature! Es macht verdammt viel Spaß, Code schreiben zu können ohne sich ständig zu fragen, ob und wo das release hin muss.

Gleichzeitig gilt dies aber auch nur für Cooca. Core Foundation, und alles was davon abhängt, möchte immer noch alt verwaltet werden, und viele wichtige Dienste sind nur darüber verfügbar. Deren System ist aber mit dem von Cocoa weitestgehend identisch, also sollte man das alte System auf jeden Fall lernen.

Ignorier keine Warnung

Das Standard-Warnungs-Niveau in Xcode ist nicht sehr hoch, was schade ist. Folgt statt dessen, was [Keith Baur alias OneSadCookie says in seinem Blog sagt][osc]. -Werror, welches alle Warnungen in Fehler verwandelt, ist das wichtigste: Code, der Warnungen enthält, ist falsch. Das mag nicht immer ein schlimmes Problem sein, aber es sollte immer behoben werden. Wenn es einen wirklich guten Grund gibt kann man den Compiler immer überreden, das Konstrukt doch anzunehmen, im Normalfall über einen einfachen Cast.

Ich mag Kategorien

Ich bin dafür, häufig verwendete Code-Schnipsel, egal wie klein sie sein mögen, in Kategorieren auszulagern. Beispielsweise kenne ich einen, der viel mit NSXMLElement arbeitet, und dort überall [element addAttribute:[NSXMLNode attributeWithName:@"attribute" stringValue:@"value"]]; hat. Ich würde statt dessen eine Kategorie definieren

@interface NSXMLElement (Additions)
- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)value;
@end

@implementation NSXMLElement (Additions)
- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)value;
{
    [self addAttribute:[NSXMLNode attributeWithName:name stringValue:value]];
}
@end

und dann immer [element addAttributeWithName:@"attribute" stringValue:@"value"] sagen. Dass ist kein großer Unterschied, aber es ist jetzt klarer was wir wollen. Das NSXMLNode-Objekt ist nicht weiter relevant, also wird es jetzt nicht mehr erwähnt.

Ein Trick, den ich gerne bei NSScanner verwende, ist scanUpToAndPastString: (tatsächlich habe ich das bis vor kurzem immer scanPastString: genannt, aber upToAndPast ist doch irgendwie eindeutiger).

@interface NSScanner (Additions)
- (BOOL)scanUpToAndPastString:(NSString *)string;
@end

@implementation NSXMLElement (Additions)
- (BOOL)scanUpToAndPastString:(NSString *)string;
{
    if (![self scanUpToString:string intoString:NULL]) return NO;
    return [self scanString:string intoString:NULL];
}
@end

Nicht viel, macht aber mein Leben deutlich einfacher. Natürlich könnte man dies auch erweitern, beispielsweise den gescannten String zurückliefern oder eine Methode für NSCharacterSet definieren, aber ich persönlich brauche das nicht.

Stay in english

Speziell für die deutschsprachigen Besucher: Wer Cocoa schreibt muss Englisch können, ansonsten sollte man es gleich vergessen. Ja, es gibt dass eine oder andere deutsche Buch, aber am Ende kommt man ohne Englisch nicht weit.

Ich würde darüber hinaus empfehlen, im Code im Normalfall immer englische Bezeichner und Kommentare zu verwenden. Das Gehirn arbeitet schon schwer genug wenn es von Objective-C nach Englisch umschalten muss, von Objective-C mit all seinen englischen Ausdrücken nach Deutsch ist da nur noch schwerer. Code lesen sollte leicht sein.

Zusammenfassung

Das ist mehr oder weniger alles. Wenn Interesse besteht schreibe ich aber auch gerne noch mehr.

Geschrieben am 5. August 2008 um 19:57

0 Kommentare

    New comments can no longer be posted because it got to annoying to fight all the spam.