昔ながらのプログラム設計論(スマホアプリ系)
0.いきさつ
先日、社内で新人に説明するために、基本的なプログラムのモジュール設計法について大急ぎでメモにまとめた。もしかしたら他にも誰かの役に立つかもしれない。せっかくなのでブログのネタにしてしまおう、ということでブログに掲載してみることにした。
ここに書いたことは全然目新しくはなくて、昔からある考え方であるが、現在でも有効なものである。非常に基礎的な考え方なので、オブジェクト指向設計でも、構造化設計でも、(場合によっては関数型でも?、)有効な考え方である。プログラム設計を分割統治法で考えようとするとき、この考え方をマスターしておくと、品質の高いプログラムを実装するのがたやすくなるはずである。
意外に、最近はこうしたことをハッキリ説明してくれている文献があまり見当たらない。特に新人が最初に参考にするような書籍やネット記事などでも触れられていることが少ないのだが、何故だろう? 非常に大事なプログラマの『感覚』なのではないかと思うのだが。
1. プログラムのモジュール(クラス・メソッド)分割方針について
プログラムを設計していく場合、クラスなりメソッドなり、何かのカタマリ(=ここではモジュールと呼びます)に分割していくわけですが、そこには経験的に上手くいく指針があります。それらを以下に説明します。
1.1. 常に『このモジュール(クラス・メソッド)の責務(Responsibility)は何か?』を明確にして分割する
- 『責務』という日本語だと分かりにくいですが、Responsibilityという英語だと、プログラマの直感に近いニュアンスです。
- Responsibilityは、'Response + ablility' という構成の語であり、『(ある要求に)反応・応答する能力を持っている』という意味です。
- クラスやメソッドを設計するときのプログラマの意識は、Responsibility=『外部からの要求を受けて、決められた仕事を実施して結果を返すユニット』にあります。
- このとき、そのモジュールの責務=『どの範囲の要求に応答し、どこまで仕事をさせることにするか』を明確に決めておくことが大事です。
- 責務を曖昧にしていると、どっちつかずで位置づけが分かりにくく、デバッグがしにくかったり、将来の機能拡張や引継ぎのときに混乱を生じる原因になったりします。
1.2. 一つのモジュールには、明確にシンプルに説明できる一つの責務だけを持たせる
- 一つのクラスや一つのメソッドにたくさんの機能を盛り込むと、ややこしくなってバグの元になります。
- 例えば、『なにかを計算して、その結果を表示する、エラーならエラー表示を行う』という機能を一つのメソッドに詰め込むのは、原則として良くない設計です。
- 以下の4つの単機能の下位メソッドを作り、それらをまとめて制御する上位モジュールを作るのが基本です。
- 計算する
- 計算結果を判定する
- 結果を表示する
- エラーを表示する
1.3. モジュール間の上下関係・主従関係を明確にする
- 上位(主位)モジュールは:
- いろいろな下位モジュールを呼び出します。
- 全体の状況(状態)を把握しています。
- 状態を持ち、その更新に責任を持ちます。
- スレッドやタイマーを管理し、システムのさまざまな処理を起動するタイミングを統括します。
- メモリやDB,リソースの初期化・確保・削除・解放に責任を持ちます。
- 別名:Activeモジュールといいます。
- 下位(従位)モジュールは:
- 上位モジュールから呼び出されたら、単純な一つの責務を実行します。
- いつ呼び出されるかは知りません。
- なぜ呼び出されるかも知りません。
- 上位モジュールの存在は知りません。
- 他の下位モジュールの存在も知りません。
- リソースの確保や解放はしません。
- 『副作用』や『状態を持つ・状態を更新する』のはナシにするのが理想です。副作用があったとしても最小限かつ明確なものにします。
- 別名:Passiveモジュールと呼びます
図:上位モジュールは、下位モジュール呼び出す
図:下位モジュールは、上位モジュールを呼び出さない。下位モジュール同士の直接のやりとりは、原則やらない。
図:下位モジュールは単純・単機能な責務にする。上位モジュールは状態を管理し、全体を統括する。
2. オブザーバ(Delegate,Listener)パターンについて
オブザーバパターンでは、プログラムコード上は、下位が上位を呼び出す形になる場合がある。ただし、呼び出し先のオブザーバ(delegate, listener)はプロトコル(Swift)やインターフェイス(Java)として抽象化されており、具体的な特定のクラスを差していないので、上記ような意味で上位クラスを呼び出すのとは異なっている。
3. iOSアプリでの上下関係の例
- 最上位モジュール:AppDelegate
- 上位モジュール:各画面の ViewController
- VC内のメソッドの上下関係
- 上位メソッド
- 状態を把握・状態遷移を行う
- クラスのプロパティを変更しても良い。
- 例:
- viewDidLoad()
- viewWillAppear()
- viewDidAppear()
- viewWillDisappear()
- UIボタンやセンサーのイベントハンドラ
- ただし、ハンドラに長々と制御コードを書くのは好ましくない。制御を集約した別クラスを作って、それを呼び出す形にする、など。
- ・・・
- ユニットテストしにくい
- 下位メソッド
- 状態を持たない(状態値を変更しない、参照しない)
- クラスのプロパティを変更しない。
- 入力=引数のみに依存し、結果は全て return 値だけに反映する。
- 例:
- 自分で定義する各種処理ルーチン
- ユニットテストがやりやすい
4. どこで深刻なバグが起きやすいか
- 上記説明における、下位モジュールでは、あまり深刻なバグは生まれません。
- ほとんどの深刻なバグは上位モジュールの状態管理や条件判定、あるいはスレッドやタイマーの制御、メモリやリソースの管理に起因します。
- したがって、デバッグしやすくするには以下の原則を守ることが大事です。
- スレッドやタイマーの制御方針を明確に、一貫したものに。
- 状態の管理や状態遷移の方法を明確に、一貫したものに。できるだけ一箇所・ワンパターンに集約する。
- 安易にフラグ制御を増やさない。
- メモリ・リソースの確保・解放は同一レイヤー(階層)に揃える。
以上
『ねこもに』のパーティクルフィルターアルゴリズムを書き、特許出願してみた
『ねこもに』は、当社オープンストリームが開発中の新商品である。来年春の発売を目指している。現在開催中のCEATECにて発表・出展し、概ね好評をいただいているようだ。
ねこもには、スマホとBluetoothを使ったシステムだ。猫の首輪にBluetooth(BLE)発信機を取り付けて、専用のスマホアプリを使って猫を探したりできる。
このシステムの肝となるのが、猫の場所を見つけるアルゴリズムである。よく誤解されるのだが、 このアルゴリズムは、iBeacon のような ON/OFF表現ーー『近くに居る・居ない』ーーで猫を見つけるのではない。二次元マップ空間上で、連続値的に猫の存在確率分布を計算して推定しているのである。その部分はちょっとした工夫をしていて、ざっと調べたところ類似の前例が見つからなかったので、現在特許を出願している。
今回、このアルゴリズムの数理的な部分は私が考案させてもらった。たまたま、この方面の知識と経験があったからである。もちろん、突然思いついた訳ではなく、ねこもにという商品のコンセプトが先にあって、その解決策を考えていくうちに発想できたものである。
『ねこもに』のコンセプトは、当社のねこもにチームが発案し、毎年開催している社内コンペに勝ち抜いたものである。猫への愛情にあふれた、なかなか良いアイデアである。ただ、最初にその構成案を聞いたときには、これはちょっと無理じゃないのかな?と思った。情報処理のモデルを単純に考えると、典型的な不良設定問題になっていたからだ。
ご存知の通り、不良設定問題とは、与えられた入力情報だけでは答えが一つに定まらない、というタイプの数学的問題である。
BLEは、個体を識別するIDの他は、電波強度=距離しか検知できないデバイスである。つまり、BLE自身は一次元センサーであって、二次元的な位置を計測する機能はない。
これでどうやって猫の位置を精度良く推定しようか?・・・離散的なON/OFF表現のメッシュマップでもできるけど、それだと、メッシュの分解能以上の精度を出すのは難しいし、広域をサポートしようとすると、メッシュのデータを保持するメモリ容量も膨大なものになる・・・考えるうちに気がついたのはモーションステレオとの類似性である。
モーションステレオは、単眼カメラでステレオ視を実現する画像処理技術である。単眼カメラは二次元センサーであり、ステレオ視は奥行きを含めた三次元情報の復元が目的である。つまりここでも、一つ次元が少ないセンサーから、一つ次元の高い情報を再構成している。その点でねこもにの課題と似ていることに気づいた。(気づいたときはちょっと嬉しかった)
モーションステレオでは、カメラを移動させながら複数の画像を撮影して、奥行き推定を行う。同様にねこもにでも、スマホを移動させながら、BLEとの距離複数回計測して推定に使えば良いだろう、これが基本コンセプトになった。
もう一つの問題がある。それはBLEの距離値の誤差がけっこう大きいことである。BLEから得られる距離値は、周囲の環境の影響を受けたりして、平気で数メートル〜数十メートルぐらいの誤差がでるときがある。画像のモーションステレオなら、それなりに良いレンズなどを揃え、適切なキャリブレーションを実施すれば、ほとんど入力誤差を考慮する必要がないが、BLEは誤差だらけであり、誤差を考慮しないとまともな答は出ないのは明白であった。
センサーから誤差の影響を減らしたいとなれば、誰もが考えるのがフィルター理論の適用であろう。私もまずは古典的なカルマンフィルターを考えたが、5秒後に却下した。カルマンフィルターでは、対象物体の運動を線型な方程式で表現できないといけない。気まぐれな猫の運動を線型な方程式で記述できるわけがないw。そうなると・・・パーティクルフィルターを使うのが常識であろう。
というわけで、ねこもにでは、パーティクルフィルターを使うことで、誤差込みの距離センサー値を複数回重ねあわせて、二次元位置を逐次推定するモデルを使うことに落ち着いた。
パーティクルフィルターを位置推定に使う特許や論文は沢山あるのだが、ほとんどがGPSや移動台車のデッドレコニングなどへの適用例であり、ねこもにのようなアプローチは意外に見つからなかった。なので特許を出願したわけである。
実際のソフトウェア実装の話も書こうと思ったが、かなり長くなったので、また別の機会にしよう。