Sakiのプログラミング学習ブログ

プログラミングについて学んだことや、学習の振返りを書いています。

フィヨルドブートキャンプのシステム開発プラクティスに取り組んでいるチームメンバーの状況を、見える化するアプリをリリースしました

はじめに

本日、フィヨルドブートキャンプシステム開発ラクティスに取り組んでいるチームメンバーの状況を見える化するサービス、「Fjord Choice」をリリースしました。

  • サービスURL

fjord-choice.herokuapp.com

github.com

目次

自己紹介

 Sakiと申します。 フィヨルドブートキャンプというオンラインプログラミングスクールで、エンジニアを目指してプログラミング学習中です。この度、スクールの最終課題である自作サービスのリリースを経て卒業することができました。
前職では税理士法人で事務職として働いていました。好きなRubyのメソッドはto_iメソッドです。

Fjord Choiceとはどんなサービスか

サービスについて知っていただくために、そもそも

を説明したいと思います。

フィヨルドブートキャンプとは?

 プログラマーとして就職を目指せるだけのスキルを身につけることを目標とした、オンラインプログラミングスクールです。 学習内容の詳細はこちらで紹介されています。 受講生は、このプラクティスを上から順にこなしていき、「学習の準備」〜「Webセキュリティ」まで全て完了したら、システム開発のプラクティスに取り組むことになります。

(学習内容 | FJORD BOOT CAMP(フィヨルドブートキャンプ)より引用)

システム開発ラクティスとは、どんなことをするのか?

 フィヨルドブートキャンプ(以降、「FBC」と記載)では、bootcampというEラーニングシステムのWebサービスを使っています。このアプリでは、日報の作成・提出、質問の投稿・回答、イベントのお知らせ・申込などを行うことができます。フィヨルドブートキャンプの受講生は、システム開発のプラクティスに参加すると、それまでエンドユーザーとして利用してきたbootcampアプリに、開発者として参加することになります。

リポジトリはこちらです。

github.com

チームの構成は、

  • スクラムマスター:メンターのkomagataさん
  • プロダクトオーナー:メンターのmachidaさん
  • チームメンバー:その時システム開発に取り組んでいる受講生の方々

となっており、このメンバーでスクラム開発をしています。

FBCでは、1スプリントを1週間とし、毎週水曜日に、今回のスプリントの振返りミーティングと、次のスプリントの計画ミーティングを行っています。

issueには、完成までにかかる時間に応じてポイントが振られることになっており、自身に割り振られたissue20ポイント分のPullRequest(以降、PRと記載)がマージされれば、このプラクティスは完了となります。
自分が担当するissueは、komagataさんとmachidaさんが、各受講生の進捗状況に合わせて、その時のレベルに応じた難易度のissueを割り振ってくださいます。例えば、一番最初はGood First Issueという、初めてコントリビュートするのに適した、軽く文言を変更する程度の1ポイントのissueが振られます。進むにつれて、徐々に大きいポイントのissueが振られるようになります。

また、自分で作成したPRは、同時期にシステム開発に取り組んでいるFBC受講生の方1名とメンターのkomagataさんにレビューしていただき、2名のレビューを通ったらマージされるルールになっています。

サービスの概要

 Fjord Choiceは、システム開発に取り組んでいる受講生の方々、komagataさん、machidaさんが、GitHubでは知ることができないチームメンバーの状況を、見える化するサービスです。

使い方

 システム開発のプラクティスに入った受講生の方に、「メンバー登録をする」をクリックして、メンバー登録をしていただきます。これによって、その方の情報がメンバーの表に追加されます。

Image from Gyazo


自身に割り振られたissue20ポイント分のPRがマージされたら、こちらのプラクティスは完了になるので、その際はご自身で「メンバーからはずれる」ボタンをクリックしてしていただきます。すると、その方の情報が表から削除されます。


表への追加と削除は、受講生の方ご自身に行っていただく必要がありますが、このチームメンバーの表自体は、URLさえ知っていればどなたでも見ることができます。

この情報は登録しなくても誰でも見ることができます

主な機能

主に3つの情報を得ることができます。

1.チームメンバーのアイコンまたはユーザー名をクリックすると、そのユーザーがbootcampリポジトリで作成したPR一覧のGitHubリンクにアクセスできます。 Image from Gyazo


2.ユーザー名の下にあるゲージは、これまでアサインされたIssueの合計ポイントです。openかclosedか問わず、アサインされた全ての合計が表示されます。

3.現在メンバーレビュー依頼されているPRの数とタイトルが分かります。タイトルをクリックするとそのPRのGitHubリンクにアクセスします。

Image from Gyazo

※ 「レビュー依頼されているPR」 とは、

  • そのユーザーがまだレビューをしていないもの
  • そのユーザーがレビュー中でまだApproveしていないもの(=CommentまたはRequest Changesした状態のもの)

が表示されます。そのユーザーがApproveしたPRやマージ済のPRは表示されません。

また、メンバー登録をした状態で、右上の自分のGitHubアカウントのアイコンをクリックすると、自分がアサインされたissueだけを表示したbootcampのかんばんにアクセスできます。

Image from Gyazo

解決したかった問題

PRのレビューを、手持ちのレビューが少ないメンバーに依頼したいが、誰がどのくらいレビューを抱えているか分からない

 システム開発では、任意の受講生1名とメンターのkomagataさんの計2名にレビューしていただくルールになっていますが、どの受講生に依頼するかは、お願いする方が自由に決めていいことになっています。
私はこのプラクティスに取り組んでいた当時、PRのレビューをお願いする際、「各メンバーが抱えているレビューの数や重さを把握した上で、今負担が少ない方にお願いしたい」と思っていました。理由は、一名の方にレビュー依頼が集中せず、チームメンバー全員がまんべんなくレビューを行った方が良いと思っていたためです。
メンターさんからは日々のミーティングで、「すでにレビュー依頼されているPRの数が多かったり、忙しい時は断って大丈夫ですよ〜」と仰っていたので、すでに2-3件持っている時はレビューをお断りすることもありました。その際も、あらかじめ誰がどのくらい手持ちのレビューを持っているか分かれば、このようなやり取りが減り、よりスムーズにレビューが進むのではないかと感じていました。

しかし、GitHubで「現在レビュー依頼されているユーザー」を確認するには、一つ一つPRにアクセスして、Reviewersを見ていくしか方法がありません。 GitHubが用意しているリポジトリのPR一覧の検索機能では、AssigneeやAuthor(PRの作成者)を指定したり、レビューの進捗状況によって検索結果を絞ることができますが、Reviewerを指定して検索することはできません。

実際にbootcampアプリでPRを検索する時の画面

このため、各チームメンバーにレビュー依頼されているPRを一目で分かるようにしたいと思い、このサービスでは、「チームメンバーが現在レビュー依頼されているPR」が分かるようにしました。 レビューが進行中のものだけ知りたいので、

そのチームメンバーが、

  • まだ一度もレビューしていないもの
  • レビューでComment または Request Changes をしたもの

を表示するようにし、Approveしたものは、そのユーザーのレビューは終わっているので、表示されないようにしました。

GitHubの「自動アサイン機能」では解決できないのか

 GitHubには元々、「自動アサイン機能」という、PRのレビュワーとしてチームをアサインした時に、そのチーム内の数名を自動アサインする機能があります。
(参考:Managing code review settings for your team - GitHub Docs)
この機能のアルゴリズムで 「ロードバランス」を選択すると、最近レビュー活動を行っていない人を優先的にレビュワーとして割り当てます。
この機能をFBCシステム開発に導入したら解決できないかを検討しましたが、レビュー活動を行っていなければissueの重さ・難易度に関係なく自動的にレビュワーとして割り当てられてしまうので、最近システム開発に入ったばかりで、レビューすること自体に慣れていない方に突然5ptレベルの重たいPRのレビューが割り振られてしまう、という問題が生じることが考えられるため、導入しない方が良いという結論に至りました。

komagataさんとmachidaさんが受講生に新しいissueを割り振る時に必要な情報が、すぐに分かるようにしたい

必要な情報1. 受講生がこれまでアサインされたIssueの合計ポイント

 FBCシステム開発では、自分の担当のissueがレビュー依頼まで行った等、きりの良いところまで終わったら、komagataさんとmachidaさんが新しいissueを割り振ってくださいます。
そして、issueを割り振る際、必ず「これまで割り振られたissueは合計で何ポイントですか?」と聞かれます。これは、komagataさんとmachidaさんが、(担当のissueが20ポイント分マージされることをゴールとした時)どれくらい進んでいるかに応じて、難易度や重さを考慮してissueを決めてくださるためです。

例えば、私が取り組んでいた際は、「まだ合計5ポイントだからRailsを使っている箇所の機能追加をお願いしよう」、「10ポイントを超えているからそろそろVue.jsを書くissueにしよう」「15ポイントでもうすぐ20ポイントに到達するから、最後に重ための5ポイント以上のissueをお願いしよう」という感じで振ってくださっていました。
(※bootcampアプリは現在、サーバーサイドはRuby on Rails、フロントエンドはVue.jsを使用しています。)

このように、FBCシステム開発では、「issueの合計ポイント」は、よく使われる情報なのですが、GitHubには、これを計算して教えてくれる機能は無いので、受講生が自身で数えるしかありません。
ポイントは、1,2,3,5,8,13,21 と7種類あり、各issueにポイントに応じた数字のラベルを貼ることで管理しています。

bootcampリポジトリで使っているポイントのラベル

そのため、今回リリースしたFjord Choiceで、各チームメンバーのissueの合計ポイントが一目で分かるようにしました。

必要な情報2. 受講生がこれまでどんなPRを作成してきたか

 また、komagataさんとmachidaさんから、「どのissueを割り振るか決める際、その方が過去に作成したPRも見て決めている」と伺ったので、チームメンバーのアイコンまたはユーザー名をクリックすると、そのチームメンバーがこれまでbootcampリポジトリで作成したPR一覧のGitHubリンクにアクセスできるようにしました。

Image from Gyazo

GitHub上のリポジトリのPull Requestsから、各ユーザーが作成したPRの一覧のみを、一から検索して表示させるには、以下のような手間がかかります。

  • Assigneeを選択する時に、選択できるユーザーの数が多いので、いちいちユーザー名を手入力して選択しないといけない
  • デフォルトでis:openというフィルターがかかっており、オープンな状態のPRしか表示されないので、マージされた等でclosedな状態のPRも表示させたい場合、Filtersのis:openを手入力で削除しないといけない
  • Assigneeを指定しても、純粋にそのユーザーが作成したPRのみが表示されるわけではなく、そのユーザーのPRがマージされたリリースブランチも検索結果に入ってくる。フィヨルドブートキャンプでは毎週木曜日と、短い頻度でリリースを行っているので、多くのリリースブランチが検索結果に入ってきてしまい、見にくくなる。これを削除するにはFiltersに-label:releaseと手入力し、releaseというラベルの付いたPRが表示されないようにしないといけない

リリースブランチも検索結果に入ってきてしまうので見にくい

実際に一から検索した時のスクリーンショットです。

Image from Gyazo

このように、GitHub上でユーザーが自力で欲しい情報に辿り着くまでに、検索条件の入力がたくさん必要で手間だと思ったので、リンクを貼ることにしました。 上記の手間をなくせるように、アクセスした時点で、

  • クリックしたチームメンバーがAssigneeとして登録されている
  • openかclosedか問わずすべてのPRが表示されている
  • releaseブランチが表示されないようになっている

状態のリンクにとぶようにしました。

私のユーザー名をクリックした時の検索結果

なぜFjord Choiceというサービス名にしたのか

 FBCのチーム開発に取り組まれている方々に使っていただくので、Fjord BootcampからFjordを頂戴して入れることにしました。 また、このサービスを使うのは、

  • 自分が作成したPRのレビュー依頼を誰にするか
  • 受講生に割り振るissueをどれにするか

を選ぶ(choice)時だと思ったので、Choiceを入れて、「Fjord Choice」というサービス名にしました。

技術スタック

開発の過程

ペーパープロトタイプの作成

どんな機能をつけるかで悩む

 作るものが決まったら、まずペーパープロトタイプを作成することになります。自分がシステム開発のプラクティスを終わらせてしまうと、一ユーザーでなくなってしまうので、「自分だったらどのような機能があったら嬉しいか」「どういう画面なら見やすいか」という視点で考えるために、システム開発に取り組んでいる最中に考えました。
作る画面を考えるために、まずどのような機能をつけるか考えました。「これが分かったら嬉しい」「これができたら便利そう」と思う機能を一通り書き出しました。 その後、「それがあるとなぜ便利か」という、その機能がなぜ必要かをじっくり検討しましたが、どこまでをファーストリリースに含めて、どこからは含めないか決めるのが難しかったです。

具体的にどのような機能をつけるかの検討過程は、以下のissueやNotionに詳しくメモしていました。

ペーパープロトタイプの作成 · Issue #6 · Saki-htr/fjord-choice · GitHub

www.notion.so

また、このサービスは、FBCのチーム開発に取り組む方々に使っていただくので、komagataさんとmachidaさんや、システム開発を経験したことがある受講生の方に、「こういう情報があったら便利だと思いますか?」等ヒアリングもしました。

最終的に、以下のプロトタイプが完成し、こちらを元に開発を進めていくことにしました。

メンバーの登録・退会は、メンバーの方自身にやっていただくことにした

 最初、極力ユーザーの手間を減らしたかったので、ユーザーが何も操作しなくても、サービスにアクセスしたら、システム開発に取り組んでいるメンバーの情報がすぐに見れるようにしたいと考えていました。
これを実現するには、プログラムを書いて「システム開発に取り組んでいるユーザー」だけを取得できなければいけません。bootcampリポジトリには、システム開発に取り組んでいる受講生の他に、FBCのメンターの方々等がコラボレーターとして登録されているので、GitHubが持っている情報では判別できません。

そこで、komagataさんにFBCのbootcampアプリのAPIを使えば取得可能か相談したところ、

  • bootcampアプリのAPIを使えば、システム開発ラクティスに入っているユーザーの名前は取れるし、ユーザーにはGitHubアカウントを登録してもらっているので、それを元にGitHubに取得しにいくことはできる
  • しかし、イレギュラーなケースがいっぱいある
  • などなどイレギュラーなケースがたくさん有るので、プログラムでの自動化はできたとしても、イレギュラーが多くなってしまうことが予想される。
  • イレギュラーが多いと「こういう場合はこうしてね」という、サービスを使う上でのルールがたくさん必要になってしまい、使い勝手が悪いので、最終的に使われなくなってしまう可能性がある

と教えていただきました。
ユーザーの手間を全面的に排除しようとすると、逆に使い勝手が悪くなってしまうのは本末転倒だと思ったため、「システム開発に入ったら、受講生の方ご自身に『メンバー登録』ボタンをクリックしていただいて、GitHub認証によってその方のGitHubアカウントの情報を取得し、その方の情報を表にさ追加する」、という仕様にしました。

チームメンバーの状況は、誰でも見られるようにした

 チームメンバーの状況を、システム開発ラクティスに取り組んでいる人だけ見られるようにするか、誰でも見られるようにするかで悩みましたが、

  • システム開発に入る前の受講生やメンターなどFBC関係の方々
  • FBCの紹介先企業で卒業生を採用したいと考えていらっしゃる方々

など、システム開発に取り組んでいないが、今誰がどのくらいの進捗か知りたい方がいるだろうと予想したため、この表自体は誰、URLさえ知っていれば誰でもアクセスできるようにしました。

ただ、誰でも見られると、FBCを受講していることをバレたくない方にとって迷惑になってしまうかもしれないので、誰でも見られる状態にして大丈夫かどうか検討しました。

FAQ | FJORD BOOT CAMP(フィヨルドブートキャンプ)より

komagataさんとmachidaさんに相談した結果、

  • フィヨルドブートキャンプ生であることが公になる=転職したいことが周囲にばれる。それは嫌な方もいるので、避けなければならない。
  • しかし、bootcampリポジトリのコラボレーター一覧は誰でも見られるようになっているので、その時点でFBC生であることがバレてしまう。つまりバレたくない場合、そもそもGitHubアカウントを別に作ってコラボレーターとして参加する必要がある。
  • なので、このサービスを使うにあたって、特定の人だけが見られるようにするための認証機能は必要ないのでは。

と仰っていただいたので、私も必要ないと判断し、誰でも見られるようにしました。

技術検証

技術検証とは、自分が考えたwebサービスが実現可能かどうか調査することです。
FBCの技術検証のプラクティスでは、以下のように説明されています。

自分のWebサービスのアイデアに必要な技術が可能かどうか検証しましょう。(もちろんやる前にIssueを作ってカンバンに登録しましょう)

  • 必要なデータを提供してくれる外部APIがあるかの調査。
  • 自分のやりたいことを助けてくれるgemがあるかの調査。
  • 使う予定のライブラリが本当に自分のアプリにマッチするのかのプロトタイプ作成。

自分のやりたいサービスが違法ではないかの調査。 調査した結果、そもそも「このサービスは実現不可能だった」となったらまた別のサービスのエレベーターピッチを考えましょう。
(技術検証をする | FBCより)

APIをたたくのが初めてで分からないことばかり

 Issueの合計ポイントやレビュー依頼されているPRを表示させるには、GitHubから必要なデータを取得してくる必要があります。なのでまず、サービスで使いたいデータをGitHubが持っているかを調査をしました。
これまで行ってきたFBCのプラクティスでは、ユーザーによってCRUD操作が行われるアプリケーションしか作ったことがなく、APIといえば、FBCシステム開発RailsからVue.jsに必要なデータを渡す時のRailsAPIくらいしか知りませんでした。
外部サービスのAPIをたたくことが初めてだったので、

  • そもそもどのようにすればGitHubAPIをたたくことができるのか
  • 自分がほしい情報はどうすれば取得できるのか

が最初全く分かりませんでした。

そんな中、GitHubの公式ドキュメントを読み進めていく中で色んな情報に触れ、

  • GitHubAPIにはRESTだけでなくGraphQLというAPIもあるらしい→GraphQLとは何だろう?それを使うかの検証もすべき?
  • アプリケーションからAPIにリクエストするにはOAuthアプリケーションとして登録して認証する必要があるらしい→ターミナル上でcurlコマンドでたたく方法もよく分かっていないのに、認証やOAuthが絡んでくるとさらに分からない...
  • ラッパーライブラリというものがあるらしい→それを使うとどういうメリットがあるか分からない。私のサービスでも使った方がいいのか?

