【大学生による個人開発】弓道部のためのWebアプリ「HitRate」をリリースしました


# 開発したアプリの概要と仕様
弓道部の運営を楽にするために、オンライン上で部員の的中率（的にあたった割合)や出欠を管理できるwebアプリを開発しました。

**現在は、ありがたいことに自身の大学を含め他大学でも使用していただいています。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/70c338f2-2c7c-2559-842e-fbd7d7dc3cbd.png)

https://atoko-hitrate.web.app/


# 実際のアプリ
**・的中の記録&記録の表示**
通常練習、強化練習、自主練習を選択して練習日、引いた本数、中った本数を決めて投稿できます。
<img width="300" alt="records.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/d606dba7-551a-8d25-7006-29587c539379.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img width="300" alt="records.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/1eda5ea8-a722-cf04-6dd7-9151be07d1f5.png">

**・欠席連絡投稿**
遅刻、早退、欠席を選択して練習日、欠席理由、幹部のメールアドレスに送信するかを決めて投稿できます。
<img width="300" alt="attendance.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/3908a03a-b7e0-9ef9-7cf8-282a8a1f11fb.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img width="300" alt="attendance.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/57a2ff31-e494-b6c8-dc09-83808325b9ed.png">


**・部内連絡投稿**
連絡項目を選択して、タイトル、内容、画像、部員のメールアドレスに送信するかを決めて投稿できます。
<img width="300" alt="attendance.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/6561b7fe-9d35-5871-b0c6-4387be3f32ef.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img width="300" alt="attendance.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/45c1429f-56d9-3519-ddb3-270c0eff25e0.png">

**・ランクやバッチ**
通常練習の月間的中率からランク画像、特定の条件で出現するバッチなどを見ることができます。
<img width="300" alt="rank.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/c1156d67-734d-99ea-4df4-4f268f27685a.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img width="300" alt="rank.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/151745fa-1b12-b99a-052c-9efbca4766b5.png">

具体的にできることを以下にまとめました。

**的中関係**
・的中率の記録
・個人の的中管理(通常練習、強化練習、自主練習ごとにグラフやカレンダー、簡易的なAIによるコメントなど表示)
・部員ごとに的中を表示(ソート、期間ごとに表示、グラフやカレンダーで的中率の変化など確認可）
・部内での順位や獲得したランクやバッチの表示
・自身の記録（グラフ、カレンダー、的中表、AIによるコメント）を画像として保存

**出欠関係**
・出欠の記録(理由つき、幹部のメールアドレスに一斉送信も可)
・早退、遅刻、欠席予定を日ごとに分けて表示
・部員ごとに過去の出欠を表示

**連絡関係**
・連絡投稿(画像の添付、部員のメールアドレスに一斉送信も可)
・連絡のタイムライン表示

**ユーザー関係**
・パスワード、名前変更
・的中率の非公開設定
・ユーザーの承認、非承認

**SNS関係**
・その日の的中をAIのコメントとともにtwitterでシェア


# 開発背景
私は、大学で3年間弓道部に所属し、3年生時には男子副将をしました。

その時の私の主な仕事は次の二つあり、次のように運営していました。

**・月ごとに、部員全員分の的中率（的にあたった割合）をまとめる**
<img width="500" alt="records.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/cd79e254-5da5-4cb7-e022-6c8dbbb61938.png">

**・部活中の運営、欠席遅刻連絡から出席部員の状況を把握**
<img width="500" alt="records.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/d2d41191-6270-72f9-12bb-672bef1ed6f7.png">

ただ、**この運営法ではいくつかの問題がありました。**

**エクセルによる部員の的中率の集計**
・手作業で行うため、集計ミスが起きる可能性がある
・月ごとに集計するため、次の翌月まで、途中記録が全員に共有されない
・特定の○週間だけの記録を見たい時、再計算する必要がある
・本数順など特定のソートができない
・集計作業が4,5時間かかる

**メールによる欠席遅刻連絡**
・連絡が順番に並んでないため、誰が休みで、誰が遅刻など確認しづらい。
・部員の過去の出欠率が分からない


私の部活では、この集計から試合のメンバーを選考するため、集計ミスがあってはいけません。そのため、できるだけ手作業による集計は避けるべきです。また欠席連絡に関しても、メールアプリでは誰がどれだけ休んだかを管理できない状態だったので、改善が必要でした。

そこで、**部員の的中率や出欠などを管理するアプリ**を開発しようと考えました。また、**部全体のレベルが上がるために、記録の管理だけではなく、練習意欲を掻き立てるようなゲーム的要素も入れることにしました。**

**現状、的中率や出欠を管理できるアプリが存在しなかった**ことと、**代々引き継がれてきたこのめんどくさい仕事を後輩に引き継がせるのが嫌だった**こともあり、開発に踏み切りました。

# アプリの仕組み
開発したwebアプリの仕組みは以下のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/09555e92-4f1e-69fa-cf05-32d6dd85443e.png)


