ディスプレイアームを導入した

うちの環境は、今は、

という、Dual 4K の環境です。
ビデオカードや、その他に関しては、以前に書いた、この記事から変わってません。

espresso3389.hatenablog.com

まぁ、LGの4K液晶が安かったので、とりあえず買ってみたら、すごく良かったので、今度は、本命と言われてる、EIZOのEV3237を買ってみるぜっていうノリだったんですが、まぁ、素晴らしい環境ですね。
27MU67-Bに関しては操作系(入力切替など)で不満はあったり、どうせなら、EV3237 2台っていう環境にしたかったりというのはあるんですが、それでも普段使っていて著しく不満を感じるということはありません。

で、この環境は素晴らしいんですが、問題としては、どちらの液晶も、スタンドが意外と大きいんですね。まぁ、スペースが邪魔。
そして、手元のスペースを確保しようとすると、当然、ディスプレイとの距離はそれなりにとる必要がある。
僕の家の机は、横180cm x 縦70cmなので、それなりの広さはあるのですが、さすがにこのサイズの液晶を2台も置くと狭くなります。

ということで、ディスプレイアームを導入しようということを思い立ちました。

エルゴトロン

ディスプレイアームといえば、エルゴトロンというぐらい、エルゴトロン以外は無視してもいいぐらいなんですが、まぁ、エルゴトロン純正品は高いですね。一台用のアームでも、15000円はくだらないですし、デュアルアームだと、30000円を超えます。
買えない額ではないですが、一瞬躊躇する額ではあります。

で、デュアルディスプレイ環境なので、デュアルアームの、45-248-026とか、45-245-026、いずれにしても、こいつらは、31.5インチの液晶には対応できないことになっていました。

なので、結果的には、シングルアームを2台置くという結論になるんですが、Amazonググる感じでは、45-241-026には、OEM品がいくつかあることに気づきました。

具体的には、Amazonベーシック シングルディスプレイマウントアームヒューレット・パッカード HP シングルモニターアーム BT861AAの2つ。

ただ、Amazonの方は、意外と安くない。純正品と比べても2000円ぐらいしか変わりません。それから比べるとHPの奴は5000円ぐらい安くて非常にお手頃感がある。

ということで、HPの奴を2台購入してみました。

設置

一人でやったら、クソほど大変でした。クーラーガンガン聞かせた部屋で設置を行ったのですが、汗だくになりました。
設置中に、アームを伸ばしてるところの写真なんですが、このアームの長さが仇になりました。

f:id:espresso3389:20160811172551j:plain

今まで、ディスプレイのケーブルは、全部、2mのものだったんですが、設置中に、2mのケーブルでは、PCまでケーブルが届かないことが発覚してしまいした。

どうやら、普通に設置する場合に比べて、+50cmぐらいはないとPCに届かなくなる可能性があるようです。

EV3237BK(7.8kg)は、やはり重いので、アームをかなりキツめに調整しないと、ゆっくりではありますが、落ちてきます。この辺の調整はちょっとだけ試行錯誤が必要でした。

ということで、一度、設置を中止して、涼しくなってから夕飯がてらに、3mのDisplayPortケーブルと、3mのUSB3.0ケーブルを買ってきました。ということで、設置完了は既に夜。

f:id:espresso3389:20160811224731j:plain

また、ケーブリングも意外と大変でした。まぁ、設置には時間がかかるのは覚悟した方がいいですね。

その他

あと、うちでは、 Windows Hello顔ログインは必須なので、わざわざUSB3.0もケーブルを買ってきたのですが、逆に、こいつらをディスプレイに軽くで良いので固定しておかないと、ディスプレイを動かすたびに落ちそうになったりするので、微妙に気を使わないといけなくなったのは事実です。

f:id:espresso3389:20160812002745j:plain

まとめ

さすがに、EV3237BKは、重くて、さらにデカいので、アームだから自由自在というわけにはいきませんが、それでも、液晶を手前に持ってくることができるようになったので、作業は格段にしやすくなりました。また、液晶の下は空洞になるので、物を置くスペースとして利用できる感じになりました。

