Das Kind beim Namen nennen

Im Buch werden zu den Naming Rules ein paar Wörter verloren. Dabei wird die inzwischen recht übliche Unterzug-Notierung (_instanzVariable) für Instanzvariablen verwendet, gleichzeitig darauf hingewiesen, dass Apple den Unterzug nicht so gern sieht. Wieso ist das so? Und wo liegt der praktische Unterschied?

Das Grundproblem der Namenskonvention liegt darin, dass zum einem Sie eine Klasse erweitern, nämlich durch Ableitung und zum anderen Apple eine Klasse erweitern kann, nämlich durch Fortentwicklung. Ganz konkret bedeutet dies, dass Sie etwa von NSObject ableiten und etwa eine Instanzvariable title hinzufügen. Gleichzeitig, aber ohne dass Sie das wissen, erweitert Apple NSObject in Leopard. Hierbei erhält jede Instanz von NSObject automatisch einen Member title. Wenn Sie jetzt Ihren Code neu übersetzen, wird der Compiler meckern, dass Sie in Ihrer Ableitung einen Bezeichner verwenden, der bereits in der Basis-klasse enthalten ist. Sie müssen jetzt entsprechend Ihren Code ändern.

Wie beseitigt man diese Problem? Apple will dies vermeiden, indem es alle Bezeichner, die mit einem Unterzug (_) beginnen, für sich selbst reserviert. Ihre Instanzvariablen dürfen demnach nicht mit einem solchen Unterzug beginnen. Halten sich beide an diese Regel, so würde Ihre Instanzvariable title heißen, die von Apple _title – Konflikt vermieden.

Nicht wirklich: Die Unterstrichkonvention gilt nämlich nicht für Accessoren, so dass diese gleich hießen.

Nur hat die Sache einen Haken: Es hat sich die Bezeichnung von Instanzvariablen mit einem Unterzug am Anfang eingebürgert. Das hat zwei Gründe:

1. Es ist eine »stille Konvention« der C-Sprachen, dass Bezeichner, die von außen nicht erreicht werden sollen, einen Unterzug erhalten. Apple selbst macht das bei internen Methoden so. Da unsere Instanzvariablen stets Accessoren haben, gilt die Regel für diese: Sie sind verborgen! Unter diesem Gesichtspunkt wäre also die Verwendung des Unterzuges richtig.

2. Es gibt ein paar Unbequemlichkeiten: Zwar dürfen Methoden und Instanzvariablen verwechslungsfrei den gleichen Bezeichner tragen. Dies gilt jedoch nicht für Parameter von Methoden und Instanzvariablen. Hier kann der Compiler im Code nämlich nicht mehr unterscheiden, ob mit einem Bezeichner eine Parametervariable oder eine Instanzvariable gemeint ist. Er wirft eine so genannte »shadow warning«, weil die eine Deklaration die andere »verdeckt«.

Während 1) nur eine Umgewöhnung verlangt, stört 2) etwas. Wir wollen hier indessen zei-gen, dass man auch diese Störung durch andere Konventionen beseitigen kann.

Nehmen wir an, dass wir folgende Klasse haben:

Instrument.h
#import <Cocoa/Cocoa.h>

@interface Instrument : NSObject {
    NSString*   name;
    […]
}

[…]

//  Accessoren
- (NSString*)name;
- (void)setName:(NSString*)name;
@end

Wir haben jetzt also die Anweisung von Apple ernst genommen und die Instanzvariable ohne Unterzug bezeichnet. Ein Standard-Setter sähe jetzt so aus:

Instrument.m
#import "Instrument.h"
[…]
@implementation Instrument
- (void)setName:(NSString*)name {
    if (name != name) {
        [name release];
        name = [name retain];
    }
}
@end

Und hier taucht das Problem auf: Man sieht sofort, dass die Instanzvariable wie der Parameter heißt und der Code offenkundig sinnlos ist. Der Compiler kann aber nicht wissen, welches name nun gerade konkret gemeint ist. Das geht also nicht.

Die Templates von Apple lösen das Problem, indem sie den Parameter value nennen. Das Ergebnis ist wieder einwandfrei. (Solange nicht die Instanzvariable selbst value heißt. ;-))

Instrument.m
- (void)setName:(NSString*)value {
    if (name != value) {
        [name release];
        name = [value retain];
    }
}

Aber bekommen wir nicht ein Problem mit der Deklaration? Diese lautet ja:

Instrument.h
- (void)setName:(NSString*)name;

Nein, bekommen wir nicht, weil es prinzipiell erlaubt ist, dass die Parameternamen in der Deklaration und in der Definition voneinander abweichen. Im Grunde interessiert sich der Compiler schlicht nicht für die Namen der Parameter in der Deklaration.

Damit wäre das erste Problem gelöst: Setter erhalten in der Definition als Parameter eine Variable namens value.

Bleibt allerdings ein weiteres Problem, welches umständlicher ist. Das Ganz wiederholt sich nämlich in den Initialisieren und convenience Allocators:

Instrument.m
- (id)initWithName:(NSString*)name {
    […]
}
    
+ (Instrument*)instrumentWithName:(NSString*)name {
   […]
}

Auch hier haben wir wieder Doppelnamigkeit der Instanzvariable mit der Parametervariablen. Auch hier kann man zunächst daran denken, auf value auszuweichen. Das Ganze funktioniert nur nicht mehr, wenn der Initialisierer mehr als einen Parameter bekommt. Die können ja nicht alle value heißen.

»Was tun?«, sprach Zeus. Wir weichen einfach auf einen anderen Vorsatz als den Unterzug aus und benennen die Parameter mit einem vorangestelltem new oder init, also etwa newName bzw. initName. Eine weitere Methode, die das Ganze kürzer hält, ist es, den Unterzug hintanzustellen, also name_ zu verwenden:

Instrument.m
- (id)initWithName:(NSString*)name_ {
   […]
}
    
+ (Instrument*)instrumentWithName:(NSString*)name_ {
   […]
}

Und geht! Dies ist auch recht kurz. Und in der Deklaration im Header dürfen wir weiter die Unterzüge weglassen, weil es den Compiler nicht interessiert.

So ist dann allen zur Zufriedenheit geholfen.

Eine dringende Warnung will ich allerdings nicht verschweigen: Da der Anfang der Instanzvariablen und der Para-metervariablen gleich beginnt, passiert es insbesondere im -init… wie im convenience Allocator schnell, dass man sie verwechselt. Bei der Fehlersuche überliest man dies. Meist zeigt sich das durch uninitialisierte Instanzvariablen. Wenn Sie damit zu kämpfen haben, denken Sie hieran.