と、「調べれば調べるほど分からないことが出てきて、何から検証していけばいいか分からない」という混乱状態に陥りました。
ちょうど、FBCの卒業生や自作サービスのリリースを終えた先輩が集まる場があったので、そこで技術検証の進め方について相談しました。
良いアドバイスをたくさんいただいたのですが、中でも

認証の仕組みと、「APIから欲しい情報を取得できるか」は分けて考えたほうが良い。
認証の理解は難しいので、まず欲しいデータを取れるかどうかを、認証を行うOAuthアプリケーションではなく、ターミナル上でcurlコマンドを使って試すといい。

というお話がとても参考になりました。
このアドバイスをもとに、認証やラッパーライブラリのことは一旦置いて、まずcurlコマンドで、GitHubAPIが必要な情報を持っているかを調べました。そして、欲しい情報が取得できると分かった後に、ラッパーライブラリを試したりOAuthアプリからAPIをたたいたら、外部サービスのAPIに関して何も知らなかった時よりもとてもスムーズに使い方を理解できました。

当時学んだことを別の記事にまとめたので、気になった方は読んでくださると嬉しいです。これからFBCの自作サービスで外部サービスのAPIを使う予定の方、GitHubAPIを使いたい方にとって参考になる記事かなと思います。

saki-htr.hatenablog.com

saki-htr.hatenablog.com

検証用のリポジトリを作って、色んなパターンのissueとPRのデータを作成して試した

技術検証を始めた当初、bootcampリポジトリの、実際のissueとPRを元にして検証を進めていましたが、検証する時によって存在するデータがばらばらなので、最初「このプログラムを書けば、これが実現できそう」と思っていても、その少し後に検証してみると、網羅していなかったパターンに遭遇することがありました。
そのため、bootcampリポジトリをForkして自分のアカウントに、検証用のリポジトリを作りました。

github.com

こちらに、実際に起こり得そうなパターンのissueやPRを作って、これに対してAPIリクエストして、「issueに誰かがアサインされている時はこのデータの中身はどうなるか」や「PRがcloseされた場合はこのデータはどう変わるのか」など、GitHubAPIの動作確認をしていました。

実際に作成して動かしていたissueとPRはこちらです。

PRは、自分が作成者だと、レビュワーに自分を登録できないので、当時同じく自作サービスの開発を進めていたFBC生の方々をコラボレーターとして追加し、PRを作成していただくなどご協力いただきました。

開発

GitHubAPIが、自作サービスで使うデータを持っていることが分かったので、フレームワークなどの技術検証を終えた後は、rails newをしてCIなどの環境構築を終え、開発に取り掛かりました。

一番大変だったこと:GitHubの最新のデータを、常にRailsのDBに保存する仕組みを作る

 GitHubAPIが、自作サービスで使いたいデータを持っていることが分かったので、後は開発に入って、検証→開発→また検証というサイクルを短く繰り返していけばいいかと思い、rails newしました。
しかし、実際に機能を作り始めたら、GitHubの最新のデータを、常にRailsのDBに保存させる仕組みを作ることが非常に難しく、この仕組みの実装に一番時間がかかりました。

このサービスは、リアルタイム性の高さが重要になってきます。例えば、GitHubで、とあるPRでチームメンバーの誰かがレビュワーとして登録されたら、すぐに私のサービスでもそのPRがチームメンバーにレビュー依頼されていることが分かるようにPRの情報を表示しなければなりません。GitHubでPRにレビュワーが登録されてから、私のサービスに反映されるまでに時間がかかると、ユーザーにとって使いにくいものになってしまいます。そのため、bootcampリポジトリのissueとPRの最新のデータをRailsのDBに保存する仕組みが必要です。この仕組みをどうやって実現するかが、一番大変でした。具体的にどんな方法を考えて検証してきたかを以下にまとめました。

検証1. GitHub上でデータに変化があったタイミングでAPIをたたく方法がないか調査

 GitHubのデータが変わったタイミングでAPIをたたくことができれば、効率的にAPIリクエストさせることができるので、GitHubAPIにそういったことができる機能が備わってないか公式ドキュメントをくまなく探しましたが、ありませんでした。

検証2. バッチ処理で定期的にAPIリクエストさせる

 データが変わったタイミングでAPIリクエストさせることができないため、バッチ処理によって、定期的に2分間に1回、APIリクエストさせることにしました。
しかし、実際にこの方法でissue/PRの情報を表示する機能を作ったところ、常に最新のGitHubの値をDBに保存することは実現できましたが、非常に無駄が多いことに気づきました。

バッチ処理でDBを更新する時のイメージ図

具体的には以下2つの問題がありました。

問題1. 実際にGitHubの値が変わる頻度よりもずっと高く、APIリクエストを行うので、アプリに負荷がかかり、ユーザーがアプリにアクセスした時の表示に時間がかかる

issueに誰かがアサインされたり、誰かがレビュワーとして登録されたり、レビューでApproveしたりといった、実際のbootcampリポジトリでのissue/PRのデータの値が変わる頻度を大きく上回った大量の回数をAPIリクエストさせるので、アプリの処理が重たくなり、ユーザーとしてアクセスする際のデータの表示にも時間がかかりました。
リアルタイム性の高さを重視しているため、「2分間に1回」というリクエストの頻度を落とすわけにもいかないので、バッチ処理ではこの問題を解決することはできませんでした。

問題2. 集計に必要ないissueやPRのデータもDBに保存してしまう

 GitHubから欲しい情報は大きく分けて以下の2つです。
1つめは、ユーザーにアサインされたissueのデータ。
2つめは、ユーザーがレビュー依頼されているPRのデータです。
GitHubが用意しているエンドポイントでは、クエリパラメーターを使ってassigneeを指定すれば、ユーザーにアサインされたissueだけを取得することができます。
例えば、私がアサインされたissueかつ、openもclosedも合わせた全てのステータスの状態のissueのリストが欲しい場合、クエリパラメーターでassigneestateというパラメーターを使って、以下のようなエンドポイントでアクセスすると取得することができます。

https://api.github.com/repos/fjordllc/bootcamp/issues?assignee=Saki-htr&state=all

しかしPRでは、とあるユーザーがレビュワーとして登録されているPRだけを取得するエンドポイントが用意されていませんでした。そのため、以下のURLのように、openな状態のPRのリストを取得し、その中のrequested_reviewerキーに入っているデータがレビュワーの情報なので、これを元にDBにPRのデータを保存するしか方法がありませんでした。

https://api.github.com/repos/fjordllc/bootcamp/pulls?state=open

APIについて調べる前に考えていたアプリのデータ作成の流れとして、

  1. ユーザーが私のサービスにGitHub認証することで、GitHubアカウントの情報を取得し、Userモデルのレコードを作成する
  2. Userモデルのレコードが作成されたら、そのユーザーがアサインされたissue、そのユーザーがレビュー依頼されているPRを取得しにいく

というのを考えていました。
しかし、レビュワーを指定してPRを取得することができないので、これは実現不可能になりました。そのため、アプリのUserモデルのレコードとは関係なく、issueとPRのデータをあらかじめダムのように貯めておいて、ユーザーが登録されたら、そのユーザーがアサインされたissueとレビュー依頼されているPRを表示することにしました。

これをバッチ処理で実現させようとすると、issueとPRのレコードの数がどんどん増え、作成されたレコードの値が変わっていないか常にバッチ処理でリクエストさせて見に行かせることになるので、どんどん処理が重たくなってしまいます。

検証3. GitHub Actionsを使って、イベントが起きたタイミングでissueとPRのデータをアプリにPOSTしてもらう

バッチ処理だと上記のような問題があったので、そのことを自作サービス進捗報告会でkomagataさんに報告したところ、

GitHub Actionsを使えば、「こういうイベントが起こったら、こういう処理を実行して」ということができるので、issueやPRに変化が起こった時に、issue/PRのデータをRailsアプリに送信させることができるのでは

とアドバイスをいただきました。
GitHub Actionsがどのようなイベントを補足してくれるかは、公式ドキュメントを読めば比較的容易に知ることができました。

しかし、

  • イベントが起きたissueとPRのデータはどうやって取得するのか
  • issue/PRのデータをどうやってRailsアプリに送るのか

を調べるのが大変でした。

GitHub Actionsというのは、そもそもGitHubでの開発をより効率的に行うためのツールなので、ネットでの記事は、「PRが作成されたら自動的にpackage.jsonファイルを更新する」「pushされたら自動的にテストが実行されるようにする」というような自動化の記事が圧倒的に多く、自分が実現したいことをどんぴしゃで行っている記事は見つかりませんでした。しかし、GitHub Actions を用いて issue が更新されたら LINE に通知する方法 - Qiitaという記事を見つけ、「GitHubリポジトリから別のアプリに、イベントが起こったissueの情報を通知できるなら、自分がやりたいことを実現する方法はあるはずだ」と考え、GitHub Actionsの公式ドキュメントをじっくり読んだり、参考になりそうな記事をくまなく探したりしました。
その結果、イベントが起きたタイミングでissueとPRのデータをRailsアプリに送信する仕組みを作ることができました。

GitHubActionsからイベントが発生したタイミングで、issue/PRのデータをRailsアプリに送信させることで、仕組みを実現

具体的にどのようにして仕組みを作ったか、説明していきます。

送信する側(GitHub Actions)の処理

以下のGitHubActionsのワークフローファイルを作成し、bootcampリポジトリに置いていただきました。

  • issueのデータを送信するワークフローファイル

github.com

issueは、

  • issueに誰かがアサインされた
  • アサインをはずされた
  • ラベルが貼られた
  • ラベルが外された

タイミングで、Railsアプリに、イベントが起こったissueのid、ラベル、アサインされたユーザーのGitHubのidをPOSTします。

  • PRのデータを送信するワークフローファイル

github.com

PRは、

  • 誰かがレビュワーとして登録された
  • レビュワーから外された
  • PRが編集された
  • PRがclosed状態になった(マージされるとclosedになります)

タイミングで、RailsアプリにPRのid、タイトル、ステータス、レビュワーとして登録されたユーザーのGitHubのidをPOSTします。

受信する側(Rails)の処理

POSTされたデータを受け取る側の処理が必要なので、issueとPRのデータを受信する用のルーティングを作成しました。

  • ルーティング
# config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    resources :issues, only: [:create]
    resources :pulls, only: [:create]
  end
end

issue/PRのidをもとに、DBに同じidのレコードがないか探し、あればそのレコードを、なければ新しくインスタンスを作成した後、POSTされてきたパラメーターを元にissue/PRのレコードを作成または更新しています。

値が変わったらリポジトリ側からデータ送信してくれるので効率が良くなった

GitHubActionsからのPOSTを待ってDBを更新する時のイメージ図

このように、GitHubActionsを使うことで、Railsアプリから値が変わってないか常に見に行かせるのではなく、必要なタイミングでbootcampリポジトリからPOSTさせたのを受信する形式にしたことで、効率的かつリアルタイム性を高く保った状態で、issueとPRのデータをRailsのDBに保存することができました。
また、GitHubREST APIは、APIリクエストのレート制限が1時間に5000回までなので、それを超えないように考慮してプログラムを作る必要がなくなったのもありがたかったです。
GitHubActionsの技術検証はこちらのNotionに整理しながら行っていました。

www.notion.so

bootcampリポジトリからのみ、POSTを受け取る仕組みを作った

 Railsは、CSRF対策として、POST,DELETEリクエストなど副作用があるリクエストに対してはあらかじめセキュリティトークンを発行しておき、その発行されたトークンをリクエスト時に送信しています。そして送信されたトークンを検証することで、第三者からのリクエストではなく自身のアプリケーションからのリクエストであるので実行しても問題ないと判定する、という手法をとっています。
つまり、元々第三者からのPOST受信はできないようになっているため、ワークフローファイルの設置とルーティングの追加をしただけでは、POSTを受け取ることができません。
受信するためにどのような設定が必要か調べたところ、「protect_from_forgery(セキュリティトークンの検証を行わない設定)と書いてCSRF対策を外せばいいだけ」など、セキュリティ面から見て危険そうな記事がいくつか出てきたので、メンターさんに正しい方法を聞いた方が良いと思い、質問しました。

結果、以下の処理を追加することで、bootcampリポジトリからのPOSTのみを受信できるようにしました。

1.bootcampリポジトリとFjord Choice、お互いしか知らないトークンを環境変数FJORD_CHOICE_TOKENとして設定する。

2.bootcampリポジトリのワークフローファイルで、データをPOSTする際に、Authorizationヘッダーにトークンを含めて送信するようにする。

curl -X POST https://fjord-choice.herokuapp.com/api/issues \
             -H 'Content-Type: application/json' \
             -H 'Authorization: Token ${{ secrets.FJORD_CHOICE_TOKEN }}' \ # トークンも含めてPOSTする
             -d '{"number": ${{ github.event.issue.number }},
                  "point": ${{ toJson(github.event.issue.labels.*.name) }},
                  "assignee_uids": ${{ toJson(github.event.issue.assignees.*.id) }}
                  }'


3.Railsの受信するコントローラに以下の設定を追加する。

class API::IssuesController < APIController
  protect_from_forgery except: :create #  createアクションでのみCSRF対策を外す
  before_action :authenticate, only: [:create]

  def create
    (省略)
  end

  private

  def fjord_choice_token
    ENV['FJORD_CHOICE_TOKEN']
  end

  def authenticate # 渡されたトークンとFjordChocieのトークンが同じか検証する
    authenticate_or_request_with_http_token do |token, _options|
      ActiveSupport::SecurityUtils.secure_compare(token, fjord_choice_token)
    end
  end
  


これで、bootcampリポジトリからのみPOSTを受信できるようになります。

※ちなみに、authenticateメソッドは、

  def authenticate
    authenticate_or_request_with_http_token do |token, _options|
      token == ENV['FJORD_CHOICE_TOKEN']
    end
  end

のように直接比較すると、タイミング攻撃によってトークンが分かってしまうリスクがあるので、secure_compareメソッドを使いました。

これまで、アプリケーション内でHTTPリクエストするアプリケーションを主に扱ってきたので、初めてのことばかりでしたが、セキュリティの勉強にもなり面白かったです。

とにかくGitHubの仕様に左右される

 サービスの開発では、とにかくGitHubの仕様に左右されるので、「こういう状態のissueでは、APIのこの値はどうなっている?」「これが起こったら、APIのこの値はどう変わる?」と調査して、それに応じてプログラムを書くのが大変でした。
GitHub Actionsでどういうイベントを使えるかはこちらのドキュメントにあるのですが、具体的にどういう時にそのイベントが発生する/しないか細かくは載っていないので、調査する必要がありました。

なので実際に検証用リポジトリに様々な状態のPRを作って、イベントが発生するか、発生した時GitHubの値はどうなるかを確認しました。

レビュー状況のパターンの表を作って確認しながら記録していました

また、はじめFBCシステム開発のルールに合わせてプログラムを書くか、ルール上はほぼ起こり得ないけれどGitHub上では起こり得る動作も考慮して書くか迷いました。
例えば、FBCシステム開発では、まず受講生の方1名にレビューをお願いし、その方がApproveした後にkomagataさんにレビューをお願いすることになっています。なので、基本的には複数名が同時にレビュワーでいることはほぼ起こり得ないです。
しかし、ルールは今後変わるかもしれないですし、GitHubの仕様上は同時に複数名をレビュワーとして登録することが可能なので、最終的にGitHubの仕様に合わせて書くようにしました。

テスト

FBC内で開催されたEverydayRails輪読会合宿に参加

多くの開発現場でテストはRSpecが使われていると聞いたので、自分のサービスでもRSpecを書きたいと思っていたので、ゴールデンウィークに開催された、現FBC卒業生の@Paruさん@トミーさんご主催のEverydayRails輪読会合宿(1週間で合計約40時間)に参加しました。


こちらは合宿内で各々が学んだことを振り返る時に記入したHackMDノートです。

hackmd.io


RSpecを書くのは初めてでしたが、この合宿に参加したおかげでスムーズにテストを書き始めることができました。主催のお2人には本当に感謝しています。

beforeをなるべく使わないようにした

 合宿には、FBCのメンターさんやエンジニアとして働かれている卒業生の方もいらっしゃり、「現場ではbeforeは極力使いたくないので、letを使う」と教えていただいたので、なるべくbeforeを避けてletを使うようにしました。

デザイン

見やすさにこだわった

デザインは、メンバーの表の見やすさにこだわりました。machidaさんから「彩度の高いものは避けた方がいい」「Gmailアプリなど参考にすると良い」と教えていただき、全体の背景色を薄いグレーにし、明るい文字やボタンが目に入るとユーザーにとって鬱陶しいと思ったので、メンバー登録のボタン以外は目立たせないようにしました。

レスポンシブ対応がとても難しい

 今回、初めてレスポンシブ対応にチャレンジしたのですが、まさかこんなに難しいとは思っていませんでした。自分が使っているMacBookでは良い感じの幅・空白にできていても、スマホで開くと幅が足りなくてヘッダーに配置したボタンが表示できなかったり、逆に大きいディスプレイだと幅に伸びてしまうので最大幅の制限が必要だったりと、どの端末で開いても綺麗に表示させることの大変さを痛感しました。

とくに、スマホは画面が小さいので、テキストによる説明ではなく、ボタンの配置や色などによってユーザーに使い方を伝えないといけないので、自分が普段スマホでサービスを使っていて、迷いなく操作できているのはデザイナーさんが使いやすさを考えてくださったり、CSSを当ててくださっているおかげなのだと学びました。

イデア出し〜リリースまでをやってみての感想

一通り全部経験できてよかった

 アイデア出しからリリースまで全部行うのは本当に大変でしたが、一通りの工程を経験したことで、世の中のサービスと、サービスに携わっている方々への感じ方も変わりました。
「この規模でこんなに大変なのに、世の中には利用していただくことでお金をいただくレベルの、もっともっと大規模なサービスがいっぱいある。そのアイデアを考えだしたり、作っている方々はなんてすごいんだ!」と思うようになりました。

私はエンジニアとして働きたいのでこれから就職活動に入りますが、働くことになったら、違う部署・職種の方々と協力しあってお仕事していくことになるので、デザイナーさんなど他の職種の方がお仕事でなさっていることを少しですが経験できて良かったです。 やったことがない上で想像だけで「大変なのだろうな」と思って一緒にお仕事をするのと、経験をしたことがあって多少大変さが分かっている上でお仕事をするのとでは全然違うと思います。