まぁ、31.5インチの液晶を手前に持ってくると、威圧感が凄いのは事実ですね。一方で、27インチの27MU67-Bにはまるで威圧感を感じません。4インチ弱の差っていうのがここまで大きいのかって思い知らされます。

Dispatcher とか Queue とかメッセージループとか

GUIのコードを書いていると、時間のかかる処理をやりたくなることは多々あると思うんですが、それを普通に実行しちゃうとGUIが固まりますよね。

で、C#には、Taskという比較的簡単に使える道具がありまして、例えば、適当なスレッドで実行して、その結果だけを非同期で待つって感じのコードなら、

var result = await Task.Run(() => SomeHeavyCalculation());

という感じで、長時間ブロックしちゃう SomeHeavyCalculation なんて処理も簡単にバックグラウンドで実行出来ちゃいます。まぁ、単発の処理ならこれでほとんど解決。何にも問題なし。

ところが、次のような何らかのオブジェクトに対して処理を行う場合にはいろいろと注意が必要になります。

SomeSharedObject obj;

async Task ProcessAsync()
{
  // これ、安全かなぁ?
  await Task.Run(() => obj.SomeHeavyCalculation());
}

何が危険なのかといえば、このオブジェクトが、スレッドセーフ(複数のスレッド間で共有してOKかどうか)がどうかが怪しいかもしれないからです。

Task.Run は、その時その時で、スレッドプール(いくつかのスレッドをあらかじめ作っておいて、いつでもすぐ使えるようにしておく)内のいずれかのスレッドで実行を開始する感じなので、obj.SomeHeavyCalculation()はいつも同じスレッドで実行されるとも限りませんし、また、await中は、処理が別のメソッドに明け渡されるので、ProcessAsync中に、またProcessAsyncが呼ばれることは普通にあり得ます。つまり、二つのobj.SomeHeavyCalculation()が同時に動いてしまう可能性もあります。
これはヤバい。下手すると一発クラッシュ。そうじゃなくても、データが破壊される可能性があります。

これを適当に解決しようとすると、例えば、

SomeSharedObject obj;

async Task ProcessAsync()
{
  await Task.Run(() => {
    // obj へのアクセスを排他的にする(一度に一つしか実行できない)
    lock (obj)
    {
      obj.SomeHeavyCalculation();
    }
  });
}

みたいなコードを書く羽目になる。lock(モニター)を使って、この処理が同時に複数個動作しないようにしちゃうわけです。
まぁ、これが一か所なら別に大した問題にもならないでしょう。

ただ、この場合、あまりにも多くのProcessAsyncが呼ばれると、スレッドプール内のスレッドの枯渇という恐ろしい事態が待っている可能性があります。スレッドプールにいくつのスレッドが入っているかは環境依存ですが、例えば、16個しかスレッドがなかった場合、この関数が短い間に16回呼ばれると、すべてのスレッドを使い果たしてしまい、他の処理がまったく実行されないという事態に陥る可能性があります。

また、別の側面で考えると、この処理は、同時には一つしか動かないわけで、普通に考えて、スレッドの無駄遣いにもなるでしょう。どう考えてももったいない。

ディスパッチャー (Dispatcher)

さて、こういう問題を解決するには、昔ながらの定石があります。
それは、キュー(FIFO:First-In,First-Out)とか、イベントループとか、ディスパッチャーとかと言われるものです。
仕組みは簡単です。仕事をお願いする方は、キューにお願いしたい仕事を投入して、仕事を請け負う側は、キューに入っている仕事を古いものから順番に処理していくっていう、とってもローテクな方法です。

基本的に、Windowsにしろ、macOSにしろ、iOSにしろ、現代的なGUIのシステムは、GUI処理用に、このキューを持っていて、それをメッセージループだの、イベントループだの、あるいは、COMだと、STA(Single Thread Apartment)とか、そういう名前で呼んでいます。GUIは、基本的には、他のプログラムから投げられたメッセージを一つずつ解析して処理していくことを繰り返すだけです。
そのため、当然、一つのメッセージの処理に時間がかかったり、あるいは、このメッセージ処理の流れをせき止められたりすれば、GUIは固まってしまいます。最初に書いた話ですね。

