列挙型のすゝめ
まえがき
最近は芝浦祭で展示するゲームを製作しています。どうも、ウグイスです。
ゲームに関しては別でブログを書こうと思います。
かなり前のとある質問のやり取り
相談者「教本通りに書いたはずなのにUnityで実行したら動かないです」
私「ソースコードとか見せてもらっていいですか?」
送られてきたコードが以下(問題部分以外は省略してます)
public class PlayerController : MonoBehaviour
{
//いろいろ変数を宣言している
public static string gameState = "Playing";
void Start()
{
//いろいろ初期化している
gameState = "Playing";
}
void Update()
{
if(gameState != "playing")
{
return;
}
//以下省略
}
}
gameStateのやらかしに気付かず、しばらくの間、Debug.Logとかで調査
私「あ~(徒労)、これgameStateが悪さしてるのか」
という一幕がありました。
これがブログで列挙型のことを書かないといけないなと思った理由です。
列挙型について
まえがきの問題についてはあとで触れるとして、まずは列挙型について触れていきます
列挙型とは
列挙型は定数の有限集合です。
もうちょっとわかりやすく書くと、プログラマが設定した名前をそのまま要素として持つ有限集合です。(わかりにくい?)
言葉だけでは説明しにくいので、ちょっと具体例を出します。(言語はC)
#include <stdio.h>
typedef enum janken{
GUU,
CHOKI,
PAA
} JANKEN;
int main(void) {
JANKEN j = GUU;
if(j == GUU) printf("グーです\n");
if(j == CHOKI) printf("チョキです\n");
if(j == PAA) printf("パーです\n");
return 0;
}
雰囲気はわかってもらえたでしょうか?このプログラムを実行すると「グーです」が出力されます。
指示がない限り、列挙型の上に書いた定数から順番に0から数字が割り当てられます。
この場合、GUUは0、CHOKIは1、PAAは2になります。
つまりJANKEN j = GUU;の行をj = 0, j = 1, j = 2でも動くということです。(言語によっては不可)
列挙型のメリット
いろいろあるとは思いますが、全ての言語において共通するのは「ソースコードを見やすくする」だと思います。
コード内に意味のある文字列が混じるので、パッと見で何をするプログラムかが理解しやすくなります。
また言語仕様や開発環境にもよりますが、列挙型を型とみなして同じ型同士でしか代入できないとか、列挙型に算術演算子を適用しないようにするとか、IDE等の補完がきくとかのメリットがある場合もあります。
まえがきの問題について
まえがきのプログラムを見てください。
あからさまに書いてあるので、プログラムがなぜ動かないかはわかると思います。
まあ、Start関数内でgameStateに”Playing”を代入しているのに、Update関数内のif文では”playing”で比較しているからですね。
結果として、Update関数が速攻returnして終わっているわけです。(そりゃ動かん)
直接の原因はタイプミスです。
ですが、そもそもgameStateをstring型で管理しているのが遠因になっています。
stringで管理をすると、二つの問題があります。
- タイポの可能性がある
- タイポした時、コンパイルではエラーにならない。
つまりミスの可能性があり、そのミスに気付きにくいということです。
解決策
いくつか解決策はあります。
- bool型のisPlayingとか作ってそれで管理
- gameStateをint型で宣言する、マジックナンバー的実装
- 列挙型を用いて実装
まずは1番で直してみましょう
public class PlayerController : MonoBehaviour
{
//いろいろ変数を宣言している
public static bool isPlaying = true;
void Start()
{
//いろいろ初期化している
isPlaying = true;
}
void Update()
{
if(!isPlaying)
{
return;
}
//以下省略
}
}
まあいい感じ。gameStateが2種類だけならboolを使えば可読性は十分でしょう。
ですがgameStateの種類が増えると、その分だけbool型の変数が増えていってしまってわかりにくくなります。
gameStateが増えてもいい、2番のやり方で直してみましょう。
public class PlayerController : MonoBehaviour
{
//いろいろ変数を宣言している
//gameState 0=初期値 1=プレイ中 2=一時停止
public static int gameState = 0;
void Start()
{
//いろいろ初期化している
gameState = 1;
}
void Update()
{
switch(gameState)
{
case 0:
return;
case 1:
//プレイ中の処理
break;
case 2:
//一時停止の処理
break;
default:
//異常状態の処理
break;
}
//以下省略
}
}
まあこんな感じでしょうか?switch文を使ってみました。個人的にはifをつなげるくらいならswitch文の方が好みです。
コメントで変数に関する説明を付けています。ただ、あくまでint型の変数なので、想定していない数値が入る可能性がありますし、いちいちコメントを付けないとプログラムが何をやっているかが分かりにくいです。
まあいわゆるマジックナンバーな実装なので当たり前っちゃ当たり前ですが。
では3番で直してみましょう
public enum GameState
{
None,
Playing,
Pause
}
public class PlayerController : MonoBehaviour
{
//いろいろ変数を宣言している
public static GameState gameState = GameState.None;
void Start()
{
//いろいろ初期化している
gameState = GameState.Playing;
}
void Update()
{
switch(gameState)
{
case GameState.None:
return;
case GameState.Playing:
//プレイ中の処理
break;
case GameState.Pause:
//一時停止の処理
break;
}
//以下省略
}
}
どうでしょうか?コメントが無くても何となく、何をしているかがわかると思います。
ついでに、GameStateで型みたいになっているので、それ以外の値を取らないとかのメリットもあります。
今回は一つのコードブロックに収めたかったので一つのファイルに書いてる感じですが、列挙型の部分は別のファイルに切り出してもいいと思います。
読みやすいコードについて
ソースコードを書く際に、コメントは大事な要素ですが、真に大事なのはコードそのものが読みやすいことだとリーダブルコードにそんなニュアンスのことが書いていた気がします。
コメントが無くてもソースコードの意図が伝わるのが理想的なコードだと私は思います。(現実はなかなか難しいですが)
端的に言えば、コメントが書いてあるからソースコードそのものはわかりにくいものでもいい、というのはダメだということです。
ソースコードそのものが読みやすいということは、ロジックが分かりやすいということで、コードの修正も機能追加も楽にできるということなのでいいことです。
まとめ
- 列挙型は定数の有限集合
- 列挙型をうまく使えればソースコードが見やすくなる
- 環境や言語によるが、型保証やらなんやらで開発しやすくなる
つらつらとブログを書き連ねましたが、上の3つがこのブログで言いたかったことです。
特に2番目が大事です。
あとがき
列挙型でブログを書いてみました。
普段のブログは何かを作ってみたとか触ってみたとかが多いので、ちょっと趣向が違いますね。
ちゃんと読めるものになっていましたか?読めるものになっているといいなと思います。
Grafana+Influxのブログのpart2は多分もうちょいで書き上げれそうです。
他にもいろいろ下書きがあるので、年内に出せたらなと思います。
それではまた別のブログでお会いしましょう。
コメント入力