こちらに関してはmachidaさんもブログ記事で、以下のように仰っていました。

いろいろな役割を経験することで、その役割の人の気持ちや出会う問題について知れることが重要なポイントだと考えます。この役割の人にはこう動いてもらえると、この役割の人にとってはこういう場合にすごく助かる、というのがわかっていることで、実際チームで開発をする際にいい動きができるようになります。
一人でサービスを一回作っただけで知れる範囲というのは限られてはいますが、それでもやったことがない人に比べて大きな差はあると思います。また、自分がその役割をやってみたことががきっかけで、開発者以外の役割のことにも興味が持てれば、チームで開発がさらに楽しくなれるかもしれません。
(フィヨルドブートキャンプに「自作サービスを作る」という課題がある理由とフィヨルドブートキャンプの取り組み | FJORD BOOT CAMP(フィヨルドブートキャンプ)より)

自作サービスは質問が難しい

 これまでのFBCでの学習では、プラクティスの課題について質問する時は、作るもの・ゴールが決まっていて、質問する自分と質問されるメンターさんがお互いに共通認識ができた状態で質問することができました。システム開発は、bootcampアプリについて、issueを元に質問させていただくので、「こういう機能を作りたい」「現状のbootcampのコードはこうなっている」という共通認識がお互いにできた状態で質問ができました。
しかし自作サービスは、「こういう機能を作りたい」「調べてみたらこういうことが分かった」「実装する上でこういう問題がある」等について、一番分かっているのは自分だけど、分かっていないこともたくさんある状態なので、質問する時に、欲しい答えを得るために必要な情報を正確に伝えることが非常に難しかったです。
開発当初は、「自分のサービスなのだから自分の力でやらねば」という気持ちがあり、なかなか質問に踏み切れない時がありましたが、それでは進みませんし、仕事することになったら、分からないことがあったら一人で抱え込まないで質問して進めることが大切になると思ったので、途中からはFBCのQ&Aやdiscordの#wakaranチャンネルでどんどん質問するようになりました。

フェードアウトしないようにFBCの方と関わる場に積極的に行った&作った

 「自作サービスは長期戦」と聞いていたので、開発に詰まってしんどい時も出てくるだろうなと予想していました。なので、FBC内でパーフェクトRuby on Railsの輪読会を主催して、平日毎日FBCの方と強制的にお話する機会を作りました。

自作サービスはシステム開発ラクティスと比べると、一人で黙々とやる時間が増えます。はじめは自作サービスと主催を両立できるか不安でしたが、やってみたらそこまで大変ではありませんでした。この輪読会はまだ続いていて、今週10章(全12章)に入りました。主催をしてみて、参加してくださる方がいないと輪読会はできないので、参加者のありがたみが増しました。(いつも来てくださる皆様ありがとうございます...🙏✨) もちろん自作サービスのためだけに主催したわけではないのですは、人と話すことで元気が出るタイプなので、平日毎日話す場を作ってよかったなと思います。また、Railsを使って開発をしていたので、Railsでやりたいことを実現する方法を調べる時に、「そういえばこの前読んだところに方法がのっていたな」と役立つことも多々ありました。

また、トミーさん主催の、同じ時期に自作サービスに取り組んでいたFBC受講生で集まり、週に一度各々のGitHubのかんばんを使って、今週取り組むissueについて説明し、ポイントを付けていただく会に参加していました。ここで自分が詰まっていることに関してアドバイスをいただいたり、励ましあったりしたので、それが心の支えとなり、なんとかリリースまで辿り着くことができました。

ユーザーの使いやすさをとことん追求するのが楽しかった

 私のサービスは、FBC受講生の方々に使っていただくことを想定して開発していたので、「この機能があったら嬉しいかな」「こういう仕様の方が使いやすそう」とあれこれ考えながら、プロトタイプを作ったり、機能を作っていくのはとっても楽しかったです。サービスはリリースしたら終わりではないので、これからも受講生の方々やkomagataさんとmachidaさんの声をお聞きして、便利な機能を作っていきたいです。
エンジニアとして働けることになったら、ただ指示された通りにコードを書くのではなく、使う方が本当に求めているものは何か、どういう仕様だと使いやすいか、をとことん追求して考えて作れるエンジニアになりたいです。

何を作るかのアイデアは一人で考えこんでボツにする前に、人に聞いてもらう

 実はこのサービスのエレベーターピッチを提出する前にボツにするか悩んだことがありました。しかし、一人で抱え込んで悩むよりも、他の方の意見を聞いた方が良いと思い、思い切って提出しました。
このサービスのエレベーターピッチを考えていた当初は、「今誰が手持ちのレビューが多いか知りたいのは私だけかもしれない」と思ったりもしましたが、提出時にmachidaさんに「すごく欲しい」と仰っていただけたり、ミートアップでアイデアを話した時に「自分も欲しい」と仰っていただけたりしました。なので、自作サービスのアイデア出しをしていらっしゃる方で悩んでいる方がいらしたら、ぜひ人に話したり、一旦エレベーターピッチを提出してみることを強くおすすめしたいです!

今後追加したい機能

リロードしなくても、表示されるデータが自動的に更新されるようにしたい

 現状、表示されるデータを最新の状態にするには、ユーザーが自分でリロードする必要があります。週一のミーティングの時など、bootcampのGitHubのかんばんを見ながら、このサービスの画面を開きっぱなしにして見ることがあると思います。ミーティングでは新しくissueが振られる = 私のサービスで表示するデータが変わるので、リロードしなくても自動的に値が変われば、ユーザーは「今は最新の状態なのだろうか」と心配してリロードする必要がなくなるので、表示されるデータが自動的に更新されるようにしたいです。

さいごに

前職での経験から、「システムにできることは人がやるのではなく、システムにやってもらうようにして、その人にしかできないことに集中できる環境を作っていきたい」という思いが強かったので、このサービスによって、komagataさんとmachidaさん、チーム開発に取り組まれる受講生の方々が少しでも、コードを書いたりレビューしたりすることに専念できるように、チーム開発の助けになればとても嬉しいです。


自作サービス進捗報告会でいつも的確なアドバイスをくださったkomagataさん、machidaさん、何度も相談にのってペアプロしてくださったcafedomancerさん、サポートしてくださったメンターの方々、ポイント付け会で一緒だった、eatplaynapさん、Paruさん、ガラムマサラさん、yocajiiさん、グループコーチングで一緒だったharunaさん、いっしーさん、niikzさん、チェリー本輪読会で一緒だった皆さん、パRails輪読会に参加してくださった皆さん、分報でリアクションくださった皆さん、本当に本当にありがとうございました。自作サービスの開発は、チーム開発と比べると、自分がプロダクトオーナー、開発、デザイナー全てを自分一人で行うので、孤独に感じることもありました。そんな中たくさんの方々が助言をくださったり、弱音を聞いてくださったりして、なんとかリリースまで辿り着くことができました。皆さんには感謝してもしきれません。
これからついに最後のプラクティスである就職活動が本格的に始まり、不安な気持ちもありますが、自分が働きたいと思う会社さんから、"一緒に働きたい"と思っていただけるように全力を尽くしたいと思っています。自分が内定をいただけるかは、選考に進むまで分からないことですが、落ちた時に「もっと努力をしておけばよかった」と悔いが残らないようにがんばりたいです。

【octokit.rb】RubyでGitHubAPI(REST)にリクエストする時に便利なラッパーライブラリ、octokit.rbの使い方

はじめに

フィヨルドブートキャンプ(以下、FBC)の「自分で考えたサービスをリリースする」という最終課題で、GitHubREST API(REST)を利用した。(※近日リリース予定)

Ruby on Railsを使って開発したのだが、RubyでGitHubAPIをたたく際に、GitHubが用意してくれている公式のラッパーライブラリ、octokit.rbを使ったらとても便利だった。

github.com


サービス開発を始めた当初、「octokit.rbというラッパーライブラリがあるよ」、とGitHubの公式ドキュメントで紹介されていて存在は知っていたが、APIをたたくのが初めてなこともあり、使うことでどんなメリットがあるか分からなかったので、最初は素のRubyAPIにリクエストする処理を書いていた。
素のRubyで処理を書いてAPIの操作に慣れてきたところで、ラッパーライブラリを使ったところ、「え!とても便利✨」と感動したので、

  • そもそもラッパーライブラリとは
  • 素のRubyでGitHubAPI(REST)をたたくと、どんなコードになるのか
  • 素のRubyでたたいた時の不便な点→octokit.rbを使えば解決
  • octokit.rbを使った認証方法
  • octokit.rbを使ったGitHubAPI(REST)のたたき方

を中心に、学んだことをまとめた。

目次

そもそもラッパーライブラリとは

ラッパーとは、

ある開発環境向けに提供されたプログラムなどを別の環境で利用できるようにするもの。
(ラッパーとは - 意味をわかりやすく - IT用語辞典 e-Words より)

GitHubAPIには、使用方法を拡張したり簡略化するためのライブラリがたくさんある。
以下の公式ドキュメントで、どのようなラッパーライブラリがあるか、非公式なものも含めて紹介されている。

Rubyには、GitHub公式のoctokit.rbがある。今回はこれを使った。

素のRubyでGitHubAPI(REST)をたたくと、どんなコードになるのか

ラッパーライブラリの便利さを伝えるために、まず素のRubyでGitHubAPIをたたく場合、どのようなコードを書く必要があるか説明していく。

RubyAPIをたたくには、以下のライブラリを使う。

例えば、「とあるリポジトリのうち、指定したidのissueを取得したい」とする。 素のRubyでリクエストする場合、以下のように書く。

require "net/http" # Net::HTTP.getを使うためにrequireする必要がある
require "uri" # parseメソッドを使うためにrequireする必要がある


# リクエストしたいAPIのエンドポイントのURIをパースする
uri = URI.parse("https://api.github.com/repos/fjordllc/bootcamp/issues/1")
json = Net::HTTP.get(uri)

これで、変数jsonの中にfjordllc/bootcampリポジトリのissueが1件、JSON形式で入っている。

変数jsonの中身(非常に長いので折りたたんだ)

"{\"url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1\",\"repository_url\":\"https://api.github.com/repos/fjordllc/bootcamp\",\"labels_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}\",\"comments_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments\",\"events_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events\",\"html_url\":\"https://github.com/fjordllc/bootcamp/pull/1\",\"id\":11961549,\"node_id\":\"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==\",\"number\":1,\"title\":\"Update rails 4.0.0.beta\",\"user\":{\"login\":\"hrysd\",\"id\":1663465,\"node_id\":\"MDQ6VXNlcjE2NjM0NjU=\",\"avatar_url\":\"https://avatars.githubusercontent.com/u/1663465?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/hrysd\",\"html_url\":\"https://github.com/hrysd\",\"followers_url\":\"https://api.github.com/users/hrysd/followers\",\"following_url\":\"https://api.github.com/users/hrysd/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/hrysd/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/hrysd/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/hrysd/subscriptions\",\"organizations_url\":\"https://api.github.com/users/hrysd/orgs\",\"repos_url\":\"https://api.github.com/users/hrysd/repos\",\"events_url\":\"https://api.github.com/users/hrysd/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/hrysd/received_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"closed\",\"locked\":false,\"assignee\":null,\"assignees\":[],\"milestone\":null,\"comments\":0,\"created_at\":\"2013-03-13T06:30:12Z\",\"updated_at\":\"2013-03-13T06:39:10Z\",\"closed_at\":\"2013-03-13T06:39:10Z\",\"author_association\":\"CONTRIBUTOR\",\"active_lock_reason\":null,\"draft\":false,\"pull_request\":{\"url\":\"https://api.github.com/repos/fjordllc/bootcamp/pulls/1\",\"html_url\":\"https://github.com/fjordllc/bootcamp/pull/1\",\"diff_url\":\"https://github.com/fjordllc/bootcamp/pull/1.diff\",\"patch_url\":\"https://github.com/fjordllc/bootcamp/pull/1.patch\",\"merged_at\":\"2013-03-13T06:39:10Z\"},\"body\":\"\",\"closed_by\":{\"login\":\"komagata\",\"id\":16577,\"node_id\":\"MDQ6VXNlcjE2NTc3\",\"avatar_url\":\"https://avatars.githubusercontent.com/u/16577?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/komagata\",\"html_url\":\"https://github.com/komagata\",\"followers_url\":\"https://api.github.com/users/komagata/followers\",\"following_url\":\"https://api.github.com/users/komagata/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/komagata/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/komagata/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/komagata/subscriptions\",\"organizations_url\":\"https://api.github.com/users/komagata/orgs\",\"repos_url\":\"https://api.github.com/users/komagata/repos\",\"events_url\":\"https://api.github.com/users/komagata/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/komagata/received_events\",\"type\":\"User\",\"site_admin\":false},\"reactions\":{\"url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions\",\"total_count\":0,\"+1\":0,\"-1\":0,\"laugh\":0,\"hooray\":0,\"confused\":0,\"heart\":0,\"rocket\":0,\"eyes\":0},\"timeline_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline\",\"performed_via_github_app\":null,\"state_reason\":null}"


JSON形式になっているので、返ってきたデータをRubyで操作したい場合、jsonライブラリのJSON.parseを使って、Ruby オブジェクトに変換する必要がある。

require "net/http"
require "uri"
# 追記
require "json" #jsonライブラリを使うためにrequireする必要がある


uri = URI.parse("https://api.github.com/repos/fjordllc/bootcamp/issues/1")
json = Net::HTTP.get(uri)

# Rubyオブジェクトに変換したいJSONデータを引数に渡す
issue = JSON.parse(json)

このように、引数にJSONデータを渡すことで、Rubyオブジェクトに変換してくれる。 issueの中身は以下のようになる。(見やすいようにppメソッドで出力している。)


変数issueの中身(非常に長いので折りたたんだ)

{"url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1",
 "repository_url"=>"https://api.github.com/repos/fjordllc/bootcamp",
 "labels_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}",
 "comments_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments",
 "events_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events",
 "html_url"=>"https://github.com/fjordllc/bootcamp/pull/1",
 "id"=>11961549,
 "node_id"=>"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==",
 "number"=>1,
 "title"=>"Update rails 4.0.0.beta",
 "user"=>
  {"login"=>"hrysd",
   "id"=>1663465,
   "node_id"=>"MDQ6VXNlcjE2NjM0NjU=",
   "avatar_url"=>"https://avatars.githubusercontent.com/u/1663465?v=4",
   "gravatar_id"=>"",
   "url"=>"https://api.github.com/users/hrysd",
   "html_url"=>"https://github.com/hrysd",
   "followers_url"=>"https://api.github.com/users/hrysd/followers",
   "following_url"=>"https://api.github.com/users/hrysd/following{/other_user}",
   "gists_url"=>"https://api.github.com/users/hrysd/gists{/gist_id}",
   "starred_url"=>"https://api.github.com/users/hrysd/starred{/owner}{/repo}",
   "subscriptions_url"=>"https://api.github.com/users/hrysd/subscriptions",
   "organizations_url"=>"https://api.github.com/users/hrysd/orgs",
   "repos_url"=>"https://api.github.com/users/hrysd/repos",
   "events_url"=>"https://api.github.com/users/hrysd/events{/privacy}",
   "received_events_url"=>"https://api.github.com/users/hrysd/received_events",
   "type"=>"User",
   "site_admin"=>false},
 "labels"=>[],
 "state"=>"closed",
 "locked"=>false,
 "assignee"=>nil,
 "assignees"=>[],
 "milestone"=>nil,
 "comments"=>0,
 "created_at"=>"2013-03-13T06:30:12Z",
 "updated_at"=>"2013-03-13T06:39:10Z",
 "closed_at"=>"2013-03-13T06:39:10Z",
 "author_association"=>"CONTRIBUTOR",
 "active_lock_reason"=>nil,
 "draft"=>false,
 "pull_request"=>
  {"url"=>"https://api.github.com/repos/fjordllc/bootcamp/pulls/1",
   "html_url"=>"https://github.com/fjordllc/bootcamp/pull/1",
   "diff_url"=>"https://github.com/fjordllc/bootcamp/pull/1.diff",
   "patch_url"=>"https://github.com/fjordllc/bootcamp/pull/1.patch",
   "merged_at"=>"2013-03-13T06:39:10Z"},
 "body"=>"",
 "closed_by"=>
  {"login"=>"komagata",
   "id"=>16577,
   "node_id"=>"MDQ6VXNlcjE2NTc3",
   "avatar_url"=>"https://avatars.githubusercontent.com/u/16577?v=4",
   "gravatar_id"=>"",
   "url"=>"https://api.github.com/users/komagata",
   "html_url"=>"https://github.com/komagata",
   "followers_url"=>"https://api.github.com/users/komagata/followers",
   "following_url"=>"https://api.github.com/users/komagata/following{/other_user}",
   "gists_url"=>"https://api.github.com/users/komagata/gists{/gist_id}",
   "starred_url"=>"https://api.github.com/users/komagata/starred{/owner}{/repo}",
   "subscriptions_url"=>"https://api.github.com/users/komagata/subscriptions",
   "organizations_url"=>"https://api.github.com/users/komagata/orgs",
   "repos_url"=>"https://api.github.com/users/komagata/repos",
   "events_url"=>"https://api.github.com/users/komagata/events{/privacy}",
   "received_events_url"=>"https://api.github.com/users/komagata/received_events",
   "type"=>"User",
   "site_admin"=>false},
 "reactions"=>
  {"url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions",
   "total_count"=>0,
   "+1"=>0,
   "-1"=>0,
   "laugh"=>0,
   "hooray"=>0,
   "confused"=>0,
   "heart"=>0,
   "rocket"=>0,
   "eyes"=>0},
 "timeline_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline",
 "performed_via_github_app"=>nil,
 "state_reason"=>nil}


このようにRubyのハッシュに変換されるので、Hashクラスのメソッド等を使って、返ってきたデータを操作することができる。 しかし、このコードでは2つの不便な点がある。

素のRubyで書いた時の不便な点

1. 認証を行っていないので、1時間のリクエスト回数が60回までになっている

上記のコードだと、GitHubに認証していない状態でREST APIにリクエストしているので、1時間あたりのリクエスト回数が60回までしか許可されていない。詳細は以下のドキュメントに載っている。

Octokit.rbを使えば簡単に認証の処理が書けるので、後で認証方法を説明する。

2. 返ってきたデータをいちいちJSON.parseしないとRubyでデータを処理できない

