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を使う
  • 異常系は先に検知して、処理を分離する。その下のコードには入らないようにする。正常系と異常系のコードを分離するということ。