＊本アプリでは、アカウント作成時に所属大学（チーム）を決めて登録していただくようにしています。アカウント作成時に登録された大学の幹部部員に通知が行き、承認することで、そのユーザーが参加できるようになります。**承認性にすることで、他の大学の部員が混じることがないようにしています。**

# 使用した技術(フロントエンド)

### ・Material-ui v4.9.14

https://material-ui.com/

UIは、Googleが提唱するデザインフレームワークであるMaterial Designに沿って作成しました。Material Designは、YoutubeやGoogle Mapで使用されていて、ユーザーに馴染みのあるデザインだと思い、使用しました。継続的にアプリを使うことになるので、できるだけ疲れにくく直感的にわかるシンプルなデザインにしたかったのも決めての一つです。


### ・Gatsby.js v2.21.21

https://www.gatsbyjs.com/

Gatsby.jsはReactで作られた静的サイトジェネレーターです。
**サーバー構築なしで爆速のサイトが生成でき、OGP設定も簡単**なので、Gatsby.jsを使用しました。

Reactで構成されたSPA(シングルページアプリケーション)は、ブラウザ上で実行されるJSからページの描画に必要なDOMを生成します。ブラウザ側はJSファイルがダウンロードされるのを待つ必要があるので、JSファイルのサイズが大きいと、最初のレンダリングが遅くなります。その代わり、一度読み込むとその後のページ遷移が速いというメリットがあります。

一方で、SSR(サーバーサイドレンダリング）は、htmlファイルを予めサーバー側で生成し、ブラウザに返す手法で、最初のレンダリングが遅いことを解決する手段として用いられています。ただ、デメリットとして、サーバーの構築が必要、サーバーに負荷がかかるという特徴があります。

それに対し、**Gatsbyは、サーバー上ではなく、ビルドマシン（PC)でビルド時に表示したい画像やデータをまとめて静的HTMLを生成します。そして、初回のレンダリングは、この用意されたHTMLを使うため読み込みが早く、2回目以降のレンダリングは、Reactアプリ同様に再度仮想DOM更新を行い、SPAとして実行してくれるためページ遷移が速いです。**

ただ、GatsbyはAPIリクエストを頻繁にするサイトには向かないので、本アプリでは、極力APIリクエスト数を減らした作りにしています。

### ・react-swipeable-views v0.13.9

https://react-swipeable-views.com/

**react-swipeable-viewsは、スワイプも可能なスライドの実装が簡単にできるReactのライブラリです。** cssのtransitionでスライドを実装するよりもずっと楽です。

パスワード変更を作る際に、Googleのパスワード変更画面のようなスライド形式のUIにしたかったので使用しました。

<img width="400" alt="password.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/0787ac98-75bb-0e7f-4f7c-0837c27b2c10.gif">

### ・Recharts v1.8.5

https://recharts.org/en-US/

Rechartsは、折れ線グラフ、棒グラフ等を簡単に実装できるReactのライブラリです。大体のグラフはこれで実装できます。なおかつレスポンシブにも対応しています。

弓道はその日の調子によって、的中率が普段よりブレることが多いスポーツです。**日ごとの的中率をグラフとして可視化することで、自分が、どういう日によくはずしているか、的中のばらつきが多いかなど分析することができます。** 部員の的中管理だけではなく、個人の成長に生かせるように、日ごとの的中率をグラフで表示しています。


<img width="400" alt="password.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/8a512097-d4c6-2974-71c4-9bf2a48e3c47.gif">


### ・html2canvas v1.0.0-rc.7

http://html2canvas.hertzen.com/

html2canvasは、WebページのDOMやCSSを読み込みその結果を画像に変換するライブラリです。

自分の各月の記録（画面）を画像として保存する機能に使用しました。PWA化しているのでフルスクリーンスクリーンショットできないため必要でした。

保存できる画像↓
<img width="200" alt="savePic" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/489ed3a3-aaa0-befc-7005-2f5032374b91.png">



# 使用した技術(バックエンド)
### ・Firebase

https://firebase.google.com/

Firebaseは、バックエンドで行う機能を提供するクラウドサービスです。

本アプリでは、ホスティング、データベース、データベースへの接続に使用する関数においてFirebaseを使用しています。

