構造化バッチファイル　～読みやすさ重視～

読みくくなりがちなバッチファイルのコードを可能な限り読みやすくしましょう。という~~ネタ~~提言です。
####1. べた書き
例として以下のコードを使用します。

```bat:Sum.bat
@echo off
set i=0
set /p m="自然数を入力 >"
set sum=0
:loop
set /a n=%i%*%i%
set /a sum+=%n%
set /a i+=1
if %i% leq %m% goto loop
echo %sum%
pause
exit
```

　これは1つの自然数mをコンソールから読み込んで、0からmまでの自然数の2乗の総和を求めるプログラムです。
　最近のプログラミング言語を普段使っている人にとってはこの程度のものでも**読みにくい**と感じるでしょう。私もそう思います。

####2. 処理ごとに分けてみた
まず、処理のまとまりがよく分かりません。このプログラムにおいて主な処理は、

- 初期化、入力の受付と出力
- 2乗の計算
- 総和の計算

に分けられるはずです。
分けてみたのが下のものです。

```bat:Sum.bat
@echo off
set /p m="自然数を入力 >"
call :calc %m%
set sum=%errorlevel%
echo %sum%
pause
exit

:calc
set i=0
:loop
call :square %i%
set /a sum+=%errorlevel%
set /a i+=1
if %i% leq %1 goto loop
exit /b %sum%

:square
set /a n=%1*%1
exit /b %n%
```

少し構造が見えてきたのではないでしょうか。

　バッチファイルでは`call :ラベル名 引数1 引数2 ... `というように**ラベルからexit /bまで**の処理をサブルーチンのように呼び出すことができます。ルーチン内で引数にアクセスするときは、指定した引数の順番に`%1 %2 ... %9`を使用します。(`%9`までの9つしか見ることができませんが、`shift`コマンドを使うことによって10個以上の引数でもアクセス可能です。)
　callで呼ばれた先で`exit /b 戻り値`とすることで、`errorlevel`環境変数に戻り値がセットされます。(戻り値は数値しか取れません。)
　基本上から下にしか進まないバッチファイルでもこのように**構造化**していくことで読みやすくなり、またコードの再利用も容易になります。

####3. インデント
　しかし、まだまだ読みにくいでしょう。なにしろインデントなしですからね。
なので、インデントを付けます。

```bat:Sum.bat
@echo off
set /p m="自然数を入力 >"
call :calc %m%
set sum=%errorlevel%
echo %sum%
pause
exit

:calc
  set i=0
  :loop
    call :square %i%
    set /a sum+=%errorlevel%
    set /a i+=1
    if %i% leq %1 (
      goto loop
    )
exit /b %sum%

:square
  set /a n=%1*%1
exit /b %n%
```

　バッチファイルでのインデントの目安は、ラベルや制御文(if, for)で1段階上げ、exitで戻すのがよいでしょう。
####4. gotoループの書き方を改善
　結構読みやすくなってきましたが、まだ分かりにくい所はどこだろうと探しますと、やはりgoto文でループしているところがわかりにくいということになりそうです。
　バッチファイルにもfor文はあるので使える場合は**for文を使うことをお勧めします。**しかし、バッチファイルは様々な部分で制約がありますので、goto文によるループを必要とする場面も出ると思います。そこで、goto文を使ってもそこまで読みにくくならないように改善してみます。

```bat:Sum.bat
@echo off
set /p m="自然数を入力 >"
call :calc %m%
set sum=%errorlevel%
echo %sum%
pause
exit

:calc
  set i=0
  :loop
    if %i% gtr %1 (
      exit /b %sum%
    )
    call :square %i%
    set /a sum+=%errorlevel%
    set /a i+=1
  goto loop
echo エラー
exit

:square
  set /a n=%1*%1
exit /b %n%
```

　if文を継続条件の記述ではなく**終了条件の記述**に変更し、位置をラベルの直後に移動しました。これで、:loopラベルのすぐ下を見ればどういうループなのか分かるようになりました。また、:loopラベルの最後に付ける`goto loop`のインデントレベルを1つ戻すことで、あたかもコードブロックを作っているかのような自然な記述にすることができました。

####5. サブルーチンに引数・戻り値アノテーションを追加
　まだまだ終わりではありません。このプログラムではラベル名をサブルーチンの目印に使っています。しかし一般的なプログラミング言語のように、**引数をいくつ取ってどんな値を返すのか**を指定することができません。これはバッチファイルの仕様上仕方のないことですが、せめて後から見返した時にすぐ理解できるようにすることはできないでしょうか。

　そこで`::`の使用を提案します。バッチファイルでは`::`の後は行の終わりまでコメントとして扱われます。これは当然ラベルの後ろにも付けられるので、これをアノテーションとして使います。

```bat:Sum.bat
@echo off
set /p m="自然数を入力 >"
call :calc %m%
set sum=%errorlevel%
echo %sum%
pause
exit

:calc::int m -> int
  set i=0
  :loop
    if %i% gtr %1 (
      exit /b %sum%
    )
    call :square %i%
    set /a sum+=%errorlevel%
    set /a i+=1
  goto loop
echo エラー
exit

:square::int n -> int
  set /a n=%1*%1
exit /b %n%
```
　これはあくまでコメントですので、**従わなくても警告も何も出ません。**しかしこうしておけばサブルーチンがどんな引数をいくつ必要とし、どんな値を返すのか一目でわかります。しかも、`::`以降はコメントですから、**呼び出しは`:ラベル名`のみでOK**です。
####6. :loopラベルにもアノテーション
　そして、これができるということは、`:loop`ラベルにも同じことができるということです。

