見出し画像

大学祭アプリ開発経験

自己紹介

名古屋工業大学のKey5nです。
2022年11月19(土), 20(日)の工大祭(名古屋工業大学の大学祭)にて予約システム兼お役立ちアプリの工大祭アプリの開発をしたのでそれの開発経験をまとめます。
僕が担当したのはPM、デザイナー、ウェブ全部です。
よろしければお付き合いください。

大学祭アプリについて

今回作成した大学祭アプリは、予約システム兼お役立ちアプリです。
次の5つの機能があります。

  • 大学祭の参加予約

  • イベントの詳細情報確認

  • イベントタイムスケジュール確認

  • お化け屋敷との連携

  • 大学内マップ

分類すると次の2つにわかれます。

  • 大学祭に入場するための予約機能

    • 大学祭の参加予約

  • 大学祭を楽しんでもらうための機能

    • イベントの詳細情報確認

    • イベントタイムスケジュール確認

    • お化け屋敷との連携

    • 大学内マップ

下の画像のように予約、キャンパス内のマップ、ステージのタイムテーブルなどの機能があります。
さらにAndroid版を開発してくださった方が分かりやすいスライドを作成してくださったのでぜひご覧ください。

大学祭アプリの概要図


メンバー

  1. 僕(PM、デザイナー、ウェブフロントエンドエンジニア)

  2. A(メインiOSエンジニア)

  3. B(サブiOSエンジニア)

  4. C(Androidエンジニア)

  5. D(僕の相談役)

使用した技術

  • iOS: Swift + Swift UI

  • Android: Kotlin (Jetpack Compose)

  • ウェブ: Next.js + TypeScript

  • バックエンド: Firebase

Firebaseを採用した理由

今回モバイルエンジニアの2人がFirebaseを使って今まで開発していたので、今回Firebaseを使うことにしました。
従量課金制なのでサーバーが落ちる心配はかなり少ないです。

大学祭アプリを作ることになったきっかけ

  1. 「なんでこのアプリを作ったのか」

  2. 「スマホアプリじゃなくてウェブでいいのでは?」
    と疑問に思った方も多いのではないでしょうか。
    それぞれお答えします。

1. この予約システムアプリを作った理由

その原因は2つあります。
1つ目がコロナです。
「大学祭でクラスターが発生したときのために、接触の疑いがある人をトラッキングできるようにしろ!入場制限をかけろ!」と大学からお達しがあったため大学祭に予約システムを導入することになりました。

2つ目が処理の自動化のためです。
このアプリ以前のコロナ1年目の大学祭は完全予約制でした。
何でもそのときはGoogle FormでデータをSpread Sheetに集め、入場者が来るたびにSpread Sheetから名前をいちいち参照して予約確認をしていたらしいです。
しかもGASはメール送信上限が一日100通までなので予約の返信は手作業だったそうです。
当時は大学祭前日にGoogle Formから2週間分の体温を送信する必要があったため、当日は「体温チェックのフォームが送られてないです」「メールが届かないです」などの問題が多かったらしいです。
流石になんとかしたほうがよいと思い、予約システムを構築することになりました。

2. ウェブアプリじゃなくてスマホアプリを作った理由

スマホアプリを作ることにした理由は、スマホアプリだと入場券の画面を瞬時に開くことが可能だからです。
去年の反省点として、当日の受付処理が手間取ったという点があります。
まず第一に、大学祭には子供からお年寄り、家族連れといった幅広い年齢の方が来られます。
それを踏まえると入場処理はIT疎い人でもわかるくらい簡単にする必要がありました。
ウェブアプリケーションにするとブラウザを開く必要があるため、スムーズな受付処理を妨げかねません。
それを考慮すると、わかりやすくてすぐに入場券を開くことができるスマホアプリが最適だと判断し、最終的に採用しました。

GASは迷惑メールに分類される可能性があるためユーザーフレンドリーではありません。
PWAは知名度が低いため採用を見送りました。
どこかの高校の文化祭でPWAを使った、みたいな記事を読みましたがが、「認知させるのが大変だった」と述べていたので、僕はスマホアプリで良かったと思ってます。たかが大学祭ごときで作るものではないと思いますけど。

当日はなんと嬉しいことに列処理はスムーズで、さらに受付トラブルが一度もありませんでした。
優秀なチームメンバーに感謝です。

反省点

というわけで反省点をまとめていきます。
僕の仕事は次の3つでした。
それぞれ語っていきます。

  1. PM

  2. ウェブ

  3. デザイン

PM

