Android
AspectJ

アスペクト指向を利用した関心事の分離

11月からAndroidエンジニアとして働いている@katsutomuです。最近は子供のクリスマスプレゼントに何を選ぶかで頭がいっぱいです。どなたか子供が喜ぶプレゼントを教えてください!!!

さて、私からはAndroid開発でのアスペクト指向を利用したアプローチをお送りできればと思います。宜しくお願い致します!

アスペクト指向プログラミング(AOP)とは何か

まずAOPが初見だという方に向けて、ざっくりと概要を説明しておきます。

AOPの大きな目的は非常にシンプルです。

  • オブジェクト指向で解決できなかった横断的な関心事を分離する

AOPはオブジェクト指向を置き換えるものではありません。オブジェクト指向だけでは分離できない物をアスペクトという単位で分離し、ストレスフリーな開発の手助けをしてくれます。

コンポーネントの限界

まずオジェクト指向をベースにした開発ではコンポーネントという単位で、クラスやパッケージを分離したり、モジュール化をするなどのアプローチで、設計を行うコトが多いかと思います。

これらはアプリケーションの利害関係者がもつ現実世界の関心事を、システムの構造にマッピングしていく作業となりますが、ここで問題になってくるのが、コンポーネントとして正しく分離するのが難しい関心事が存在することです。

これらの存在によりシステムは複雑になり、各コンポーネント間の依存が強くなり、下記の様なデメリットが生まれてきます。

  • クラスの責務が増えて単一責務の原理を守れなくなる
  • コードリーディングの妨げになる
  • テストが書きづらくなる

横断的な関心事

コンポーネントとして正しく分離するのが難しい関心事や、複数のコンポーネントに跨る関心事は横断的な関心事と呼ばれています。代表例として下記のものが挙げられます。

  • ロギング
  • トラッキング
  • 認可
  • 永続性
  • キャッシュ
  • デバッグ
  • トレース
  • 分散処理
  • 例外処理

皆様も、ロギング処理を全てのクラスの全てのメソッドの先頭行に書いたり、データ取得APIのリクエスト前に認可APIを叩くといった処理をどの様に分離しようかと、頭を悩ませたことあるのではないのでしょうか?
またというオブジェクトに対してロギングというオブジェクトが依存してしまうと、現実世界に置き換えた際に違和感が生まれてしまいますし、本来の責務を超えて結合度が高まると思わぬ不具合を生みやすくなってしまいます。

これらの解決を目指しているのがAOPなのです。

サンプル

長くなりましたが、本題です。
Androidでの事例を挙げさせていただきます。

トラッキング

例としてAnalytics Toolとの連携を挙げさせてもらいました。

Androidを開発ではFirebaseやmixipanelなどのAnalytics Toolを利用してサービスの改善を重ねていく必要があり、全ての画面やボタンにイベントトラッキングの処理を埋め込んでいる・・・という方も多いのではないのでしょうか。

その場合下記の様なコードを書いているかと思います。

MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Tracker.sendEvent("ShowMainActivity")
    fab.setOnClickListener { view ->
        onClickFab(view)
    }
}

fun onClickFab(view:View) {
    Tracker.sendEvent("ClickFab")
    val intent = Intent(this, SecondActivity::class.java)
    startActivity(intent)
}
SecondActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Tracker.sendEvent("ShowSecondActivity")
}

このアプローチだと全Activityに対してTrackerへの依存が生まれてしまい、下記のような課題が生まれます。

  • 本来の責務を超えて管理するものが増えていく
  • Activityの単体テストでTrackerも関心事になってしまう
    • そもそもApplicationでの初期化が必要なツールが存在すると単体テストが出来ない
  • コードリーディングの妨げになる

これをアスペクト指向で解決すると以下のようになります。

MainActivity.kt
@SendEvent(eventParam = "ShowMainActivity")
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)
    fab.setOnClickListener { view ->
        onClickFab(view)
    }
}

@SendEvent(eventParam = "ClickFab")
fun onClickFab(view:View) {
    val intent = Intent(this, SecondActivity::class.java)
    startActivity(intent)
}
SecondActivity.kt
@SendEvent(eventParam = "ShowSecondActivity")
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
}

これでActivityからTrackerへの依存がなくすことが出来ました。
この様に基盤的関心毎への依存を減らす事により、Activityが持つ本来の責務である
画面ライフサイクルとUIイベントの管理のみがコード上に存在する事になります。
またTrackerがApplicationに依存による間接的な依存も排除することが出来ました。

まとめるとこんな感じです。

  • 本来の責務のみが存在するコードになった
  • Activityの単体テストが書きやすくなった
  • コードリーディングしやすくなった

最後に

今回は、AOPの目的のAndroidでの利用事例について書かせて頂きました。

筆者はAOPの持つ関心事への考え方に触れた事により、以前とは違った視点で、アプローチをとることが出来るようになり、以前よりも楽しく開発に関われています!!

なお、今回のサンプルはgithubに挙げています。何かの参考になればと思います。
以上、ありがとうございました。明日は@dachiの記事です!お楽しみに!!

https://github.com/katsutomu/AspectSample

参考文献:ユースケースによるアスペクト指向ソフトウェア開発