「PowerShellの関数を手続き的に書きたい(出力が戻り値に含まれてしまう問題)」を書いて約0.08年が経過しました

tyru.hatenablog.com

上記の記事を書いてから、あまり良いプラクティスとは思えなくなって、結局実際仕事で書いてるソースコードの方も上記の記事の書き方はやめてたりする。
なぜかと言うと出力を出さない事で、

  • ブロック内のデバッグが恐ろしくし辛い
  • ブロック内でエラー出ても出力されない

という非常にまずい書き方だというのが分かったため。
まぁ少し考えれば分かりますね…

代わりにどう書いてるかと言うと、関数内の出力はそのままで、「関数の終了コード」に当たるものは戻り値で受け取るのではなく「$global:LASTEXITCODE」を参照する事にした。
この方がややこしくないしずっと直感的ですね。

function func {
    ...
}

func
if ($global:LASTEXITCODE -ne 0) {
    # 例外処理
}

余談

ちなみにコマンドレットとそうでない外部コマンドとで、$LASTEXITCODEと$?が設定されるか否かが違うのって地味にハマりますね。

function func {
    ...
}

Move-Item $src $dest
if ($global:LASTEXITCODE -ne 0) {
    # $LASTEXITCODEはセットされないのでエラーが起きてもここには来ない。
}
if ($? -eq $False) {
    # $? を見るか、もしくはコマンドレットの引数に
    # -ErrorAction Stopを付けてtry catchで囲むかする必要がある。
}

表にするとこんな感じ。

種類 コマンドレット それ以外のコマンド
$LASTEXITCODE セットされない セットされる
$? セットされる セットされる

まぁシェルスクリプトにもシェルの内部コマンドだったりエイリアスだったり外部コマンドだったりあるのでPowerShellよりずっと複雑なように思えるけど、そんな事はなくて戻り値は一貫して$?で取れるので、困るのって上記の種類が違う同名のコマンドが定義されてた場合にどれが起動されるかでハマる位でその他ハマる点ってないんですよね。
なので正直PowerShellには戻り値のインターフェースぐらい統一してほしい感がありますね…