Product Managerです。大規模開発となると誰かが務めなくてはならず、さらに僕は表に立ってまとめることができる人間なので僕が今回5人チームのPMを務めました。PMとして意識したのは

  1. 迅速な連絡をすること

  2. 納期に絶対に間に合わせること

  3. エンジニアがソースコードを書く以外の仕事をしないようにすること

  4. 適度に進捗管理をすること

  5. ユーザーが求めてるものを的確に実装すること

です。特に2の納期に絶対に間に合わせることが大変でした。
というのも今回のプロジェクトは開発期間が3ヶ月だったからです。
僕が何度も機能をつけ足したりデザインを変えたりしたので仕様変更は毎週のことでした。
そんな雨のように降ってくる仕様変更にもついてきてくれた開発メンバーにはとても感謝しています。

次に意識したのは3のエンジニアがソースコードを書く以外の仕事をしないようにすることです。
僕はエンジニアはソースコードを書く以外の仕事をしたくないものと思っています。
そのためPMとなったからにはユーザーの目に触れるモバイルエンジニアの方々には自分の仕事に集中してもらえるように、僕はソースコードを書く以外のすべての仕事を持つように心がけていました。
そんな感じでPMを務めていました。

PMとして悩んでいたことは進捗管理をどれくらいの頻度でどのようにするかということです。
僕はできる限りソースコードを書くことだけに集中したいです。
そのためできる限り進捗報告で余計な時間を使わないようにしました。
ですが進捗報告の時間を割いてよかったな、と思う理由は次の2つです。

  1. 開発スケジュールを組む必要があるから

  2. エンジニアがぶつかっている障害を取り除く必要があるから

特に2つ目の理由の存在が大きかったです。
僕はできる限り連絡をSlack内で完結しようと心がけていました。
ですが困りごとってSlackじゃ伝えにくいと思います。
そのため適切な頻度でミーティングを設けることでエンジニアが直面している課題などを共有しあう時間を設けました。
他にもiOSとAndroidで細かな仕様を統一するのにもミーティングは役立ちました。

ウェブ

ここまでPMの話をしてきましたが、ここから僕の本業の話をします。
僕はウェブフロントエンドエンジニアです。
今回はスマホアプリがメインなため、ウェブ側はスマホアプリよりもウェブのほうが向いていることやスマホアプリ側で表示するための情報を管理者側
から入力できるようにしました。

使用した技術(フレームワーク)

  • Next.js

  • TypeScript

  • CSS Modules

  • Redux

  • Firebase

基本的にはこの4つです。
Next.jsを使用した理由は、SSGとSSRの両方を使用したかったからです。プライバシーポリシーやよくある質問など、静的サイトを作成するのにSSGを利用したかったため、Next.jsを採用しました。
静的サイトを作成するだけならReact専用のSSGであるGatsbyでもできますが、後述する「企画との連動」でSSRをするのでGatsbyでは力不足という判断です。

TypeScriptを使用した理由はDXの良さです。
まぁTypeScriptが使えるならTypeScriptを使ったほうがいいですね。
(どこかのOSSフレームワークがTypeScriptではなくJavaScriptを採用していると聞いたような気が)

CSSについては Tailwind CSS、 styled-component、emotionなどいろいろありますよね。僕は今回CSS Modulesを使用しました。
まぁぶっちゃけ Zero-runtime であればなんでもいいです。というわけでstyled-componentとemotionは脱落。
Tailwind CSS と CSS Modulesのどちらを使うかは悩みどころでした。
Tailwind CSSは流行っており、さらにユーティリティクラスでスタイリングするため容量が小さいというメリットがあります。
Tailwind CSSでもパフォーマンス面に申し分ないのですが、今回はCSS Modulesを採用しました。
これは僕が「素のCSSを書け」志向が強いからです。Tailwind CSSはスタイルを仮想DOMにインラインで書くので、疑似要素が現れると見た目がまー地獄。そういう意味で個人的にはCSS Modulesのほうがスタイルとロジックを分離できるので取り掛かりやすいです。

CSS ModulesのメリットはNext.jsのドキュメント曰くビルドが早いことですね。あとはほぼ素のCSSだということ。
デメリットはコンポーネントの数だけファイルを作る必要があることです。
コンポーネントファイルを作成するたびに毎回 hoge.module.cssを作成しなくてはならないので少し面倒です。自動化させればいくらかましですけど。

