途中return禁止、goto禁止の時のdo~while(0)

2018-11-18
php%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E9%9B%91%E8%AB%87
    

目次

はじめに

細かいロジックが多い手続き型のプログラムを書こうと思った時のメモ。

1つのプログラム内において、いくつかの処理を行いそれらが失敗したらエラーを出力して終了するといったフローのプログラムを考えてみます。

※言語=PHPで、かつ、クラスとかは利用しないことを前提として話を進めます。

_log("START");

startProc();

if (!proc1()) {
    _log("ERROR1");
    endProc();
    _log("END");
    exit;
}
_log("SUCCESS1");

if (!proc2()) {
    _log("ERROR2");
    endProc();
    _log("END");
    exit;
}
_log("SUCCESS2");

if (!proc3()) {
    _log("ERROR3");
    endProc();
    _log("END");
    exit;
}
_log("SUCCESS3");

//...

endProc();
_log("END");
exit;

正しく実行されれば

START
SUCCESS1
SUCCESS2
SUCCESS3
END

func2でエラーが発生した場合は

START
SUCCESS1
ERROR2
END

このような出力を期待値とします。

構造化

このように書くと、エラー時の処理が見苦しくなります。

なので、 条件に引っかかったらreturnさせると言う感じで実装に変更してみます。

function main() {

    _log("START");

    startProc();

    $result = _sub();
	
    endProc();
	
    _log("END");

    return $result;
}

function _sub() {
    if (!proc1()) {
        _log("ERROR1");
        return false;
    }
    _log("SUCCESS1");
    
    if (!proc2()) {
        _log("ERROR2");
        return false;
    }
    _log("SUCCESS2");
    
    if (!proc3()) {
        _log("ERROR3");
        return false;
    }
    _log("SUCCESS3");

    return true;
}

個人的には問題ないと思っておりましたが、「関数の途中でreturnを書くな」と指摘されたことがあります。

いろいろ理由はあるそうですが、このケースは置いといて別のパターンを考えてみます。

参考

https://anopara.net/2014/06/27/do-not-write-deep-nested-code/

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1226287612

goto

こんな時こそgotoの出番ではないんでしょうか?

function main() {
    $result = true;

    _log("START");

    startProc();

    $result = proc1();
    if (!$result) {
        _log("ERROR1");
        goto finalize;
    }
    _log("SUCCESS1");
    
    $result = proc2();
    if (!$result) {
        _log("ERROR2");
        goto finalize;
    }
    _log("SUCCESS2");
    
    $result = proc3();
    if (!$result) {
        _log("ERROR3");
        goto finalize;
    }
    _log("SUCCESS3");
    goto finalize;

    finalize:

    endProc();

    _log("END");
    return $result;
}

キレイに書けました。

do~while(0)を使う

しかしながら、コーディング規約や組織のポリシー的に「とにかくgotoは使ってはいけない!」というケースがあると思います。

そんな時に見つけたテクニックがdo~while(0)を使うと言うもの。

初めて見たときは正直気持ち悪いなと思いましたが、ググると広く使われているテクニックのようでした。

それでも、なんか改めて見てみるとモヤモヤします。

ソースがこちら。

function main() {
    $result = true;

    _log("START");

    startProc();

    do {
        $result = proc1();
        if (!$result) {
            _log("ERROR1");
            break;
        }
        _log("SUCCESS1");
        
        $result = proc2();
        if (!$result) {
            _log("ERROR2");
            break;
        }
        _log("SUCCESS2");
        
        $result = proc3();
        if (!$result) {
            _log("ERROR3");
            break;
        }
        _log("SUCCESS3");

    } while(0);

    endProc();

    _log("END");
    return $result;
}

https://stackoverflow.com/questions/243967/do-you-consider-this-technique-bad

https://ja.stackoverflow.com/questions/1510/do-whilefalse%E3%81%AE%E5%88%A9%E7%82%B9%E3%81%AF%E4%BD%95%E3%81%A7%E3%81%99%E3%81%8B