上記のコードだと、

  • APIリクエストしたいエンドポイントのURLをパースする
  • 返ってきたJSONデータをRubyで操作したい場合は、JSON.parseRubyオブジェクトに変換する

といった処理を必ず書かないといけない。
Octokit.rbを使えば、これらの処理を自分で書く必要がない。具体的な使い方は後で説明する。

Octokit.rbの使い方

インストール

gem installコマンドで、

$ gem install octokit

を実行するか、

Gemfileに以下を追記して、

gem "octokit", "~> 5.0"

追記後bundle installを実行する。

※さらに、Ruby on Railsではなく、RubyファイルでOctokit.rbを使いたい場合は、ファイルの一行目にrequire 'octokit'を追加する。

(octokit/octokit.rb - installationより)

認証方法

パーソナルアクセストークンを使う場合

require 'octokit'

client = Octokit::Client.new(:access_token => '<取得したパーソナルアクセストークン>')

userメソッドを使うと、現在認証しているユーザーの情報が返ってくる。

p client.user

#=>
{:login=>"Saki-htr",
 :id=>58052292,
 :node_id=>"MDQ6VXNlcjU4MDUyMjky",
 :avatar_url=>"https://avatars.githubusercontent.com/u/58052292?v=4",
 :gravatar_id=>"",
 :url=>"https://api.github.com/users/Saki-htr",
 :html_url=>"https://github.com/Saki-htr",
 :followers_url=>"https://api.github.com/users/Saki-htr/followers",
 :following_url=>
  "https://api.github.com/users/Saki-htr/following{/other_user}",
 :gists_url=>"https://api.github.com/users/Saki-htr/gists{/gist_id}",
 :starred_url=>"https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
 :subscriptions_url=>"https://api.github.com/users/Saki-htr/subscriptions",
 :organizations_url=>"https://api.github.com/users/Saki-htr/orgs",
 :repos_url=>"https://api.github.com/users/Saki-htr/repos",
 :events_url=>"https://api.github.com/users/Saki-htr/events{/privacy}",
 :received_events_url=>"https://api.github.com/users/Saki-htr/received_events",
 :type=>"User",
 :site_admin=>false,
 :name=>"服部紗希",
 :company=>nil,
 :blog=>"https://saki-htr.hatenablog.com/",
 :location=>"Tokyo Japan",
 :email=>nil,
 :hireable=>nil,
 :bio=>nil,
 :twitter_username=>nil,
 :public_repos=>33,
 :public_gists=>1,
 :followers=>1,
 :following=>0,
 :created_at=>2019-11-21 21:35:26 UTC,
 :updated_at=>2022-07-25 12:05:10 UTC,
 :private_gists=>16,
 :total_private_repos=>1,
 :owned_private_repos=>1,
 :disk_usage=>6912,
 :collaborators=>0,
 :two_factor_authentication=>true,
 :plan=>
  {:name=>"free", :space=>976562499, :collaborators=>0, :private_repos=>10000}}

これで認証は完了している。

ヘッダー情報の取得

リクエスト回数の制限が増えているか確認したい場合、ヘッダー情報を取得する。 取得するには、last_responseメソッドとheadersメソッドを使う。(octokit/octokit.rb - HTTP 応答へのアクセス より)

require 'octokit'

client = Octokit::Client.new(:access_token => '<取得したパーソナルアクセストークン>')
client.user #ヘッダー情報を見るために何かしらのAPIリクエストを行う必要があるので、ここでリクエストしている

pp client.last_response.headers

#=>
{"server"=>"GitHub.com",
 "date"=>"Thu, 10 Nov 2022 06:41:12 GMT",
 "content-type"=>"application/json; charset=utf-8",
 "transfer-encoding"=>"chunked",
 "cache-control"=>"private, max-age=60, s-maxage=60",
 "vary"=>"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With",
 "etag"=>"W/\"cc2ff6079e9d00572f10fdd5af28e5d360d38cb29e52ded493a68d130e011c26\"",
 "last-modified"=>"Mon, 25 Jul 2022 12:05:10 GMT",
 "x-oauth-scopes"=>"read:enterprise, read:public_key, read:repo_hook, read:user, user:email",
 "x-accepted-oauth-scopes"=>"",
 "github-authentication-token-expiration"=>"2023-05-12 15:00:00 UTC",
 "x-github-media-type"=>"github.v3; format=json",
 "x-ratelimit-limit"=>"5000",
 "x-ratelimit-remaining"=>"4989",
 "x-ratelimit-reset"=>"1668064617",
 "x-ratelimit-used"=>"11",
 "x-ratelimit-resource"=>"core",
 "access-control-expose-headers"=>
  "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
 "access-control-allow-origin"=>"*",
 "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload",
 "x-frame-options"=>"deny",
 "x-content-type-options"=>"nosniff",
 "x-xss-protection"=>"0",
 "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin",
 "content-security-policy"=>"default-src 'none'",
 "content-encoding"=>"gzip",
 "x-github-request-id"=>"CC82:5060:5E4B98:66FA98:636C9D08"}

client.last_response.headersは、最後のAPIリクエストに対するレスポンスの、ヘッダー情報を返している。
このうち、以下の部分がリクエスト回数の制限についての情報を示している。

# 1時間あたりに許可されるリクエストの最大数
 "x-ratelimit-limit"=>"5000",
# 現在のレート制限ウィンドウに残っているリクエストの数
 "x-ratelimit-remaining"=>"4989",
# 現在のレート制限ウィンドウがリセットされる時刻 (UTCエポック秒)
 "x-ratelimit-reset"=>"1668064617",
# 現在のレート制限ウィンドウで行ったリクエストの数
 "x-ratelimit-used"=>"11",

x-ratelimit-limitが5000になっているので、認証した上でAPIリクエストしていることが分かる。

OAuthトークンを使う場合

他のユーザーに代わってAPIを使用して個人情報を読み書きする必要があるアプリは、OAuthトークンを使用する必要がある。
APIリクエストさせたいアプリケーションをOAuthアプリとして登録する方法は、以下のドキュメントに詳しく載っている。

取得したら、APIリクエストさせるファイルに、以下のように書く。(octokit/octokit.rb - Application authenticationより)

client = Octokit::Client.new(client_id: '<OAuthトークンのキー20文字>', client_secret: '<OAuthトークンのシークレット40文字>')

このclientインスタンスを使ってAPIリクエストすると、認証した上でリクエストを送ることができる。
例えば、指定したユーザーのpublicな情報を取得したい場合は、以下のように書く。

client = Octokit::Client.new(client_id: '<OAuthトークンのキー20文字>', client_secret: '<OAuthトークンのシークレット40文字>')#

# 指定したユーザーのpublicな情報を取得する
client.user 'Saki-htr'

#=>
{:login=>"Saki-htr",
 :id=>58052292,
 :node_id=>"MDQ6VXNlcjU4MDUyMjky",
 :avatar_url=>"https://avatars.githubusercontent.com/u/58052292?v=4",
 :gravatar_id=>"",
 :url=>"https://api.github.com/users/Saki-htr",
 :html_url=>"https://github.com/Saki-htr",
 :followers_url=>"https://api.github.com/users/Saki-htr/followers",
 :following_url=>
  "https://api.github.com/users/Saki-htr/following{/other_user}",
 :gists_url=>"https://api.github.com/users/Saki-htr/gists{/gist_id}",
 :starred_url=>"https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
 :subscriptions_url=>"https://api.github.com/users/Saki-htr/subscriptions",
 :organizations_url=>"https://api.github.com/users/Saki-htr/orgs",
 :repos_url=>"https://api.github.com/users/Saki-htr/repos",
 :events_url=>"https://api.github.com/users/Saki-htr/events{/privacy}",
 :received_events_url=>"https://api.github.com/users/Saki-htr/received_events",
 :type=>"User",
 :site_admin=>false,
 :name=>"服部紗希",
 :company=>nil,
 :blog=>"https://saki-htr.hatenablog.com/",
 :location=>"Tokyo Japan",
 :email=>nil,
 :hireable=>nil,
 :bio=>nil,
 :twitter_username=>nil,
 :public_repos=>33,
 :public_gists=>1,
 :followers=>1,
 :following=>0,
 :created_at=>2019-11-21 21:35:26 UTC,
 :updated_at=>2022-07-25 12:05:10 UTC}

これは、REST APIのエンドポイントhttps://api.github.com/users/<ユーザー名>にアクセスしている。 このエンドポイントについての詳細は以下に載っている。

ヘッダー情報の取得

これもパーソナルアクセストークンと同じで、client.last_response.headersで取得できる。ここにレート制限の情報も入っている。

pp client.last_response.headers

#=>
{"server"=>"GitHub.com",
 "date"=>"Thu, 10 Nov 2022 07:04:45 GMT",
 "content-type"=>"application/json; charset=utf-8",
 "transfer-encoding"=>"chunked",
 "cache-control"=>"public, max-age=60, s-maxage=60",
 "vary"=>"Accept, Accept-Encoding, Accept, X-Requested-With",
 "etag"=>"W/\"49e08a708a784b2cf84ba6a0f6b5a79b665b2a6449e103c7a5ade32d925a82dc\"",
 "last-modified"=>"Mon, 25 Jul 2022 12:05:10 GMT",
 "x-github-media-type"=>"github.v3; format=json",
 "x-ratelimit-limit"=>"5000",
 "x-ratelimit-remaining"=>"4994",
 "x-ratelimit-reset"=>"1668066924",
 "x-ratelimit-used"=>"6",
 "x-ratelimit-resource"=>"core",
 "access-control-expose-headers"=>
  "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
 "access-control-allow-origin"=>"*",
 "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload",
 "x-frame-options"=>"deny",
 "x-content-type-options"=>"nosniff",
 "x-xss-protection"=>"0",
 "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin",
 "content-security-policy"=>"default-src 'none'",
 "content-encoding"=>"gzip",
 "x-github-request-id"=>"CE6B:379A:141FCB:1C9892:636CA28D"}

Octokit.rbを使うととても簡単に認証できる

このように、Octokit.rbを使えば、Octokit::Client.newの引数にトークンを渡すだけで簡単に認証することができるので、とても便利。

Octokit.rbでAPIをたたく

GitHubAPIに対して自分がやりたいことを実現する方法を探すには、以下の公式ドキュメントから探す。

自分が使うOctokit.rbのメソッドが、APIのどのエンドポイントにリクエストするか知りたい場合は、各メソッドの説明の「See Also:」に、各エンドポイントへの公式ドキュメントのURLが載っているので、そちらを見ると分かる。

Module: Octokit::Client::Users —より

例1:指定したリポジトリのうち、指定したidのissueを取得する

上述の素のRubyAPIリクエストした例と同じように、「とあるリポジトリの、指定したidのissueを取得したい」とする。
これを取得するには、Octokit.rbが用意しているissueメソッドを使う。

Module: Octokit::Client::Issues —より

これを使う場合、以下のように書く。

# 使い方
client.issue('アカウント名/リポジトリ名', 'issueのid')
# 認証方法はどちらでも良い
  # パーソナルアクセストークンで認証
client = Octokit::Client.new(:access_token => '<取得したパーソナルアクセストークン>')

  # OAuthトークンで認証
client = Octokit::Client.new(client_id: '<OAuthトークンのキー20文字>', client_secret: '<OAuthトークンのシークレット40文字>')

# fjordllc/bootcampリポジトリのidが1のissueを取得する
client.issue('fjordllc/bootcamp', '1')

これで、以下のデータが返ってくる。

返ってくるデータ(長いので折り畳んだ)

{:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1",
 :repository_url=>"https://api.github.com/repos/fjordllc/bootcamp",
 :labels_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}",
 :comments_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments",
 :events_url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events",
 :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
 :id=>11961549,
 :node_id=>"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==",
 :number=>1,
 :title=>"Update rails 4.0.0.beta",
 :user=>
  {:login=>"hrysd",
   :id=>1663465,
   :node_id=>"MDQ6VXNlcjE2NjM0NjU=",
   :avatar_url=>"https://avatars.githubusercontent.com/u/1663465?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/hrysd",
   :html_url=>"https://github.com/hrysd",
   :followers_url=>"https://api.github.com/users/hrysd/followers",
   :following_url=>"https://api.github.com/users/hrysd/following{/other_user}",
   :gists_url=>"https://api.github.com/users/hrysd/gists{/gist_id}",
   :starred_url=>"https://api.github.com/users/hrysd/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/hrysd/subscriptions",
   :organizations_url=>"https://api.github.com/users/hrysd/orgs",
   :repos_url=>"https://api.github.com/users/hrysd/repos",
   :events_url=>"https://api.github.com/users/hrysd/events{/privacy}",
   :received_events_url=>"https://api.github.com/users/hrysd/received_events",
   :type=>"User",
   :site_admin=>false},
 :labels=>[],
 :state=>"closed",
 :locked=>false,
 :assignee=>nil,
 :assignees=>[],
 :milestone=>nil,
 :comments=>0,
 :created_at=>2013-03-13 06:30:12 UTC,
 :updated_at=>2013-03-13 06:39:10 UTC,
 :closed_at=>2013-03-13 06:39:10 UTC,
 :author_association=>"CONTRIBUTOR",
 :active_lock_reason=>nil,
 :draft=>false,
 :pull_request=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/pulls/1",
   :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
   :diff_url=>"https://github.com/fjordllc/bootcamp/pull/1.diff",
   :patch_url=>"https://github.com/fjordllc/bootcamp/pull/1.patch",
   :merged_at=>2013-03-13 06:39:10 UTC},
 :body=>"",
 :closed_by=>
  {:login=>"komagata",
   :id=>16577,
   :node_id=>"MDQ6VXNlcjE2NTc3",
   :avatar_url=>"https://avatars.githubusercontent.com/u/16577?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/komagata",
   :html_url=>"https://github.com/komagata",
   :followers_url=>"https://api.github.com/users/komagata/followers",
   :following_url=>
    "https://api.github.com/users/komagata/following{/other_user}",
   :gists_url=>"https://api.github.com/users/komagata/gists{/gist_id}",
   :starred_url=>
    "https://api.github.com/users/komagata/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/komagata/subscriptions",
   :organizations_url=>"https://api.github.com/users/komagata/orgs",
   :repos_url=>"https://api.github.com/users/komagata/repos",
   :events_url=>"https://api.github.com/users/komagata/events{/privacy}",
   :received_events_url=>
    "https://api.github.com/users/komagata/received_events",
   :type=>"User",
   :site_admin=>false},
 :reactions=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions",
   :total_count=>0,
   :"+1"=>0,
   :"-1"=>0,
   :laugh=>0,
   :hooray=>0,
   :confused=>0,
   :heart=>0,
   :rocket=>0,
   :eyes=>0},
 :timeline_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline",
 :performed_via_github_app=>nil,
 :state_reason=>nil}

hattorisaki@Mac-4 ~/blog (elevator)> ruby app.rb
{:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1",
 :repository_url=>"https://api.github.com/repos/fjordllc/bootcamp",
 :labels_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}",
 :comments_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments",
 :events_url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events",
 :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
 :id=>11961549,
 :node_id=>"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==",
 :number=>1,
 :title=>"Update rails 4.0.0.beta",
 :user=>
  {:login=>"hrysd",
   :id=>1663465,
   :node_id=>"MDQ6VXNlcjE2NjM0NjU=",
   :avatar_url=>"https://avatars.githubusercontent.com/u/1663465?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/hrysd",
   :html_url=>"https://github.com/hrysd",
   :followers_url=>"https://api.github.com/users/hrysd/followers",
   :following_url=>"https://api.github.com/users/hrysd/following{/other_user}",
   :gists_url=>"https://api.github.com/users/hrysd/gists{/gist_id}",
   :starred_url=>"https://api.github.com/users/hrysd/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/hrysd/subscriptions",
   :organizations_url=>"https://api.github.com/users/hrysd/orgs",
   :repos_url=>"https://api.github.com/users/hrysd/repos",
   :events_url=>"https://api.github.com/users/hrysd/events{/privacy}",
   :received_events_url=>"https://api.github.com/users/hrysd/received_events",
   :type=>"User",
   :site_admin=>false},
 :labels=>[],
 :state=>"closed",
 :locked=>false,
 :assignee=>nil,
 :assignees=>[],
 :milestone=>nil,
 :comments=>0,
 :created_at=>2013-03-13 06:30:12 UTC,
 :updated_at=>2013-03-13 06:39:10 UTC,
 :closed_at=>2013-03-13 06:39:10 UTC,
 :author_association=>"CONTRIBUTOR",
 :active_lock_reason=>nil,
 :draft=>false,
 :pull_request=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/pulls/1",
   :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
   :diff_url=>"https://github.com/fjordllc/bootcamp/pull/1.diff",
   :patch_url=>"https://github.com/fjordllc/bootcamp/pull/1.patch",
   :merged_at=>2013-03-13 06:39:10 UTC},
 :body=>"",
 :closed_by=>
  {:login=>"komagata",
   :id=>16577,
   :node_id=>"MDQ6VXNlcjE2NTc3",
   :avatar_url=>"https://avatars.githubusercontent.com/u/16577?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/komagata",
   :html_url=>"https://github.com/komagata",
   :followers_url=>"https://api.github.com/users/komagata/followers",
   :following_url=>
    "https://api.github.com/users/komagata/following{/other_user}",
   :gists_url=>"https://api.github.com/users/komagata/gists{/gist_id}",
   :starred_url=>
    "https://api.github.com/users/komagata/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/komagata/subscriptions",
   :organizations_url=>"https://api.github.com/users/komagata/orgs",
   :repos_url=>"https://api.github.com/users/komagata/repos",
   :events_url=>"https://api.github.com/users/komagata/events{/privacy}",
   :received_events_url=>
    "https://api.github.com/users/komagata/received_events",
   :type=>"User",
   :site_admin=>false},
 :reactions=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions",
   :total_count=>0,
   :"+1"=>0,
   :"-1"=>0,
   :laugh=>0,
   :hooray=>0,
   :confused=>0,
   :heart=>0,
   :rocket=>0,
   :eyes=>0},
 :timeline_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline",
 :performed_via_github_app=>nil,
 :state_reason=>nil}


このように、octokit.rbを使うと、JSON形式ではなく、Rubyオブジェクトでデータを返してくれるので、JSON.parseRubyオブジェクトに変換しなくてすむので、とても便利。

例2: 指定したリポジトリの中で、指定したユーザーにアサインされたissueの一覧を取得する

issueの一覧を取得する場合、list_issuesメソッドを使う。