あと僕がCSS Modulesを使っているのは Takepepe さんの影響が強いです。
TakepepeさんのZennの記事にはとてもお世話になっています。(技術記事書いてくださるエンジニアの皆様に感謝。)
ですが残念なことに、CSS Modulesを使用している方は少なく、参考にできる方がそこまでいらっしゃいませんでした。
なぜこんな人気ないんですかね。以前CSS書けないフロントエンドエンジニアの話とかありましたが、それってCSS in JSに注目しすぎだからじゃないか?🤔と思ってます。

CSS Modulesはほぼ素のCSSなので、昔の流れを踏襲できるのがよいところだと思います。
Vanilla JSやJQueryの時代はJavaScriptでDOMのクラス名を命令的につけたり、外したりする時代でした。
Reactのような宣言的UIの時代でも、DOMのクラスをつけたり外したりするにはJavaScriptの演算でできますし、とっつきやすいのは間違いないと思います。
ちなみにクラスをつけたり外したりするには clsx というライブラリを使うとやりやすかったです。
classnamesの高速で軽量な代替として作られました。
Material-UIもバンドルサイズを小さくするためにこれを使っているらしいです。
もちろんTailwind CSSが良いところも理解していますし、今後仲良くしていきたいです。

僕がCSS Modulesで最初迷っていたことは、どのようにアーキテクチャを組むかですね。
CSS Modulesにcomposesという、CSSにはないプロパティがあることをご存知でしょうか。
これはいわゆるSassの@extendsです。少しだけ違いがありますが。
CSS Modulesの開発者のGlen Maddernさんが書いたCSS Modulesの紹介の記事(2015年)の中にも紹介があります。
僕はCSS Modulesのベストプラクティスを探しているうちにこの記事に遭遇しました。
このcomposesを使ったものが次です。

.article {
  composes: flex vertical centered from "./layout.css";
}

.masthead {
  composes: serif bold 48pt centered from "./typography.css";
  composes: paragraph-margin-below from "./layout.css";
}

.body {
  composes: max720 paragraph-margin-below from "layout.css";
  composes: sans light paragraph-line-height from "./typography.css";
}

これ何かに似てませんか?これはユーティリティクラスを自作しています。つまり TailWind CSSもどきです。Glen Maddern さんはこの設計について次のように述べてます。

This is a technique I’m really interested in exploring further.
(これはもっと探索したくなるテクニックだ)

https://glenmaddern.com/articles/css-modules

ユーティリティクラスを予見していたのか…
流石です。
ということで真似してみました。VSCodeに書いてみた。

CSS Modulesのアーキテクチャの1例

文字のユーティリティクラスと、色のユーティリティクラス、他にはレイアウトのユーティリティクラスに分け、必要なクラスだけを引っ張ってくるというものです。
使った感想ですが、とても後悔しました。
なぜかというと、composesプロパティがものすごく横に伸びます。
しかもPrettierはCSSが長くても自動で折り返ししません。
そのため見栄えが最高級に悪かったです。
あとはユーティリティクラスの名前を変えるとcomposeしているほうの名前も変える必要があるためとても面倒でした。
これやるならTailwind使ったほうがいいのでは?

話が脱線しました。
僕はCSS Moduleが好きです。Next.jsの公式に推されていますし、CSS in JSのような気取った書き方をしなくてよいですし。
ですがそのうちNext.jsでTailwind CSSがデフォルトになるとか

状態管理ライブラリにはReduxを使用しました。
今までuseStateとContext APIを使用していましたが、やはりpropsバケツリレー(props drilling)はコンポーネントが密結合になり、再利用しにくいので本格的な状態管理ライブラリを使用しました。
1年ほど前はReduxが分からなくてダウンしましたが、概念を知ると容易に用いることができました。
他の状態管理ライブラリといえばRecoil、Jotai、XState、そしてZustandです。
どの状態管理ライブラリか、迷いました。
僕的には1番がRedux、2番は簡潔なJotai、または軽量なZustandです。
まぁ今回はダウンロード数が圧倒的ということでReduxを使用しました。


状態管理ライブラリのダウンロード数

Reduxを使用した感想ですが、Fluxアーキテクチャが良いと思いました。
Actionを発火するという概念が好きです。
ですが同じFluxアーキテクチャのライブラリと比較すると、ソースコードが少し複雑で、簡潔さを望むならjotaiやZustandを使うべきだと思いました。
次はこの2つを真剣に検討してみたいです。

バックエンドはモバイルと同じFirebaseを用いてます。
ウェブの担当は以下の3つです。

  1. 入場処理(QRコード読み取り)

  2. 企画の情報入力

  3. 静的サイトの作成(プライバシーポリシーやよくある質問)

入場処理