http://php.net/manual/ja/control-structures.do.while.php

PHPは関数名の文字列を関数として実行できるので以下のように構造的に書くこともできました。

function main() {
    $result = true;

    $funcs = [
        "proc1" => [
            "error"   => "ERROR1",
            "success" => "SUCCESS1",
        ],
        "proc2" => [
            "error"   => "ERROR2",
            "success" => "SUCCESS2",
        ],
        "proc3" => [
            "error"   => "ERROR3",
            "success" => "SUCCESS3",
        ]
    ];

    _log("START");

    startProc();
    
    foreach ($funcs as $key => $value) {
        $result = $key();
        if (!$result) {
            _log($value["error"]);
            break;
        }
        _log($value["success"]);
    }

    endProc();

    _log("END");
}

try ~ catchを使う

ググると、try~catchの実装例も出てきます。 ただ、ロジック的なエラーなのか、例外的なエラーなのかでぐちゃぐちゃになってしまいそうなので、個人的には好きではありません。

function main() {
    $result = true;

    _log("START");

    startProc();

    try 
    {
        $result = proc1();
        if (!$result) {
            throw new Exception("ERROR1");
        }
        _log("SUCCESS1");
        
        $result = proc2();
        if (!$result) {
            throw new Exception("ERROR2");
        }
        _log("SUCCESS2");
        
        $result = proc3();
        if (!$result) {
            throw new Exception("ERROR3");
        }
        _log("SUCCESS3");
    } 
    catch(Exception $e) {
        _log($e->getMessage());
    } 
    finally {
        endProc();
        _log("END");
        return $result;
    }
}

その他

環境

PHP 7.1.19

    
s-yoshiki
s-yoshiki
githubtwitterqiita
Web作ってますが、インタラクティブなプログラミングも好きです。
JavaScript / Vue / node.js / PHP / AWS / OpenCV

関連記事

PostfixでメールリレーしてMailHogで受信する開発用Dockerコンテナの構築
環境 Dockerイメージ作成 コンテナの起動 telnetで送信テスト phpで送信テスト Postfixのリレーを介して送信されたメールをMailHog(開発用SMTPサーバ)でキャッチするDocker開発環境を構築した際のメモです。 環境 Docker…

php-fpmのステータスページを表示 Apache & htaccess
試した環境 php-fpm の pm.status_path について php-fpmのconfの設定 .htaccess の設定 アクセスしてみる 参考にしたサイト Apache環境で php-fpm のステータスページを htaccess…

JSで32ビット符号付き整数に対してのビット演算でハマった
具体例 参考にしたサイト JSでサブネットマスクの計算を行おうとしたとき、ビット演算でハマりました。その時のメモです。 JSでサブネットマスクの計算 JSでビット演算子を利用する場合 3…

JSでIPアドレスがサブネットマスクで指定した範囲内にあるか判定する
IPアドレスが指定した範囲内にあるかどうか判定 参考にしたサイト JSでIPアドレス(IPv4)が指定したサブネットの範囲に含まれるか判定するロジックを作った時の記録です。 IPアドレスが指定した範囲内にあるかどうか判定 処理としては、IP…

プログラムの数値計算で発生する誤差の種類 丸め誤差・打ち切り誤差・桁落ち
はじめに 誤差の種類 丸め誤差 打ち切り誤差 桁落ち 情報落ち 桁溢れ誤差 参考にしたサイト コンピュータで出てくる誤差はいくつかありますが、 それらをコードに落として整理しました。 はじめに 例えば の計算の答えは 0.6666666666…

JSでサブネットマスクの計算
JSによるサブネットマスク関連の計算 IPv4アドレス文字列をNumber型に変換する CIDR と サブネットの相互変換 ネットワークアドレス と ブロードキャストアドレス クラス 改めて計算方法を整理する 参考にさせていただいたサイト JSでIPv…