じゃぁ、なんでそれが便利なのか。
上に書いた、別々のスレッドで同時に動かされてしまうかもしれない問題が解決するからです。一つずつしか処理しないので、当然ながら、同時に処理が動くなんてことはありません。

要は、先ほどの例でいえば、SomeSharedObject に対する処理を専任で行うディスパッチャーを一つ作ってしまえばいいんですね。作りましょう。それ。

で、実はこの手のものは、さっきも書いたとおり、OSが既に実装しています。Windowsなら、メッセージループがそれですし、WPFならば、Dispatcher というそのものズバリの名前のものがあります。

WPF の Dispatcher を使って実装してみる

要は、専用のスレッドを一つ立てて、そこに Dispatcher を置いておけば、さっきの SomeSharedObject に対する処理は全部、それでやれちゃうよねっていう感じです。

で、いきなり、SomeSharedObjectの実装の話になっちゃいますが、SomeHeavyCalculationを直接呼べないようにして、それをディスパッチャー経由で実行するのを、awaitできるようにしちゃいます。

class SomeSharedObject
{
  Dispatcher dispatcher;

  public SomeSharedObject()
  {
    ...
    // スレッドを起動して、そこで dispatcher を実行する
    var dispatcherSource = new TaskCompletionSource<Dispatcher>();
    var thread = new Thread(new ThreadStart(() => {
      dispatcherSource.SetResult(Dispatcher.CurrentDispatcher);
      Dispatcher.Run();
    }));
    thread.Start();
    dispatcher = dispatcherSource.Task.Result; // メンバ変数に dispatcher を保存
  }

  // 重い処理: 外からは直接呼べないようにしちゃう
  private void SomeHeavyCalculation() {...}

  // 外の人にはこっちを呼び出してもらおう
  public async Task SomeHeavyCalculationAsync()
  {
    await dispatcher.InvokeAsync(() => SomeHeavyCalculation());
  }
}

これで、使う人は、余計なスレッドとか考えなくてよくなりました。SomeHeavyCalculationAsync を await するだけで良いので、かなり簡単です。

内部的にも、SomeHeavyCalculationを無茶な呼ばれ方をしたりしなくなるので、心配事がかなり減りました。SomeHeavyCalculationは、必ず、単独でしか実行されません。スレッドの競合とか、面倒な話を考えなくて良くなりました。

みんな幸せ。

WPFのDispatcherの闇

まぁ、このコードには、ほとんど説明してないコンストラクタのコードにヤバい部分があります。
WPFのDispatcherの設計ミスとも言えるのですが、スレッド内で実行する Dispatcher を確実にメンバ変数に保存する部分に闇があります。 Task.Result とかいうあんまり使わない方が良いヤバい奴がいます。詳しくは「ConfigureAwait」とかで調べてみてくださいな。TaskCompletionSourceっていう謎の奴が出てきていますが、こちらもどうしても知りたければ調べてください。
このコンストラクタは、なるべく、await を使ってない、プログラムの最初の方で呼び出してね。さもなくば、プログラムが固まっちゃう(デッドロックしちゃう)っていう問題があるんです。悩ましい。

そして、もう一つ、特大級の闇があります。
WPFのプログラムは、そのプログラム内で実行しているすべての Dispatcher が完全に停止するまで、終了しません。つまり、この Dispatcher を殺さないと、プログラムを[X]ボタンとかで終了しようとしても終了できないのです。なんということだ・・・。

こちらには有効な解決策があります。簡単です。GUIのスレッドというか、フォアグラウンド・ディスパッチャーというか、呼び出し元の表で動いているDispatcherを監視して、そいつが終了するタイミングで、こちらのDispatcherも終了すればいいだけです。

これは、次のようにコンストラクターに書き足せばいいだけです。

class SomeSharedObject
{
  Dispatcher dispatcher;

  public SomeSharedObject()
  {
    ...
    // スレッドを起動して、そこで dispatcher を実行する
    var dispatcherSource = new TaskCompletionSource<Dispatcher>();
    var thread = new Thread(new ThreadStart(() => {
      dispatcherSource.SetResult(Dispatcher.CurrentDispatcher);
      Dispatcher.Run();
    }));
    thread.Start();
    dispatcher = dispatcherSource.Task.Result; // メンバ変数に dispatcher を保存

    // 表のディスパッチャーが終了するタイミングで、こちらのディスパッチャーも終了する
    Dispatcher.CurrentDispatcher.ShutdownStarted += (s, e) =>
      dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
  }
  ...
}