Module: Octokit::Client::Issues —より

Options Hashの欄に、:assignee (String) — User login.とある。Optionsはハッシュで渡すので、以下のように書く。

client.list_issues('アカウント名/リポジトリ名', { assignee: 'ユーザー名' })

すると、以下のようなイメージで、配列に各issueのデータがハッシュに入って返ってくる。

[
  {1つ目のissueのデータ},
  {2つ目のissueのデータ},
  {3つ目のissueのデータ}
]

複数人分、一気に取得したい場合

自作サービスで、複数人分、つまりTanakaさんにアサインされたissueとSatoさんにアサインされたissueを一気にAPIリクエストして取得したかったので、以下のように書いて取得した。

# 取得したいユーザー名を入れた配列を用意しておく
students = ['Tanaka', 'Sato']

# studentをeachで回してissueを取得する
assigned_issues = students.flat_map do |student|
  client.list_issues('fjordllc/bootcamp', { assignee: student })
end

flat_mapメソッドを使っている理由は、mapメソッドを使うと、

[[
  {1つ目のissue},
  {2つ目のissue},
  {3つ目のissue}
]]

のように、二重配列になってしまうため、ネストしている配列をなくした結果を返してくれるflat_mapメソッドを使った。
(参考:配列に使える便利メソッド flat_map/collect_concatについて - Qiita)

おわりに

一回素のRubyで書いたことで、ラッパーライブラリの便利さが分かり、開発してくださった方へのありがたみが増したので、遠回りかもしれないが、使う場合と使わない場合の両方のパターンでAPIリクエストの処理を書いてみてよかったなと思う。

【REST】curlコマンドでGitHubAPIをたたく方法

はじめに

 フィヨルドブートキャンプ(以下、FBC)の「自分で考えたサービスをリリースする」という最終課題で、GitHubREST APIを利用した。(※近日リリース予定)
外部サービスのAPIをたたくのは今回が初めてで、

  • いきなりラッパーライブラリ*1を使ってAPIをたたこうとしたら、使い方が全く分からなかった
  • GitHubが用意してくれているSinatraアプリAPIをたたこうとしたら、認証の話などが出てきて大混乱

という事態に陥った。
API,ラッパーライブラリ,OAuthアプリケーションによる認証など、分からないことが同時に複数出てきて、どのようにサービス開発のための技術検証を進めていけばいいか悩んだので、FBCの卒業生の方々に相談したところ、

「認証の仕組み」と、「APIから欲しい情報を取得できるか」は分けて考えたほうが良い。
認証の理解は難しいので、まず欲しいデータを取れるかどうかを、認証を行うOAuthアプリケーションではなく、ターミナル上でcurlコマンドを使って試すといい。

とアドバイスをいただいた。
なので、「自分が欲しい情報を、そもそもGitHub APIが持っているか」 を検証するために、curlコマンドで様々なAPIリクエストを試した。

この記事の内容

当時curlコマンドでAPIをたたいた時につまづいたことを中心に、

  • REST と GraphQL の違い・特徴
  • curlコマンドを使ったAPIのたたき方
  • 「GitHubAPIに対してやりたいこと」を実現できるエンドポイントの探し方
  • 認証した上でAPIリクエストする方法
  • 今自分が許可されているリクエスト回数の上限の確認方法
  • クエリパラメーターの使い方

についてまとめた。

目次

GitHubAPIを使うと、どんなことができるのか?

GitHubAPIを使うと、

  • Issueの作成・更新・取得・削除
  • Organizationsの一覧やメンバーの取得
  • Pull Requestの生成・更新・取得・削除
  • Repositoryの生成・更新・取得・削除

などを行うことができる。できることは多岐に渡るので、この他にもたくさんある。

REST API と GraphQLのちがい

GitHubが用意しているAPIには、RESTとGraphQLの2種類がある。

REST

欲しいデータが入っているAPIのエンドポイント*2を指定してデータを得る。
自分が欲しいデータ以外もごっそり入っているので、アプリケーション開発等で特定の情報だけ使いたい場合、コードを書いて欲しい情報だけ得られるように加工する必要がある。

特徴

  • クエリの実行:URLで欲しい情報を要求する(クエリを実行する)
  • 欲しい情報が入っているエンドポイントを指定する
  • データ形式:受け取るデータ形式は決まっている
  • 欲しいデータ以外も取得してしまう

使用例

例えば、とあるユーザーのGitHubアカウント名とアイコン画像のURLを取得したい場合、以下のようなエンドポイントを指定してデータを得ることになる。(参考:ユーザー - GitHub Docsより)

# Saki-htrというユーザーのpublicな情報を取得する
$ curl https://api.github.com/users/Saki-htr

{
  "login": "Saki-htr", # アカウント名
  "id": 58052292,
  "node_id": "MDQ6VXNlcjU4MDUyMjky",
  "avatar_url": "https://avatars.githubusercontent.com/u/58052292?v=4", # アイコン画像のURL
  "gravatar_id": "",
  "url": "https://api.github.com/users/Saki-htr",
  "html_url": "https://github.com/Saki-htr",
  "followers_url": "https://api.github.com/users/Saki-htr/followers",
  "following_url": "https://api.github.com/users/Saki-htr/following{/other_user}",
  "gists_url": "https://api.github.com/users/Saki-htr/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/Saki-htr/subscriptions",
  "organizations_url": "https://api.github.com/users/Saki-htr/orgs",
  "repos_url": "https://api.github.com/users/Saki-htr/repos",
  "events_url": "https://api.github.com/users/Saki-htr/events{/privacy}",
  "received_events_url": "https://api.github.com/users/Saki-htr/received_events",
  "type": "User",
  "site_admin": false,
  "name": "服部紗希",
  "company": null,
  "blog": "https://saki-htr.hatenablog.com/",
  "location": "Tokyo Japan",
  "email": null,
  "hireable": null,
  "bio": null,
  "twitter_username": null,
  "public_repos": 33,
  "public_gists": 1,
  "followers": 1,
  "following": 0,
  "created_at": "2019-11-21T21:35:26Z",
  "updated_at": "2022-07-25T12:05:10Z",
  "private_gists": 16,
  "total_private_repos": 1,
  "owned_private_repos": 1,
  "disk_usage": 6885,
  "collaborators": 0,
  "two_factor_authentication": true,
  "plan": {
    "name": "free",
    "space": 976562499,
    "collaborators": 0,
    "private_repos": 10000
  }
}

このJSONデータのloginキーにアカウント名、avatar_urlキーにアイコン画像のURLが入っている。 このように、REST APIでは、欲しい情報のみを取得することはできない。

GraphQLとは

クライアント側が、必要なデータだけをAPIから取ってこれるように設計された言語。

特徴

  • GraphQLは欲しいデータと型をピンポイントで指定して、欲しい情報だけ受け取れる
  • クエリの実行:言語で欲しい情報を要求する(クエリを実行する)
  • 受け取る方の型を指定できる
  • 必要とするデータだけを取得できる

使用例

先程のRESTと同じように、とあるユーザーのGitHubアカウント名とアイコン画像のURLを取得したい場合、以下のようなクエリを書くと、アカウント名とアイコン画像だけが入ったJSONデータを取得できる。

# 実行するクエリ
query { 
  user(login: "Saki-htr") { 
    login
    avatarUrl
  }
}
# レスポンス
{
  "data": {
    "user": {
      "login": "Saki-htr",
      "avatarUrl": "https://avatars.githubusercontent.com/u/58052292?u=655c1af43b9c8331b65387923ef25d6bc197cded&v=4"
    }
  }
}

このように、欲しい情報のみを取得することができる。
公式が用意してくれている以下のページで、手軽にクエリを実行&レスポンスの確認ができる。

REST APIに、curlコマンドでリクエストする

RESTにリクエストする時の基本

REST API にリクエストを行うときは、HTTP メソッド(例:GET,POST)とパス(URL)を指定する。これに加えて、リクエストヘッダー、パス、クエリ、またはボディのパラメーターを指定することもできる。

以下に実際にcurlコマンドでたたいた時の例を書いた。

例:指定したリポジトリのissueのリストを取得する

# 書き方
curl https://api.github.com/repos/<リポジトリのオーナーになっているユーザー名>/<リポジトリ名>/issues

# 例:fjordllc/bootcampリポジトリのissue一覧を取得する
curl https://api.github.com/repos/fjordllc/bootcamp/issues

(返ってくるデータの量が膨大なので、返ってくるJSONデータを見たい方は以下のURLにアクセスをお願いします🙏)

「GitHubAPIに対してやりたいこと」を実現できるエンドポイントの探し方

以下の公式ドキュメントに一覧が載っているので、ここから探す。

ただ、この目次にはHTTPメソッドとパスしか記載されていないため、APIを触り慣れていない当初はここから探すのは難しかった。
「やりたいこと」から探したい場合、各見出しのエンドポイントをクリックすると、実現したい事柄ごとのリストのページにアクセスできる。

例えば、issueに対してやりたいことを実現するURLを探したい場合、まず「issues」という見出しをクリックする。

すると、issue - GitHub Docsというページにとぶ。

そのエンドポイントでできることが目次で載っているので、分かりやすい。

ヘッダー情報も取得したい場合

curlコマンドで-iオプションを付けると、ヘッダー情報も含めてレスポンスが返される。

$ curl -i https://api.github.com/users/Saki-htr

# レスポンスヘッダー
HTTP/2 200
server: GitHub.com
date: Sun, 06 Nov 2022 03:15:35 GMT
content-type: application/json; charset=utf-8
cache-control: public, max-age=60, s-maxage=60
vary: Accept, Accept-Encoding, Accept, X-Requested-With
etag: W/"368a3f29b158b6dcc6f93af02e427ef105cfea0a8dc8eeeed6824f664419d4dc"
last-modified: Mon, 25 Jul 2022 12:05:10 GMT
x-github-media-type: github.v3; format=json
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
access-control-allow-origin: *
strict-transport-security: max-age=31536000; includeSubdomains; preload
x-frame-options: deny
x-content-type-options: nosniff
x-xss-protection: 0
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
content-security-policy: default-src 'none'
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
x-ratelimit-reset: 1667708135
x-ratelimit-resource: core
x-ratelimit-used: 1
accept-ranges: bytes
content-length: 1340
x-github-request-id: C80B:546D:182C4C:1EA570:636726D7

# レスポンスボディ
{
  "login": "Saki-htr",
  "id": 58052292,
  "node_id": "MDQ6VXNlcjU4MDUyMjky",
  "avatar_url": "https://avatars.githubusercontent.com/u/58052292?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/Saki-htr",
  "html_url": "https://github.com/Saki-htr",
  "followers_url": "https://api.github.com/users/Saki-htr/followers",
  "following_url": "https://api.github.com/users/Saki-htr/following{/other_user}",
  "gists_url": "https://api.github.com/users/Saki-htr/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/Saki-htr/subscriptions",
  "organizations_url": "https://api.github.com/users/Saki-htr/orgs",
  "repos_url": "https://api.github.com/users/Saki-htr/repos",
  "events_url": "https://api.github.com/users/Saki-htr/events{/privacy}",
  "received_events_url": "https://api.github.com/users/Saki-htr/received_events",
  "type": "User",
  "site_admin": false,
  "name": "服部紗希",
  "company": null,
  "blog": "https://saki-htr.hatenablog.com/",
  "location": "Tokyo Japan",
  "email": null,
  "hireable": null,
  "bio": null,
  "twitter_username": null,
  "public_repos": 33,
  "public_gists": 1,
  "followers": 1,
  "following": 0,
  "created_at": "2019-11-21T21:35:26Z",
  "updated_at": "2022-07-25T12:05:10Z"
}

REST APIのリクエスト制限

リクエスト回数には制限があるので注意

REST APIには、リクエスト回数に制限がある。条件によって、どのような制限がかかるか異なる。
詳細は公式ドキュメントのREST API のリソース - GitHub Docsで説明されている。

これまでの上述の例のように、curlコマンドで未認証でREST APIにリクエストする場合、1時間あたり最大60件しかリクエストが許可されていない。この上限を超えるとAPIにリクエストできなくなり、たたいても以下のレスポンスが返ってくるようになる。

{
  "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
  "documentation_url": "https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting"
}

現在許可されているリクエストの最大回数,残りの回数を確認する方法

-iオプションをつけた時に返ってくるヘッダーの中にリクエスト制限に関する情報がのっている。

HTTP/2 200

(...省略...)

# 1時間あたりに許可されるリクエストの最大数
x-ratelimit-limit: 60
# 現在のレート制限ウィンドウに残っているリクエストの数
x-ratelimit-remaining: 58
# 現在のレート制限ウィンドウがリセットされる時刻 (UTCエポック秒)
x-ratelimit-reset: 1667708135
# 現在のレート制限ウィンドウで行ったリクエストの数
x-ratelimit-used: 2

(...省略...)

1時間あたりのリクエスト回数を上げるには、認証が必要

1時間あたりのリクエスト回数を上げるには、GitHub API で認証を行う必要がある。
(公式:REST API の使用を開始する - GitHub Docsより)

ターミナルでcurlコマンドを使ってAPIリクエストする場合

認証方法は複数あるが、私は、公式ドキュメントで一番簡単な方法と紹介されていた、パーソナルアクセストークンによる認証を使った。

1. パーソナルアクセストークンの作成

https://github.com にログインし、https://github.com/settings/tokens にアクセスし、パーソナルアクセストークンを作成する。

具体的な手順は以下のドキュメントに載っている

2. 作成したトークンを環境変数として保存する

取得したトークンを直打ちしても認証することはできる。 が、公式ドキュメントに

これらの値は、GitHub や、その他あらゆるパブリックな場所には、決して保存しないでください。それらを 環境変数として保存することをお勧めします。 トークン用の変数を設定することで、シェルの履歴にトークンが残るのを避けることができます。
(認証の基本 - GitHub Docsより)

とあるので、環境変数として保存した。
私の場合はfishシェルを使っているので、以下のように実行することで、環境変数を設定した。

$ set <任意の環境変数名> <作成したパーソナルアクセストークン>

# 例
$ set API_TOKEN <作成したパーソナルアクセストークン>


echoコマンドで出力して設定できていることを確認する。※環境変数名の前に$を付けるのを忘れない。

$ echo $API_TOKEN
#=><作成したパーソナルアクセストークン>

3. -uオプションを使って、認証した上でAPIリクエストを実行する

curlコマンドの-uオプションを使って、ユーザー名とパーソナルアクセストークンを送信することで、GitHubAPIに認証をした上でAPIリクエストを実行できる

$ curl -u <自分のGitHubアカウントのユーザー名>:<パーソナルアクセストークンを設定した環境変数名> https://api.github.com/<リクエストしたいパス>

# 例
$ curl -u Saki-htr:$API_TOKEN https://api.github.com/repos/fjordllc/bootcamp/issues


-iオプションも合わせて使い、ヘッダーを確認することで、リクエスト回数の上限が増えたことが確認できる。

$ curl -u Saki-htr:$API_TOKEN https://api.github.com/repos/fjordllc/bootcamp/issues

(...省略...)

# 1時間あたりに許可されるリクエストの最大数
x-ratelimit-limit: 5000 ✅60→5000に増えている!
# 現在のレート制限ウィンドウに残っているリクエストの数
x-ratelimit-remaining: 4997
# 現在のレート制限ウィンドウがリセットされる時刻 (UTCエポック秒)
x-ratelimit-reset: 1667710840
# 現在のレート制限ウィンドウで行ったリクエストの数
x-ratelimit-used: 3

(...省略...)

アプリケーションからAPIリクエストする場合

 自分のPCからではなく、自分が開発しているアプリケーションから認証させてAPIリクエストを行いたい場合は、パーソナルアクセストークンではなく、APIリクエストさせたいアプリケーションをOAuthアプリとして登録する必要がある。

OAuthアプリの登録方法は以下のドキュメントに詳しく載っている。

OAuthアプリの作成 - GitHub Docs

クエリパラメーターの使い方

各エンドポイントには、そのエンドポイントで使えるクエリパラメーターがある。何が使えるかは、公式ドキュメントの各エンドポイントのドキュメントに載っている。

以下は、Pulls (List pull requests)- GitHub Docsのドキュメント。

「Query parameters」の下に、使えるクエリパラメーターが載っている

このエンドポイントにAPIリクエストすると、指定したリポジトリのPullRequestの一覧が取得できるが、もっと条件をしぼって取得したいとする。
そのような場合、用意されているクエリパラメーターを使って以下のように、取得するPullRequestを絞ることができる。

# stateがclosed状態になっているPR
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed"

# 更新された順にPRが返ってくる
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?sort=updated"

# 検索にヒットした最初の3件だけ取得する
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?per_page=3"

クエリパラメーターを使う時は、URL全体を""で囲まないと、以下のようにエラーになってしまうので、注意。

$ curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed

fish: No matches for wildcard 'https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed'. See `help wildcards-globbing`.
curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed
     ^

複数使いたい場合は、&で繋ぐ。

# updated順に並び替えをして、最初の3つのPRを取得する
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?sort=updated&per_page=3"

クエリパラメーターを使っていない時の挙動は、どこを見たら分かるのか?

そもそもクエリパラメーターを使わないでAPIリクエストした時の挙動がどうなっているかは、公式ドキュメントの各クエリパラメーターのDefault:を見ると分かる

List pull requests の Query parameters

今回の場合、Defaultは

  • state : open
  • sort: created
  • per_page:30
  • page:1

とあるので、https://api.github.com/repos/fjordllc/bootcamp/pullsにリクエストすると、

  • openになっているPR かつ
  • 作成された順に並び替え かつ
  • 1ページあたりの検索結果数が30件 かつ
  • 1ページ目

つまり、openな状態のPRを、作成された順に検索してヒットした最初の30件が返ってくることになる。

1回のリクエストでもっと多くの数を取得したい

なので、1回のリクエストでもっと多くの数を取得したい場合、per_pageで件数を指定する必要がある。最大100件まで指定することができる

per_page と page とは?

per_page と pageが少し分かりにくいので補足する。
例えば、指定したリポジトリのPRのうち、openなものが100件あるとする。
この時、

$ curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=open&per_page=40

とリクエストすると、以下のような構成になる。

  • page1: 最初の40件
  • page2: その次の40件
  • page3: その次の20件

ここで、page2の部分のPR40件を取得したい場合は、

curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=open&per_page=40&page=2

というようにpageを指定する。

おわりに