Homebrew で php7.4 + Xdebug をインストール
php7.4のインストール Xdebugのインストール php.ini に追記 参考にさせていただいたサイト phpunitのカバレッジを算出を行うためにMacにHomebrewでphp7.4をインストールしようとした際の記録です。 php7.…

PHP-FPM(php7.4) Apache2.4 on Ubutnu20.04 Webサーバ構築
環境 パッケージの更新 Apache と PHP のインストール Apache のサービスを開始する PHPファイルを作成 参考にしたサイト Ubuntu20.04 に PHP7.4 + Apache2.4 をインストールしてWeb…

PHP-FPM(php7.4) Apache2.4 でWebサーバ構築 on CentOS8
環境 php7.4 のインストール apacheのインストール php-fpmの設定を変更する php-fpm の起動 apacheの起動 確認 おまけ: エラーと解決方法 "System has not been booted with systemd as…

CentOS8 に PHP7.4 インストール
環境 普通にインストールしようとするとphp7.2がインストールされる modularityについて php7.4 インストール CentOS8 に modularity を利用して PHP7.4をインストールした際のメモです。 環境 CentOS8.…

最新の投稿

[JS]ラジアンから度数に度数からラジアンに変換する
コード 度数からラジアンへ ラジアンから度数へ サンプル ラジアンから度数に度数からラジアンに変換する際のスニペット。 コード 度数からラジアンへ ラジアンから度数へ サンプル

CentOS8 に Python + OpenCV をインストール
インストール テスト CentOS8 で標準で提供されているパッケージで Python + OpenCV 環境を構築する方法です。 検証した環境は CentOS8.3 (Docker) です。 インストール まず opencv…

[Perl] CentOS8 に plenv をインストール
インストール Step1 事前準備 Step2 PATHを通す (README通りにインストール) Step2 PATHを通す ($HOME以外にplenvをインストール) Step3 Perlインストール Step4 cpanmインストール CentOS…

JS/TSのclassでclass名を取得する
コード JS/TSのconstructorを利用して自分自身のクラス名を取得する際のメモ。 コード このコードの結果は次のようになります。

CentOS6(Docker)でyum update できなくなった
エラー内容 対応 CentOS6.10 で yum update しようとしたところエラーが出てアップデートできなかったので対応した時の記録 エラー内容 以下のようなエラーが出ました。 対応 を以下のように変更したところ解決しました。

PostfixでメールリレーしてMailHogで受信する開発用Dockerコンテナの構築
環境 Dockerイメージ作成 コンテナの起動 telnetで送信テスト phpで送信テスト Postfixのリレーを介して送信されたメールをMailHog(開発用SMTPサーバ)でキャッチするDocker開発環境を構築した際のメモです。 環境 Docker…

GitLab.com のコンテナレジストリで1つのプロジェクトに複数のDockerイメージをpushする
手順 GitLab.com のコンテナレジストリで1つのプロジェクトに複数のDockerイメージをpushする方法についてのメモです。 手順 まず、gitlab.comにて適当なリポジトリを…

Python poetryでパッケージ開発 PyPIで公開 Pytestでテスト CIをGitHub Actionsで回す
Poetry でパッケージ開発 pytest でユニットテストを実施しカバレッジを算出する パッケージをビルドし PyPI で公開する 検証環境にデプロイする 本番環境にデプロイする GitHub Actions で CI を回す codecovの設定 GitHub…

Perlでconstant(定数)をhashのキーに使う
ハマった事象 解決方法 1 括弧をつける 2 & をつける 参考にしたサイト Perlでconstant(定数)をhash…

php-fpmのステータスページを表示 Apache & htaccess
試した環境 php-fpm の pm.status_path について php-fpmのconfの設定 .htaccess の設定 アクセスしてみる 参考にしたサイト Apache環境で php-fpm のステータスページを htaccess…

Tags

Dates

© 2021   404 motivation not found