iOS/macOSGrand Central Dispatch (GCD)

iOS/macOSにも、Grand Central Dispatchといわれる同様の仕組みがあります。なんか、仰々しい名前ですが、やってることはまるで同じです。こちらの方がWPFよりも便利なのは、スレッド込みで作りこまれているものが用意されていることです。

class SomeSharedObject
{
  DispatchQueue dispatcher = new DispatchQueue("SomeSharedObjectQueue");

  ...

  // 重い処理: 外からは直接呼べないようにしちゃう
  private void SomeHeavyCalculation() {...}

  // 外の人にはこっちを呼び出してもらおう
  public async Task SomeHeavyCalculationAsync()
  {
    await InvokeAsync(() => SomeHeavyCalculation());
  }

  // DispatchQueueをawaitできるようにするラッパー(戻り値を返すことのできるバージョン)
  public async Task<T> InvokeAsync<T>(Func<T> func)
  {
    var tcs = new TaskCompletionSource<T>();
    dispatcher.DispatchAsync(() => {
      try
      {
        tcs.SetResult(func());
      }
      catch (Exception e)
      {
        tcs.SetException(e); // ここで例外をちゃんとキャッチしておく(※下の追記を参照)
      }
    });
    return await tcs.Task;
  }

  // DispatchQueueをawaitできるようにするラッパー(Action用)
  public async Task InvokeAsync(Action action)
  {
    await InvokeAsync(() => { action(); return 0; });
  }
}

全体的には、WPF版よりも簡単なコードであることがわかるでしょう。

※追記(2016/08/13) GCD における例外の扱い

重要なことを書き忘れていたので追記しておきます。
まぁ、例のごとく、上にあるコードは、全部、サンプルなので、いろいろ現実的なことを言い出せば追記することはたくさんあるのですが、
XamarinのDispatchQueue.DispatchAsyncは、内部的には、例外をそのままディスパッチャー内にぶちまけちゃう実装になっているんですが、これについては、

https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/developer.apple.com

には、以下のように、例外とか投げたら知らんぜって書かれています。

IMPORTANT
GCD is a C level API; it does not catch exceptions generated by higher level languages. Your application must catch all exceptions before returning from a block submitted to a dispatch queue.

試しに、上記の InvokeAsync で呼び出すActionの中で例外を放ったりすると、

=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================

みたいな感じでアプリごと死にます。
なので、例外の処理は明示的にやってやる必要があります。

まとめ(適当)

ここまで見れば、WPFiOS/macOSで同じインターフェイスでこのディスパッチャーを実装できそうなことはすぐにわかると思います。実際にできます。

また、ここでは書いていませんが、Androidでも、Handlerというものを使って、ほぼ同等の事が簡単にできます(どこかで暇になったら加筆します)。

要は、どこのプラットフォームでも、基本的には、同じ考え方が通用するということです。そこが一番重要です。

Bash on Ubuntu on Windows をインストールしたらやっておいた方がいいこと

別に特に重要なことを書くわけでもありませんが、一応。

/etc/hosts の調整

いろんなコマンドをたたく度に、例えば、 sudo のケースですが、

sudo: unable to resolve host KOFFIE

みたいな警告が出てきます(KOFFIEは私のマシンの名前)。これは別に実害はないんですが、鬱陶しいので、 /etc/hosts の最初の行に、ホスト名を書き足せばいいです。具体的には、こんな感じ:

127.0.0.1 localhost koffie

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

/etc/apt/source.list の修正

ubuntu だと皆さん大好き apt-get が使えるわけですが、デフォルトでは、USのサーバーを参照しに行っちゃいます。
これを日本のミラーに変更しておきましょう。日本国内のミラーとしては、次のページが詳しいです。

日本国内のダウンロードサイト | Ubuntu Japanese Team

私は、 jaist を使っています。1行目と2行目の http://... の部分だけをミラーのURLに書き換えます。

