「とりあえず動く」を卒業するためのアーキテクチャ入門編(2)

2.プロジェクトの変化に対応する

皆さんこんにちは。株式会社コスモルートでアンドロイドアプリの請け負い開発をしているT.Mです。

前章でKotlinの感触が少しだけ理解できたと思います。
javaで書くよりもコードが減り、バグも少なくなるのでお勧めです。
興味のある方は充実したオンラインコースやサンプルアプリの実装例がgithubに載っているので、それを元に勉強するのもおすすめします。
もしかしたら簡単なアプリを作ったことがある人もいるかもしれませんね。
単にボタンを押したら画面遷移するアプリから、電卓アプリやToDo管理アプリを作ってみるのは学習としてテッパンですね。
そういう場合は自分で要件定義をして仕様を決めているので混乱はありませんよね。


一方、実際のプロジェクトではあなたが要件定義に参加することは稀です。
中規模以上の開発(10人月~)になると、要件も複雑になり、それぞれ矛盾する仕様も出てくるでしょう。
完璧な人間は存在しないのと同様、完璧な仕様もあり得ません。
ウォーターフォール型のプロジェクトであれば、要件定義と開発期間を区切りますが、開発期間にも仕様変更って絶対ありますよね?
要するに、変更に強い開発手法を探ることが大事です。

2.1 プロジェクト構成を考えよう

改めて、変更に強いという意味を考えてみましょう。もう少し詳しく定義すると
仕様変更により生じるバグが少ないと置き換えても良いと思います。これをもう少し掘り下げると、

・変更によるコード修正の範囲が明確かつ限定的
・テストコードが書きやすい

が挙げられます。
他にも「コードの意図が伝わりやすい」、「ドキュメントが整備されている」など挙げられますが、上の2つに絞って考えてみましょう。

2.2コード修正の範囲が明確かつ限定的とは?

まず、「変更によるコード修正の範囲が明確かつ限定的」を意識したプロジェクトはどうすればよいでしょうか?
考えるヒントとして、Twitterのバスツイートだけを表示するアプリを作ることを考えてみましょう。

要件としては、簡単のため

・バスツイート=フォロアーの100倍のいいね!がついたツイート
・当日のバズツイートをリストで表示
のみ考えます。
さて、どうやって実装しましょうか?

まずはActivityに全部実装してみましょう。

```kotlin                            
                              
  class MainActivity: AppCompatActivity(){                            
                              
      //Retrofit(Httpライブラリ)                        
      val twitterService = Retrofit.Builder().baseUrl("https://twitter....").build().create(TwitterService::class)                        
                              
       override fun onCreate(state:Bundle?){                        
            super.onCreate(state)                
            setContentView(R.layout.activity_main)                
                            
            val listView = findViewById<RecyclerView>(R.id.list_view)                
            //TODO adapterの設定                
                            
            //コルーチン(メインスレッドでの処理を回避する目的)                
            GlobalScope.launch(context = Dispatchers.Main){                
                withContext(context = Dispachers.IO){            
                               
                    val info:TweetInfo = twitterService.get(LocalDate.now())        
                    val items = info.tweets.filter{ tweet->        
                        //フォロアー数×100以上の場合をフィルター    
                        tweet.likes>=info.followers.size*100    
                    }        
                    //リストを表示する        
                    listview.adapter = TweetListAdapter(items)        
                }            
            }                
        }                    
  }                            
                            
```      


サンプルコードなのである程度省略して書きました。
コードが何をしているかは理解できると思います。

しかし、急にクライアントから次の要件が加わったとしましょう。
①オフラインでも直近に取得した情報を表示したい
②データを取得している間は,ローディングを表示してほしい
③リスト上からいいね!を押せるようにしてほしい

上記の修正に対応するには、
①はデータベースを用意し、
②はactivity_main.xmlにローディングダイアログを用意、
③はいいね!を押すAPIを用意すれば良いでしょう。
しかし、確実にコードは分かりにくく、複雑になっていきます。

複雑さを回避する方法として、コードを分割すれば良いと考えるのが自然でしょう。
ではどうやって分ければよいのか、基準を設ける必要があります。
有名なMVC(モデル・ビュー・コントローラー)でも良いかもしれません。
この場合は
・Model アプリ特有のロジックのこと。
    例えば,バズツイートの取得,オフライン時にデータベースから直近のデータを取得など。
・View バズツイートのリスト表示,ローディングダイアログの表示など。

  • Controller ユーザーによる画面アクション。「いいね!」を押す動作。

でそれぞれフォルダーを分けてみると、恐らく次のような構成になると思います。

  • model/
    • TwitterRepository.kt オンライン/オフライン時を判断して、TwitterApiとTwitterDatabaseからデータを取得および、いいね!の反映。
    • TwitterApi.kt ツイートの取得と、いいね!を押して反映するAPI
    • TwitterDatabase.kt ツイートのデータベース
    • TwitterModel.kt ツイートのモデル(フォロアー数やツイート内容などを格納、バスツイートを判断)
  • view/
    • MainActivity.kt TwitterListFragmentのActivity
    • TwitterListFragment.kt リスト表示画面
    • TwitterItem.kt リストに表示される内容を格納
    • LoadingView.kt ローディング画面のカスタムView
  • controller/
    • PushLike.kt View.onClickの実装。TwitterRepositoryから、いいね!を反映する

実際にはもう少しファイルが増えると思いますが、機能ごとにうまく分けられている感じがします。
肝心のメリットについて考えてみましょう。
例えば、更に次の要件修正が入るとします。

・リツイート数も表示してほしい。

  • バスツイートの定義を、100,000ツイートで固定にしてほしい

「この要件、もっと早く知りたかったな…」って思いながらも、どのファイルを修正すれば良いか、初めてコードを見た人でも理解ができるのではないでしょうか?


1番目の修正については、TwitterItem.ktを修正、3番目はTwitterModel.ktを修正すれば良いと分かります。
これがMainActivity.ktにすべて実装してあったら、修正箇所の当たりを付けづらく、1つの修正が他の機能に影響するかも知れません。
上手くコードを分割した場合のメリットが分かりましたね。

さて、今回のタイトルが「アーキテクチャー入門」なのでMVCの概念を知らない前提で話しましたが、改めて見ると問題点もあります。
例えばcontroller部分ですが、わざわざonClickの実装だけ分けるメリットはあるでしょうか。
またデータ取得の途中でアプリを閉じた場合はどうなるでしょうか?


Android特有のライフサイクルが考慮されてない気がします。
Androidフレームワークに依存する部分とそうでない(いわばPureなkotlinで書ける)部分を分けなくてもよいでしょうか?
実際には上記に挙げた問題点は、ライブラリの発展とアーキテクチャーの洗練で補えますが、それは第3章に任せましょう。

今回は、
2 プロジェクトの変化に対応する
2.1 プロジェクト構成を考えよう
2.2 コード修正の範囲が明確かつ限定的とは?
について、説明しました。

次回は、テストコードについて考えていきます。