開発ツールのないWindowsでの極限環境プログラミング

#対象読者
皆様は
_何らかの理由で開発を行わなければならないが、開発環境は自由に入れられないしもちろんOSはWindows_
という環境に追い込まれたことはあるでしょうか。

はっきりいってそんな環境で開発などやりたくありませんが、セキュリティの制限でアプリのダウンロードが許可されておらず、上記のような環境に追い込まれることはよくあります。
……ありますよね？

とにかく、そんな貧弱な環境でもアプリの開発を行えるよう、備忘録もかねて対策を書きます。
#前提
極限環境にもいろいろあると思いますが、本記事はとりあえず以下のような環境での開発を想定しています。

- インターネットからのプログラムやライブラリのダウンロードは禁止
- PCのセキュリティ制限は自由に解除できない（Windowsの開発者モードなどは利用不可）
- OSはWindows　(７~１０あたりを想定)
- ページによっては、有害サイトフィルタで弾かれる
- ブラウザは最新版に更新してもらえるが、原則としてIE11をサポートしなければならない
- 納期はそこそこ厳しい
- 開発メンバーは自分だけ
- OSに標準インストールされているようなアプリは利用できる

※ごめんなさい、若干盛りました。私もここまでひどい環境では開発していません。ただ、この記事では上記の条件を想定します。
※この記事に掲載される手法は、やむを得ない場合にのみ利用してください。通常の開発でマネをすると開発効率やメンテナンス性を損ないます。

#極限環境で利用可能な言語
極限環境下でも利用可能な言語として以下があります。

- ☆バッチ
- ☆HTML+JavaScript (IE や Edge ブラウザを利用した fileプロトコルでの クライアントサイドのみ)
- ☆C# (ただしバージョンは古い)
- Visual Basic
- VBScript (Windows Script Host上で動作させる)
- JScript
- PowerShell

先頭に☆を付けた言語が、私がお勧めする言語になります。

まず、**バッチ**は手軽に利用できるため、極限環境で最も利用する言語といってよいでしょう。

**JavaScript** については、ランタイムがブラウザでローカルファイルへの書き込みができなかったりと制限こそ厳しいですが、やはり手軽に利用でき、何よりブラウザの開発者ツールを利用すれば、コードにブレークポイントを設定したり、ステップ実行したり、変数をウォッチしたりと、まともな開発環境で開発している気分を味わえます。しかもCDNにリンクすれば外部ファイルはダウンロードしていない（ように見える）という屁理屈が通る場合もあります。

