2回目のチーム開発を終えて
記事概要
「アプレンティス」に参加し、課題の中でチーム開発を経験させて頂きました。
前回のチーム開発時の記事はこちら。
前回の反省点も踏まえつつ、期間中に私が考えていたこと、チームに貢献したことなどを中心に書いていきます。
アプレンティス チーム開発とは
私たちのチームは4人。約1ヶ月間で「ワクワクするもの」を考え、設計、開発し、ハッカソン形式でプレゼンまで行いました。
ハッカソンは、採点形式で行われ、受講生からの評価が最も高いチームに「Best Student Award」、メンターからの評価が最も高いチームに「Best Award」が授与されるといったものでした。
プロダクトについて
「リフモン」という目標管理アプリを作成しました。
例えば「10kg痩せる」という目標を設定し、それに対して毎日のタスクを作成します。例えば「ジムに行って1時間トレーニングをする」「朝ランニングを5kmする」などです。
これらのタスクを達成することで、モンスター(リフモン)はタスクを食べて成長するというアプリです。
成長し過ぎですね 笑
モンスターは最初卵から始まり、孵化したあと、さらにタスクを食べて進化するという2段階に設定しました。(1枚目の画像は孵化後のモンスターです。)
開発の背景
既存のテキストベースでの目標管理アプリや、TODOアプリだと結局続かなくてやめてしまうという課題があります。
そこで、もっとタスクをこなすのが楽しくなるような要素が欲しいということで、このようなモンスターが成長する要素を加えてみました。
モンスターの生成はAIによって行われるので、どんなモンスターが出てくるかわからないというワクワク感と、それに加えて、目標やタスクの内容に沿った姿に進化させることができるので、進化したときに達成感が生まれます。
使用した技術
-
Next.js
フロントエンド -
Ruby on Rails
バックエンドAPI -
Docker
開発環境 -
NLP CloudのClassification
自然言語処理のAPIで、文章からカテゴリを分類してくれる。 -
stability.ai text2image
テキストから画像を生成してくれる。 -
removebg
背景画像を自然に除去してくれる。
処理の流れ
- 目標とタスクのContentをNLP CloudのClassification APIへ投げる。
- あらかじめ用意したカテゴリとの関連度を数値化して返してくれる。
- 選択されたカテゴリに応じたプロンプトをStability.aiのtext2image APIへ投げる。
- 生成された画像がbase64形式で返ってくる。
- このbase64形式の画像をremovebgのAPIへ投げると、背景を自然に除去したbase64形式の画像が返ってくる。
- この画像をsupabaseにアップロードして、そのurlをDBに保存する。
ポイント
Stability.aiの画像生成は、同じプロンプトを投げても生成される画像にばらつきが生じます。
そこで、1度目の画像生成時(孵化)に一緒に返ってくるseed値を、2回目のモンスター生成時(進化)に使用することで、同じ系統のモンスターを成長させて生成することができました。
開発期間中に自分が担当したこと
- ① テーブル設計
- ② API設計
- ③ gitルールの作成
- ④ イラスト生成のロジック
- ⑤ discordとgithubの連携
- ⑥ フロントのコア機能の実装
① テーブル設計
アプリの内容は途中で方向転換したため、初期の設計は全く異なるものになっています。
当初は、大中小の目標作成機能、タスクができていなかったら通知する機能、など多くのアイデアがでていましたが、発表時間が3分であること、チーム全員が仕事をしながらの開発になることから、機能を絞って開発するべきということになりました。
しかし、複雑なテーブル設計を考えた経験は、自分にとって学びになりました。
チーム開発なので、設計した自分だけが理解しているだけではいけません。
チームメンバーに対してなぜこのような設計を行ったのか、理解してもらえるようにスプレッドシートにまとめ、「この機能はこういうロジックで動くと思うので、このテーブルにはこのカラムが必要だと思います。」というような説明を行いました。
その後、テーブル定義書と、ER図を作成し共有しました。
② API設計
Apidogというサービスを使って、API設計をドキュメント化しました。
必要な機能からどんなエンドポイントが必要で、レスポンスにはどのようなデータが必要かを考えながら作成しました。
こちらについても、チームメンバーにどのような考えでこのAPIを作成したのかを共有しました。
また、ドキュメント化するにあたって、「これだけ読めばどんなアプリか想像できる」ドキュメントを目指しましたが、そもそも自分でもこの設計で正しいのか、最後まで自信を持てなかったため、伝わるドキュメントが作成できなかったのが、今回の反省点でもあります。
③ gitルール
前回のチーム開発でも、gitを使って開発していましたが、ブランチの切り方や、プルリクエストのフォーマットなどを決めていなかったため、後からみるとわかりにくいものになってしまいました。
今回は、そのようなことがないように、あらかじめルールを決めておこうということで、そのルールを設定させていただきました。
もちろんチーム開発の経験は今回が2回目なので、ベストプラクティスを探していろんな方のルールを参考に、良さそうなものを取り入れて作成しました。
内容としては、git flowに則ったブランチをベースに、コミットメッセージや、プルリクエストのテンプレートを作成し共有しました。
また、タスク管理についてはgit hubのissuesを使って、そこからブランチを切るという方法で、常にissueとブランチをセットにして開発を進めました。
最終的には、期限が迫っていたこともあり、ルールを守らずにぐちゃぐちゃになってしまったので、ルールを最後まで徹底できなかったことが心残りです。
④ イラスト生成のロジック
今回のコア機能にあたる重要な部分です。
目標に入力した文章をモンスターに与え、そのジャンルに関連した姿に進化させる必要がありました。
ここはAIを使う分難易度が高く、なかなか思った通りのイラストを返してくれなかったため、一番苦労した部分でもあります。
順番をたどると、以下のような流れで最終的に目的どおりの機能を実装することができました。
案① 目標とタスクの内容をそのままプロンプトにしてみる
得られた文章をそのまま英訳してプロンプトに入れることを考えました。
例えば、「英訳された文章 + モンスター生成用のプロンプト」のような構成で投げてみて、それっぽい変化をしたモンスターが生成されてくれないかな?と期待していました。
しかし、当然ですが適当な文章では、思った通りのイラストを生成してくれません。
案② 文章をカテゴリに分けて、プロンプトは用意しておく
文章をカテゴリ分けすることができれば、カテゴリごとにちゃんとしたモンスターが生成されるプロンプトを使えるのでは?と考えました。
そこで次の課題は、「ランダムに与えられた文章をカテゴリ分けする方法」を探すことです。
技術の紹介でも書きましたが、NLP Cloudが提供するAPIに、「Classification」というものがありました。
JSON形式で例えば以下のようなリクエストを投げます。
{
"text": "マラソン大会で優勝する。 10kmランニングする。 プロテインを飲む。",
"labels": ["sports", "study", "love", "money"]
}
テキストと、ラベルを配列で渡しています。
そうすると、以下のようなJSONデータが返ってきます。
{
"labels":["sports","study","love","money"],
"scores":[0.90478515625,0.47119140625,0.358642578125,0.4833984375]
}
これはつまり、送信したテキストとラベルそれぞれの関連度を数値化してくれるのです。
あとは、カテゴリごとにプロンプトを用意しておけば、最も関連度が高いと判定されたカテゴリに紐づくプロンプトを投げればうまくいくはずです。
生成結果
何度もプロンプトを微調整しながら、「sports」「study」「money」「love」の4つのカテゴリを用意し、カテゴリにあったモンスターを生成させるようにしました。
⑤ discordとgithubの連携
apprenticeではdiscordを使ってコミュニケーションをとっています。
チーム開発では頻繁にプルリクやレビューをする場面があって、それをいちいちチャット欄で必要なときにお願いしていました。
そこで、毎回自動で通知されれば便利なのにと思い、そういった方法がないか探してみると、webhookを使って連携することが可能でした。
apprenticeの運営へwebhookの権限を貰えるようお願いし、プルリクやレビューコメントなど、重要な通知に絞って自動で通知してくれるよう設定を行いました。
その結果、面倒な作業を減らし、開発に集中できる環境を作ることができたと思います。
⑥ フロントのコア機能の実装
タスクを達成するとモンスターに経験値が付与され、モンスターが生成されるという部分を担当しました。
Next.jsを使って実装しました。タスクの追加、モンスターの画像、経験値バーなどをコンポーネントに分け開発を進めていましたが、親ページに記述するstateの量が肥大化してしまう問題がありました。
そこで、カスタムフックを作成することで、ロジックを切り離すことができ、各コンポーネントにはカスタムフックから必要なpropsを渡すだけになりました。
カスタムフックについては、それまで使ったことがなかったため、学習コストはかかりましたが、今回の開発を通して、とても理解が深まりました。
反省点
良かったことばかりではなく、まだまだ足りていない部分も見つかりました。
- ① 知識不足によってタスク出しが甘くなってしまったこと
- ② 時間と知識がなく他の人に読みづらいコードを書いてしまったこと
① 知識不足によってタスク出しが甘くなってしまったこと
開発経験の少なさから、作りたいものは決まっているけど、どのようにタスクを分解して、どのように開発を進めていけば良いか、なかなか決まらないまま開発がスタートしてしまいました。
結局、開発中にいくつかのAPIが足りていないことに気づいて、何度か追加や修正をお願いすることがありました。
もっと知識があれば、この機能はこういう風に実装することになると思われるので、必要なタスクはあれとあれだな、という風に考えることができたのではないかと思います。
② 時間と知識がなく他の人に読みづらいコードを書いてしまったこと
開発期間は1週間であり、とにかく急いで動作するところまで仕上げることが求められていました。
急いで開発していたのもあって、自分が担当したモンスター生成部分は動くには動くけど、決して読みやすいコードとは言えないものになっていました。
その結果、タスクを細分化しようにも、自分にしか分からないコードになってしまっているため、自分が最後まで実装するのが一番早いということになり、チームメンバーに協力してもらうことができないままになってしまいました。
本番で動くところまで持っていけたから良かったものの、読みにくいコードを書いてしまうと、結果的に自分の首を絞めることになるし、チームのメンバーにも迷惑をかけてしまうし、何より期限に間に合わなくなってしまうかもしれないということを今回学びました。
まとめ
前回のチーム開発での反省点を踏まえつつ、また新たな反省点が見つかり、とても学ぶことの多い貴重な経験ができました。
わたしたちのチームは、受講生からの評価が最も高いチームとして「Best Student Award」に選ばれました。
もちろん、チーム全員で勝ち取った賞ですので、この場を借りてチームメンバーに御礼申し上げます。(読んでくれているかわかりませんが...)
本当にありがとうございました。
Discussion