予約された方にはユーザーIDが入ったQRコードが渡されます。
そのQRコードを読み取り、ユーザー情報を確認し、予約した日に入場処理を受けると入場可能判定となります。
noteに動画を載せられないのでcanvaに載せました。

https://www.canva.com/design/DAFh12KGrVg/vDktW6VGiUpFmMndn2QceA/watch?utm_content=DAFh12KGrVg&utm_campaign=designshare&utm_medium=link&utm_source=publishsharelink

余談ですが、古い方のフローティングナビゲーションは本番の5時間前(深夜5時)に完成しました。
PMやっていたおかげでウェブの開発スケジュールが遅れてしまっていたのが原因です。
スケジュールが遅れに遅れていたので当時はものすごく大変でした。

さらに余談
大きな不満があります。
それはLINEのウェブビューからカメラを起動することができないことです。
前日のテスト日、運営用のLINEグループにこのQRコード読み取りのリンクを渡したのですが、誰もカメラが起動できない始末。
調べたらウェブビューからカメラを起動するにはウェブビューからカメラ起動を許可する設定が必要らしいです。
Discordのウェブビューは起動できましたがLINEだとできませんでした。
変な知識を持てて嬉しかったです^^

静的サイトの作成

プライバシーポリシーやらよくある質問やらをNext.jsのSSGを用いて作りました。
中身はMarkdownにして、unifiedとAST、HTML変換のプラグインとremark-tocで目次付きの静的サイトを作成しました。
この辺は変更が即座に可能なウェブで担当しました。

大変だったこと

時間が足りない
僕はPM、デザイン、ウェブ全部を担当しました。
スマホアプリが本番だったので、PMとデザインの仕事だけが先行し、ウェブになかなか手がつけられませんでした。
完全に僕が仕事を請け負いすぎたなと思ってます。
今回の開発で危機が2回ありました。
1. ネットチケット販売サービスe+との提携
2. 大学、コロナ規制の手のひら返し

第1の危機:ネットチケット飯場いサービスe+との提携
今回外部からバンド団体を3組、そして声優を1名大学にお呼びしました。
そのイベントは事前にネットでチケットを購入するというものです。
このネット販売にe+というチケット販売サービスを使っていたのですが、これが地獄でした。
このe+はAPIがありません。
今回作るスマホアプリは予約システム兼便利アプリです。
APIがないのならどうやって対応するんだ、とチームメンバーと激しい議論を繰り広げました。

途中まではチケット購入者専用の予約用のハイパーリンクを用意し、e+の自動返信メールにそのリンクを記載することを考えてました。
ですがメールだと見過ごす可能性があり、悩んだ結果最終的にはチケット用の枠600を予約可能数2000から差っ引くことにして解決しました。
欠点はチケットが売れ残ったときに枠が減ってしまうところです。
ですがこうするとチケットを購入した人はそれだけで大学祭に予約したことになり、メールの見落としのような事態もなくなるのでこれで大丈夫だろうと言う結論に至りました。
実際には通常予約は埋まりましたが、チケットは数枚余ってしまったらしいです。
メールのハイパーリンクによる予約が見落とされるリスクと天秤にかけたら、数人予約できないくらいならましかもしれません。
これが第1の危機でした。

第2の危機:大学のコロナ規制手のひら返し
第2の危機が、大学によるコロナ規制の緩和です。
もともとこのアプリは大学祭実行には大学側が「来訪者にコロナ陽性者がでたときのために、大学祭内で来訪者が何の企画にいつ参加したのか確認できるようにしろ」というためのものでした。
ですがなんと9月初頭に、大学側から「コロナ収まってきたし、企画にいつ参加したとかもういらないから、基本的な予約だけでいいよ!」というお達しが来ました。
最初は内部が企画のidであるQRコードをそれぞれ企画の場所に用意して、来訪者が参加する企画のQRコードを読み取ってどの企画にいつ参加したのか記録という予定でした。
ですが全部ぶっこわれたので1ヶ月やってたことはほぼ無駄になりました。
8月ナンマンダブ。
開発中のチームメンバーに「今やってること使わなくなるかもしれないので一旦開発ストップでお願いします。」と言わざるを得なかった僕の気持ちがわかりますか。
これが第2の危機でした。