どうしても高度なことをする必要がでた場合には、**C#** を利用するとよいでしょう。
組み込みの機能が豊富なため、ライブラリを利用できない極限環境プログラミングでは非常に重宝します。しかも、[microsoft.comのリファレンスページ](https://docs.microsoft.com)は有害サイトフィルタに弾かれにくいため、極限環境下でもリファレンスが読める場合があります。
ただし、.netのバージョンだけでなく、コンパイラのバージョンまで気にしなくてはならないのが玉に瑕です。
コンパイルには [コマンドラインコンパイラ csc.exe で C# コードをコンパイルする](https://qiita.com/yuki451/items/c9c6a2b79ce96ae0d252)が参考になります。
また、Windows7 以降ではC# や Visual Basic のためのビルドツールである、MSBuild が標準で利用できます。
こちら→[MSBuildで始めるWPF(C#、VB)](https://qiita.com/yosgspec/items/edb3f22741ea362cea1a)の記事などを参考にしてください。

#やりながら理解する極限環境プログラミング
以降では、簡単なツール開発を例に、極限環境プログラミングの流れを追っていきます。

##作成するアプリ概要
送られてきたメールに対する返信の候補を返す、以下のようなツールを作成します。
![screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/48164/20ed50ca-dce1-33a8-f36b-c35511467852.png)


##ツールの設計
作成するツールの設計はこんな感じ。

```text
 mail-support.html : ツールの基本画面。
 ↓
 ・mail-support.js   : 与えられたメールに対して返信文案を返す関数を公開する。
   mail-support.js-translate(input) => body() と footer() の結果を結合した返信を作成する。
   ↓
   ・mail-support.js-body(input) => 推奨するメール本文を返す
   ・mail-support.js-footer(input) => 推奨するメールフッターを返す
```

設計のやり方は、通常の開発でも極限環境でもそんなに変わりませんね。
強いて違いを挙げるなら、極限環境では細部にこだわらずざっくりやった方が良いということでしょうか。
どうせ大規模アプリなんて作れないので、脳内設計図でも管理できなくなることはまずありません。

## エディタの準備
プログラムの開発にはエディタが必須ですが、極限環境では贅沢を言えません。

最悪の場合はメモ帳での開発となります。

ただ、ローカルにエディタを落とすことが許されていなくても、ブラウザ上で動作するエディタが利用できる場合があります。
コードハイライトや自動インデントなど、メモ帳よりはずっと効率が良いものがあるので、可能ならこれらを利用しましょう。

例えば[JDOODLEのサイト](https://www.jdoodle.com/)など、便利なものがいくつかあります。

ウェブページの開発を行う際など、文字コードがShift_JIS ではまずい場合があります。最近のメモ帳は文字コードを指定して保存することができますが、それができない古い時代のOSの場合は、以下のように文字コード変換ツールを作成するとよいでしょう。

```cs:ChangeEncode.cs
using System;
using System.IO;

class ChangeEncode
{
    static void Main()
    {
        var res = "";
        var input = "";
        while((input=Console.In.ReadLine())!=null){
            res += input + "\n";
        }
        using(var output = Console.OpenStandardOutput()){
            var bytes = System.Text.Encoding.UTF8.GetBytes(res);
            output.Write(bytes, 0, bytes.Length);
        }
    }
}
```
C#コードのコンパイルは、[コマンドラインコンパイラ csc.exe で C# コードをコンパイルする](https://qiita.com/yuki451/items/c9c6a2b79ce96ae0d252) の記事を参考に、OS付属のCSCコンパイラを利用します。

CSCコンパイラは、エクスプローラの検索機能で、csc.exe を検索するとよいでしょう。
あとは、 `{コンパイラのパス}\csc.exe ChangeEncode.cs`とすればコンパイルが実行できます。

##テストの作成
極限環境では、テスティングフレームワークを自分で用意する必要があります。

今回はHTML+JavaScript でツールを作成するので、テストツールもJavaScript で記述しましょう。

ここで重要なのは、**こだわりすぎない** ことです。
極限環境プログラミングでは、エディタのサポートが受けられないため、コーディングには非常に多くの時間を要します。そのため、細部にはこだわらず、とにかく必要最低限の機能の実装を目指しましょう。

例えば、こんな感じで実装します。

```javascript:tools/test.js
/*
 * Utility for Unit-Testing.
 * Call 'describe' and 'it' (Note that the 'describe' cannot nest).
 * At the end of the define tests, call mountTestResults(element);
 */
(function(ns){
    'use strict';
    const describes = [];
    const its = [];
    ns.describe = function describe(name, callback){
        its.length = 0;
        callback();
        describes.push({ name: name, its: its.slice() });
    }
    ns.it = function it(name, testMethod){
        let error = null;
        try{ testMethod() }
        catch(e){ error = e; }
        its.push({name: name, result: error, status: error?'fail':'ok  '});
    }
    function element(tag, html, classes){
        const res = document.createElement(tag);
        res.innerHTML = html || '';
        [].slice.call(arguments, 2)
            .forEach(function(c){ res.classList.add(c) });
        return res;
    }
    function wrap(parent, child){
        parent.appendChild(child);
        return parent;
    }
    function itResToNode(itRes){
        return [
            element('span', itRes.status),
            element('span', itRes.name  ),
            element('span', itRes.result || '')
        ].reduce(
            function(root, el){ root.appendChild(el); return root; },
            element('li', null,'test-'+itRes.status.trim())
        );
    }
    ns.mountTestResults = function moutTestResults(el){
        describes.map(function(descRes){
            const ul = element('ul');
            descRes.its
                .map(itResToNode)
                .forEach(function(el){ ul.appendChild(el) });
            return wrap(element('div', descRes.name, 'test-describe'), ul);
        }).forEach(function(e){ el.appendChild(e) } );
    }
})(this);
```

グローバル汚染？　`describe`をネストできない？　そんなことは知ったこっちゃありません。
欲をかくと肝心の本体が完成しなくなるので、このくらいの簡単な設計がいいでしょう。

これで、以下のようにテストを書けるようになりました。

```javascript:src/hello.test.js
describe('Hello World', function(){
    it('Fail Test', function(){ throw new Error('Fail Assertion'); });
    it('Pass Test', function(){ });
});
describe('Kitty on your lap', function(){
    it('Cute!!', function(){});
});
```

```html:runtest.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <script src="./tools/test.js"></script>
        <script src="./src/hello.test.js"></script>
        <script>
            document.addEventListener('DOMContentLoaded', function(){
                mountTestResults(document.getElementById('test-result'));
            });
        </script>
    </head>
    <body>
        <div id="test-result"></div>
    </body>
</html>
```
見づらければ、スタイルを適当に設定します。

---

テストツールも完成したので、テスト本体の実装を行いましょう。

Browserify などでトランスパイルができないことを忘れてはいけません。
テスト実行の際は、対応しなければいけない環境の中で最も機能の少ないものを利用します。

セキュリティが厳しい環境でも、互換性の関係で IE が生き残っている場合が多いので、今回は IE11 をサポートする体で作成していきます。

開発は、テストファーストで行うのが良いでしょう（自作のテストツールを利用する場合、テストツール自体にバグがある可能性があるため）。

先に合格しないテストを記述して確認することで、テストツール自体の検証にもなります。

```javascript:src/mail-support.test.js
describe('mail-support.js-body() のテスト', function(){
    const body = MAIL_SUPPORT.body;
    it('よくわからないメールはお茶を濁す', function(){
        const res = body('hogefuga');
        if(res!=='検討いたします。') throw new Error('Expected "検討いたします" but was "'+res+'"');
    });
    it('バグがあっても素知らぬふり', function(){
        const res = body('uiui611 さま、以下のバグの修正をお願いいたします。');
        if(res!=='仕様です。') throw new Error('Expected "仕様です。" but was "'+res+'"');
    });
});
```

完成したら、先ほど作成したテスト実行用のHTMLファイル (runtest.html) で読み込むようにしましょう。
全てのテストが不合格ならOKです。

##バージョン管理
バージョン管理などという贅沢は言えませんが、バックアップくらいはないと不安で変更をためらってしまうことがあります。
不安な時は、適当にバックアップ用のバッチを記述しておきましょう。

```bat:backup.bat
@echo off
robocopy /S src backup
```
などとしておけば十分でしょう。
複数バージョン保存しておきたい場合は、日付時刻をつけたフォルダに保存するなどしてください。
[こちらの記事](https://qiita.com/na0AaooQ/items/c26b637261d9962f981f)が参考になります。

##本体の実装
では、いよいよツール本体の実装に移ります。

注意点はテスト作成のときといっしょで、**こだわりすぎないこと**、**トランスパイルできないことを忘れないこと** の二つだけです。


```javascript:src/mail-support.js
﻿var MAIL_SUPPORT = MAIL_SUPPORT || {};
(function(ns){
    'use strict';
    ns.body = function body(input){
        if(/(修正|バグ|不具合)/.test(input)) return '仕様です。';
        else return '検討いたします。';
    }
    ns.footer = function footer(input){
        return '/* @uiui611 */';
    }
    ns.translate = function translate(input){
        return [this.body, this.footer]
            .map(function(callback){ return callback(input) })
            .join('\n\n');
    }
})(MAIL_SUPPORT);
```

こちらもテスト実行用の HTMLファイル (runtest.html) で読み込むようにし、テストの合格を確認します。

あとは適当にツールの起動ページとなる HTML を作成し、最後に動作確認を行ってください。
一人で開発しているということは、バグがあったときに全責任を負わされるリスクがあるので、デバッグは慎重に行いましょう。運用実績の無いテストツールを利用しているので、手動でのテストもしっかり行います。
注意する項目はこのあたりです。

- [ ] 対象のブラウザ全てで動くか
- [ ] 自動テストはすべて合格しているか
- [ ] バックアップはとったか

普段ツールなどで自動処理していたり、バージョン管理システムに頼っている作業は、極限環境では忘れがちです。気を付けてください。


以上で開発は終了です。お疲れさまでした。

#最後に
小細工を弄するより先に、開発環境改善の交渉をしましょう。