1.はじめに
私はWEBエンジニアを目指す22歳です。
エンジニアになりたいと思った理由は、「人々の役に立つサービスを作りたい」と考えたからです。
現在参加させていただいているアプレンティスシップにて、初めてのチーム開発を経験することができました。忘れられない最高の体験になったので、その記録をここに残したいと思います。
ツッコミたくなるところも多々あるかと思いますが、温かい目で読んでいただけると幸いです!
2.何を作ったかと開発背景
Study Record. というエンジニア初学者向けの学習記録アプリを開発しました。
開発背景
なぜこのアプリを開発したのかというと、アプレンティスシップでは日々の学習記録をDsicord上に「日報」という形で投稿しているのですが、ここでの日報投稿が「もっと楽にできたらなぁ」と思ったのがきっかけです。
具体的にどんな課題を解決したのか
今回解決できた課題は2つあります。
1.投稿のしやすさ
学習記録をDiscordに手動で投稿する際、毎回[学習時間]などの項目を一つずつ入力するのが面倒でした。そこで、Study Record.では日報のフォーマットを統一し、共通の項目やテンプレートを活用することで、手間を大幅に削減しました。これにより、ユーザーは簡単かつ迅速に学習の進捗を記録できるようになりました。
2.過去の日報を簡単に見られるようにする
Discord上では自分の過去の日報を確認するためには手順が煩雑でした。しかし、Study Record.ではマイページを導入し、そこで過去の日報を簡単に閲覧できるようになりました。ユーザーは自身の学習の過程を迅速に振り返り、必要に応じて情報を更新することができます。これにより、投稿の漏れや振り返りの機会が増え、モチベーションの維持に寄与しました。
プロジェクトは「要件定義って何?」という初歩的な段階からスタートしましたが、なんとか進めることができました。プロダクトマネージャーは全員経験できるように回しながら交代交代でやりました。
ここから開発の話に入ります(前置きが長くてごめんなさい🙏)
3.使用技術
- フロントエンド : HTML/CSS/JavaScript/Ajax
- バックエンド : Ruby/WEBrick
- 開発環境 : ローカル(mac)
- ソース管理 : GitHub
フレームワークの使用は禁止でした
4.開発の流れ
- アイデア決め
- スライド作成
- 要件定義
- 設計
- タスク出し
- 環境構築
- 実装
- プレゼン準備
5.作業内容
ここから具体的な作業内容になります。温かい目で見てください!
5-1.アイデア決め ・ 要件定義
チームメンバーと 何の課題を解決するのか、どのような状態になればよいのか を決めました。
- 背景・目的
- 日報の投稿を手軽にしたい
- 日報を統一的で見やすくしたい
- 自分の過去の投稿を振り返りたい。編集したい
- ゴール
- 日報を統一的で見やすいフォーマットに整える
- ユーザーが直感的に操作できるUI
- 日報を投稿できる
- マイページで過去の投稿を編集、削除できる
- ログイン機能を実装する
要件定義によって、誰のどんな問題を解決するのかが明確になったので次に設計をおこないました。
-- ここで反省 --
本来ならこのタイミングで非機能要件の定義をするそうです!
例えば、
- 拡張性 : CSSの共通部分をまとまる 等
- セキュリティ : SQLインジェクション対策・パスワードのハッシュ化 等
これに関してですが、例えば、
「SQLインジェクション対策」はログイン機能を作ってみて初めて知りました。このように、この段階では知識不足でなにもわかりませんでした!次回から取り入れようと思います!
5-2.設計
ここでやるべきことを具体的にし迷わないようにしました。
しかし、この設計が不十分だったと実装が始まってから気付かされました、、、
以下のような設計を行いました。
- 画面遷移図
- ワイヤーフレーム
- テーブル定義
- 技術アーキテクチャ
ツールを誰一人持っていなかったので、canvaというアプリを使って行いました。
4人チームだったのですが、フロントエンドとバックエンドで2:2に分かれました。私はバックエンドを担当しました。
DB設計は以下のようになりました。
usersテーブル
カラム名 | データ型 | NULL | キー | 初期値 | AUTOINCLEMENT |
---|---|---|---|---|---|
user_id | INT | PRIMARY | YES | ||
user_name | VARCHAR | YES | |||
password | VARCHAR | YES | |||
created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
reportsテーブル
カラム名 | データ型 | NULL | キー | 初期値 | AUTOINCLEMENT |
---|---|---|---|---|---|
report_id | INT | PRIMARY | YES | ||
user_id | INT | FOREIGN | |||
date | DATE | ||||
study_time | INT | YES | |||
study_content | VARCHAR | ||||
reflection | VARCHAR | ||||
created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP |
外部キー制約 : reporst.user_id → users.user_id
5-3.タスク出し
要件定義で具体化したものを1つ1つのタスクに細分化しました。
例えばこんな感じです!
新規投稿
「新規投稿」→ データベースに保存
「日付」に初期値で「当日」の値を持たせる(編集可)
「名前」にユーザー名を表示(編集不可)
「投稿」
成功→日報一覧画面に遷移・「投稿完了しました」
失敗→「文字数が超過しています」・直前の画面へ
言語化することでやった気になって「いけんじゃね?」と思ってたこの時の自分が恥ずかしいです。その後痛い目見ました。
見積もりを出してガンチャート(風のものを作った)に落とし込みました。しかし、この見積もりが舐めた見積もりになっていました。
月曜日に実装を初めて、金曜日には完成。ガンチャートにはこう書かれていました。しかし実際は日曜日(提出日)ギリギリまでかかりました。原因は経験・知識不足だと思います。「そんなん言葉でまとめんなよ」と思うかもしれませんが、本当に何も分からなかったんです。Cookieすら何かわかっていませんでした。例えるなら、泳ぎ方も分からずプールに突き落とされた感じです。
今回のチーム開発で、少しは感覚が掴めたので次回の見積もりに関してはもう少し正確な時間を見積もれるようになりたいと思っています。
5-4.実装
実装期間は1週間でした。
実装した機能は下記になります。
- ログイン/ログアウト
- 新規登録
- 新規投稿作成
- 投稿編集/削除(自分の投稿だけ)
- 投稿一覧表示(すべてのユーザーの投稿)
- マイページ
私はログイン/ログアウト、新規登録、マイページを担当しました。
いざ、実装スタートです!
しかし、ここでチームメンバー全員が思いました。
「どうやって作るの?」
具体的には、
- どうやってフロントエンド、バックエンド、データベースをつなげればいいの?
- SQL は学んだけど、ブラウザで入力された値をどうやってデータベースに保存するの?
- 検索しても、Rails を使った開発のやり方しか出てこない!
- ChatGPT に聞いても、すぐ Rails や Sinatra を使わせようとしてくる!フレームワークは使わないと言っているのに!
私含めチームの方たちは、今までそれぞれプログラミング言語を学んできました。ですが学んできたことをどう使って連携して実際のアプリを作るのかが分かっていませんでした。
解決するために実践したこと:
小さくていいからとりあえずやってみる!
プロジェクトを始める際に、まずはローカルサーバーを構築してみることからスタートしました。サーバーが立った時は、これでさえ嬉しかったです。
このように、まずは小さいところから作ってみて、徐々に大きくしていき完成まで持っていきました。
こだわったポイントは、マイページに自分の過去の投稿を新しい順で表示した点です。
マイページにアクセスすると、瞬時に自分の過去の投稿が表示されます。これにより、いつでも簡単に投稿の編集や振り返りが可能です。特に勉強に疲れた時には、過去の投稿を振り返ることができ、これがモチベーションの維持に効果的です。
6.実装で得たこと
ここでは、技術的に得たことを書こうと思います。
以下は実際のログイン処理のコードです。
# ログイン処理
server.mount_proc('/login') do |req, res| #form actionに対応
if req.request_method == 'POST'
# リクエストボディからパラメータを解析
params = req.body.split('&').map { |pair| pair.split('=') }.to_h
username = params['username']
password = params['password']
# MySQL2に接続
client = Mysql2::Client.new(
host: 'localhost',
username: 'root',
password: '自分が設定したパスワード',
database: '自分が作ったデータベース名'
)
# データベースからユーザーを検索 クエリをバインド変数を使用して構築
stmt = client.prepare("SELECT * FROM users WHERE username = ?")
result = stmt.execute(username)
if result.count == 1
# ログイン成功時の処理
user = result.first
# パスワードが一致するか確認(BCrypt gemを使用)
if BCrypt::Password.new(user['password']) == password
# CookieにユーザーIDを保存
res.cookies << WEBrick::Cookie.new("user_id", user['user_id'].to_s)
res.set_redirect(WEBrick::HTTPStatus::SeeOther, '/home.html')
else
# パスワードが一致しない場合の処理
res.set_redirect(WEBrick::HTTPStatus::SeeOther, '/login.html')
end
else
# ログイン失敗時の処理
res.set_redirect(WEBrick::HTTPStatus::SeeOther, '/top.html')
end
else
res.status = 400
res.body = 'Bad Request'
end
end
本当はオブジェクト指向に基づいて、複数ファイルを用意して開発する予定だったのですが、サーバーを立てているWEBrick.rbの中で行わないとエラーが出るため仕方なく一つのファイルに書きました。(これに気づくまでに相当時間かかりました。)
また、
server.mount_proc 'mypage' do |req, res|
cookie_data = req.cookies.find { |cookie| cookie.name == 'user_id' }
if cookie_data && cookie_data.value != ""
# 以下いろんな処理(この例だとmypage)
このコードを各処理の先頭に配置することで、特定のURLへのリクエストがあった場合、そのリクエストに 'user_id' という名前のCookieが付随していれば、特定の処理を実行できるようしました。
ログインと認証の実装は非常に有益な経験でした。この部分を担当させてもらったおかげで、WEBアプリの仕組みが少しわかったきがします。今後もこの学びを基にして、セッション管理やハッシュ化などのセキュリティ概念を学び、より堅牢で安全なWEBアプリケーションの開発に挑戦していきたいです。
6-1.大活躍した "puts"
彼がエラーから私を救ってくれました!
正直、彼のことはただ文字列を出力するだけのスライム的な立ち位置だと思っていました。しかし、今回の開発で彼は、プロジェクトに欠かせない要素となりました。彼が提供するメッセージがなければ、エラーの原因の発見が難しかったでしょう。彼は単なるメソッドを超えて、プロジェクトの進行を支え、私たちのコードに生命を吹き込んでくれました。
例えば、このコード
if req.request_method == 'POST'
# リクエストボディからパラメータを解析
params = req.body.split('&').map { |pair| pair.split('=') }.to_h # ここ!
username = params['username']
password = params['password']
2行目。やけにややこしいコードですが、これがないと正しく動いてくれません。
はじめは、以下のように書いていました。
if req.request_method == 'POST'
# リクエストボディからパラメータを解析
params = WEBrick::HTTPUtils.parse_query(req.body)
username = params['username']
password = params['password']
しかし、エラーが続くので "puts" を使いparamsをコンソールで確認すると、
username = *** & password = ***
と出力されました。"puts"のおかげです!
他にも、怪しいコードの前後にputs "aaa" を置いたりして、「ここまで動いてる」などの確認にも使ったりしました。
"puts"大活躍です! "puts"最高!
6-2.セキュリティ対策
例えば、プリペアドステートメントを使用してSQLインジェクションを防ぐ。
☝️の記事を参考にさせていただきました!
insert_query = "INSERT INTO users (username, password) VALUES (?, ?)"
db_client.query(insert_query, username, password)
SQLインジェクションは、不正なSQLコードがアプリケーションのデータベースに注入される攻撃手法の一つです。攻撃者が意図せずデータベースに不正なSQLコードを挿入することによって、機密情報の漏洩やデータベースの破壊などが発生する可能性があります。
他にも、パスワードをハッシュ化しました!
require 'bcrypt'
hashed_password = BCrypt::Password.create(password)
セキュリティ対策に関して、まだまだ知識不足なので学習しようと思います!
7.初めてのチーム開発を終えて
何よりも、チームメンバーのみなさまありがとうございまいました!最高に楽しかったです!雰囲気は最高でした。チームのみんながいなければこんな最高の経験はできませんでした。
チーム開発が終わった後にも感謝の言葉は伝えましたが、いくら伝えても伝えきれません!今後も仲良くなったメンバーとの交流を大切にしていきたいです。
チーム開発の最後に各メンバーへ「良かった所」と「良くなかったところ」を書いていただきました。以降は、これを参考にしつつ感想などを書いていこうと思います。
7-1.反省
まず反省から入ろうと思います。反省すべきことを列挙していきます。
-
より細分化されたタスク出し
- 個人的にこの部分がしっかりしてきれば後々迷わずに済むと思いました。実装でやるべきタスクを全て洗い出せるようにします。
-
課題定義
- 他の人に「使ってみたい」と思わせれらるようなアプリを開発したいです。
-
プルリクエスト
- 実際の開発現場に近い形式でプルリクエストを作成していきます。
-
有意義なミーティング
- 今回のチーム開発では特に問題はありませんでした。今回ミーティングの大切さに気づけたので、今後他の人達とミーティングする際にも有意義なミーティングを行っていきます。
-
質問の精度を上げる・具体的にする
1.何に困っていて、何を達成しようとしているか
2.まずは自分で調べて、解決策がないか確認
3.簡潔で読みやすい形
4.該当のエラーメッセージを提供
5.敬意を払う
まだまだ課題や反省は山盛り過ぎるのですが、技術的な面での経験値とそもそものレベルが著しく低いため、すぐにできることを記載しました。
7-2.よかった点
良かった点はこれからもっと伸ばしていきます!
-
全員と円滑なコミュニケーションが取れた
- 「話しやすい」や、「場の雰囲気をよくしてくれた」「話していて楽しかった」と言っていただけました。
-
検索力
- 今まで以上に調べる力がつきました。チームのメンバーは「技術力がある」と言ってくれましたが、私的にはこっちの方が正しいかなって思います。(でも褒められて嬉しいです!)
7-3.最後に
ここまで読んでくださりありがとうございました!最後に今回の開発で思ったことを書きます!
今回のチーム開発を終えて、「開発楽しい!!」 と思いました。最初にこれが頭に浮かんだときには、自分でも驚きました。
「大変だった」や「疲れた」とか「期間内に作れてよかった」という言葉よりも、「楽しい」が一番の印象でした。
振り返ってみれば、寝る間も惜しんで没頭していました。分からないところが出てくるたびに調べては試行錯誤の繰り返しで、非常に楽しく学びになりました。
期待通りに動いてくれた時のあの達成感はやみつきでした。
今回チームの開発はあくまで夢への通過点だと考えています。もっともっと学習して成長していきます!
ここまで読んでいただき本当にありがとうございました。