亀岡的プログラマ日記

京都のベッドタウン、亀岡よりだらだらとお送りいたします。

Friendlyの「功」と「罪」

この記事は、Friendly Advent Calendar 2014 - Qiitaの12/14日分の記事となります。

ここでは、僕なりのFriendlyの強力さと、その強力さ故の落とし穴について話しておこうと思います。割と妄想込みです。ハイ。

Friendlyが可能にしたこと

Friendlyの本当の強力さ

Friendlyはとても強力です。これは本当に、心からそう思います。

過去の恥を晒しましょう。実は僕、最初にFriendlyのLTを見た時、そんなにすごいとおもいませんでした。 なぜなら、これはWindowsAPIに詳しい人ならば、Friendlyが実現していることは昔からそこそこ試みられていたことだからです。

すなわち、WinAPIによるDLLインジェクション + インジェクションしたDLLによるプロセス介入です。これは10年くらい前の本にも書いてある、割と古典的な方法です。

APIで学ぶWindowsプログラミング (日経BPパソコンベストムック)

APIで学ぶWindowsプログラミング (日経BPパソコンベストムック)

WPF=XAMLの内部構造解析ツールであるSnoopもおそらく同じことをしています。

ところが、Friendlyを実際に触ってコーディングしてみて(そして@StoneGuitar777 さんに色々教えてもらって)、本当のFriendlyの凄さを知ることができました。

Friendlyの本当にすごいところは、他アプリに介入した後の、できることの自由度です。

Friendlyの「他アセンブリの操作を呼び出す」という部分は恐ろしく究まっています。ベースライブラリを少し触れば分かりますが、Genricsやref/outなど「やれるのは分かっているけどめんどくさそう」という呼び出しも完璧に対応していますし、同期/非同期の呼び出し切り替えというテストでは必須の考え方、モーダルダイアログ処理を非常に楽にするサポート関数群、さらにはDLLのインジェクションといったさらに高度なテストを行うための機能、などなど、、、

これは、これまでのFriendly Advent Calenderで石川さんが述べられていたように、石川さんの経験によるものなのでしょう。基本的に「これできるんかな?」と思ったことは大体できるようになっています。.NETのアプリに絞れば、おそらく外から操作できないアプリの動きは、存在しないでしょう。

こういった強力な機能により、Friendlyでは壁を取っ払ってくれます。

「プロセスの壁」?いや、違います。「設計構造の壁」です。

設計構造の壁を打ち壊すテスト

Friendlyをつかえば、Windowsアプリであれば、どんなアプリケーションも自由自在にあやつることができます。

特に強力なのは、悪名高き「Smart UI」パターンに陥っているレガシーなアプリケーションのテストでしょう。

Smart UI(利口なUI)アンチパターン

層状アーキテクチャの対極をなすアンチパターンビジネスロジックやデータアクセスのコードが、UIのコードと一緒になってしまっている、いわばスパゲッティな状態。利口なUIと呼ぶのは、ビジネスロジックを含むすべての処理がUIの中で行なわれるから。最もやっつけで手軽なやり方がこれなので、設計を何も考ないとこの状態に陥ってしまう。

https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap2.html

Smart UIとは、言ってしまえば「ボタンのイベントハンドラですべての処理をやってしまう」系のアンチパターンです。テスタビリティを泣きたいくらいに引き下げてくれます。なんせ「階層化、なにそれおいしいの?」みたいなパターンですから、メソッドレベルでの検証なんてできません。ボタンを押して、UIがどう変わるか、くらいしかできないわけです。つらい。

Friendlyは、そこに対して様々なアプローチでアクセスすることができます。例えば、、、

  • もしイベントハンドラ内で呼んでいるメソッドがあるのなら、そのメソッド単位での検証を簡単に書いて、テストのAtomic性を向上させられる
  • UIではなく、内部のフィールド変数やプロパティを検証することで、UIに現れにくいバグの検証を容易にする
  • モーダル呼び出しが頻発しても、Thread.Sleepに頼らない頑健なテストを書くことができる

などなど。

言ってしまえば、Friendlyを使えば、マズイ設計構造を何とかテストする、そういうレガシーコード改善ガイド的なことができるようになるんです。

しかも、既存アプリの書き換えを行わずに!!

Friendlyが可能にしてしまったこと

そう、そこにFriendlyの怖さが有ります。

Friendlyを使うと、マズイ設計構造でもわりとテストが書けてしまうんです。

「いや、イイ事じゃない」とおっしゃる方もいると思いますし、こっから先は個人的な考えで強要する気はありません。

んが。

僕の中で「自動テストを書く」というのは2つの意味があります。

一つは、もちろん品質を保証して、デグレードを起こさせないため、回帰テストを簡単に書いていくため。 これは、Friendlyで十分に満たされます。

もう一つは、設計構造をより良くするため。自動テストが書ける、ということは、基本的にはコードの分離度がある程度以上高いことを示します。*1 自動テストを書きにくいな、と思った時には、

  • 本来独立すべきコンポーネントが別のコンポーネントに強依存している。
  • 本来設定可能であるべきモノが設定できるようになっていない
    • 検証したい値がモジュールの外に公開されていない、とか
    • もっと大きいレベルだと、開発用の環境とリリース用の環境を切り替える術がない、とか

などが考えられるわけです。

こういったことにテスト設計・実装を通じて気づきながら、よりよい設計実装を進めていく、という話が、、つまりは「レガシーコード改善ガイド」や「GOOS本:実践テスト駆動開発」な世界観、だと思ってます。

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)

そして、こういった気づきを得られるはずのテスト自動化実装が、Friendlyの強力さによって、見えにくくなるばあいがあるのでは・・・?という点が個人的な懸念点です。

僕が考えるFriendlyとの付き合い方

さて、まとめなのですが。

レガシーなプロダクトのテストにFriendlyは大変有効です。それは先にも述べたとおり。

では、ちゃんとレイヤー化したものは?

それに対してももちろん強力です。たとえば、MVVMモデルできっちり作られたアプリケーションのシステムテスト、Viewの検証は手動に任せて、VMレベルを触ってテストを行うことが、Friendlyならできます。 このように、実際動いているアプリケーションで任意のレイヤーを切り出してテストできるのですから、従来の結合テストでは発見が難しかった不具合も、Friendlyなら容易に検出可能になるでしょう。

しかしながら、レイヤ化をきっちり行うためには、Friendlyに頼らないテストを書いておくことも非常に重要です。Friendlyを使わない低レイヤレベルでの単体テストを開発時に整備しつつ、End To EndテストとしてFriendlyの強力さ・安定さを利用する。

そういった付き合い方を、Friendlyとはしていきたいなと思っています。

*1:高すぎても問題ですけれど