第21問の答え

答え

typedefや大文字・小文字の使い方がバラバラだと読みづらい!
typedef enum {
  rock,
  Paper,
  Scissors
} Hand;

enum result {
  Won,
  Lost,
  draw
};
書き方を統一したほうが読みやすい!
typedef enum {
  Rock,
  Paper,
  Scissors
} Hand;

typedef enum {
  Won,
  Lost,
  Draw
} Result;

解説

今回は、プログラムを読みやすくするための練習問題でした。手直しできる部分がいくつかあるので、順番に説明していきます。

あのー、質問いいですか?
いいよ〜!
えっと、どうしてプログラムは読みやすいほうがいいんですか?
あらら、そこが疑問だったの?
いや、読みやすいほうがいいとは思ってるんですよ?でも、理由をちゃんと説明できないというか……
そういうことね。プログラミングって、けっこう頭を使う作業でしょう?
はい。デバッグとかで悩むことも多いです。
だとしたら少しでも読みやすいほうが、肝心な部分に集中しやすいと思わない?
たしかに、そのほうが考えもまとまりそうですね。
だから多少時間をかけてでも、読みやすいプログラムにするのはいいことなのよ。
やっぱり、そうですよねー!
府に落ちたみたいね。それじゃあ、問題のプログラムを手直ししていきましょうか。

プログラムには、「こういう書き方にしないとダメ」という決まりがあるわけではありません。でも、読みやすさのためには、ある程度の一貫性が大切です。例えば変数や関数などの名前の付け方(命名法)を統一するのは、すぐにでも実践できることの一つでしょう。

問題のプログラム中にあった次のenumは、名前の付け方が統一されていませんでした。

typedef enum {
  rock, /* ← ここだけ小文字で始まっている */
  Paper,
  Scissors
} Hand;
ここではenumを大文字で始まる名前に統一!
typedef enum {
  Rock,
  Paper,
  Scissors
} Hand;

次のenumはどうでしょう。大文字・小文字の使い方がバラバラなうえに、typedefもありません。

enum result { /* ← typedef がない */
  Won,
  Lost,
  draw /* ← 小文字 */
};
enumにはtypedefを付けるルールで統一するのがおすすめ!
typedef enum {
  Won,
  Lost,
  Draw
} Result;

structの書き方も統一しましょう。次の部分では、メンバーになっている変数名の付け方がバラバラですね。

struct game {
  Hand myHand; /* ← 途中に大文字を入れる形式 */
  Hand your_hand; /* ← アンダースコアでつなげる形式 */
};
メンバー名の書き方を揃えて、structにもtypedefを付けるルールで統一!
typedef struct {
  Hand myHand;
  Hand yourHand;
} Game;

これで、もう一つのstructとも書き方が揃いました。

typedef struct {
  Hand wins;
  Hand loses;
} Rule;

では、関数の部分はどうでしょうか。問題のプログラムには、main()のほかに次の2つの関数がありますね。

enum result result_of(struct game game) {
  ……
}

void Play(struct game game) {
  ……
}

enumstructの書き方を統一したので、ここは次のようになります。

Result result_of(Game game) { /* ← typedef のおかげで短くなった! */
  ……
}

void Play(Game game) { /* ← こちらも! */
  ……
}

typedefのおかげで記述量が減ってスッキリしましたが、まだ関数名の付け方がバラバラですね。

ここではアンダースコア(_)を使わず、大文字で始まる書き方に統一!
Result ResultOf(Game game) {
  ……
}

void Play(Game game) {
  ……
}

統一感が出て、かなり読みやすくなったのではないでしょうか。

あとは、enumstructの名前が変わった部分を全体に反映させていけば、修正完了です。

ここがポイント!
プログラムは読みやすく書いたほうが、考えもまとまりやすくなる!
ちなみに……「switch文にdefaultが足りない」って思わなかった?
あ、そこ少しだけ気になってました。
ここにdefaultがないのはいいのかなぁ。
void Play(Game game) {
  switch (ResultOf(game)) {
    case Won:
      puts("The winner is me!");
      break;
    case Lost:
      puts("The winner is you!");
      break;
    case Draw:
      puts("It's a draw.");
      break;
  }
}
今回の出題内容とは趣旨が違うのだけど、switch文にはdefaultを付けておくとさらにいい感じね。
このdefaultには到達しないはずだから、assert(false)を入れておくのがベター!
#include <stdbool.h>
#include <assert.h>

void Play(Game game) {
  switch (ResultOf(game)) {
    case Won:
      puts("The winner is me!");
      break;
    case Lost:
      puts("The winner is you!");
      break;
    case Draw:
      puts("It's a draw.");
      break;
    default:
      assert(false);
  }
}

こういう風にassertを入れておくと、将来ジャンケンの判定結果が増えた場合に備えられます。例えば、「連勝」や「反則負け」が増えるかもしれませんね。するとdefaultに到達してassert(false)でプログラムが停止するので、caseを追加しなければならないことが早期に分かるというわけです。

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef enum {
  Rock,
  Paper,
  Scissors
} Hand;

typedef enum {
  Won,
  Lost,
  Draw
} Result;

typedef struct {
  Hand myHand;
  Hand yourHand;
} Game;

typedef struct {
  Hand wins;
  Hand loses;
} Rule;

Result ResultOf(Game game) {
  Rule rules[] = {
    [Rock] = { .wins = Scissors, .loses = Paper },
    [Paper] = { .wins = Rock, .loses = Scissors },
    [Scissors] = { .wins = Paper, .loses = Rock }
  };

  Rule myRule = rules[game.myHand];
  if (myRule.wins == game.yourHand) { return Won; }
  if (myRule.loses == game.yourHand) { return Lost; }
  return Draw;
}

void Play(Game game) {
  switch (ResultOf(game)) {
    case Won:
      puts("The winner is me!");
      break;
    case Lost:
      puts("The winner is you!");
      break;
    case Draw:
      puts("It's a draw.");
      break;
    default:
      assert(false);
  }
}

int main(void) {
  Play((Game) { .myHand = Rock, .yourHand = Scissors });
  Play((Game) { .myHand = Scissors, .yourHand = Rock });
  Play((Game) { .myHand = Paper, .yourHand = Paper });

  return EXIT_SUCCESS;
}
実行結果
The winner is me!
The winner is you!
It's a draw.