答え
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) {
……
}
enum
とstruct
の書き方を統一したので、ここは次のようになります。
Result result_of(Game game) { /* ← typedef のおかげで短くなった! */
……
}
void Play(Game game) { /* ← こちらも! */
……
}
typedef
のおかげで記述量が減ってスッキリしましたが、まだ関数名の付け方がバラバラですね。
ここではアンダースコア(
_
)を使わず、大文字で始まる書き方に統一!
Result ResultOf(Game game) {
……
}
void Play(Game game) {
……
}
統一感が出て、かなり読みやすくなったのではないでしょうか。
あとは、enum
やstruct
の名前が変わった部分を全体に反映させていけば、修正完了です。
ここがポイント!
プログラムは読みやすく書いたほうが、考えもまとまりやすくなる!
ちなみに……「
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.