```bat:Sum.bat
@echo off
set /p m="自然数を入力 >"
call :calc %m%
set sum=%errorlevel%
echo %sum%
pause
exit

:calc::int m -> int
  set i=0
  :loop::i in 0:1:m
    if %i% gtr %1 (
      exit /b %sum%
    )
    call :square %i%
    set /a sum+=%errorlevel%
    set /a i+=1
  goto loop
echo エラー
exit

:square::int n -> int
  set /a n=%1*%1
exit /b %n%
```
　書き方はわかれば何でもいいです。もっとわかりやすい書き方があれば教えてください。
　こんな感じで、例えバッチファイルであろうともそれなりに現代的な書き方ができることをつい最近**思いついた**ので勢いのまま記事にしました。
####7. 自身の過去記事のプログラムを改善してみた
　私が過去[この記事](http://qiita.com/fvh_P/items/8774e854729cb433f87b)で書いた`example.bat`を上記の記法に倣いつつ、新たにいくつかの新たな記法を導入して書き直したものが以下になります。

```bat:example.bat
@echo off
:Main
rem 入力が適切な数値であるか検証し、初項A、末項L、公差Dとして等差数列の総和を計算し表示。
setlocal
  echo 初項がA、末項がL、公差がDの等差数列の総和を求めます。
  echo AとDで定まる、L以下で最大の項を末項として扱います。
  call :GetALD A L D
  call :Verify %A% %L% %D%
  if %errorlevel% == 1 (
    echo 入力された数値が正常ではありません。何かキーを押すと終了します。
    pause >nul
    exit
  )
  call :GetSum %A% %L% %D%
  echo 初項%A%、末項%L%、公差%D%の等差数列の総和：%errorlevel%
  echo 何かキーを押すと終了します。
  pause >nul
exit

:GetALD::ref, ref, ref -> void
rem A,L,Dの取得
  set /p %1="1以上の整数値で初項を入力してEnter >"
  set /p %2="1以上の整数値で末項を入力してEnter >"
  set /p %3="0以外の整数値で公差を入力してEnter >"
exit /b

:Verify::int A, int L, int D -> bool
rem 入力値が正しいかどうか判別。
rem A,Lが1以上かどうかを検証。
setlocal
  set A=%1
  set L=%2
  set D=%3
  if %A% lss 1 (
    exit /b 1
  ) else if %L% lss 1 (
    exit /b 1
  ) else if %D% lss 0 (
    exit /b 1
  )
rem 初項末項の大小関係と公差の値が正しいか検証。
  if %A% lss %L% if %D% leq 0 (
    exit /b 1
  )
  if %A% gtr %L% if %D% geq 0 (
    exit /b 1
  )
exit /b 0

:GetSum::int A, int L, int D -> int
rem 総和を計算
setlocal
  set A=%1
  set L=%2
  set D=%3
  set sum=0
  for /l %%i in (%A%,%D%,%L%) do (
    set /a sum+=%%i
  )
exit /b %sum%
```

　`Main`ラベルについては特にエントリポイントなどの機能はありませんが、主処理がとこからどこまでかを示すには使えそうです。(バッチファイルは何も指示がない限り完全に上から下に実行します。ラベルで区切ったサブルーチンの中にも勝手に入ってしまうので、主処理群は基本的に一番上に記述してください。)

　`Main`、`Verify`、`GetSum`の各ラベルの下にある`setlocal`は環境変数のスコープを作るコマンドです。スコープの有効範囲は`endlocal`か`exit`に到達するまでです。つまりこれらのサブルーチンでは、`setlocal`以下で`set`された環境変数についてはそのサブルーチン内で有効なローカル変数になります。

　`GetALD`では引数アノテーションに`ref, ref, ref`と書きましたが、これは**環境変数名**を引数に取ってくださいという意味です。（使い方がreferenceつまり参照に近いと思ったのでrefを使いましたがもっと的確な表現がありましたらコメントください。)
環境変数名を引数に取り、その環境変数に対してそれぞれ入力を受け付けて代入します。上のプログラムでは、
`set /p %1="1以上の整数値で初項を入力してEnter >"`
は`%1`(引数の1番目)に`A`が入っているので、
`set /p A="1以上の整数値で初項を入力してEnter >"`
として動作します。また、このサブルーチンに対して`setlocal`を指定してしまうと、このサブルーチンで代入した環境変数が呼び出し元に引き継がれないため、何も指定せずスコープは呼び出し元と結合しています。

####8. まとめ ~これどこで使うの？~
　どうでしょう、それなりによく使われるプログラミング言語の記述にかなり近づいたように見えないでしょうか。まぁ、バッチファイルでこのような記述をすると正直コードが長くなるので、進んでやる人は少ないと思います~~(というより、わざわざバッチファイルなんかで書かないよね)~~
　バッチファイルでゲーム制作とか苦行がしたい(特に複数人で)という方はこのような書き方で可読性を大幅に向上させて圧倒的成長:muscle::muscle::muscle:が期待できるでしょう。（本当か？）