deb http://ftp.jaist.ac.jp/pub/Linux/ubuntu trusty main restricted universe multiverse
deb http://ftp.jaist.ac.jp/pub/Linux/ubuntu trusty-updates main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu trusty-security main restricted universe multiverse                                                                                          

/etc/default/locale

俺は日本語が嫌いなんじゃっていう人(私のことですが)は、 /etc/default/locale を修正して、一度、 bash を起動しなおすと、英語に変更できます。

#  File generated by update-locale
LANG=en_US.UTF-8

まとめ

Windows の環境とシームレスというわけではないですが繋がっていて、Windowsのフォルダ内のファイルを探し回るっていうのにも、 findstr とかいう得体のしれないコマンドではなくて、 find とか grep が使えるようになりました。

Linux のマジバイナリが動くので、例えば、 Android SDK/NDK なども、Linux版が使えるようになり、 Windows版の存在価値が半減したというか、個人的には、もう、Linux版使えばいいや的な感じになってきました。

cygwin もそろそろ引退かなっていう感じすらしてきます。
まぁ、cygwinには、minttyが付属しているので、正直、cmd.exeなんか使いたくないっていう話もあるでしょう。
そういう人には、

mobaxterm.mobatek.net

なんかは意外とお勧めです。接続先に、「Ubuntu Bash (WSL)」という項目があり、これを選択すると、ローカルの bash に接続できます。

f:id:espresso3389:20160805150016p:plain

こいつ、ローカルのGUIに癖があり過ぎたり、起動が微妙に遅かったりなど問題がないわけではないですが、機能面では他のターミナルアプリ群は太刀打ちできないレベルなので、インストールしておいても損はないです。

おまけ

Visual Studio Code の Integrated Terminal を bash on Windows にすることもできます。
settings.json に、次の一行を足すだけ。

"terminal.integrated.shell.windows": "C:\\Windows\\sysnative\\bash.exe"

f:id:espresso3389:20160805150926p:plain

sysctlbyname でデバイスの種類を取得する

iOSiPad mini 2 とか、そういう識別子がとりたかったんですが、 UIDevice.CurrentDevice だと、 iPad だとか適当な種類しか取れず、どうやって調べるんだろう?ってググったら、

developer.apple.com

を使えばいいことは割とすぐに出てきます。
で、今をときめく Swift のサンプルなんかは比較的たくさん見つかるんですが、 Xamarin のものについては、あんまり出てきません。出てきたのは、これぐらい。

forums.xamarin.com

ところがですね。こいつ、資料としては古くて、いろいろ問題があるんです。

  • Unified API じゃない (MonoTouch.Constants.SystemLibraryってもはやない!)
  • 64-bit のことがちゃんと考慮されてない (最後の引数が uint)
  • 確保したメモリを開放してない(最低)
  • というか、p/invoke の使い方がダサい

という、主に最後の理由で、自分で書き直そうって思いました。
SysCtl.HwMachine で、内部のモデル識別子が得られます。
これを人間が読める奴に変換したい人は、下のページでググればいいと思います。

Models - The iPhone Wiki

おまけで、SysCtl.HwMemSize っていうメモリサイズが取得できる奴も搭載してみました。サンプルですね。

public static class SysCtl
{
  /// <summary>
  /// Internal model description.
  /// For the full list of the values, see the following URL:
  /// https://www.theiphonewiki.com/wiki/Models
  /// </summary>
  public static string HwMachine => GetSystemProperty("hw.machine");
  /// <summary>
  /// Memory equiped on the machine.
  /// </summary>
  public static long HwMemSize => GetSystemProperty64("hw.memsize");

  public static string GetSystemProperty(string name)
  {
    IntPtr len64 = IntPtr.Zero;
    if (sysctlbyname(name, null, ref len64, IntPtr.Zero, IntPtr.Zero) < 0)
      return null;
    var len = len64.ToInt32();
    if (len == 1)
      return "";
    var buf = new byte[len];
    if (sysctlbyname(name, buf, ref len64, IntPtr.Zero, IntPtr.Zero) < 0)
      return null;
    return Encoding.ASCII.GetString(buf, 0, len - 1); // removing the terminating \0.
  }