今は欲しい情報を取得できるエンドポイントをささっと調べることができるが、APIを初めてたたいた当初は、公式ドキュメントを見てもリクエストのやり方が全然分からなかったので、成長を感じて嬉しい😁

参考記事

*1:ある開発環境向けに提供されたプログラムなどを別の環境で利用できるようにするものをラッパーという。Rubyのラッパーライブラリには、GitHub公式のoctokit.rbがある。

*2:あるプログラムがAPIなどの形で外部に公開している機能の所在を示す識別名やネットワーク上のアドレス、URL/URIなどのこと。

フィヨルドブートキャンプのシステム開発プラクティスを卒業したので、振返りをしました

はじめに

 1月末〜システム開発のプラクティスに入り、先日自分が作った全てのPRがマージされたので、振返りを書きました。

途中、自作サービスのエレベーターピッチ&ペーパープロトタイプ作成や技術検証に取り組んだり、1月中旬〜3月中旬の2ヶ月間のみ、働いているパートのシフトを週4から週5に増やしたり、FBC内でゴールデンウィークに開催されたEverydayRailsという書籍の短期集中輪読会に参加したり、とこのプラクティス以外にも色んなことに取り組んでいたのもあり、想定していたよりも時間がかかってしまいました。

システム開発に取り組む中で、たくさんの学びや気づきがあったので、「最近月ごとの振返りも書いていないし、自作サービスの開発に本格的に着手する前に、しっかり振返りをしておきたい。今の自分の気持ちは今しか書けないので残しておきたい!」という強い気持ちがあったので書きました。
また、これからシステム開発のプラクティスに入られる受講生の方が、システム開発がどんなものかを知る助けになれば嬉しいです。書きたいこと・伝えたいことを全部詰め込んだら、2万字を超えとんでもない文量になってしまったので、気になった箇所をつまみ食いして読んでいただけたら嬉しいです🙏

目次

そもそもシステム開発ラクティスとは、どんなことをするのか

 システム開発は、FBCのカリキュラム(プラクティス)の、「開発に参加して PR を送りマージする」 の部分に当たります。

学習内容 | FJORD BOOT CAMP(フィヨルドブートキャンプ)より引用

日報の作成・提出、質問の投稿・回答、イベントのお知らせ・申込などでいつもお世話になっているFBCのWebアプリの開発をチームで行います。
OSSなので、リポジトリはこちらで公開されています。もしアプリを使っていて、「あれ?これはもしかしてバグかな?」とか「ここの挙動がこうだったら使いやすいと思うな〜」といったことがあれば、システム開発ラクティスに到達しているかどうか関係なく、誰でもissueを立てたり、PRを作成することができます。

github.com

どのissueを優先して着手していくか、誰にどのissueが割り振られているのかといったissueの管理は、こちらのかんばんでされています。

issueには完成までにかかる時間に応じてポイントが振られることになっており、20ポイント分のPull Request(以降、PRと記載)がマージされれば、このプラクティスは完了となります。
また、自分で作ったPRは、必ず同時期にシステム開発に取り組んでいるFBC受講生の方1名とkomagataさんにレビューしていただき、2名のレビューを通ったらマージされるルールになっています。

毎週水曜日に1時間ミーティングがあり、以下のことを話し合います。

  • この1週間で自分の担当issueについて、やったこと・困っていること・難しかったことを、デモ(自分が作った機能を実際に画面共有して見せること)を交えながら共有する
  • issueに取り組んでいて疑問に思ったことを質問する
  • 次の1週間で取り組むissueを、割り振っていただく
  • 割り振っていただいたissueのうち、ポイントが付いてないものは全員で何ポイントか見積もる

気になる方は、ラジオ参加(マイクオフ、カメラオフで参加)自由なので、見学に行ってみるといいと思います。2022年6月現在は、毎週水曜の15:00-16:00と22:00-23:00に開催されています。(1日に2回開催されていますが、実際にプラクティスに取り組む際は、好きな方の時間帯に出席することになります。)

私が作成したPR一覧

Point 内容 Issue PR
1 ユーザー > 質問 のページタイトルを変更 #3709 #4075
1 ブックマークのタブに合計数を表示 #3749 #4088
1 メンター、アドバイザー、管理者はダッシュボードの学習時間(水草)を非表示にした #4110 #4211
2 管理者でログインしたときに、ユーザー個別ページに相談部屋へのリンクを表示した #4094 #4303
2 ユーザー情報変更ページで、管理者の場合はコース変更ができるようにした #4138 #4596
3 categories.positionを削除した #4245 #4641
2 js-choices-single-selectを使っている箇所において、Railsで渡したデータの並び順がJavaScriptに渡っても保持されるようにした。 #4746 #4748
2 [abstract_notifier] sadアイコンの通知を置き換えた #4682 #4889
5 未完了の提出物一覧を担当メンターで絞り込めるようにした #4853 #4954
2 メンター向け提出物一覧 >未完了 > 未返信 の一覧で表示される提出物を変更した #5028 #4954
2 大名エンジニアカレッジ特有の機能・フラグを削除した #4769 #5012
合計23

レビューしたPR一覧