カメラの取得
カメラの取得が思ったよりも大変でした。
カメラはブラウザ、端末ごとに差異があるからです。
何事もなければよかったのですが、残念ながらテストプレイ中に「ChromeやSafariではカメラが取得できるが、Firefoxだとカメラ取得できない」という自体が発生してしまいました。
それだけならまだ他のブラウザを使用することで解決しました。
さらにこの場合はブラウザがエラーを吐いてくれるのでまだマシでした。
だがしかし、スペックが低いスマホでカメラ取得にエラーが生じないにも関わらず、カメラが映されない事態が発生しました。
こんなお手上げな事態が起きるのは予想外でした。
よくゲームで「推奨スペックは〜」のようなことを見てましたが、実際にも起こるのだなと心に刻まれました。

ウェブ版UIがひどい
今回デザインは僕がすべて担当しました。
というのもデザイナーがいなかったことと、仕様設計も僕がやっていたのでデザインも僕がやったほうがええやろ!という回転焼きのあんこよりも甘い考えが原因です。
そしてスマホ版のデザインはやったのですが、ウェブのデスクトップ版のデザインを考えておりませんでした。
というわけでとてもひどかったです。
やはりウェブ用のレイアウトとスマホ用のレイアウトって違うんだなと思い知りました。
例えば固定のフッターはスマホアプリでは定番ですが、ウェブではめったに見ません。

トーストナビゲーション
自動削除の他にも任意のタイミングでxボタンを押して削除可能にしました。
世の中にはreact-toast-notificationやreact-toastifyなどがありますが、これくらい自力で実装しろよと思い、このサイトを参考に作りました。
この参考にしたサイトのソースコードが不要なuseEffect盛り盛りでひどかったので自力で実装しなおしました。
いくら何でもpropsの値で状態を初期化するのに、その状態更新を副作用で行うのはおかしいです。
QRコード読み取りの画面で使用されているので見て下さい。

悩んだこと

  1. コンポーネントアーキテクチャ

  2. Reduxでそのページだけグローバルな状態を定義したい

  3. ファイルの命名規則

  4. 型ファイルの置き場所

コンポーネントアーキテクチャ
次の2つで迷いました。
1. Atomic Design
2. Feature駆動
僕も最初はAtomic Designを使用していました。
なぜなら僕が参考にしたTakepepeさんや「りあクト!」の大岡さんはAtomic Designを採用していたからです。
ですが僕はAtomic Designがどうも苦手でした。
それはmoleculesとorganismsの分類や、moleculeのコンテナはmoleculeなのかorganismで迷って時間を浪費していたからです。
しかもmoleculesのディレクトリを見て互いにmoleculeというだけの特に関係ないコンポーネント同士が並列的に並ぶのが乱雑に感じました。

そんなときに出会ったのが"React Screaming Architecture"という記事でした。
ざっくりいうと機能駆動なディレクトリ構成にしようぜというものです。
Atomic Designだとファイルをどこに置くか考えるときに、「Atom」「Molecule」「Organism」「Template」「Page」のどれかで判断する必要があり、分類にとても悩みます。
それに対し機能ごとにディレクトリを分けると、判断基準が「その機能にローカルなコンポーネントか」「グローバルなコンポーネントか」というものだけで非常に明確です。
Atomic Designよりも設計が甘く感じるのが難点ですが、これからなんとかしたいです。

Reduxでそのページだけグローバルな状態を定義したい
例えばモーダルウィンドウのようなそのページ内のコンポーネントだけ自由にインポートしてこれるような状態管理をしたいときどうすればいいでしょうか。
それを調べた結果、なんとReduxとReact Contextを併用するという選択肢があることを知りました。
「状態管理ライブラリを複数使用していいのか?相互の状態の調整が必要になって複雑にならないか?」と、これを見つけたときとてもびっくりしました。
あとReact Contextを使用する以上、Fluxアーキテクチャではなくなります。
嫌な予感が少ししますが、挑戦してみたいです。

ファイルの命名規則
これもこの記事"React Screaming Architecture"にあるものですが、コンポーネントのファイル名はケバブケースにして "*.component.tsx" という命名規則を設けることです。
ケバブケースにすることの利点はわかりましたが、".component.tsx"にする利点はよくわかりませんでした。
そこまでする必要あるのか、というのが今のお気持ちです。

型ファイルの置き場所
型定義ファイル(*.d.ts)にするかわからなかったのですが、自作型定義ファイルに .d.ts をつけないほうがいいという話を聞き、今後はただの ".d.ts"にすることにしました。

終わり

今回は大学祭の予約システム兼便利アプリを作りましたが、非常によい経験となりました。
とりあえずしばらくは納期があるプロジェクトは精神面にも大きく来ることを学びました。
今後はもっとフロントエンドの知識を伸ばしてよいアプリケーションを開発したいです。
ここまでお読みいただきありがとうございました。


この記事が気に入ったらサポートをしてみませんか?