【ワレコのコラム】正常終了した関数の戻り値はゼロで良いか?

この記事は約7分で読めます。
スポンサーリンク

どんなプログラミング言語でも、関数を実行すると戻り値を返す機能がある。

当記事では、正常終了した関数の戻り値はゼロで良いのかどうか考察した。

では、本題に入ろう。

スポンサーリンク
ワテ推薦のプログラミングスクールで学ぶ
スポンサーリンク
スポンサーリンク

戻り値を返す関数の例

例えば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
異常終了なら -1

こんな感じ。

戻り値がゼロと言うのは、何も問題無く無事に終わった状況を表現するのに適していると思う。

問題が起こらなかった訳だから、呼び出し元に不必要な情報を返す必要は無い訳なので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でも良いと思うが(ワテはこちらを良く使う)。

ステータスコード

後者のステータスコードなら、処理結果の詳細を伝達する目的に利用する。

必要なら自前のステータスコードを列挙型などで定義しておいて、

enum ReturnStatusEN
{
    success = 1,
    file_not_found = 2,
    disk_full = 3,
}
ReturnStatusEN func()
{
    ...
    return ReturnStatusEN.success;
}
Enum status = func();

こんな感じか。

処理結果に応じて複数の状態を呼び出し元に伝えたい場合に適している。

まとめ

この記事では、関数の戻り値に付いて考察した。

リターンコードとステイタスコードを区別して考えると良い。

両方を一緒くたにして扱うと訳分からなくなる。

ワテの場合には、リターンコードの場合には、関数の正常終了は0を返し、それ以外の場合は -1 を返すようにしている。

ステイタスコードの場合には、3値以上の状態を返したい場合が多いので、システムが持っている列挙型定数あるいは自前で定義した列挙型定数を返すようにしている。

以上、あくまでワテ流の方式ですので、業界の標準的な方式かどうかは未確認です。

プログラミングの本を読む

著者の「クジラ飛行机」って何やねん⁉

怪しいわ。

でも気になるから読んでみたい。

スポンサーリンク
ワテ推薦のプログラミングスクールで学ぶ
コメント募集

この記事に関して何か質問とか補足など有りましたら、このページ下部にあるコメント欄からお知らせ下さい。

C#C/C++
スポンサーリンク
warekoをフォローする
スポンサーリンク
われこ われこ

コメント