Point 内容 Issue PR
1 ユーザー > 日報 のページのタイトルを変更した #3705 #4061
1 ユーザー > コメント のページのタイトルを変更した #3707 #4073
1 相談部屋一覧において、ローディング中にプレースホルダが表示されるようにした #4091 #4111
3 アドバイザーでログイン時、自社の研修生のプロフィールには「自社研修生」と表示されるようにした #2992 #4176
2 Vue.jsで表示されるページにおいて、ページネーションをクリックしたら、そのページの先頭に飛ぶようにした #2677 #4217
2 一度公開したお知らせを、誰でも公開の状態で保存できるように変更した #4127 #4215
1 就職関係のイベントの場合、企業研修生には参加不可と表示されるようにした #4216 #4272
0 お知らせ作成ページに不要な公開ボタンを表示しないようにした
(PR#4215で発生したバグ修正)
#4327 #4328
2 質問投稿時の通知に、質問のタイトルが表示されるようにした #4285 #4350
3 提出物一覧のページの各タブの並びを昇順にした #4312 #4399
1 左メニューの相談ボタンを全員に表示させた #4467 #4560
3 ニコニコカレンダーの日報未投稿の日付をクリックしたら、その日の日報作成画面が表示されるようにした #3295 #4550
1 カテゴリー並び替えページの、URLスラッグの行を削除した #4563 #4639
1 .eslintrcファイルに拡張子を付けた #3508 #4649
2 企業個別ページ > 提出物一覧において、ローディング中にプレースホルダが表示されるようにした #4669 #4706
1 管理画面の企業一覧の企業名に、企業個別ページのリンクを貼った #4665 #4737
2 相談部屋で管理者から受講生にコメントをした時の、通知メッセージを変更した #4409 #4778
1 企業一覧のリンクを、企業個別ページの企業情報タブに追加した #4768 #4785
1 相談部屋のボタンを削除し、タブを設置した #4155 #4830
5 定期イベントの一覧、個別、作成、削除を作成した #4537 #4862
1 日報一覧のtruncateを削除し、日報タイトルを全て表示するようにした #4835 #4911
2 提出物の担当通知の実装を、abstract_notifier(gem)に置き換えた #4681 #4823
1 企業のユーザー一覧ページに、企業一覧リンクを貼った #4773 #4943
2 Q&Aを削除する際に表示する確認ダイアログの文言を変更した #1531 #4981
1 企業の提出物ページに企業一覧リンクを貼った #4775 #4997
1 フッターの「参考書籍一覧」リンクの文言と位置を変更した #4956 #5001
1 リリースのために定期イベントのタブをコメントアウトした #4925 #5034
合計43

 issueに着手していた時に作業していたことの詳細は、Notionのチーム開発の作業ログ置き場に都度書くようにしていました。
基本的に自分がやってきたことを復習するため、振返るために書きましたが、「こんな感じで進めていた人もいるんだ~」と参考になれば嬉しいです🙏

システム開発に入る前の心境&入ってみてどうだったか

システム開発が近づくにつれ、嬉しい反面、不安も感じていた

 プラクティスが進み、システム開発がじわじわと迫ってくるにつれて、とても嬉しい反面、不安も感じていました。 FBC卒業生の方々には、「チーム開発が一番楽しかった」「たしかにこれまでの課題よりも難易度は上がるが、今までやってきたプラクティスでの学び・経験をフル活用するので、プラクティスを乗り越えてきた人なら大丈夫。」といったお話も伺っていましたが、「それでも自分の場合はだめかもしれない」と根拠のない不安を感じることもありました。
そのため、「今までやってきたプラクティスを復習したりもっと深く学んでから、システム開発に挑みたい」と思う時もありました。 ポケモンで例えると、「今の手持ちのポケモンでは勝てる気がしないので、ジムリーダーに挑戦する前に、草むらでレベルアップしてから挑みたい」みたいな心境でした。

実際に入ってみたら、想像していたよりも大丈夫だった

 実際に開発に入ってみたら、「全く何も分からなくて、手も足も出ない。詰んだ。」ということは、無かったです。
たしかにこれまでのプラクティスの課題で作ったアプリよりも、bootcampアプリのコードはずっと量も多くて複雑ですし、APIjbuilderといった、ここで初めて触れるものもありました。自分が作りたい機能の実装方法が思い浮かばず、「どこから手をつけていこう」と途方に暮れることもありました。

しかし、土台となる知識や考え方は、これまでのプラクティスで身についているなと感じました。
システム開発に入る前のプラクティスでは、「この知識は実務でどういう時に使うのだろう」と、あまり実際のお仕事で使う時のイメージが湧かない時もありましたが、開発に入ってみると、「あのプラクティスでやったことが、この機能の実装で使われてる!」「あの知識はここで大事だったのか!」という伏線回収をずっとしている感じでした。

「今までのプラクティスの知識は、実際の開発でどう活きてくるか」は、FBC卒業生のアルトさんがLTで発表してくださっているので、とても参考になると思います。

alto-i0.hatenablog.com

開発に入った頃の失敗いろいろ

 開発に入ったばかりの頃は、初めてのことばかりで慣れておらず、色々と失敗もしました。

例えば、

  • これまでコードを見ていただく側(レビュイー)しか経験したことがなかったので、GitHubのレビュワーの操作がよく分からず、初めてレビューしたPRで、Approveした後に間違えて「Re-request review」ボタンをクリックしてしまった。(本来はレビューをお願いした人が、もう一度同じ人に見ていただく時に押すボタン)

  • 開発環境とテスト環境の違いが分かっていなかったので、db/fixturestest/fixturesの違いも分からなかった
  • FBCシステム開発では、rebase方式を採用していて、merge方式は行わないことをあらかじめFBC内のドキュメントに書かれていて、それを読んだことがあったのに、間違えてmerge方式でコンフリクトの解消を行ってしまった。
  • かんばんの自分のissueのタスクボードを、ステージング環境で動作確認して大丈夫だったら「完了」に移動すると勘違いし、移動させてしまった。本当は「完成」に移動させるのは、本番リリースしてから動作環境確認できたら移動するルール。
  • レビューで、OKだと思って通したら、次の日テストとLintが落ちていたことに気づいた。すでにkomagataさんにレビュー依頼の連絡が行っていたので、慌ててご報告した。

というように、最初色々な失敗をしたので、落ち込むこともありました。

ですが、そういった失敗も自分の分報チャンネルに逐一書いていて、同じ時期にチーム開発に入った方に「自分も同じところを勘違いしていました。」 など仲間の方からコメントいただいたり、 すでにシステム開発ラクティスを終えられたFBC卒業生の方に、アドバイスをいただけたので、そこまで落ち込まずに前向きに行動を変えていく方にシフトしていくことができました。

GitHubの操作でミスをした時に、FBC卒業生の方からいただいたコメント

勿論、失敗しないように事前にしっかり調べることは大事ですが、起こしてしまった時は、反省をしっかりしつつ、気持ちはひきずりすぎないようにして、「次同じ失敗をしないようにするには、どういう工夫ができるか」、これからの行動をどうするかに意識を向けるようになりました。

学んだこと

名前重要を改めて実感

 コードを書く時に、まず自分で一から書く前に、実装するのに使えそうなメソッドが無いか探すのですが、まずメソッド名を見て、「これを使えばほしい情報を取得できそう」と判断して、メソッドの中身のコードを見るので、「名前重要」の大切さを実感しました。
これまでも、Rubyのプログラムの課題をやっていて、「名前付けが下手で、時間を置いて読んだら、自分で書いたコードなのにメソッドの中身が何をしているか分からなくなってしまった😂」ということがありました。ですが、自分一人だけでなくチームで開発するという経験をしたことで、他の人から見ても分かりやすいコードを書く意識の大切さをより実感しました。

コードを書くよりも、読んでいる時間の方がずっと長い

 これは、システム開発に入る前から、メンターさんからよく聞いていた言葉でしたが、本当にその通りでした。
自分が担当しているissueを実装するにあたって、書く前にまず、既存のコードがどうなっているかを理解する必要があります。
例えば、コントローラにインスタンス変数が元々定義されている時、自分のやりたい実装にも使い回せるものか、追記する必要があるかは、コードの中身を読んでみるまでは分かりません。

レビュー依頼された時は、いつ見られるかこまめに連絡する

 自分の作成したPRを、同じ時期にシステム開発をやっている、任意のFBC生1名に、レビューをお願いすることになっています。 お願いされたレビューをなかなか返せず、「レビュー依頼させていただいているのですが、どうなっていますか〜」とご連絡をいただいたことがありました。
最初の頃はレビューを終えるのにどれくらいの時間がかかるのか見積もりができず、「少なくともこれくらいまでには返せるだろう」と思っていたら予想が外れて、予定よりも返すのが遅くなってしまうことがありました。

そのため、もちろんすぐにレビューをお返しするのが一番なのですが、3月末までは、働きながらシステム開発をやっていて、すぐにお返しできなかったり、手持ちのレビュー依頼をすでに複数持っていたりすることがあったので、時間がかかりそうな時は、「すみません、返すのが○日くらいになりそうなのですが、それでもよろしければ、ぜひレビューやらせていただきたいです」というように連絡を取るようになりました。

https://github.com/fjordllc/bootcamp/pull/4215 より

人によって、レビューをいつ頃までに返してほしいかの考え方は異なりますし(もちろん早ければ早いほど良いですが😅)、レビューを依頼した側から、「早くレビューしてほしい」とか「時間がかかりそうなので他の方にお願いしますね」などとは言いにくいと思ったので、「もし▲日にお返しだとちょっと遅いな〜ということであれば、他の方にご依頼いただけましたらありがたいです。せっかくご依頼くださったのに申し訳ないです。」とお伝えするようになりました。


振り返って考えると、お仕事は自分一人で完結するものではなく、ベルトコンベアのように、誰かが止まるとその後ろも止まってしまうので、なるべくこまめにコミュニケーションをとって、流れが止まらないようにすべきだったと思います。
アジャイル開発の考え方も、小さく作って早くユーザーに届けて、反応を見てまた開発を進めて、という風に開発サイクルをどんどん回していくという考え方なので、エンジニアとして働かせていただけることになったら、「早くユーザーの元に届けるには、今自分がどう動くのが最適か」を意識して仕事せねばと思っています。

心がけていたこと・やって良かったこと

大声で作業する

 システム開発ラクティスに入る前から、machidaさんから、「システム開発ラクティスは、周りに自分の状況を伝えたり、詰まった時に素直に頼れたりする人が、順調に卒業していく」と伺っていたので、分報や日報に作業していることを実況しながら取り組むようにしていました。
こちらは、machidaさんから教えていただいた参考記事です。

blog.studysapuri.jp

私の場合は、システム開発に入る前から、自分の分報チャンネル で作業していることやその時の気持ちを、逐一書きながら課題に取り組んでいたので、とくに変わらず続けてやっていたという感じでした。
詰まった時に、この分報チャンネルで数えきれないほど何度も助けていただき、コメントで教えていただいたり、「画面共有してもらって一緒に見ましょうか?」と声をかけていただいたりしました。助けてくださった方々には感謝の気持ちでいっぱいです🙏 本当にありがとうございました!

vueファイルがどのページで使われているか探す方法を教えていただいた時のコメント

CIが通らなくて困っていた際に助けていただいた時のコメント

作業したことを記録しながら進める

 分報とはまた別に、Notionに、取り組んだissue/レビューしたPRごとにページを作って、作業ログを書くようにしていました。
具体的には、調べて理解したこと、分からないこと、自分で考えた実装のロジックや、何が分からないか分からない時は、その時分からないことを全部書き出して現状整理、などなど何でも書いていました。

これを始めたきっかけは、FBC卒業生のikuma-tさんがLTで発表されていたのを聞いて、自分もやってみようと思ったためです。
始めた時の詳細はこちらの記事に書いています。

saki-htr.hatenablog.com

これのおかげで、あんまり実装が進んでなくても、「あんまり進んではないけど、今日もがんばった!」と思えました。
issueに取り組んでいると、「この機能をどうやったら実装できるか」「このテストはどう書けばいいのか」という、分からないことと向き合っている時間が長いです。
そしてできなかったことができて「やったー!」と思っても、すぐにまた分からないことがやってきて、調べるの繰り返しです。 分からないことが出てくるたびに、「自分でできるのか」と不安になるので、本当は進んでいるし、できたこともあるのに、目の前の分からないことに囚われて落ち込む時もありました。
そういう時に、作業ログを見直して、「1週間前はこれも実装できてなかったんだ」「3日前はこれも分からなかったんだ」と、自分の成長を実感できました。

ミーティング前に、デモの準備と報告内容の整理をする

 週1のミーティングでは、必ず15~30分前にはデモのための画面の用意と、報告内容の原稿作成をしていました。原稿を用意していたことで、緊張していても、落ち着いて報告ができ、「伝えようと思っていたのに忘れた😨」という事態も防げました。

事前にMacメモアプリに書いていた報告内容

分かりやすいデモをする

 ふりかえり・計画ミーティングのやり方のドキュメントに、「今週やったissueの報告は、プログラマー以外にもわかるように説明しましょう。」とあったので、デモも見やすいように工夫していました。
例えば、以下のPRのデモをする時は、タブが2つあり、全てタブと未返信タブ両方の表示結果をお見せする必要があったのですが、1画面だと、一度に片方のタブの表示結果しか見ることができないので、画面を分割して同時に2つ共お見せしました。(この時machidaさんに「見やすい」と仰っていただけて嬉しかったです🥳)

github.com

画面を2分割して同時に両方見せた時のデモ画面

仕様について疑問に思ったら、すぐに確認する

 自分の担当issueの仕様について疑問に思ったことは、すぐに質問をして認識のすり合わせをしていました。 雑談タイムをやっている日であればそこで確認して、issueのコメントに記録し、やっていない平日や土日であれば、issueのコメント上で質問していました。

システム開発ラクティスに入る前から、メンターさんに

相手が言ったことを認識の擦り合わせをしないで、「こういうことか!」と思って、全部作り上げてプルリクを送ると「違うそうじゃない」となりがち。
インドカレー」だと思って作ったら、相手は「ビーフシチュー」と思っていることも。
お互いの認識が同じということはごく稀で、認識が違うのが当たり前の毎日

と教えていただいていたので、出戻りを防ぐためにも、仕様で気になったことはこまめに聞くようにしていました。
(参考:顧客が本当に必要だったものから学ぶこと | キャスレーコンサルティング株式会社)

https://github.com/fjordllc/bootcamp/issues/4853 より

ただ、以前ミーティングで、「仕様についてどれくらい自分で考えて、どれくらい確認すればいいのか」という質問が挙がった時に、machidaさんが

一旦自分の考えで実装してみたのを「こんな感じでいいですか」と確認してもらう方法で進める方が多い

と仰っていたので、人によってやり方は違いますし、私も自分のやり方に固執せず、臨機応変にアップデートしていこうと思っています。

自分の意見を持つ

 上述の通り、仕様について疑問に思うことはmachidaさんとkomagataさんに適宜確認していたのですが、エンジニアとして働くことになったら、より主体性が求められ、自分の意見を持つことが大事だと考えていたので、「どうすればいいですか」という丸投げではなく、自分の考えも添えて質問するようにしていました。

仕様について、ユーザーの気持ちをあれこれ想像して考えるのは楽しかったです。また、「こういう理由で、この仕様にしてほしい」と教えてくださるので、とても勉強になりました。

https://github.com/fjordllc/bootcamp/issues/4094 より

テキストコミュニケーションは、受け手の立場に立って書く

 システム開発ラクティスでのコミュニケーションは、テキストでやり取りする割合が多いです。(これまでもテキストコミュニケーションが主だとは思いますが😅)
例えば、仕様について確認したい時に、雑談タイムがやっていない日はGitHubのissueにコメントして聞いたり、自分が作ったPRのdescriptionに、概要・変更点・動作確認手順を書いたり、レビューのやり取りは基本的にPR上のコメントでやり取りをします。

自分の担当issueや作ったPRについては、自分が一番状況を理解しています。理解している側が分かりやすく丁寧に書いた方が、受け手の負担が少なく済み、より少ない時間で効率的に仕事を進められると思ったので、そのようにしていました。
具体的には、以下のことをやっていました。

質問したいことが複数ある時は、最初に「◯点質問したい」と個数を書く

https://github.com/fjordllc/bootcamp/issues/4094 より

見出しをつける

最後まで読まないと内容が分からないのは受け手にとって読みづらいと思います。見出しをつけることで、何を伝えたいかが最初に分かり、「この下の文章にはだいたいこういうことが書いてある」とイメージしやすいと思ったため、コメントに見出しを付けるようにしていました。

https://github.com/fjordllc/bootcamp/issues/4094 より

descriptionには詳細を丁寧に書く

 説明が不十分だと、レビュワーが理解しづらいので、スクリーンショットの変更点を必ず赤い枠で囲ったり、変更点が多い場合は、マークダウンのTableを使って見やすくしたり、動作確認手順を詳細に書くようにしていました。

ちなみに私が作ったPRの中で、一番気合いの入ったdescriptionはこちらです(笑)

github.com

これとほぼ同じ作業を行うであろうissueがもう一つあるため、そのissueの担当になった方のためにも、なるべく理解しやすいように丁寧に書くことを心がけました。ちなみにこちらです。

practices.positionを削除したい · Issue #4246 · fjordllc/bootcamp · GitHub

レビューでは、テキストより口頭でのコミュニケーションが適している時は、ペアレビューの時間をいただく

 以下の3つのPRは、FilesChangedの数が多い等重ためのPRだったので、ペアレビューのお時間をいただきました。

はじめはペアレビューってどんな風に進めていけばいいんだろう💦と進め方が分からなかったですが、何回か重ねていくうちに、コツが掴めてきました。

具体的には、

  • 一緒に動作確認をして、挙動がおかしい点や自分のローカル環境と異なる点があったら2人で原因を調べる
  • RailsとVueの両方を書いていて、コードの流れの理解が難しいPRは、大まかなコードの流れや各コードが何をしているかを説明する
  • レビュワーの方から質問していただいて、それに答える

というようなことをやっていました。これはレビュワーの方によって聞きたいことが異なるので、事前に「これをやるぞ!」とガッチリ決めておくというよりは、当日臨機応変に進めていました。

ペアレビューを何回か経験したことで、開発メンバーの方とコミュニケーションを取る時に、テキストと口頭どちらが適しているかの判断がしやすくなりました。また、ペアレビューでお話したことは、GitHubのPRにも書いて必ず残すようにし、他の開発メンバーや後からこのPRを見る方が話した内容を把握できるようにしていました。

レビューする時は、良いと思った所を伝える

 レビューをやり始めた当初、PRをApproveする時に、何とコメントしてお返しすればいいか迷ったので、「LGTMです〜」と返していました。LGTMとは、「looks good to me」の略で、「私は良いと思います〜」的な意味です。(参考:「looks good to me」)
他の方はどんな風にコメントされているのかな〜と、色んな方のPRを見ていく中で、良いと思った部分を褒めている方がいらしたので、真似をしました。
レビューでどんなことを書くかの考え方は、人それぞれだと思いますが、個人的には、「チームで開発をしているので、開発メンバーの良いと思った所は積極的に伝えたい」と思っているので、メンバーのPRの良いと思った所や、自分が知らなくて勉強になった所を伝えるようにしていました。

https://github.com/fjordllc/bootcamp/pull/4823 より

https://github.com/fjordllc/bootcamp/pull/4215 より

"こうすればよかった"と思うこと

PRが、issueの範囲を超えそうだったら、別PRに切り出す

以下のPRを作成した時に、後から振り返ると、PRを分けるべきだったと思いました。

github.com

これは、categories.positionが使われている部分を削除するissueだったのですが、取り組んでいる過程で、「削除した後、この箇所の表示内容や並び順はどうしたらいいのだろう?」と疑問に思う箇所がありました。
ミーティングで質問したところ、2箇所、カテゴリーの表示内容や並び順を変えることになりました。すぐに実装できるだとうと思って、そのまま同じ一つのPRで作成しましたが、今振り返ると、

  • categories.positionが使われていた部分を削除するPR
  • 表示内容や並び順を変更するPR

に分けるべきだったと思いました。
このことについて日報にかいたところ、メンターさんから、

アジャイル的にはフィードバックサイクルを速くまわすことが善なので、「細かい単位でフィードバックを受けられるようにする」というのは意識してみてもいいかもしれません。

と教えていただきました。
このissueを終えるまでは、PRの範囲について考えたことがなく、「なんとなく1つのissueにつき1つのPR」という思い込みがありましたが、

  • フィードバックサイクルを速く回すことが大事なので、diffが大量になったり、issueの範囲を超えそうな場合は、もともとのPRはレビュー依頼に回し、追加の実装は別PRで行うことも検討する。
  • 変更箇所が多かったり、複数の仕様変更が一つのPRにあると、レビュワーが確認するのが大変なので、自分のPRがレビュワーや後から見た方にとって見やすいかも考える。

という視点を得ました。

自作サービスのアイデア出しを、システム開発に入る前にやっておけばよかった

 自作サービスの案出しを日頃からやっておくことの大切さは、FBC内の色んな所で言われていることですが、本当にその通りでした。
私はシステム開発で10pt程マージされた段階で、元々考えていた自作サービス案のエレベーターピッチ作成、ペーパープロトタイプ作成、技術検証(途中)をやったのですが、システム開発と自作サービスをマルチタスクでやっていくのは頭の切り替えがなかなかできず難しかったです。結局、うまく同時並行できず、自作サービスやっている週はシステム開発の進捗が出ず、システム開発を進めている週は自作サービスが全然できなかった...という感じでした。

システム開発ラクティスに入ると、チームメンバーにレビューをお願いされたり、毎週水曜にミーティングがあったり、自分のPRがマージされたら動作確認やリリースノートの記入をしたりと、これまでのプラクティスの課題とは違って、100%自分のペースでやる、とはいかなくなります

ただ私の場合、システム開発に取り組む中で「こうだったらいいな〜」という部分を発見して、FBCシステム開発ラクティスに取り組まれている方専用のwebサービスを作ることに決めたので、どこでアイディアがふってくるか分からないな、とも思っています。
私から言えるのは、システム開発に取り組みながら、「システム開発が終わる前に、早く自作サービスで作るものを決めなきゃ」と思いながら両方やるのは大変なので、入る前に作るものが決まっていると気持ちに余裕が持てていいのではないかな、ということです😅

課題に感じていること

分からないことがあった時の質問のタイミングが難しい

 issueに取り組んでいて、「どこまで自力でがんばって、どこから周りに助けを求めるか」のバランスが難しかったです。これは、どこで詰まるかも人によって異なる & 人によって考え方も違うため、正解は一つではないんだろうなと思っています。

仕事となると、お給料をいただいてプログラムを書くことになり、かつ締切があるので、自分の力で実装したり理解することよりも、早くユーザーに機能を届けることを優先しなければなりません。なので、「◯分悩んだら質問して、理解が不十分な所は終業後調べる」など、質問するタイミングの基準をはっきり作りやすいと思います。

ですが、今は「エンジニアになるための学習」が目的なので、分からないことに遭遇した時に、「これぐらいできていないと、エンジニアとしてお仕事する時にまずいのでは...もう少し自力でがんばろうかな」と考えたりしてなかなか質問に踏み切れない時もありました。
自作サービス開発でも、このことで悩むことがあると思いますが、「一人で抱え込まない」ことは大事にしたいと思っています。

適切なコミットの粒度が分からない

 レビューで様々なPRを見ましたが、人によってコミットの粒度が違うので、これも何が正解なのか分かりませんでした。
私は何か変更する度に細かくコミットしていましたが、他の方のPRでは、「機能追加」と「テスト追加」でコミットが2つのものもありました。

これは正解が一つだけあるというよりは、会社さんや開発チーム、人によって違うのかなと思うので、勉強して、自作サービスでは色んな方法を試してみようと思っています。

嬉しかったこと

 これはわざわざ自分から言うのもどうなのかなと、書くか迷ったのですが、素直に嬉しかったので書きます🙏
数えて見たら、9名くらいの方に、一番最初のPRのレビュワーとして選んでいただいていました。
自分が周囲から話しかけにくい存在だと、コミュニケーションに支障が出る = 仕事に支障が出ると思っていて、なるべく「話しかけやすそうな人」と思ってもらえるように意識していたので、頼みやすいと思っていただけたのかな〜と感じてとても嬉しかったです🥰

おわりに

 長々と書いてきましたが、システム開発はとても人やタイミングに恵まれたと思っています。ちょうど同じ時期に入ったFBC受講生のトミーさんが、good first issueをモブプロでやる会を主催してくださったり、ペアプロ・ペアレビュー文化を根付かせてくださいました。開発メンバーとコミュニケーションを取る時に、テキストでのやり取りだけでなく、ペアレビューも選択肢として増えたことで、より開発メンバーの方とコミュニケーションが取りやすくなりました

また、平日夜18:00-19:00にFBC内で開催されている輪読会に参加しているのですが、そちらの参加者の方がちょうど同じ時期にシステム開発に取り組まれていたり、すでに終えた方が多かったので、進め方や詰まったことで相談にのっていただくことも多かったです。
「皆さんはこういう時はどういう風にやってましたか?」「この機能を作ってる部分のコードが読み解けなくて詰まってます〜」と相談したり、「▲▲については、自分も同じ所を勉強して日報にまとめたのでよかったら参考にしてみてください」「そのissueは、元々××の実装をされた方が●●さんだから、詰まったら●●さんに聞くといいよ〜」とアドバイスをくださる場が平日毎日あったことは、私にとってとても大きかったです

メンターの方々や、輪読会で助言くださったり私の分報チャンネルにコメントで色々と教えてくださった方々、本当にありがとうございました!
まだ自作サービスという最後の一山が残っているので、このプラクティスで学んだことを存分に活かして、引き続きがんばっていきます。

2月〜チーム開発、なんとか生き延びています〜

 2月は過ぎるのが凄まじく早かった! これまで仕事しながらフィヨルドブートキャンプの学習を進めてきたが、チーム開発と両立するのは大変だった。
カーレースの中で自分だけ自転車で参加していて、置いていかれないように必死に漕いでるような気持ちだった🏎🏎🏎    🚴💦
記憶がほとんど無いため、日報*1を見返して思い出しながら書いている。

ニコニコカレンダー*2を見ると、なんと28日中27日間、学習して日報を書いていた。2月がほぼ全部埋まっていて嬉しい😊✨

f:id:Saki-Htr:20220304114637p:plain
2月のニコニコカレンダー

怒涛のチーム開発 🌊🏄‍♀️

これまでのプラクティスはジェットコースター、チーム開発はフリーフォール

 1月末にチーム開発のプラクティス*3に入ったので、引き続き自分のissueを進めたり、他の受講生の方のプルリクエストのレビューを行ったり、毎週水曜のミーティングに参加したり、予期せぬエラーやコンフリクトとたたかったりしていた。

分からなくて辛い時と分かって嬉しい時の気持ちの揺れ幅を乗り物で表すと、チーム開発に入るまでのプラクティスではジェットコースター🎢 という感じだったが、チーム開発は、気持ちの揺れ幅が大きくなり、何か一つ問題を解決してやっとプルリクエストを作れる(上に上がっていく)と思ったら、すぐにまた違う問題が立ちはだかる(落ちる)ので、フリーフォールのようだな~と感じている。
分からないことばかりで大変だが、その分できるようになると嬉しさも大きい

たくさんつまずき、たくさん助けていただいた🙏

 ニコニコカレンダーは全てオレンジ色(=いい感じ)なので、順調そうに見えるが、チーム開発に入ってから分からないことだらけで、はじめはエラー解消で休日一日が終わってしまうこともあった。
そんなにつまずいているのになぜ毎日日報のマークが笑顔かというと、分報*4に困ったことを呟いた時に同じ開発チームの受講生や卒業生の方が解決法を教えてくださったり、チェリー本輪読会のメンバーに相談にのっていただいたり、メンターさんに質問して丁寧に教えていただいたりして、解決できたためだ。(いつも助けてくださっている皆さま、本当にありがとうございます🙇‍♀️)
つまずいてばかり&助けていただいてばかりで情けなくなることもあったが、失敗してしまったことを無かったことにすることはできないので、しっかり反省した上で、同じ失敗を起こさないように工夫したり、ブログやscrapboxに解決策をアウトプットして他の方(と未来の自分)の役に立てるようにしたりすればいいと思うことにした 💪

休日はトミーさんとペアプロざんまい 🙌

 4週のうち2週ほどは、同じ時期にチーム開発に入ったトミー(id:eatplaynap329)さんと休日にペアプロをしていた。 お互いの簡単な1~2ptのissueを持ち寄って、25分片方がドライバー→5分休憩→25分もう片方がドライバーという感じで進めた。(トミーさんご考案)

これまで経験したペアプロは、分からないことをメンターさんから教わるためのペアプロが主だったので、どんな感じになるか想像がつかなかったが、とってもとっても楽しかった
プルリクエストを作成できた後は、そのレビューをお互いに行うことにした。*5
トミーさんが「ペアプロした人同士でレビューしたらいいのでは」と仰っていて、たしかにその方が、レビュワーが1からコードを読み解く手間が無くなり、コミュニケーションがスムーズになって良いと思ったので、「さすがトミーさん👏👏👏」と思った。

チームメンバー同士のペアプロは、

  • 効率的にレビューができる
  • コミュニケーションが取れる

と良いことばかりなので、ペアプロ文化をフィヨルドブートキャンプに根付かせてくださったトミーさんには本当に感謝の気持ちでいっぱいです🙏✨

進捗がほぼ無くてもミーティングには必ず出る 💪

 毎週水曜日に1スプリント(1週間)でやったこと、難しかったこと、次のスプリントでやることを話し合うミーティングがあるのだが、1スプリントで1つもプルリクエストを作れなかった日もあった。
けれども、やっていなかったわけではなく、issueもちょっとずつだが進めているのでちゃんとミーティングに出た。「進捗無いので欠席します」を一回でもすると、私の場合は逃げ癖がついてしまいそうなので、どんなに進捗が少なくても、絶対に出るつもりだ。

プロダクトバックログを真似てタスク管理してみた 📝

 チーム開発のプラクティスでは、自分のissueや開発メンバーのレビューに加えて、ステージング環境や本番環境での動作確認、mainにマージされた自分のプルリクエストのリリースノートを木曜15時までに作成しなければならないなど、小さいタスクがたくさんあり、忘れてしまうのが怖いので、プロダクトバックログ*6 を真似て、Todoistというタスク管理アプリに、優先順で上から順にタスクを並べて管理することにした。シンプルに一列で、ここだけ見ておけばやるべきことが明確なので重宝している。

f:id:Saki-Htr:20220308002703p:plain
実際に登録したタスク

自分の書いた技術記事が、GoogleSafariの検索結果で最初に表示されるようになっていた🎖

 去年の8月に書いたESLintのインストール&使い方の記事が、GoogleSafariで「eslint インストール」で検索すると、一番最初に表示されていることを知った😳 saki-htr.hatenablog.com


スマートフォンで自分のはてなブログを見る時、いつも注目記事の1位がこの記事で、はてなスターも1個しか付いていないし、フィヨルドの方から感想をいただいたことも無かったので、「そんなに見られているはずないのに、何かのバグかな?」と思っていた。

↑というのをdiscordの自分の分報チャンネルでつぶやいたら、フィヨルドブートキャンプのメンターさんが、「eslint インストール」で検索すると、私の記事が一番上に出てくることを教えてくださり、知ったときはびっくりしすぎて叫んだ 🤯


f:id:Saki-Htr:20220307234120p:plain


気になって自分のブログのアクセス解析を見ると、私のブログはGoogleからのアクセスが75%になっており、Googleから来た方の64%はこの記事にアクセスしていることが分かった。

f:id:Saki-Htr:20220308004333p:plain
はてなブログアクセス解析

私のアウトプットは振返りや自分の考えていることなど、非技術系が多く、昨年出たLTも3回ともすべて非技術系なので、技術系のアウトプットにあまり自信がなかったが、今回のことで少し自信が持てた😁

前にフィヨルドブートキャンプのメンターさんが、

アウトプットの価値は、書き手でなく読み手が決める

と仰っていたことを思い出した。
なので、日報や分報などクローズドな場だけでなく、ブログやscrapboxなどもっとオープンな場所でもアウトプットを積極的にしていこうと思った。

手始めに、Notionに書いているチーム開発の作業ログを、誰でも見られるようにして、フィヨルドブートキャンプを知らない方が見ても分かりやすいように紹介ページを作った

www.notion.so

今の時点ではこれが人の役に立つかはわからないが、せっかくアウトプットしているので外に出していこう & これからチーム開発に入る方の少しでも参考になればいいな〜という気持ちでいる。

3月の抱負

2月は自作サービスは全く進められなかったが、作りたいと思った案が一つ浮かび、チーム開発にも慣れてきたので、その案について深堀りして考えてエレベーターピッチを作りたいと思っている。

*1:フィヨルドブートキャンプでは、学習した日は、やったこと・学んだこと・分からなかったこと等を日報に書いて提出し、メンターさんに見ていただく仕組みがある

*2:日報には「今日の気分」という項目があり、自分のプロフィール画面で、その日の気分がどうだったかを月ごとに見ることができる

*3:フィヨルドブートキャンプの学習で使っているWebアプリはOSSで公開していて、フィヨルドブートキャンプ受講生がチーム開発のプラクティスで機能を追加したり、バグを修正したりなどの開発をしている。Issueにポイントが振られており、20ポイント分のIssueがマージされたら完了となる。受講生同士でお互いのプルリクエストのレビューも行う。リポジトリこちら

*4:自分の状況を逐一実況することで、いわゆるひとり言。フィヨルドブートキャンプではコミュニケーションツールとしてDiscordを使っており、メンターや受講生はそこで自分専用の分報チャンネルを作って好きに書き込むことができる。

*5:チーム開発では、自分が作成したプルリクエストのレビューを、その時一緒に開発を行っている受講生にお願いし、それが通ったらkomagataさんにレビュー依頼をすることになっている

*6:機能や要望など実現したいことを、優先順位順にリストにしたもの

1月〜自作npm&チーム開発にジョイン!〜

今年ももう1ヶ月が経ってしまった! この前、年が明けたばかりだと思っていたのに🙊
今年は一日一日を大事に生きて、自分がやってきたこと・感じたことを丁寧に振り返りたいと思ったので、毎月末振返りと次月の抱負を書くことにした。

やったこと

  • VueCLIの課題の修正
  • アジャイル開発/スクラム開発の本を2冊読んでまとめる
  • 自作npmを公開
  • チーム開発にいよいよジョイン!
    • ローカルの立ち上げに苦戦
    • 同じ時期に入った同期とモブプロでgood first issueを行う

1月のハイライト

自作npmのアイデア出しに苦戦😂

1月の前半は自作npmのアイデア出しで詰まり、ちょっとメンタルが沈んでいた。 その頃の自分の気持ちは以下のような感じだった。

npmのアイデア出しがうまくいかず、帰宅直後ネガティブな気持ちになっていたが、今日は仕事初めで、お腹が空いていて、眠かったのでネガティブになっていたんだな〜と、お風呂から上がった時(疲れがとれた時)に気づいた。 気持ちはその時の環境や自分の状態に結構左右されていると思っていて、なんでネガティブな気持ちになったのか環境要因を探ってみると、マイナスの感情に振り回されずにすむと思った (1/5水の日報)

沈んでいた理由は、「自分がどんなものを作りたいか」を考える前に、他の方のレベルの高い提出物を見て「すごいものを作らなくては」と考えてしまい、アイデア出しに集中できていなかったからだと思う。

自分がその精神に陥ってると気づけてからは、すごいものかどうかという視点を捨てて、純粋に「どんなものを作りたいか・どんな人に使ってほしいか」を楽しんで考えることができた。

コマンドライン上で動かすので、「どういう時に動かしたいかな〜」と考え、今回詰まって沈んでしまった私のように、学習中やコードを書いてる最中に落ち込んだ時に、役に立つものを作りたいと思った。
その時にメンターのりほやんさんがこちらのPodcastで、 www.yancan.tech

学習のモチベーションを握る鍵は自己肯定感だと思う。 一番大事なのは、今自分の自己肯定感がどれくらいの高さにあるか認識すること。認識が低い時はちょっとしたことでも大ごとに感じてしまう。下がっていることが分かれば対策ができる。今日は仕事を早めに切り上げようとか。ヒロアカ見ようとか。

と仰ってたのを思い出した。

なので、今の自分の自己肯定感の高さを診断するnpmを作った。

www.npmjs.com

すごいコードを書いたりすごい技術を使ったりしたわけではないのだが、自分が作りたいものを1から考えて作り上げることができたので、満足している。
案出しが大変なことがわかったので、自作サービスのアイデア出しも今からしっかりやっておかないといけないことにも気づけてよかった。

フィヨルドリポジトリやかんばんを見てワクワク😊

開発に入るための準備で、フィヨルドブートキャンプのリポジトリかんばんに目を通した。
眺めていると、今まで使っていたものが当たり前でなく、だれかが作ってくださったものなんだということを改めて感じた。これまで「使う側」しか経験したことがなかったので、いよいよ「作る側」ができることにワクワクした。

ローカルの立ち上げに苦戦💦

そんなに苦戦しないだろうと思っていたが、Rubyのバージョン2.7.4をインストールするためにrbenvを使ったら、なぜか使えなくなっていて驚いた。遭遇したことのないエラーで焦ったが、しっかりエラーやログを見たらどこに問題があるか分かって、なんとか解決することができた。大変だった分、ローカル立ち上げに成功した時は本当に嬉しかった〜

good first issueを同期と一緒にモブプロでわいわい進めた🥳

私と同じく今週からチーム開発に入ったトミーさんとガラムマサラさんと一緒に、good first issueを、プルリクエストを作ってレビュー依頼するところまでをモブプロで行った。
(主催してくださったトミーさん、一緒にissueをやったガラムマサラさん、参加してくださったメンターさんや受講生の方々、本当にありがとうございました🙏✨)
Ruby on Railsを触るのが久々だったり、今まで使ったことのないGitのコマンドを実行しなければならなかったり、テストを実行する時処理が重たいせいか私の声が皆さんにとぎれとぎれで聞こえなくなったりと、テンパリっぱなしだったが、みなさんが助け舟を出してくださったおかげで、致命的なミスをせずに済んだ。一人でやっていたら解決方法がすぐには分からなかったであろうエラーも何度か起こったので、一番最初のissueをモブプロでやらせていただけて本当に良かった。
なにより、みんなでワイワイしながらコードを書くのが本当に楽しかった!今までメンターさんとペアプロをしたことはあっても、モブプロは初めてだったので、主催のトミーさんに本当に感謝です。

初めて一人でissueをレビュー依頼するところまでやった💪

前回の振返りミーティングでissueをもう一つ振っていただいていたので、今日初めて自分一人でissueに取り組んだ。一人でできるか不安だったが、プルリクエストを送るまでの手順をしっかり予習した後進めたので、レビュー依頼まで完了することができた。
簡単なissueではあるが、自分一人で完了することができて少し自信がついた!

ログ駆動開発を始めてみた✍🏻

先日、フィヨルド受講生のikuma-tさんがLTで、

チーム開発など難しいタスクでは、タスク駆動開発(チェックリストにチェックが入った時に進捗があがる)だと進んだ気がせず、取り組むことが億劫になってしまう。
そこでログ駆動開発(作業に取り組んだことを記録して進捗を管理して、作業ログが書き込まれたと瞬間に進捗があがる)を行ったら、進捗が捗った

という話をされていて、「私もチーム開発に入ったらログ駆動開発をやりたい!」と思っていたので、早速やってみることにした。

(詳しいLTの内容についてはikuma-tさんがこちらで紹介してくださっています。)

ikmbear.hatenablog.com

書き出すこと自体はDiscordの分報によく書いているので記録する癖はあるのだが、その時取り組んでいるissueのこと以外もたくさん呟いていて見づらいので、Notionにissueごとにページを作って、そこにやったことや自分の気持ちを時系列順に書いてみることにした。↓

www.notion.so

おまけ:フィヨルドで培ったことを仕事で活かせた

具体的に何をやったのか

1~3月の間だけ、エクセルに切り出された情報を手打ちでアプリに大量(私含めたパートさん5人で合計3000件!)に入力するお仕事があり、エクセルを入力しやすくカスタマイズした方が正確性もスピードも上がるので、上司に「職場のみんなでどの方法が一番効率的か共有し合いませんか」と勇気を出して切り出してみたら、快くOKを貰えた。
当日、私のやり方を職場の人に見てもらいながら、どういうやり方が1番正確で早いかをワイワイ話し合った。私も知らなかった効率的なやり方を他の人から知れて勉強になった。
そして、元々自分がやっているやり方をドキュメント化したのを皆に共有したり、関数使うなど難しい箇所はマニュアルを作ったりした。

フィヨルドで培ったこととは

これができたのはフィヨルドブートキャンプで

  • 実現したいことを調べ抜く力
  • 気になったらまずとびこんで行動してみる精神

を培ったからできたことだと思っている。
フィヨルドブートキャンプでの学習を通して、「この操作をもっと楽にできないか?」「これをやりたい人は私の他にもいっぱいいるはずだから、調べれば機能が用意されていたり、すでに方法を見つけている人がいるはず」と考えて、調べて実現することができた。
また、主体的に動いて改善策を提案するのは私にとっては少し勇気が要ることで、入社したての頃の私だったら「失敗したらどうしよう」と尻込みしていたかもしれないが、フィヨルフドブートキャンプで「大勢の前で登壇するのは怖いけどLTやってみたい!」「人見知りだけど、輪読会に参加してじっくり理解したり他の受講生と仲良くなりたい」というように、最初怖かったけれど一歩踏み出してみたら、"楽しい!飛び込んでみてよかった!"と思えるようになってきたので、今回行動を起こすことができた。

行動してみて思ったこと

どうしたらもっと良くなるかを職場の皆さんと話し合って改善していくのは、とてもやりがいがあると思った。
他のパートさんに、「自力ではこれ以上スピードアップできないからどうしようかと思ってた、ありがとう」「マニュアル通りにやってみたら入力しやすくなって楽しい」と言っていただけるなど、職場の人に感謝されたのもとても嬉しかった。
このことについてDiscordの自分の分報につぶやいたら、アドバイザーの方に「それはもう完全にエンジニアリングしていますね👏」と仰っていただけたのもとても嬉しかった!

2月の抱負

去年、一週間ごとに学習の目標を立てて、進捗を週報に書いていた時期は学習が捗っていたので、本当は今年も明確な目標を立てたいのだが、チーム開発は未知な部分が多く、「何日まで何ptのissueをやる」等の目標を立てるのは難しそうだなと思ったので、まずチーム開発・振返りミーティングに慣れることと、自分のペースでissueに取り組むことを意識して進めたいと思っている。あと、自作サービスのアイデア出しも、一日でぱっと思いつくものではないので、日常で感じるもやもやを記録したりとちょっとずつでも進めていく。

雑談タイムやペアプロで、スムーズにコミュニケーションを取るために心がけていること

こんにちは。フィヨルドブートキャンプ生のSakiと申します。

この記事は、フィヨルドブートキャンプで開催されている、フィヨルドブートキャンプ Part 2 Advent Calendar 2021 - Adventarの24日目の記事です🎄🎁

昨日12/23は、azitamaさんの勉強を続けた結果を振り返る ゆっくりでも変化し続けていこう|あじたま家.comでした。
本日Part1の記事は、Maedaさんの輪読会のすすめ - maedaの日記です。私もMaedaさんと同じチェリー本輪読会に参加しています🍒気になった方はラジオ参加で少し覗きに来てもらえると、メンバー全員大喜びします☺

目次

はじめに

フィヨルドブートキャンプで学習をしていて分からないことに遭遇すると、雑談タイムで質問をすることがあると思います。また、コードを書くプラクティスでは、テキストでのやり取りのみだと非効率なので、メンターさんとペアプロをすることがあると思います。

私は元々、雑談タイムやペアプロのような同期型コミュニケーション*1に対して苦手意識があったのですが、場数を踏んで失敗もする中で、「こういうことを意識するとコミュニケーションがスムーズになっていいな」という気づきがありました。

この記事では、

  • 日報やDiscordの#wakaranチャンネルなどテキストで質問したことはあるけれど、雑談タイムで質問するのはハードルが高く、まだしたことがない
  • メンターさんにペアプロをお願いしたいけれど、緊張してなかなか依頼する勇気が出ない

という方に向けて、

  • どんな失敗をしたか
  • どんなことを意識するとコミュニケーションをスムーズにできるか

を書くことで、少しでも背中を押せたら嬉しいです。

なぜ苦手意識を持っていたのか

もともと私は、Discordでの通話など同期型のコミュニケーションに苦手意識を持っていました。
日報などテキストでのコミュニケーションですと、ある程度自分のペースを保って、相手の言葉を理解し、自分が返す言葉を考えて話せますが、Discordで質問をすると、相手の言葉を瞬時に理解して返さないといけないというプレッシャーを感じていたためです。
そのため、「うまく疑問を伝えられなかったらどうしよう」「変なことを言ってしまったらどうしよう」「相手の言ってることが分からなかったらどうしよう」という不安がありました。
質問は日報やQ&Aなどテキストでもできますが、コードに関する質問など、直接お話した方が効率が良い質問もあるので、Rubyのボウリングのスコア計算プログラムでつまずいた時に、初めて勇気を振り絞って雑談タイムで質問しました。

どんな失敗をしたか

雑談タイムで質問し始めた最初の頃は失敗の連続でした。
具体的には、

  • Rubyのボウリングのスコア計算プログラムについて質問した際、あらかじめ現状・解決したいこと・試したことを整理してから挑んだのに、うまく説明できなかった。
  • メンターさんに「ボウリングのゲーム全体のことで困っているのか、それとも10フレーム目の計算で困っているのか」と聞かれたのに、焦ってしまいその問いに答えずに、自分のコードの現状を話してしまった。
  • データベース設計のやり方について質問した際、一からER図を書いて説明していただいたが、途中からついていけなくなってしまい、あまり理解できなかった。

といった失敗をしました。

また、初めてメンターさんとペアプロをした際は、緊張して仰ってることがすぐに理解できなかったり、今何をしようとしているか分からなくなり混乱して会話も手も止めてしまったりと、コミュニケーションを上手く取れませんでした。

最終的には疑問を解消することができたので、厳密には失敗ではないのですが、メンターさんになんとか汲み取っていただいて解決してもらいましたし、「こういう風にコミュニケーションを取ればよかった」「自分が困っていることをうまく伝えられなかった」という落ち込みがすごかったです。

それでも挑み続けて得た気づき

それでもめげずに、雑談タイムで質問したり、メンターさんにペアプロしていただいたりする中で、「こういうことに気をつけるとコミュニケーションがスムーズだな」という気づきがありました。

1. 相手の言ったことと、それを聞いた自分の理解の擦り合せをする

相手の言った内容と、それを聞いて自分が理解した内容が同じかどうかの擦り合わせはとても大事です。
例えば、

AB
BC

という式があるとしたら、A=Cになることが分かります。 A=Cと分かるためには、A = BB = Cを理解していなければなりません。

質問でも、相手の言った内容と、それを聞いた自分の理解が合っていないと、お互いの認識がズレたまま会話が進んでしまいます。 上記の例でいうと、A=BB=Cという前提が分かっていないのをそのままにして進んでしまうと、A=Cが理解できなくなってしまいます。
自分がどこまで分かっているか、メンターさんの言葉をどのように理解しているかは、自分にしか分かりません。 なので、

  • メンターさんの仰った内容に対して、「〇〇という理解で合っていますか??」「〇〇という意味でしょうか??」と自分が理解した内容を逐一伝える
  • メンターさんの仰ってる内容が分からない時は、その場で質問する
  • 仰った内容に対して何が分からないかすぐに言語化できない時は、「〇〇の部分がなぜそうなるのか分からないです」「○○の部分が分からないのですが、何がどう分からないかすぐに説明できないので、少し考えてもよろしいでしょうか」のように、分からない状態であることを伝える

といいと思います。

2. ペアプロでは自分の状況を実況する

ペアプロでは基本的に生徒さんがドライバー(コードを書く人)となり、エディタを画面共有してメンターさんに見ていただきながらコードを書いていきます。

最初はメンターさんとマンツーマンでお話しすること自体に緊張したり、焦ってメンターさんの仰ってることが理解できなかったり、今自分が何を実現しようとしているかが分からなくなってしまったりすると思います。
そういった時に無言で手が止まってしまうと、メンターさんはなぜ生徒さんの手が止まっているか状況が分からず困ってしまいます。

自分の頭の中の状況はメンターさんには見えないので、

  • これから何を実現しようとしているか・どういうコードを書こうとしているか伝える
  • もしコードの書き方が分からない・疑問が浮かんだ等で詰まったら、「これをやりたいと思っているのですが、やり方が分からず止まってしまいました」と現状を伝えたり、「こういう方向性で合ってますか?」と確認する

など、自分の状況をこまめに実況するといいと思います。

最後に私が伝えたいこと

色々書きましたが、初めて質問する時は、

  • そもそもメンターさんと話すことに緊張する
  • 画面共有などDiscordでのやり取りに慣れていない

などで、会話をするので精一杯だと思います。
私も初めて雑談タイムで質問した時は、フィヨルドの質問に関するDocsを読んだり、疑問点の整理を書き出したりと準備をしていきましたが、結果はぼろぼろでした。 でも、何度か挑戦しているうちに慣れてくるので、場数を踏むことが大事だと思います。
慣れるまではしんどいかもしれませんが、質問できるようになると、分からないことがあった時に「まあ自分で調べても解決できなかったら、雑談タイムで聞けばいいか」と思え、分からなくて辛い気持ちが少し楽になると思います。
この記事を読んで、質問することが怖いと思ってる方の背中を少しでも押せたら嬉しいです😊

*1:複数の人が同じ時間を共有してやり取りをするコミュニケーションスタイル。代表的なのは会議や電話。