はじめに
今年の四月から今月までの八ヶ月間、大学の部活でチーム開発をしました。この記事で、今回の体験で学んだことを主に技術的な部分とチーム開発で学んだ事の二つに分けて説明していきます。初投稿なので色々な部分で拙さがにじみ出ていると思いますが、ご容赦ください。
技術面
まず、今回のチーム開発とTechTrainサービスを利用して学んだことの総括としてQiitaAPIを利用して記事を取得するアプリケーションを作成してみました。これを用いて以下の説明をします。
https://github.com/Wyutaka/QiitaAPITest/
MVPの前に
個人的なデザインパターンに関する見解です。すでに理解しているという方は読み飛ばしてください。
Androidのアーキテクチャには、MVC(Model-View-Controller)、MVP(Model-View-Presenter)、Clean Architectureなど様々なデザインパターンが有ります。しかし、
そもそもデザインパターンはなぜ必要なのでしょうか。
- 関心の分離
- テスタビリティの向上
- 複数人での開発
- Activityの肥大化
といった問題を解消するためにデザインパターンがあります。あるデザインパターンに則ることで、ファイル名やクラス名が一元化され統一感が生まれます。この一元化により、例えば複数人で開発する時、自分が他の人の書いたコードを全て読まなくても、「このクラスにはこういう名前がついている。なら、このクラスにはこういう処理が書かれているはずだ」と見立てることができます。また、関心を分離させることでAcitiviyのコード量が減少し、肥大化を防ぐことができます。
つまり、デザインパターンを採用することにはあるルールを決めることで、可読性を向上させるというメリットが有ります。
参考
MVP(Model-View-Presenter)とは?
- アーキテクチャにおけるデザインパターンの一つ
- Model
- データクラスとのやり取り(APIでリモートからデータを取得したり、ローカルDBからデータを取得したり)
- 図でのREPOSITORY部分
- View
- ActivityやFragment
- Presenterにビジネスロジックを委託(ViewでAPIを叩くとかはしない)
- Presenter
- ビジネスロジックを担当(Modelの操作等)
Model
QiitaAPIの仕様書を参考にしてModelを定義します。
data class ItemModel(
val id: String?,
val title: String?,
val body: String?,
val url: String?,
val comments_count: Int?,
val likes_count: Int?,
val created_at: String?,
val user: User?
)
Presenter用にRepositoryを作成します。
class RemoteListRepositoryImpl(
private val api: QiitaApi
) : RemoteListRepository {
override suspend fun getlist(searchWord: String): List<ItemModel> = api.getlists(searchWord)
}
View
ViewはPresenterにインターフェースを経由してViewのイベントをpresenterに通知するので、専用のViewを作成します。
interface QiitaListView : LifecycleScopeSupport{
fun showDetail(view: View, position: Int)
fun showList(list: List<ItemModel>)
}
ActivityにViewを実装します。showListはAPIを利用して一覧を取得する必要があるので、発火するタイミングはpresenterに委託しています。
class QiitaListActivity : AppCompatActivity(), QiitaListView {
private lateinit var qiitaListRecyclerViewAdapter: QiitaListRecyclerViewAdapter
private lateinit var qiitaListRecyclerView: CardRecyclerView
private lateinit var presenter: QiitaListPresenter
override val scope = LifecycleScope(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_qiita_list)
presenter = QiitaListPresenter(this, this)
qiitaListRecyclerView = qiita_item_list
qiitaListRecyclerViewAdapter = QiitaListRecyclerViewAdapter(this, this)
qiitaListRecyclerView.adapter = qiitaListRecyclerViewAdapter
qiitaListRecyclerView.layoutManager = LinearLayoutManager(this)
search_menu_search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
presenter.showList(requireNotNull(query))
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
})
}
override fun onDestroy() {
presenter.onDestroy()
super.onDestroy()
}
override fun showDetail(view: View, position: Int) {
val builder = CustomTabsIntent.Builder()
val customTabsIntent = builder.build()
customTabsIntent.launchUrl(
this,
Uri.parse(qiitaListRecyclerViewAdapter.getItem(position).url)
)
}
override fun showList(list: List<ItemModel>) {
qiitaListRecyclerViewAdapter.update(list)
}
}
Presenter
Presenterは主にrepositoryを用いてデータを取得し、Viewにデータを反映させています。
class QiitaListPresenter(
private var view: QiitaListView?,
private var context: Context,
private val qiitaRepository: RemoteListRepository = context.QiitaAPIApplication.qiitaListRepository
) {
fun onDestroy() {
view = null
}
fun showList(text: String) {
view?.bindLaunch {
val list = qiitaRepository.getlist(text)
view?.showList(list)
}
}
}
技術面まとめ
デザインパターンを用いることで、1ファイル毎のコード量の増加を防ぐだけでなく、機能ごとのテストがしやすくなり、バグの発見も容易になります。今後のチーム開発において必ず役に立つと思うので、今後積極的に使っていきたいと思います。
参考
Api
https://qiita.com/api/v2/docs
recyclerview
https://qiita.com/kubode/items/92c1190a6421ba055cc0
チーム開発振り返り
先述したとおり、八ヶ月間学校の部活でチーム開発をしました。
今回のチーム開発では、Android,iOS,Serverの3つのグループに分割し、自分はAndroidのグループに入り、一つ上の先輩と一緒に開発をしました。その中でこうするべきだったと反省するべきシーンがいくつかあったので、それらを備忘録としてこの記事に残しておきます。
主体性・協調性
当初は先輩が先行してアーキテクチャを選定し、その後の実装は二人で分割して行うという流れで開発をする予定でした。しかし、自分の開発が遅れたために、作業量が先輩に偏ってしまう問題が発生しました。自分の開発が遅れた原因は主に主体性、協調性の2つにありました。
プロジェクトが始動した頃、自分はアーキテクチャを全く知らない初心者でしたので、アーキテクチャの選定については先輩やメンターの方に丸投げしていました。しかし、最終的には自分も決められたパターンに沿って開発していかなければいけません。結局、コードを実装する直前になってそのアーキテクチャについて勉強する羽目になり、思うようにコードがかけませんでした。自分からアンテナを張って周りが何をしているのか、なぜそうしているのかを把握することが大切です。こうすることで、後になって「あれ、これってどういう仕様だったっけ」というような状況になったときに全然自分の思っていた仕様と違う、みたいな説明する側もされる側も不幸になるような事態を減らすことができます。
無理をしない
周りに聞くのが申し訳ないと思って、中々聞けずに最後まで自分で調べる、みたいなことはよくあると思います。
しかし、その中には最後まで自分でどれだけ調べてもわからない、または理解するのに相当な時間を費やした、という時もあると思います。なので、ある程度(15〜30分)自分で調べてわからないときは周りに聞くことにするほうがいいです。むしろ、周りに聞く事はチーム全体のコミュニケーションを活発にさせるメリットがあると考えましょう。
ある時、ひょんなことから部活の同期に「お前しんどそう、でも何してるのかわからないから何をしてあげられるのかがわからない」と言われました。自分が周りを心配させていることがその時初めてわかりました。つまり、申し訳ないと思って無理に調べて周りに尋ねないという行為は、逆にチーム全体のムードをさげてしまう可能性があることがわかりました。
わからない事を慎重に、積極的に尋ねる
では、いざ周りに尋ねるときには何に気をつければいいでしょうか。
まず、答えてくれる人の負担を出来る限り減らすことを目標に質問することが大切だと思います。その為に、
- 自分のレベルを伝える
- 何が問題かを概要で伝える
- 自分が行った作業手順を伝える
- エラーメッセージを載せる
- やりたかった事と実際の挙動を伝える
- 自分が何を調べたかを伝える(記事のurlなど)
- 問題があると思われるコードを提示する
これらを意識することで、答えてくれる人が質問する側がどこでミスをしているかの検討がつけやすくなります。また、こういったテンプレートを作成しておくことで、自分も質問をするときに楽ができます。この記事がとても役に立ったので、ぜひ参考にしてみてください。
終わりに
今回のプロジェクトを通して、失敗もたくさんありましたが、貴重な体験をさせていただきました。このプロジェクトに関わってくださった皆さんへの謝意とともに、この記事の締めとさせていただきます。