cc2eで感じた違和感①
cc2eを読んでいて、正直なところほっとする。
自分がコードを書くときの姿勢とかが間違っていないんだなという感じ。
でもやっぱりいくつか違和感もある。
一番大きかったのはエラー処理の書き方と擬似コードプログラミング。
エラー処理については、だいたい議論は出尽くしていると思うんだけど、
今の自分の考えをまとめておこう。
エラー処理は、gotoを使ったほうがわかりやすく書ける。
こんなコード。
/* 整数配列 */ struct array { int len ; int *data ; }; struct array *create_array(int entry_count) { struct array *new_array; new_array = malloc( sizeof(struct array) ); if ( new_array == NULL ) { goto NULL_RETURN ; } array->len = n_data ; array->data = malloc( sizeof(somedata) * n_data ) ; if ( array->data == NULL ) { goto FREE_DATA ; } /* calloc 使えばいいけど、何か初期化する、ということで。*/ memset( array->data 0, sizeof(int) * entry_count ) ; return array ; FREE_DATA : free( array ) ; NULL_RETURN : return NULL ; }
ま、この例はいまいちだけど、複雑なデータでmallocするのが3つ4つになってくるとgotoの威力が出てくると思う。
一番ダメなのはこんなコード。
/* 整数配列 */ struct array { int len ; int *data ; }; struct array *create_array(int entry_count) { struct array *new_array; new_array = malloc( sizeof(struct array) ); if ( new_array != NULL ) { array->len = n_data ; array->data = malloc( sizeof(somedata) * n_data ) ; } if ( new_array != NULL && new_array->data != NULL ) { memset( new_array->data, 0, sizeof( int ) * entry_count ) ; } return new_array ; }
これくらいならまだきれいだけど、正常、異常のパスが重複しているのダメ。
重複しているから、仕様変更などで拡張をすると、変更を入れた下のif文全部に条件を追加する必要があるし、
このコードは最初のコードと違ってメモリリークするけど、分かりにくい。
昔Appleのサイトにあった旧MacOS APIのサンプルコードがこんな感じで
最悪だった。
こういうのもよく見かける。cc2eは最初のgotoを使うタイプも紹介していたけど、
実際に推しているのはこのタイプ。
/* 整数配列 */ struct array { int len ; int *data ; }; struct array *create_array(int entry_count) { struct array *new_array; new_array = malloc( sizeof(struct array) ); if ( new_array != NULL ) { array->len = n_data ; array->data = malloc( sizeof(somedata) * n_data ) ; if ( new_array->data != NULL ) { memset( new_array->data, 0, sizeof( int ) * entry_count ) ; } else { free( new_array ) ; new_array = NULL ; } } return new_array ; }
ダメ。はっきり言って読みにくい。
正常・異常系が分かれてはいるけど
拡張すると正常系がどんどん深くなっていくし、
エラー処理と、エラー条件の対応が分かりにくすぎ。
gotoを使っても物理的には離れるけど、ラベルがあって
名前をつけられるので、こっちよりは読みやすいと思ってる。
言語がサポートしていれば例外使って
最初のコードをきれいに書くことができるんだけど、
例外がない言語では、goto文でエラー処理を書くのは
正しいことだと思う。
まとめると、
- 正常系は基本的にインデントしていない部分に書く。ネストが深くなるのを避けるためにgotoを使う。
- 異常系は先に検知して、処理を分離する。その下のコードには入らないようにする。正常系と異常系のコードを分離するということ。