  public static long GetSystemProperty64(string name)
  {
    long val;
    var len = new IntPtr(Marshal.SizeOf<long>());
    if (sysctlbyname(name, out val, ref len, IntPtr.Zero, IntPtr.Zero) < 0)
      return -1;
    return val;
  }

  /// <summary>
  /// https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctlbyname.3.html
  /// </summary>
  /// <param name="property"></param>
  /// <param name="buffer"></param>
  /// <param name="oldlenp"></param>
  /// <param name="_null1"></param>
  /// <param name="newlen"></param>
  /// <returns></returns>
  [DllImport(ObjCRuntime.Constants.SystemLibrary)]
  static extern int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string property, byte[] buffer, ref IntPtr oldlenp, IntPtr _null1, IntPtr newlen);

  [DllImport(ObjCRuntime.Constants.SystemLibrary)]
  static extern int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string property, out long value, ref IntPtr oldlenp, IntPtr _null1, IntPtr newlen);
}

HttpStream を .NET Platform Standard 1.1 対応のパッケージとして NuGet で公開しました

実は、以前に HttpStream というライブラリを作ってました。

espresso3389.hatenablog.com

これ、普通に HttpClient で ReponseStream とかをランダムアクセスしようとすると、対応してないぜって感じで例外が出て悲しいので、うまーい具合にラッパーを作ることで、HTTP上のリソースにあたかもランダムアクセスできているかのようにしちゃうというもので、まぁ、便利なので、弊社プロダクトでも利用していたわけです。

NuGet化

で、血迷って、このライブラリは、GitHub で公開したのに、弊社プロダクトは、社内Gitサーバーで管理されているという状態で、そんな状態にもかかわらず、なぜか、git submodule 使ってしまったので、泣きそうな目にあっているという酷い early summer だったんですが、モジュールとしてはかなり安定もしてるので、この際、NuGet にアップロードしてバイナリ運用の方が良くない?みたいな感じになりました。

.NET Platform Standard

https://www.microsoft.com/net/core

で、このライブラリ自体は、今まで、 Profile259 の PCL だったわけですが、そういや、.NET Core 1.0リリースされたよなぁ。ってことで、あの辺もちゃんとサポートするにはどうするんだっけ?って調べてたら、

github.com

PCL から、.NET Platform Standard ってやつに切り替えようぜって書いてありました。
PCL は Profile がもうぐちゃぐちゃで、Xamarin プロフェショナルが謎の番号を暗唱するっていう最悪な状態でしたが、この辺が綺麗に整理されて、.NET Platform Standard のバージョンいくつみたいな感じで管理できるようになりました。
マトリックスも非常にわかりやすい。まぁ、Windows 8って何だっけ?みたいな記憶のかなたになっていることもありますけどw

で、Profile259 なら、.NET Platform Standard 1.0 なんですが、もはや、 Silverlight 要らんのじゃ疑惑もあり、.NET Platform Standard 1.1 でってことになりました。

この辺の移行作業については、次のページは参考になるかもしれませんが、ならないかもしれません。

xamarinhelp.com

このページでも書かれているように、既に NuGet 配布しているパッケージを無理に .NET Platform Standard に移行すると、そのパッケージに依存しているプログラムがぶっ壊れるというか、.NET Platform Standard 移行の道連れになるのでお勧めできませぬ。
別パッケージとしてリリースしなおす方がよいでしょう。

まぁ、要はまだ過渡期過ぎて、ちょっと痛みを伴いますなぁという話ですね。

僕の場合は、 HttpStream は、NuGet 初デビューなので、何にも問題ありませんでした。
また、あと、良い点としては、PCL の時には、Microsoft.Net.HttpClient など、BCL 系のパッケージに依存している形だったのが、.NET Platform Standard では、この部分が、フレームワーク側に統合される形になったので、要は、何にも依存しないパッケージになるので、管理は凄く楽になりました。

公開したパッケージ

NuGet のパッケージはこいつ:

www.nuget.org

GitHub

github.com

使う側からすれば、ビルドしなくて良い。空から降ってくる感があるので、非常に楽になりました。