ホスティングは、Gatsbyと相性のいいNetlifyの使用を考えましたが、こちらの配信速度を比較した実験からFirebaseの方が早いという結果だったので、Firebaseでホスティングすることにしました。Firebaseの方が早い理由として、[fastly](https://www.fastly.com/)という非常に優秀と言われているCDNが使われていることが挙げられるようです。（*ただ、試しにNetlifyでもホスティングしてみましたが、あまり速度の違いは感じませんでした。）

https://www.savjee.be/2017/10/Static-website-hosting-who-is-fastest/

Cloud Functions APIの使用についても無料枠が多いのと従量課金制と使い勝手がいいので、バックエンドは、Firebaseに全まかせしました。

Cloud Functions APIの制約↓
>https://cloud.google.com/functions/pricing?hl=ja

>呼び出し
関数の呼び出し料金は定額制です。HTTP リクエストから呼び出される関数（HTTP 関数）、バックグラウンド関数、call API から行われる呼び出しなど、呼び出し元によって料金が変わることはありません。

| 月間呼び出し回数 | 料金（100 万単位） |
|:--|:--|
| 最初の 200 万回 | 無料 |
| 200 万回を超えた分 | $0.40 |
>米ドル以外の通貨でお支払いの場合は、Cloud Platform SKU に記載されている該当通貨の料金が適用されます。
呼び出し料金は 1 回あたり $0.0000004 の単価制で、関数の結果や実行時間に関係なく請求されます。ただし、毎月最初の 200 万回までは無料です。



### ・nodemailer v6.4.6

https://nodemailer.com/about/

nodemailerは、node.jsからメール送信を可能にするモジュールです。

メールアドレス認証、確認コードの送信、アプリ内の連絡などでメール送信が必要だったので使用しました。また、現状webアプリではios端末において通知ができないため、メールを通知の変わりとして利用しています。

<img width="300" alt="attendance.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/47249ee0-7c68-a7ea-3bcf-93033fcc0349.png">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img width="300" alt="attendance.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/da59c0e2-526c-2461-813b-40197228f2fb.png">

### ・crypto v1.0.1

https://nodejs.org/api/crypto.html

cryptoは、Node.jsをインストールすれば使用でき、特定のアルゴリズムでハッシュ化できるモジュールです。

**本アプリに登録されているユーザーのパスワードは、パスワードそのものではなく、パスワードのハッシュ値を保管するようにしています。** ハッシュ値から元データに復元することはできないため、万が一第三者にパスワードが盗まれたとしても不正ログインを防ぐことができます。名前や大学などの個人情報を用いているため、セキュリティ強化の観点から実装しています。


# 力をいれたポイント
### 1,統一感のあるUI/使いやすくストレスを感じないUX
UIは、**Materialデザインを勉強し、その規則に乗っ取ってアプリの開発を行うことで、統一感のあるUIに仕上げました。** また、PC、タブレット、スマホどの端末においてもデザインを統一しつつ、レスポンシブを実現させました。その理由として、ユーザー視点で考えた時に、PCとスマホで操作性が異なると違和感を覚えてしまうのではないかと考えたからです。

UXは、**自分が実際に使用して使いやすくストレスを感じないアプリを研究してそれを真似しました。**

具体的には、

#### ・別のボトムタブにてアクションした際に、その投稿が反映されるページに自動遷移
アクション後の自動遷移は[うんログ](https://unlog.co.jp/unlog/)アプリの一部で実装されています。個人的に、アクション後のユーザーの動きを予想した作りになっていて、使いやすいと感じ取り入れました。
<img width="400" alt="ux1.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/8d62b79d-a8ca-277e-7e9f-52390e02717b.gif">

#### ・待機時間のアニメーション表示、アクション完了後のメッセージ表示
ローディングのアニメーションがないアプリに遭遇した時に、本当に更新されているのか不安になり、ストレスを感じたことがありました。そのため、どんな些細な更新処理でも待機時間のアニメーション表示、アクション完了後のメッセージ表示は確実に表示するようにしました。
<img width="400" alt="ux1.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467535/e6bcad89-d2d5-065b-b01a-2d47329d9719.gif">


### 2,PWA(ネイティブアプリの動作実現）
主に学生を対象としたアプリであり、スマホで操作することが多いと予想できるため、ネイティブアプリのような動作を追求しました。その中でも、ネイティブアプリのような動作を実現できるPWAという技術に目をつけ、実装しました。

GatsbyでPWA化した時の恩恵、方法についてこちらに詳しくまとめているので、気になる方がおられましたらご覧ください。

https://qiita.com/citron1605/items/1015db523feb4fdeec81

最初からネイティブのコードで開発しなかったのは、以下の理由です。

**・開発に時間がかかる**
私は、昔webサイト制作に興味を持ち、Reactを用いて簡易的な弓道部のホームページを作ったことがありました。今からネイティブ言語を勉強するよりも少しでも知見のあるwebサイトベースで作った方が、開発しやすいと思いました。

**・アプリストアに手数料が取られるのが嫌**
ネイティブで開発したアプリをアプリストアでリリースするには、お金がかかります。また、収益化時も売り上げの一部をストアに支払わなければいけません。将来収益化した時にの利益率を考えた時にwebベースのほうがいいなと思いました。


### 3,セキュリティ面
個人情報を取り扱うので、セキュリティ面を強化しました。例えば、パスワードのハッシュ化や不正登録を防ぐために仮登録メールに載せるリンクのパラメータの暗号化を行いました。暗号化は、すでにある暗号化方式とオリジナルのkeyを用いました。

```node.js:
//暗号化
const encrypted = (text) => {
  let buf = Buffer.from(orignal_key)
  let cipher = crypto.createCipheriv(method, Buffer.from(encryption_key), buf)
  let encrypted = cipher.update(text)
  encrypted = Buffer.concat([encrypted, cipher.final()])//cipher.final()のreturn値は残りの解読されたコンテンツ
  return encrypted.toString(encoding)
}

//復号化
const decrypted = (text) => {
  let buf = Buffer.from(orignal_key)
  let encryptedText = Buffer.from(text, encoding)
  let decipher = crypto.createDecipheriv(method, Buffer.from(encryption_key), buf)
  let decrypted = decipher.update(encryptedText)
  decrypted = Buffer.concat([decrypted, decipher.final()])
  return decrypted.toString()
}
```

# 開発中の反省点
### 1、入念な仕様決め、ワイヤーフレーム作成をしなかったこと

個人で開発しているということもあり、欲しい機能を思いついた瞬間、細かい仕様を決めずにコーディングしていました。そして、よく完成した後に、"やっぱりこのボタンはこっちの配置がいいな、ここはこういう仕様の方が便利だな"と、修正をすることが結構ありました。小さなUIの変更でもある程度のワイヤーフレームを作り、仕様を変更する際は、本当にこれでいいのかを何回も考えることが大事だと気が付きました。**今は、効率よく開発するためには、実際にコードを書く時間以上に仕様決めに時間をさくべきだと考えています。**

### 2、使用技術の調査を怠ったこと
以前までは、Google Apps Script（GAS）とスプレッドシートをバックエンドとして使用していました。そのため、部員一人一人のシートを用意する必要がありました。全てのシートにアクセスするとGASの実行時間がどうしても長くなってしまい、待機時間が長くなってしまったので、バックエンドをNode.jsとCloud Firebaseに移行するという大規模な変更をしました。

その時の処理速度の違いを書いた記事です↓

https://qiita.com/citron1605/items/03be4a5014557c1d6ac2

**もっと早い段階、技術選びの時から、ユーザー数が増えた時にこの使用技術で問題ないか考えていれば、大規模な移行作業などする必要がなかったと思います。**

勉強になったので、時間の無駄とは思いませんが、正直大変でした。

### ３、リファクタリングを意識しなかったこと
長い間、このアプリの開発をすると思っていなかったので、作り始めた当初は、関数を柔軟に使いまわせるにように工夫しなかったり、共通設定を一つのファイルにまとめるということをしていませんでした。そのせいで、追加機能や仕様の変更をする際に、全て変更しなければいけないという状況になり、開発のスピードがグンと落ちていました。**開発スピードの向上、変更漏れなどによるバグを生まないためにもリファクタリングは常に意識して開発するべきでした。**

# 最後に

弓道部の運営問題を解決できたと同時にアプリ制作を通して多くのことを学ぶことができました。何よりも**ユーザーに使用され便利と言ってもらえた**ことが嬉しく、開発してよかったと感じています。

また、このプロダクトは個人で開発しましたが、実際はリリースするまでに多くの人と関わりました。エンジニアインターン先の社員さん、部活の後輩達、他大学の弓道友達から沢山意見をいただき、出来上がったアプリだと思っています。


これからもアップデートを行い、多くの弓道人に愛用されるアプリにしていきたいと思います。**引き続き、利便性だけではなくHitRateを用いることで弓道レベルが向上できるようなアプリにしていきたいと考えています。**

HitRate開発までの記録やアップデート内容はTwitterに載せているので、気になる方がおられましたらご覧ください。

https://twitter.com/atoko_hitrate

読んでくださりありがとうございました。