どんなプログラミング言語でも、関数を実行すると戻り値を返す機能がある。
当記事では、正常終了した関数の戻り値はゼロで良いのかどうか考察した。
では、本題に入ろう。
戻り値を返す関数の例
例えばMicrosoftのC#の場合なら、
class Program { static void Main(string[] args) { int i = 10; int ii = func(i); Console.WriteLine("i={0} の二乗は {1} です。", i, ii); } public static int func(int i) { return i * i; } }
こんな感じになる。
関数func(int i)は与えられた引数iの二乗を求める処理なので、戻り値は計算結果を返せばよい。
なのでこういう数値計算の場合には、戻り値を何にすべきかなどで悩む必要は無いのだ。
計算結果を返さない関数の戻り値は?
処理結果のデータを戻り値で返さなくても良い状況でも、処理が上手く行ったのかどうかを知りたいので、それを戻り値で返す事は良くやるだろう。
func()の中で何らかの処理をしてその処理結果に応じて
- 正常終了
- 異常終了(何らかのエラーがあった)
を返すなど。
この時にリターン文で何を返すか?
昔のワテは良く迷っていた。
具体的に言うと、
return 0; return true; あるいは return 1; などの非0値
などである。
どっちにすべきか?
こればっかりは好き好きなのかもしれないが、ワテの場合は、正常終了の意味で整数値を返すなら、0を返すようにしている。
こんな感じ。
戻り値がゼロと言うのは、何も問題無く無事に終わった状況を表現するのに適していると思う。
問題が起こらなかった訳だから、呼び出し元に不必要な情報を返す必要は無い訳なので0が相応しいと思う。
「なにも足さない。なにも引かない。」みたいな昔のウイスキーのCMみたいなもんか?
ちょっと違うか。
もしそれを逆にして、正常終了を 1 などで表現するとしたら、では 2 とは何が違うのか?みたいな変な疑問も湧くし。
なので、何も問題が無く正常終了したならそれは0で良いだろう(ワテの意見)。
bool型戻り値は真偽判定関数の戻り値に使うのが良い
一方、正常終了/異常終了の区別にtrue/falseのbool型を返すとややこしいのでお勧めしない。
bool型はあくまで、真偽を判定する関数の戻り値に使うのが良いと思う。
こんな感じか。
static void Main(string[] args) { bool b1 = IsStringUppercase("abCD\r\nefg"); Console.WriteLine("大文字判定結果:{0}", b1); // False bool b2 = IsStringUppercase("ABCD\r\nEFG"); Console.WriteLine("大文字判定結果:{0}", b2); // True } public static bool IsStringUppercase(string str) { for (int i = 0; i < str.Length; i++) { if (Char.IsLetter(str[i])) // \r や \n などの文字はスキップする { if (Char.IsUpper(str[i])) continue; else return false; } } return true; }
コード MicrosoftのC#で文字列が大文字のみかどうかを判定する関数
複数の状態を戻り値で返したい場合
さて、実際にプログラミングをしているとこれらの例以外の状況として、関数func()の処理結果として複数の状況が想定されて、呼び出し元に帰す値も0,1などの二通りではなくて3通り以上の状態を返したい場合もある。
例えば、ファイルにデータを書き込む処理をする場合なら、
正常終了した場合と、エラーして書き込みできなかった場合に分かれるが、エラーの詳細な状況を返したい場合などだ。
- ファイルのオープンで失敗した(他のアプリが開いている)
- ファイルのオープンで失敗した(書き込み権限がない)
- ファイルを書き込み中に失敗した(HDDが一杯になった)
- ファイルの書き込み中に失敗した(ファイルサイズが大きすぎてOSの制限に違反)
などかな。
そういう時には列挙型を使って必要な数だけ要素を定義すれば良いが、あまり多くの変数や定数を自分で管理するのは面倒なので、こういう場合なら.NET Frameworkの持っている例外Exceptionを返すのも良いと思う。
File.Open メソッドに失敗すると以下の例外が発生する。
- ArgumentException
- ArgumentNullException
- PathTooLongException
- DirectoryNotFoundException
- IOException
- UnauthorizedAccessException
- ArgumentOutOfRangeException
- FileNotFoundException
- NotSupportedException
自分でヘンテコな戻り値を列挙型で作るよりもこういうのを利用するほうが何かと便利だと思う。
というように、現在のワテは関数の戻り値をこんなふうにやっているのだが、そもそも昔のワテが混乱していた理由は何か?
リターンコードとステータスコード
つまりその、多くの人が混乱するのは(ワテもだが)関数の戻り値として、
- リターンコード (return code)
- ステータスコード(status code)とかエラーコード (error code)
の二種類を区別して理解すると良いと思う。
リターンコード
前者のリターンコードなら、関数の処理結果に応じて
int rc = func(); // rc=0:正常終了、rc=1:異常終了
と言う感じ。
成功か失敗かのどちらかの状態しかない。
0/1なので分かり易い。
0/-1でも良いと思うが(ワテはこちらを良く使う)。
つまりまあUNIX系のコマンドやシェルスクリプトと同じ流儀をワテも採用しているのだ。
UNIX/Linuxの場合には各コマンドの実行結果(=リターンコード、rc)は一般的には、
- コマンド成功時:0
- コマンド失敗時:1(あるいは1以外の場合もある。)
がリターンされる。
ちなみにワテの場合は、昔はLinuxでgccコンパイラをコンパイルして良く使っていた。Pentiumの頃なので、コンパイルするのに何時間も掛かったものだ。
今なら無料で使えるVisual Studioがお勧めだ。手軽に使えるから。
ステータスコード
後者のステータスコードなら、処理結果の詳細を伝達する目的に利用する。
必要なら自前のステータスコードを列挙型などで定義しておいて、
enum ReturnStatusEN { success = 1, file_not_found = 2, disk_full = 3, } ReturnStatusEN func() { ... return ReturnStatusEN.success; } Enum status = func();
こんな感じか。
ステータスコードは処理結果に応じて複数の状態を呼び出し元に伝えたい場合に適している。
まとめ
この記事では、関数の戻り値に付いて考察した。
リターンコードとステイタスコードを区別して考えると良い。
両方を一緒くたにして扱うと訳分からなくなる。
結論としてはワテの場合には、リターンコードの場合は関数の正常終了は0を返し、それ以外の場合は -1 を返すようにしている。
一方、ステイタスコードの場合には3種類以上の状態を返したい場合が多いので、システムが持っている列挙型定数あるいは自前で定義した列挙型定数を返すようにしている。
ただし、列挙型定数を自前で定義するのは極力避ける。ヘンテコな列挙型定数をドンドン増やすと訳分からなくなるからだ。なので出来る限りシステムの列挙型定数を使う。
以上、あくまでワテ流の方式ですので、業界の標準的な方式かどうかは未確認です。
プログラミングの本を読む
著者の「クジラ飛行机」って何やねん⁉
怪しいわ。
でも気になるから読んでみたい。
コメント