メソッド設計で守るべき10個のルール
以前メソッド設計の原則に関する記事を書きましたが
それ以前にメソッド設計する上で最低限守った方がよいルールをまとめてみました。
- プロパティをメソッドの戻り値代わりに使ってはいけない
- ファンクションメソッドでプロパティの値を変更してはいけない
- プロパティをリターンしない
- インスタンス変数やプロパティをメソッドの引数に渡さない
- 参照渡の引数をリターンしてはいけない
- 例外処理を GoTo 文の代わりに使ってはいけない
- 理由なく id 型をメソッドの戻り値にしない
- 特定メソッドの呼びだしが前提になったメソッドを作ってはいけない
- パブリックメソッドからパブリックメソッドを呼ばない
- プライベートメソッドからパブリックメソッドを呼ばない
以下その詳細です。
プロパティをメソッドの戻り値代わりに使ってはいけない
メソッドが呼ばれるたびにプロパティを release して作り直すような場合、プロパティを使う必要がありません。プロパティに限らずインスタンス変数を使用する場合も同じです。
悪い例
@interface Hoge : NSObject { NSArray *_array; } @property (retain) NSArray *array; - (void)foo; @end @implementation Hoge @synthesize array = _array; - (void)foo { [self.array release]; : 何らかの処理 self.array = 処理結果; } @end
改善例
メソッドの戻り値ですむならプロパティ使用をやめる。なぜプロパティが必要なのかよく考えてからプロパティを宣言すること。
@interface Hoge : NSObject { } - (NSArray *)foo; @end @implementation Hoge - (NSArray *)foo { : 何らかの処理 return 処理結果; } @end
ファンクションメソッドでプロパティの値を変更してはいけない
ファンクションメソッド内でプロパティの値を変更してしまうと副作用が発生することになります。メソッド呼び出しが他のメソッドの処理結果に影響を与えてしまうので副作用のあるファンクション設計はやめましょうという話です。
詳細は以下の記事を参考にしてください。
悪い例
@interface Hoge : NSObject { NSArray *_array; } @property (retain) NSArray *array; - (NSString *)foo; @end @implementation Hoge @synthesize array = _array; - (NSString *)foo { : 何らかの処理 self.array = [NSArray arrayWithObjects:@"aaa", @"bbb", nil]; : 何らかの処理 return 処理結果; } @end
改善例
ファンクションメソッドではプロパティの値を変更しない。呼び出し元で行うこと。
ファンクションメソッドでは常に単純な処理のみを行うようにすること。ファンクションメソッドでプロパティの値を変更してしまうと呼び出し元でその影響が見えなくなることがある。
@interface Hoge : NSObject { NSArray *_array; } @property (retain) NSArray *array; - (NSString *)foo; @end @implementation Hoge @synthesize array = _array; - (NSString *)foo { : 何らかの処理 return 処理結果; } @end
呼び出し元
- (void)bar { Hoge *hoge = [[Hoge alloc] init]; NSString *returnValue = [hoge foo]; hoge.array = [NSArray arrayWithObjects:@"aaa", @"bbb", nil]; : 後続処理 }
プロパティをリターンしない
メソッドの戻り値にするのであればそもそもプロパティにする必要ないですね。
悪い例
@interface Hoge : NSObject { NSArray *_array; } @property (retain) NSArray *array; - (NSArray *)foo; @end @implementation Hoge @synthesize array = _array; - (NSArray *)foo { : 何らかの処理 self.array = 処理結果; return self.array; } @end
改善例
プロパティを使わずにメソッドの戻り値を使う。
@interface Hoge : NSObject { } - (NSArray *)foo; @end @implementation Hoge - (NSArray *)foo { : 何らかの処理 return 処理結果; } @end
インスタンス変数やプロパティをメソッドの引数に渡さない
クラス内でどこからでもアクセスできる変数を引数に渡してもあまり意味がありません。
悪い例
@interface Hoge: NSObject { NSString *_title; } @property (copy) NSString *title; - (void)foo:(NSString *)title; - (void)bar; @end @implementation @synthesize title = _title; - (void)foo:(NSString *)title { NSMutableString *s = [NSMutableString stringWithString:@"Hello "]; [s appendString:title]; : 何らかの処理 } - (void)bar { title_ = @"World"; [self foo:_title]; // [self foo:self.title]; } @end
改善例1
引数を廃止する
@interface Hoge: NSObject { NSString *_title; } @property (copy) NSString *title; - (void)foo; - (void)bar; @end @implementation @synthesize title = _title; - (void)foo { NSMutableString *s = [NSMutableString stringWithString:@"Hello "]; [s appendString:_title]; : 何らかの処理 } - (void)bar { _title = @"World"; [self foo]; } @end
改善例2
インスタンス変数を廃止する
@interface Hoge: NSObject { } - (void)foo:(NSString *)title; - (void)bar; @end @implementation - (void)foo:(NSString *)title { NSMutableString *s = [NSMutableString stringWithString:@"Hello "]; [s appendString:title]; : 何らかの処理 } - (void)bar { NSString *title = @"World"; [self foo:title]; } @end
参照渡の引数をリターンしてはいけない
なんのために参照渡しているか全くわかりませんね。引数をリターンするのは論外です。
悪い例
@interface Hoge : NSObject { } - (NSArray *)fooWithArray:(NSArray **)array; @end @implementation Hoge - (NSArray *)fooWithArray:(NSArray **)array { : 何らかの処理 *array = 処理結果; return *array; } @end
呼び出し元
- (void)bar { NSArray *array; array = [self fooWithArray:&array]; }
改善例
引数の参照渡をやめる。
@interface Hoge : NSObject { } - (NSArray *)foo; @end @implementation Hoge - (NSArray *)foo { : 何らかの処理 return 処理結果; } @end
例外処理を GoTo 文の代わりに使ってはいけない
例外処理はジャンプ命令ではありません。
悪い例
- (void)foo { @try { : 何らかの処理 if (処理結果不正) { @throw [NSException exceptionWithName:@"" reason:@"" userInfo:nil]; } : 何らかの処理 } @catch (NSException *e) { NSLog(@"Error!"); } }
改善例
リターンで処理を終了させる。
- (void)foo { : 何らかの処理 if (処理結果不正) { NSLog(@"Error!"); return; } : 何らかの処理 }
理由なく id 型をメソッドの戻り値にしない
呼び出し側でどんな型のデータが戻ってくるかわからないので明示的に戻り値がわかるように設計しましょう。ただし init 系のメソッド(クラスファクトリメソッドも含む)はのぞきます。
悪い例
- (id)foo { }
補足
Objective-Cは動的型付け言語の特性と静的型付け言語の特性両方を持っています。メソッドの戻り値やプロパティの型など、使う側に仕様をわかりやすく伝える必要がある場面では静的型付け言語のメリットを生かして明示的に型を指定する。逆に変数の型に縛られずに柔軟なプログラミングをしたい場合は動的型付け言語の特性を生かすためにid型を使うといった使いわけが必要です。
id型の使いどころとしては
などがあげられます(常にこうしろというわけではありません)。
// メソッドの戻りをid型で受ける - (void)bar { Hoge *hoge = [[Hoge alloc] init]; id array = [hoge foo]; : 後続処理 } // メソッドの引数をid型にする - (IBAction)respondToButtonClick:(id)sender { : 処理 }
JavaやC♯なんかで
という場面でid型を使うと良いと思います。
特定メソッドの呼びだしが前提になったメソッドを作ってはいけない
fooメソッドと呼ばないと bar メソッドは正しく処理をしませんという場合、呼び出し元で bar メソッドを呼んだのに想定した処理が走らないなんてことがおこります。
あるメソッドを呼ばないと正しく動かないメソッドというのは、そもそも処理をメソッドとして独立させる必要がありません。
悪い例
@interface Hoge : NSObject { NSArray *_array; } @property (retain) NSArray *array; - (void)foo; - (void)bar; @end @implementation Hoge @synthesize array = _array; - (void)foo { : 何らかの処理 self.array = [NSArray arrayWithObjects:@"aaa", @"bbb", nil]; : 何らかの処理 } - (void)bar { // arrayプロパティに値がセットされていないと呼び出しても処理が走らない! for (NSString * s in self.array) { : 何らかの処理 } } @end
改善例
メソッドを統合する。
@interface Hoge : NSObject { } - (NSString *)foo; @end @implementation Hoge @synthesize array = _array; - (void)foo { : 何らかの処理 NSArray *array = [NSArray arrayWithObjects:@"aaa", @"bbb", nil]; for (NSString * s in array) { : 何らかの処理 } } @end
パブリックメソッドからパブリックメソッドを呼ばない
あくまで同一クラス内の話です。メソッドを使う側がインタフェースを見ただけでどのような処理が行われるのかわかりづらくなってしまいます。
特にファンクションメソッドからプロシージャメソッドを呼ぶと副作用が発生する可能性が高いので避けた方がよいと思います。
悪い例
@interface Hoge : NSObject { } - (void)foo; - (void)bar; @end @implementation Hoge @synthesize array = _array; - (void)foo { : 何らかの処理 } - (void)bar { [self foo]; : 何らかの処理 } @end
呼び出し元
- (void)methodA { Hoge *hoge = [[Hoge alloc] init]; [hoge foo]; } - (void)methodB { Hoge *hoge = [[Hoge alloc] init]; [hoge bar]; }
改善例1
どのメソッドを呼ぶべきか呼び出し元で判断させる
@interface Hoge : NSObject { } - (void)foo; - (void)bar; @end @implementation Hoge @synthesize array = _array; - (void)foo { : 何らかの処理 } - (void)bar { : 何らかの処理 } @end
呼び出し元
- (void)methodA { Hoge *hoge = [[Hoge alloc] init]; [hoge foo]; } - (void)methodB { Hoge *hoge = [[Hoge alloc] init]; [hoge foo]; [hoge bar]; }
改善例2
呼び出し順が決まっているのであれば外部公開用の別メソッドを作成してそれを呼び出すようにする。
@interface Hoge : NSObject { } /* プライベートメソッドにする - (void)foo; - (void)bar; */ - (void)newFoo; - (void)newBar; @end @implementation Hoge @synthesize array = _array; - (void)foo { : 何らかの処理 } - (void)bar { : 何らかの処理 } - (void)newFoo { [self foo]; } - (void)newBar { [self foo]; [self bar]; } @end
呼び出し元
- (void)methodA { Hoge *hoge = [[Hoge alloc] init]; [hoge newFoo]; } - (void)methodB { Hoge *hoge = [[Hoge alloc] init]; [hoge newBar]; }
プライベートメソッドからパブリックメソッドを呼ばない
これも同一クラス内の話です。プライベートメソッドは必ずといっていいほどパブリックメソッドから呼び出されるので、実質的にパブリックメソッドからパブリックメソッドを呼ぶのと同じです。
悪い例
@interface Hoge : NSObject { } - (void)foo; - (void)bar; @end @implementation Hoge @synthesize array = _array; - (void)baz { [self foo]; : 何らかの処理 } - (void)foo { : 何らかの処理 } - (void)bar { [self baz]; : 何らかの処理 } @end
改善例
プライベートメソッドからパブリックメソッドを呼ぶのをやめる。
@interface Hoge : NSObject { } - (void)foo; - (void)bar; @end @implementation Hoge @synthesize array = _array; - (void)baz { : 何らかの処理 } - (void)foo { : 何らかの処理 } - (void)bar { [self baz]; : 何らかの処理 } @end
呼び出し元
- (void)methodA { Hoge *hoge = [[Hoge alloc] init]; [hoge foo]; [hoge bar]; }