koheitakahashiのブログ

2020.07.01にプログラマーとして生を受けた私が学んだことや、日常について徒然に書いていきます。

ChatGPTに自前のドキュメントを読んでもらって回答してもらう方法

この記事の概要

最近、ChatGPT に自前のドキュメントを読み込ませる方法を調べて、実際にコードを書いたりしてみたので、その中で得られた知識をまとめた記事になります。 自分用のメモとして、雑多に書いていますので悪しからず。

2つの方法がある

Fine Turning と Prompt Design の2つの方法があります。

Fine Turning

  • 自前のドキュメントを、モデルに追加で学習させる方法
  • OpenAI が Fine Turning ができる CLI ツールと、Fine Turning したモデルを使えるように API を用意しているため、それを利用して Fine Turning ができる

メリット

  • Prompt Design 与えられる学習量が多いため、うまく学習ができたら、Prompt Design よりも、望んだ回答が得られやすい

(実際に試してないけど)デメリット

  • 以下のような教師データを作成する必要があり、学習させるデータを作成するのにコストがかかる(そのまま自前のドキュメントを読み込ませればよいわけではなく、教師データの形に加工する必要がある。以下がフォーマット。)
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
...
  • Fine Turning に使えるモデルが GPT-3 ベースのもの
    • 2023/06/03現在、OpenAI が提供している API では Fine Turning に使用できるモデルは GPT-3 ベースのもの
    • Fine Turning をさせた GPT-3 と Prompt Design をした GPT-3.5-turbo or GPT-4 だったら、どっちが意図した結果が出やすいのかという問題はある
  • Fine Turning だと、学習された後に出てきた情報を読み込ませることはできない
    • プロンプトを投げた時点での最新のデータを参照できない

Prompt Design

  • プロンプトを工夫する方法
  • 例えば、以下のようにプロンプトに自前のドキュメントを読み込ませて ChatGPT に回答させるイメージ
以下の文章を読んだ上で、次の質問に答えてください。

# 読み込む文章

koheitakahashi の信念は「選択と集中」である。

# 質問

koheitakahashi の信念は?
> koheitakahashi の信念は「選択と集中」です。

メリット

  • プロンプトを投げた時点の最新のデータを読み込ませることができる

デメリット

  • 一度に読み込ませられるドキュメントの量に限りがある
    • GPT3.5 turbo だと、4096トークン
    • GPT4 だと、最大16k
    • この制限を回避するために、一度に読み込ませるドキュメントを選定するというステップが必要

Prompt Design におけるトークン量の制約を緩和するための方法

自前のドキュメントを予め Embedding API を用いてベクトル化しておき、そのベクトルをもとに、与えられたプロンプトと類似するチャンクを抽出して、それをプロンプトに加えるという方法がある。

Embedding API を使った流れ

雑な図だが、以下のような流れになる。

Embedding API を使った流れ

ちなみに、チャンクとプロンプトの距離の計算はコサイン類似度を使って求めることが OpenAI のドキュメントでは推奨されているが、距離の計算方法の違いによる影響はほとんどないらしい。

We recommend cosine similarity. The choice of distance function typically doesn’t matter much. https://platform.openai.com/docs/guides/embeddings/which-distance-function-should-i-use

考えなければならないこと

  • チャンク化する単位
    • チャンクに対して、ベクトルを返してもらう都合上、同じドキュメントでもチャンク化する単位により返されるベクトルの値が異なるため、適切なチャンクの単位を見定める必要がある
  • 読み込ませるドキュメントの量に応じて、チャンクとプロンプトの距離の計算量が線形に増えていく
    • ここについては、ベクトルデータベースを使えば早くなるらしい

参考資料

Rails におけるネストしたリソースを扱うフォームの実装方法

はじめに

本記事の動機

最近、既存のフォームオブジェクトのリファクタリングを考えることがありました。 そのときにふと「ネストしたリソースを扱うフォームの実装方法にはどのような選択肢があるのか」という疑問が浮かびました。

この記事では、上記の疑問について調べた内容、それぞれの選択肢について実際に手を動かしてみた所感をまとめています。 同じようなケースに遭遇した方の参考になれば幸いです。

対象読者

この記事は以下の方々を対象としています。

  • ネストしたリソースを扱うフォームの実装方法の選択肢を知りたい
  • フォームオブジェクトの概要は知っているが、フォームオブジェクトの実装パターンを知りたい

環境

以下のサンプルコードは Ruby3.1.0・Rails7.0.2 で動作することを確認しています。

サンプルコードの前提

サンプルコードを交えた具体的な実装方法の説明をする前に、データ構造と今回のサンプルコードで実装したフォームの要件をまとめます。

サンプルコードで扱うデータの構造は以下です。Shop と Book は1対多の関係です。

shops

Column Type
name string
address string

books

Column Type
title string
description string
shops_id integer(FK)
# app/models/shop.rb
class Shop < ApplicationRecord
  has_many :books, dependent: :destroy

  validates :name, presence: true
end
# app/models/book.rb
class Book < ApplicationRecord
  belongs_to :shop

  validates :title, presence: true
end

フォームの要件は以下としました。

  • Shop と Book を一括で新規作成・編集できる
    • Book は必ず2冊登録する(サンプルコードをできるだけシンプルにするための要件です)
  • Shop と Book のバリデーションエラーが表示される

次にサンプルコードを交えた実装方法の説明しますが、元となるコードに対して、各種選択肢による実装を PR で見れるサンプルのリポジトリを作成しましたので必要に応じてご参照ください。

ちなみに元となるコード(main ブランチ)は scaffold し、Shop・Book に必要な関連、バリデーションを追加した状態です。

https://github.com/koheitakahashi/form-sample-app/tree/main

ネストしたリソースを扱うフォームの選択肢

ネストしたリソースを扱うフォームの選択肢には以下のようなものがあります。

  • 1.accept_nested_attributes_for を使う
  • 2.フォームオブジェクトを使う
    • 2-1. gem を使わない
    • 2-2. gem を使う
      • 2-2-1. yaaf を使う
      • 2-2-2. reform を使う

これらについて、サンプルコードとともに所感を述べます。

1. accept_nested_attributes_for を使う

accept_nested_attributes_for は ActiveRecord の機能になります(参考)。accept_nested_attributes_for を使用すると、関連付けられたmodelに値を入れるためのメソッドが生えます。

サンプルコード

この実装による、サンプルコードの全体は以下です。 https://github.com/koheitakahashi/form-sample-app/pull/1/files

重要な部分は以下です。Shop モデルに記述することで books_attribute= メソッドが定義されます。そのメソッドが生えることで、Shop モデルが Books の作成・更新をできます。

# app/models/shop.rb
class Shop < ApplicationRecord
  has_many :books
+ accepts_nested_attributes_for :books
end

所感

コードの記述量が少なくて済むことがメリットだと感じます。Model は上記の変更だけです。Controller ではフォームの初期表示のために必要な Book モデルのインスタンスを作っておくだけですみます。View では ActionView::Helpers::FormBuilder#fields_for を使えば、フィールドとパラメータの送り方に悩む必要はありません。

子モデル(Book)のエラーは親モデル(Shop)に ActiveModel::NestedError オブジェクトとして格納されるので、後述するフォームオブジェクトと比べてエラー格納について考えることが少なくなります。

一方で、特定のフォームでのみバリデーションを有効にしたいケースには向かないと考えられます。例えば、A と B のフォームがあって、A のフォームでは Book モデルの title は空を許容しないが、B のフォームでは許容したいという場合などです。

一応、ActiveRecord::Validations では特定のコンテキストでバリデーションを実行できます。これを使えば、上記のケースには対応できます。

しかし、関連するフォームや、そこで扱うモデルが多くなってきた場合に、必要なコンテキストが多くなることが予想されます。

2-1. フォームオブジェクトを使う(gemを使わない場合)

フォームオブジェクトとは Rails のデザインパターンの一つです。View(ユーザー入力を受け取るフォーム)と Model の間にフォームに関する責務を負った間接層を導入するというパターンです。 このパターンの導入により、「ユーザーの入力値の加工・検証」などのフォーム独自の責務をカプセル化できます。そのため、特定のフォームに関する処理はそのフォームに対応するフォームオブジェクトにまとめることができます。

サンプルコード

この実装による、サンプルコードの全体は以下です。 https://github.com/koheitakahashi/form-sample-app/pull/2/files

重要な箇所は app/forms/shop_form.rbです。これが今回導入したフォームオブジェクトです。

このフォームオブジェクトでは、以下をやっています。

  • ユーザー入力の検証
  • Shop・Book モデルの save・update
  • 入力フォームの初期表示に必要なモデルのインスタンスを作成する

所感

フォームに関する処理をまとめる置き場ができたことにより、accept_nested_attributes_for で挙げた問題点を解消できました。 一方で、accept_nested_attributes_for に比べてコードの記述量が多いことがデメリットとして挙げられます。関連するモデルの更新だけではなく、フォームオブジェクトに関連するモデルのエラーオブジェクトを格納することも自前で実装する必要があります。

また、フォームオブジェクトの実装は自由度が高いため、複数人で開発する際には実装に差異が出やすいように思いました。例えば、以下のようなところが実装が分かれそうです。

  1. 新規作成フォームと編集フォームで処理が異なる場合に、どのフォームかを判断する方法
  2. 関連するモデルのインスタンスを生成するタイミング

a について、サンプルコードでは ShopForm の shop というキーワード引数を渡したとき、そして、Book のパラメータに id が含まれていた場合は編集画面用の処理が走るようになっています。 しかし、ShopForm の引数に edit のようなキーワード引数を用意して、編集画面の処理を走らせたい場合は ShopForm.new(edit: true) などのように呼び出すように実装することも可能です。

b に関しては、サンプルコードでは、books_attributes= が呼ばれたタイミングで、必要な Book モデルのインスタンスを生成しています。しかし、ShopForm が initialize されたときに、インスタンスを生成することも可能です。

このように、実装が別れやすいポイントがあるので、複数人で開発をしていた場合にフォームオブジェクト間でインターフェースや、内部処理が分かれることになるということが起こりそうです。内部処理の差異の問題は深刻ではないかも知れません。しかし、インターフェースが異なるなどはフォームオブジェクトを使う際に混乱してしまうため、開発者間で実装のルールを作っておくなどの対応は必要だと思いました。

2-2-1. yaaf を利用したフォームオブジェクト

フォームオブジェクトを自前で書くこともできますが、実装しやすくするための gem があります。その中の1つが yaafです。yaaf 自体は100行に満たない薄い gem で、関連するオブジェクトを、トランザクションをかけて save する、エラーオブジェクトをフォームオブジェクトに格納するなどをしてくれます。

サンプルコード

この実装による、サンプルコードの全体は以下です。 https://github.com/koheitakahashi/form-sample-app/pull/3/files

ポイントとしては以下です。

  • YAAF::Form を継承して、フォームオブジェクトを作成する
  • フォームオブジェクトの initialize の中で、そのフォームオブジェクトが扱うモデルのインスタンスを全て @models に入れる
    • @models に格納されたインスタンスをループで回して save するという内部処理となっている

所感

YAAF の導入によりメリットに感じた点は以下です。

  • YAAF がモデルを検証・作成・更新をするため、自前でそのようなメソッドを用意しなくて良い
  • バリデーションエラーもフォームオブジェクトに格納される
  • 薄い gem のため、全体像の把握が容易

逆にデメリットに感じた点は特にありませんでした。

2-2-2. reform を利用したフォームオブジェクト

yaaf の他には reform があります。trailblazerというフレームワークの一部になります。

サンプルコード

この実装による、サンプルコードの全体は以下です。 https://github.com/koheitakahashi/form-sample-app/pull/4/files

ポイントとしては以下です。

  • propertycollectionなどの DSL が生えて、それを使ってフォームオブジェクトを実装する
  • 作成されたフォームオブジェクトには validate などのメソッドが生える
  • #save を実行することで、ネストしたモデルを一括で作成・更新することができる

所感

reform で用意されている DSL を使うことで、少ない記述量でフォームオブジェクトを実装することができます。そのため、少ないコードでフォームオブジェクトを実装できることがメリットでしょう。

一方で、DSL の使い方が ActiveModel と違うため戸惑いがありました。そういう意味では、DSL に慣れる必要があるデことがメリットとして挙げられます。

まとめ

ここまで、ネストしたリソースを扱うフォームの実装方法の選択肢を挙げて、それらの実装例と所感を見てきました。これらの選択肢は、まとめると以下のような対比構造になります。

  1. accept_nested_attributes_for かフォームオブジェクトか
  2. フォームオブジェクトを実装する場合、gem を使うか、使わないか
  3. gem を使ったフォームオブジェクトの実装の場合、yaaf か reform か

1. accept_nested_attributes_for かフォームオブジェクトか

  • accept_nested_attributes_for
    • メリット
      • ActiveModel の機能を使うだけなのでコードの記述量が少ない
    • デメリット
      • 特定のフォームだけに〇〇のバリデーションを有効するなどが難しい
      • Model・Controller が肥大化する
  • フォームオブジェクト
    • メリット
      • 特定のフォームだけに〇〇のバリデーションを有効するなどができる
      • Controller・Model の肥大化を防げる
    • デメリット
      • accept_nested_attributes_for に比べてコードの記述量が多い

2. フォームオブジェクトの実装について、gem を使うか、使わないか

  • gem を使わない場合
    • メリット
      • 実装の自由度がある
    • デメリット
      • 自由度があるため、複数人で開発する場合はフォームオブジェクト間の実装が異なりやすい
  • gem を使う場合
    • メリット
      • gem の使い方に従うようになるためフォームオブジェクトの実装にルールを持ち込める
      • 自前で実装することが少なくなる
    • デメリット
      • gem によっては DSL が慣れないものがある
      • フォームオブジェクト実装の自由度が失われる

3. gem を使ったフォームオブジェクトの実装の場合、yaaf か reform か

  • yaaf
    • メリット
      • やっていることは ActiveModel のレールに乗っていることなので、ActiveModel に慣れていれば実装はしやすい
      • gem が薄いので、gem の全容が追いやすい
    • デメリット
      • 特になし
  • reform
    • メリット
      • DSL が提供され yaaf よりもさらにコードの記述量が少なく実装できる
    • デメリット
      • DSL に慣れる必要がある

最後に

ここまで、ネストしたリソースを扱うフォームの実装方法をまとめました。個人的には以下の順序で実装を考えると思います。

  1. accept_nested_attributes_for で実装できないかを考える
  2. フォームオブジェクトを導入する場合は yaaf の導入を検討する

Rails で実装されている機能を使えば事足りるのであれば、それを使いたいと考えているからです。

また、フォームオブジェクトを使った場合でも、ある程度のルールがあった方がフォームオブジェクトの読みやすさや改修しやすさがあると思うため、そのルールを持ち込む意味で yaaf を導入を検討したいです。また、reform は色々やってくれる印象があるのですが全容を把握しきれていないです。そのため、全容を把握しやすい yaaf の方が好みでした。

今回の記事作成にあたって参考にした資料

フォームオブジェクトとは

accept_nested_attributes_for について

yaaf

reform

2021年のふりかえりと2022年の抱負

はじめに

正月三が日も終わり、大分出遅れてしまった感がありますが、2021年のふりかえりと2022年の抱負を書きます。

2021年の動向

仕事

  • 1月~3月 開発チームのリーダー的役割を担う
    • 2020年から引き続いての案件でしたが、小さいチームのリーダー的役割を担いました
    • 自分にとっては大分チャレンジングなことでしたが、社内の方々に助けられ、なんとかやっていくことができました
    • 技術的には、JRuby・Rails・Vue.js などを使って開発しました
  • 4月〜9月 新しい案件に参画
    • 新しい案件に参画しました
    • 技術的には Ruby・Rails・jQuery・OpenAPI などを使って開発しました
  • 10月〜12月 チームビルディングを学ぶ
    • 4月〜9月の案件を引き続き同じ案件でしたが、メンバーや開発体制、開発手法が変わりました
    • その中で、チームビルディングを率先する役割になりました
    • 難しかったものの、チームメンバーの協力のもと開発がしやすいように色々整備することができました
    • SQL を読み書きする機会が多かったです

プライベート

  • 1月〜 今まで作りかけていたアプリの開発を再開
    • 主に CSS と Vue3 に触れていました
  • 2月 引っ越し
  • 4月〜 Ruby・Rails 強化期間
  • 7月 個人開発していたアプリの ver0.1 をリリース
  • 8月〜 個人開発アプリの ver1.0 開発を進める
    • AWS、デザイン、Docker などを学びました
  • 10月〜 コミュニケーション・スクラム・SQLなどを学ぶ
  • 12月 個人開発アプリの ver1.0.0 をリリース

2021年の目標を達成できたかどうか

2020年の振り返り の記事では、2021年の抱負を以下のように書いていました。

Ruby・Rails 力を高めたい

Docker を理解したい

フロントエンド力を高めたい

今作成しているアプリをリリースする

チームでの自分の振る舞いを客観的に見られるようになりたい

これらの目標を達成できたのかをふりかえります。

Ruby・Rails 力を高めたい

これは書籍やgemを読むことで、コードの書き方について自分の引き出しを増せたと思います

一方で、設計力や開発速度はまだまだだです。設計力についてはコードリーディングしても、設計パターンをインプットすることができませんでした。開発速度については、コードを書く量がまだまだだと思うので、意識的にコードを書いて早さを上げていきたいです。

総じて、抱負としていた目標は達成できましたが、インプットをアウトプットに繋げる力を磨いていきたいと考えました。

Docker を理解したい

書籍を読み、Docker tutorial に取り組みました。また、個人開発したアプリでも Dockerfile を書きました。そのため、Docker の基本のキは把握できたと思います。抱負としていた目標は達成できました。

フロントエンド力を高めたい

抱負としたのは以下です。

  • CSS の苦手意識をなくす
  • Vue3 を少し扱えるようにする
  • React の勉強をする

個人開発したアプリでは、CSSフレームワークを使わずにデザインを当てたことで、CSSに対する苦手意識がなくなりました。また、Vue3 を導入して動くものが作れたため、こちらも目標は達成できたと思います。

React については、勉強できなかったため目標未達成です。

今作成しているアプリをリリースする

12月に以下のリリースブログを書いて、アプリをリリースできたため目標達成です。

音楽をシェアしやすくするアプリ SoundLinks をリリースしました

チームでの自分の振る舞いを客観的に見られるようになりたい

「自分の振る舞いはチームの役に立っているか、チームにとって建設的なものかどうか」を判断して、チームの役に立ちたいという目標でした。

仕事ではチームビルディングを考えて、実践していました。その結果、色々整備できてチーム開発がしやすくなったように思えます。

もちろん私一人の力ではないですが、チームの役に立つことを考えて、アクションを起こせたことから目標は達成できたと思います。

学んだこと

技術的なこと

Tomcat・IISの基本を学ぶ

Tomcat・IIS の環境に Rails アプリをデプロイするための手順を学びました。また、IISの仮想ディレクトリを使って認証の設定方法についても学びました。

CSSへの苦手意識克服

CSS について苦手意識がありました。そこで、CSS設計完全ガイド を読み、個人開発したサービスでCSSを書いたことで、苦手意識が少なくなりました。

Ruby・Railsの引き出しを増やす

Ruby で綺麗なコードを書くために最近勉強していたこと で書いたことに取り組み、良いコードの引き出しを増やすことを意識しました。

特に、gem のコードリーディング会を同僚と始め、SinatraSorceryPunditOctkitActiveSupport を読みました。知らないメソッド、テクニックを知ることができ、メタプログラミングへの理解が深まりました。

一方で、gem の全体像を捉えて設計を理解し、設計パターンの引き出しを増やすということはあまりできませんでした。この点はコードリーディングの仕方を工夫して、設計の引き出しを増やすことを意識していきたいです。

また、開発速度はまだまだなので、素早くコードを書けるようにコードを書く時間を増やしたいです。

Web API の設計

Web API の設計や OpenAPI の書き方について学びました。

API設計については、API利用者が達成したいゴールを元にリソースを決めて、レスポンスを構造化するという基本的な設計手順や考え方は理解することができました。

一方で、設計したAPIは利用者が限られるクローズドなものだったため、パブリックなAPIの設計やセキュリティを意識した設計については理解できたとは言えません。

OpenAPIの基本的な書き方

OpenAPI の基本的な書き方については理解できたと思います。一方で、Grapecommittee-rails などの gem やツールは使いませんでした。そのため、OpenAPI を Rails で便利に扱うためのツール・gemの使い方などは習得できていません。

Dockerの基本的な使い方

上述したように書籍を読んで、Docker tutorial に取り組み、個人開発したアプリでDockerfile を作成したため、基本のキは理解できたと思っています。しかし、浅い理解なので、使いこなせるように深く理解していきたいです。

AWSに少し触れる

ECR・ECS・ALB・Route53・RDS などを使用してアプリを開発しました。しかし、体系的に理解できているとはいえず、使いこなせているわけではないため、深く理解していきたいです。

SQL

仕事でSQLを書く機会が多かったため、この際SQLをしっかり読み書きできるようにしようと思い意識的に勉強していました。

書籍を読み、手を動かして、それなりに読み書きはできるようになったと思います。一方で、パフォーマンスを意識したSQLを記述することについては、まだ自信がありません。

非技術的なこと

非技術的なことについて、学んだことが多かったので列挙します。

  • 見積もりについて
  • コミュニケーションについて
    • 議論する際には、聴くことから始める
    • 自分の考え、気持ちをはっきり伝えた上で、してもらいたいことを伝える
    • 簡潔な文章の書き方
    • 相手に何をしてもらいたいのかをはっきり伝えてから、情報を伝える
  • ミーティングの進め方
    • ミーティングの目的、議題をあらかじめ参加者に伝えておく(それかミーティング冒頭で伝える)
    • 参加者に意見を聞く(振る)
  • チームビルディングについて
    • チームの課題を自分1人で解決しようとせずに、チームに共有してチームで解決していくという心構え
    • 課題発見→議論→解決のアクション→ふりかえりの流れを作ることを意識する
  • その他
    • 自分だったらどう設計、実装するかを思い浮かべて仕様を把握する・コードを読む
    • プロジェクトを円滑に進めるためにあった方が良いドキュメントを踏まえた上で、ドキュメントを作成する
    • メリット、デメリットを伝えた提案の仕方
    • 自分の素直な気持ちを伝えること
    • 人に相談すること

2022年の抱負

2022年は「理解したつもりの(あるいは、なんとなく使っている)概念、技術を深く理解する」ことを目標にしたいと思います。

2021年はDocker・AWSなど、扱える技術の幅を広げた1年だったように思います。一方で、それらを使って動くモノはできたけど、それがどんな理屈で動いているのかという根本の仕組みを理解せずになんとなく使っている状態です。それは Docker・AWS に限らず、Ruby・Rails、UNIX・Linux のファイルシステム。REST・オブジェクト指向などの概念なども含みます。

そのような理解したつもりになっている、あるいは、なんとなく使っている技術や概念を深く理解することを目指したいです。そのための取り組みとして、その概念・技術についてのレポートのようなブログ記事を定期的に書くなどの試みを考え中です。

所感

この1年は色々失敗を重ねた1年だったと思います。ただ、社内の方々や懇意にしているエンジニアの方々のおかげで、なんとか生き残ることができました。大変感謝しています。

チームのリーダーを務める、チームビルディングをするなどは、自分にとってはチャレンジでした。その中で大小様々な失敗をしましたが、その分学んだことは多かったです。

また、個人開発したアプリをリリースできたことがとても嬉しかったです。開発期間は長くかかってしまいましたが、0からリリースまでやりきることができて達成感があります。

まだまだできないこと・知らないことばかりです。一方で、できたこと・知ったことも沢山あった1年でした。2022年も引き続きなんとか生き延びていきたいです。

参考: 2021年に読んだ本

上記のことを学ぶために以下の本を読みました。

CSS・デザイン

Ruby

Web API

Docker

AWS

SQL

アジャイル・スクラム

コミュニケーションスキル

その他

音楽をシェアしやすくするアプリ SoundLinks をリリースしました

f:id:NMP300:20211205194826p:plain

はじめに

koheitakahashi と申します。2019年からFJORD BOOT CAMPに入会し、2020年7月からWEBエンジニアとして受託開発会社に務めています。

今回、FJORD BOOT CAMPに在学していた頃から開発を続けていたWebアプリをリリースしました。

SoundLinksという、音楽をシェアしやすくするWebアプリです。この記事は、そのWebアプリの紹介と開発過程のまとめです。

SoundLinksの紹介

SoundLinksとは

SoundLinksは、楽曲を共有したい時に、音楽プラットフォームごとに楽曲のリンクを探すのが面倒という問題を解決したい、楽曲を共有したい人向けの音楽プラットフォーム横断検索サービスです。

ユーザーは、楽曲のタイトルを入力すると各音楽プラットフォーム毎の楽曲リンクを取得することができます。

これは、自分でそれぞれの音楽プラットフォーム内を検索してリンクを取得する場合とは違って、一度に複数の音楽プラットフォームのリンクを取得できることが特徴です。

リポジトリのURL: https://github.com/koheitakahashi/sound_links

アプリのURL: https://sound-links.com

解決したかった問題

Spotify、AppleMusic、KKBOX、YouTubeMusic...など、音楽プラットフォームが多様化しています。そのため、仲間内のSlackやDiscordで「この曲を共有したい」ときに気軽に楽曲を共有できない問題があると思いました。

例えば、自分はSpotifyを使っているが、楽曲を共有したい相手はAppleMusicを使っている場合です。Spotifyの楽曲リンクだけ共有しても、共有された側のAppleMusicユーザーは自分が使っているプラットフォームで共有された曲を聞くことができません。

この問題を回避しようと思ったら、相手が使っているプラットフォーム内で共有したい楽曲を検索しなければいけませんが、これでは気軽に楽曲を共有することができません。

そのような楽曲をシェアしづらい問題を解決するために、SoundLinksをリリースしました。

SoundLinksでできること

以下のキャプチャのように、音楽プラットフォームを横断検索できます。

そして、各プラットフォームごとの楽曲リンクをワンクリックでクリップボードにコピーすることができます。

SoundLinksの検索フォームに「リライト」を入力して、検索結果画面に遷移しているgif

クリップボードにコピーされる内容の具体例は以下です。楽曲名・アーティスト名・各プラットフォームのリンクがコピーされます。

リライト(ASIAN KUNG-FU GENERATION)
Spotify
https://open.spotify.com/track/3txqYlzoDZGLoW8MGll9tQ
Apple Music
https://music.apple.com/jp/album/%E3%83%AA%E3%83%A9%E3%82%A4%E3%83%88/1536394883?i=1536394888
KKBOX
https://www.kkbox.com/jp/ja/song/FcqGD-90I.n6HlVI7lVI70P4-index.html

後は、お使いのチャットツールなどにこの内容を投稿すれば、相手が使っているプラットフォームを気にすることなく楽曲をシェアできます。

2021/11/27現在、Spotify・AppleMusic・KKBOXの3つの音楽プラットフォームに対応しています。

開発過程

SoundLinksはリリースに至るまで、大きく分けて以下の過程がありました。

  1. バージョン0.1.0 開発・公開
  2. バージョン0.1.0で見つかった改善点を踏まえてバージョン1.0.0の開発・リリース

実際にどのようなことをしたのかを以下に書きます。

1. バージョン0.1.0開発〜リリース

1-1. エレベーターピッチを作る

エレベーターピッチとは、自分が作りたいサービスを30秒で伝えられるように明確化した文章です。

上記の SoundLinksとは の段落で書いている文章を作りました。

エレベーターピッチを作ることで、自分がこれから作りたいサービスの本質は何なのかということが明確になりました。それが文章化されていることで、実装したい機能を思いついた時に、実装するかどうかの判断がしやすくなりました。

具体的には「ユーザーログイン機能」「楽曲をお気に入り登録する機能」を思いつきましたが、このサービスは「シェアしやすくすることが目的」だと立ち戻ることができ、これらの機能は実装しない判断ができました。

1-2. ペーパープロトタイプを作る

ペーパープロトタイプとは、アプリの画面・UIを紙などに起こしたものです。実際に以下のようなものを作成しました。

SoundLinksのトップページのペーパープロトタイプ
SoundLinksのペーパープロトタイプ(トップページ)

SoundLinksの検索結果ページのペーパープロトタイプ
SoundLinksのペーパープロトタイプ(検索結果ページ)

これを作成したおかげで、どのような画面が必要なのか、どのようなUIが適しているのかなどの検討ができました。また、画面のゴールがイメージできたことで実装の見通しが立ち、リリースまでに実装しなければいけない機能・画面が洗い出しやすくなりました。

1-3. 実現可能かの調査 + 技術選定

このアプリが実現できるかどうかを判断するために、以下のことを調査・検討しました。

  • どのような音楽プラットフォームがあるのかを調査
  • 音楽プラットフォームから、どのようにして楽曲情報を取得するかの検討
    • スクレイピングでデータを取得するか、APIからデータを取得するか
    • スクレイピング可能かどうかを判断するために各プラットフォームの利用規約を読み込む
    • 各プラットフォームのAPI公開状況を調査

上記を調べた結果、スクレイピングは避けた方が無難という結論にいきつきました。そして、Spotify・AppleMusic・KKBOX・YouTubeがAPIを公開していたため、それらの4つのサービスに対応することとしました。しかし、YouTubeは後述の理由によりサポートを断念します。

技術選定について、Railsを採用・Herokuにデプロイすることを当初は考えていました。それまでRailsを勉強していたということと、フロントエンド側では複雑な処理をしないことから、JavaScriptのフレームワークを導入する必要はないという判断をしたためです。

しかし、開発を進めていく中でVue.jsを勉強したくなり、途中でVue.jsを導入しました。

1-4. 開発

タスク管理について

開発上のタスクの管理は、GitHub Projects でカンバン方式で管理しました。

その中で、大事だと思ったことが2点あります。

1点目が、やりたいと思ったこと、アイディアをすぐにissueに立てるということです。私が忘れっぽいため、アイディアを思いついたものの issue 化しておらず、結果で取りこぼしてしまったものがあると思います。そのようなアイディアを取りこぼさないためにも、すぐに issue 化してやるかどうかの判断は後でするということを実践できていればよかったです。

2点目が、調査や検討系のタスクもissueに切り出して、調査・検討のログを残しておくことです。APIの調査やアーキテクチャの検討などは開発の初期段階でやっていました。しかし、その内容をまとめていませんでした。今回は開発期間が大分長くなってしまったので、調査内容や意思判断の過程を忘れてしまって、再度調べ直すということがありました。

そのため、調査・検討のタスクをissueに切り出して、ログに残すことが大事だと思いました。

開発中の出来事

様々ありますが、大きな出来事は以下の2点でした。

1点目が、YouTube Data API から取得した楽曲と、他のAPIから取得した楽曲が同一のものだと判断できない問題に直面したことです。 そもそも YouTube Music 専用の API は公開されていなかったため、YouTube Data API v3 から楽曲情報を取得しようと考えました。しかし、その API から ISRC というレコーディングに対して付与される識別子(書籍の ISBN のようなもの)が取れませんでした。

各 API から取得される楽曲が同一かどうかの判断に ISRC を使用していたため、YouTube 対応を諦めざるを得ませんでした。

2021/11/28 現在、よい解決策が思いつかず YouTube は未対応となっています。しかし、自分で SoundLinks を利用していて YouTube のURLが取得できないとなると、楽曲をシェアできる相手が限られてしまうので解決策を考えています。

2点目が、Vue.js の導入です。 これは、自分が Vue.js を勉強したくなったという理由からです。最初はVue 2 で実装を進めていましたが、途中で Vue 3 を勉強したくなったため、Vue 3 を導入しました。Composition API の書き方については手探りで、これで良いのかという懸念がありますが、動くものができました。

1-5. 身近な方々へ公開

Herokuにデプロイして、身近な方々へ告知して実際に使っていただきました。フィードバックをいただき、実際に自分でも継続して使ってみて、デザインが大きな課題だと分かりました。

当初のデザインは以下のスクショです。

f:id:NMP300:20211128171644p:plain
SoundLinks バージョン0.1.0 のトップページ

f:id:NMP300:20211128171642p:plain
SoundLinks バージョン0.1.0 の検索結果ページ

デザイン面の課題を細分化すると以下の2つだと捉えました。

  • SoundLinks で何ができるかが分かりづらい
  • ユーザーが使ってみようと思えるデザインではない

そのため、バージョン1.0.0では上記の課題が解決できるように対応していきました。

2. バージョン1.0.0開発〜リリース

バージョン1.0.0の開発でやったことは大きく分けて以下の2点です。

  1. バージョン0.1.0の課題を改善
  2. アプリ構成の変更

2-1. バージョン0.1.0の課題を改善

前述したように「何ができるかが分かりづらい」「ユーザーが使ってみようと思えないデザインではない」ことが課題でした。そのため、「何ができるかのメッセージを強調する」「ユーザーが触ってみようと思える最低限のデザインレベルをクリアする」ということを目標にデザインを再度練り直しました。

デザインについては、これまで体系的に勉強したことがありませんでした。そのため、この際簡単な本を読んで基本を抑えようと思い、以下の本を読みました。

ページのデザインを再度練り上げるために使ったツールは Figma です。

f:id:NMP300:20211205185826p:plain
Figma でデザインを練り上げていたときの図

また、ファビコン・ロゴ・アイコンの作成は Canva を使いました。

f:id:NMP300:20211205190037p:plain
Canva でロゴを練り上げていたときのイメージ

そして、最終的には以下のようなデザインに落ち着きました。

f:id:NMP300:20211205190806p:plain
トップページ

f:id:NMP300:20211205190840p:plain
検索結果ページ

より詳しくデザインの変更を見たい場合は以下をご覧ください。

https://github.com/koheitakahashi/sound_links/releases/tag/v1.0.0

2-2. アプリ構成の変更

ユーザーの使い勝手を向上させるという目的ではなくて、自分の勉強を目的として以下のことに取り組みました。

  • 完全SPA化
  • AWSの利用

上記のどちらも経験が少なかったため、今回ある程度学んでみて「SPAとAWSの触りは知っている」という状態になりたかったという動機です。

合わせて Docker の基本も勉強したいと思い、以下のようなインプットをしました。

そして、Docker を使うなら ECR・ECS の構成が便利だと聞き、その構成を採用しました。こちらは体系的に学んだというわけではありませんが、以下の記事を参考にさせていただきました。その中で、分からないところは公式リファレンスを見て解決していきました。

最終的には以下の構成となりました。

f:id:NMP300:20211128171943p:plain
SoundLinksの構成図

工夫した点

Spotify・AppleMusic・KKBOX のAPIを使っているため、各 API で定められているリクエストの上限を超えないように、少しでもAPI へのリクエストを抑えようとしました。

具体的には、外部 API から取得した楽曲情報を一時的に DB に保存しています(有効期限は1日)。有効期限内にユーザーが同じ条件で楽曲を検索した場合は SoundLinks の API サーバーは外部 API にリクエストを投げません。代わりに、DB に保存された楽曲情報をブラウザに返します。

このような仕組みで、外部 API へのリクエストを少しでも抑えています。

開発の進め方の改善点

開発を進めるにあたって「開発期間が長くなった」という反省点があります。最初のコミットが2020年4月だったため、開発に1年8ヶ月かかっています(途中、半年ほど開発できていない期間がありましたが)。

実際にこの要因を考えてみると技術選定の段階で、「どのうような構成にするか」「どの技術を採用するのか」を詰めきれていなかったことが挙げられます。詰めきれていないせいで、採用する技術や構成を大きく変更してしまい、それが手戻りにつながったと考えます。

そのため、技術選定の段階で「どのような構成が一般的に採用されるのか」についてのインプットを増やさなければならなかったと思います。その上で「自分は何を勉強したいのか」をはっきり決めて、開発を進めていけると手戻りが少なく開発できたように考えます。

そもそも、使ってもらえるアプリを開発するためには、自分が思い描いた課題が本当にあるのかの検証を早めにしなければならなかったと思います。個人開発でリソースも限られているので、その検証をして課題があると分かってからてリソースを割くという戦略をとってもよかったかもしれません。

この経験を今後のアプリ開発に生かしていきたいと思います。

リリースしてみての感想

フィヨルドブートキャンプのメンターの方々、SoundLinks v0.1.0を公開した時に使ってくださった方々、意見をくださった方々に大変感謝しております。色々な方々の助けをお借りしながらも、開発するアプリを考えるところから始めてリリースするというフローを経験できたことで、勉強になることが多かったです。

また、開発を通してデザイン・Docker・AWS・Vue 3 などについて「あまり分からない状態」から「触りを知っている状態」になれました。

対応サービスが少ないこと、検索結果の表示速度など気になるところは多々ありますが、今後の課題としたいと思います。

0からアプリを開発することは非常に学びが多く、今後に活かしたい課題や改善点も見つかったため、これからのアプリ開発に生かしていきたいと思います。

SoundLinksの不具合・要望などがありましたら、SoundLinks のリポジトリの Issuesか、TwitterのDMでお知らせください。

Ruby で綺麗なコードを書くために最近勉強していたこと

はじめに

最近、綺麗なコード(自分の中では変更しやすいコードという意味)とは、どのようなコードなのかということが分からなくなっていました。

また、コードレビューの中で「これは自分の好みを押し付けているだけではないだろうか」と感じることがありました。
どこまでが一般的に言われる保守しやすいコードなのか、どこまでが自分の好みなのかという線引きが分からなくなってきていました。

そのため「綺麗なコード(保守しやすいコード)とはどのようなコードなのか」を再確認するために、ここ2ヶ月ほど集中的に勉強していました。

そこで、同じような悩みを持っている方の参考になればと思い、どのようなことを勉強していたのかをまとめました。

勉強していたこと

オブジェクト指向を学び直す

そもそも、オブジェクト指向に則って考えられないと適切なクラス設計はできないと考えて『オブジェクト指向設計実践ガイド』を再読しました。

本書は過去3回ほど読んだのですが、その時は理解できていなかった部分が多かったです。
今回は写経しながら読み進めたこともあり、ようやく「依存 = 変更する際の摩擦」であることを実感を伴って理解できました。

それにより「クラスが他のクラスの情報を知りすぎている(つまり依存度合いが大きい)」という状態を検知でき始めてきました。

リファクタリングのレシピを学ぶ

今までは臭うコードを見ても「どこが良くないのか、どのようにすれば良くなるのか」を言語化できていませんでした。

そこで、リファクタリングについて学びたいと思い『リファクタリング Ruby エディション』を読みました。

本書では「どのようなコードが臭うコードなのか。そのような臭うコードには、このリファクタリングの手段が使える」といった、リファクタリングのレシピが明確に示されており大変勉強になりました。

本書を読んだことで、リファクタリングをより言語化して考えやすくなった感覚があります。

コミュニティにおいて望ましいとされるスタイルルールを知る

望ましいとされるスタイルルールを知るために「Ruby Style Guide」を読みました。

Rubocop のデフォルトに従っていれば、このスタイルを逸脱することはないですが、そもそもどのようなルールに従っているのかを把握するために読んでみました。

「自分の好みを押し付けているかも知れない」と感じていた部分が Style Guide には明記されていました。
そのため、「自分の好みの書き方なのか、共通認識を得られている書き方なのか」の線引きができ始めてきました。

引き出しを増やす

良いコードの引き出しを増やすために以下の3点に取り組みました。

デザインパターンを知る

問題への解決方法がパターン化されているものは、それを学んだ方が早いと思い『Ruby によるデザインパターン』を読みました。

「あのコードは、このパターンを使っていたんだ」ということが発見できて面白かったです。
また、パターンを知ってることで問題に直面した時、解決方法までの道のりをショートカットできそうだと思いました。

リファレンスマニュアルを読む

メソッドを知っていれば、綺麗に書けたという場面が数えきれないくらいあるので Ruby リファレンスマニュアルの「組み込みライブラリ」まで一通り読みました。

Enumerable#partition など知らなかったけれども、役立つメソッドを知ることができてよかったです。

コードリーディング

Sinatra を読んで、今は Sorcery を読んでいる最中です。

Sinatra の throw catch を使った処理の抜け方や、Sorcery の Adapter の考え方など勉強になりました。
まだまだ読んだコードの絶対量が足りないので引き続き読んでいきたい所存です。

最後に

上記のようなことをインプットしたからといって、すぐ綺麗なコードを書けるわけではないとは思っています。
特に自分の場合はコードを書いた量・読んだ量がまだまだ足りないので、インプットしたことをコードに反映して試行錯誤していくことが必要なのだと感じました。

ActionController::Liveを使っていてハマったところ

概要

この記事の内容を大まかにまとめると以下になります。

ActionController::Liveを使っていてstream.closeされたら、cookieの値を変更するという処理を実装しようとしたのですが、cookieの値が変更できないという問題に直面しました。

そもそも、今回はチャンク形式転送エンコーディングという方式でレスポンスを返していたのですが、その仕組みは最初にクライアントにレスポンスを送って、そのレスポンスボディに逐次データを書き込んでいるというものでした。

そのため、レスポンスを最初に送っているため、レスポンスヘッダーが変更不可能な状態になります。よって、データを送りはじめたら、それ以降同一レスポンス内ではcookieは変更できないということでした。

環境

  • Rails 6.0.3

本題

実装しようとしていたこと

フロントエンドがVue.js、バックエンドがRailsと別れている中で、ファイルダウンロードの仕組みに追加実装しようとしていました。
そのファイルダウンロードの仕組みは、ActionController::Liveを使用して、ある程度のデータの塊に分割してクライアントにデータを送るというものでした。(具体的には以下のようなコードになります)

class FileDownloadsController < ApplicationController
  include ActionController::Live
  
  def create
    file = fetch_file # fetch_fileはファイルをとってくる処理
    
    # 100KBごとにクライアントにデータを送る
    while (chunk = file.read(100000).presence)
      response.stream.write chunk
    end
    
    response.stream.close
  end
end

上記のコードがある中で、今回私が実装しようとしたものは、ダウンロードの完了を検知して画面の表示を変えるというものでした。

当初考えた実装方針は以下のようなものになります。

  1. フロントエンド側は、ファイルダウンロードボタンを押された後から、1秒に1回ほどの頻度でcookieを監視する

  2. FileDownloadsController#createで、ダウンロードが完了したら、cookieにdownloading=0などの値を入れる

  3. フロントエンドは、downloading=0になったことを検知できたら画面の表示を変更させて、cookieの監視をやめる

コードレベルでは以下のようなイメージです。

class FileDownloadsController < ApplicationController
  include ActionController::Live

  def create
    cookie[:downloading] = 1 # ここを追加
    file = fetch_file # fetch_fileはファイルをとってくる処理

    # 100KBごとにクライアントにデータを送る
    while (chunk = file.read(100000).presence)
      response.stream.write chunk
    end

    response.stream.close
    cookie[:downloading] = 0 # ここを追加
  end
end

ハマったこと

上記のようなコードを実装して、フロントエンド側でcookieを監視してdownloading = 1となっていることは確認できました。
しかし、ダウンロードを完了してもdownloading = 0には変更されていないという問題がありました。

なぜ上記の方法で実現できなかったのか

そもそも、どうやって分割してデータが送られているのかという大枠を知らなければなりませんでした。
具体的にはチャンク形式転送エンコーディングという方法がHTTPレイヤーでは使われています。

Transfer-Encoding - HTTP | MDN に記載がありますが、チャンク化転送エンコーディングというのは、データを分割してそれを逐次送っていくという仕組みです。

具体的には、最初にレスポンスヘッダーとレスポンスボディの最初のチャンクを送って、それ以降は\r\n区切りで分割したデータがレスポンスボディに逐次書き込まれていきます。

このような仕組みにより、ファイルの分割ダウンロードが実現されていました。

ここで、最初にレスポンスヘッダーをクライアントに送っているという都合上、レスポンスヘッダーは後から変更不可能な状態になります。
ゆえに、ダウンロードが完了した瞬間にcookieを変更することができないということでした。

感想

なぜcookieが書き換えられないのだと数時間悩んでいたのですが、HTTPレイヤーではどのような仕組みが採用されているのかに関心を向けられれば、原因が分かるのが早かったと思いました。

「ActionController::Live cookie」などで検索しても、中々情報がヒットしなかったので、同じような問題に直面した方の参考になれば嬉しいです。

参考

見積もり関連のことで最近失敗したことと、そこから学んだこと

はじめに

年明けから従事しているプロジェクトで、初めて開発チームのマネージャー的な役割を少しだけ担うこととなりました。
社の方々に多大なサポートをいただきながら、自分なりに考えてなんとかやってはいるものの、マネージャー的役割が初めての経験ということもあり色々と失敗しました。

この失敗を次に生かすために、失敗したこととそこから学んだことをまとめたいと思います。

ちなみに、以下のような意味合いで言葉を使っています。

マネージャー的役割
プロダクトオーナーから開発しているアプリの方向性を聞き、それをチームが取り組めるタスクに細分化して、それを見積もる。 他のチームとの窓口的な役割を担い、開発チームが開発を進めていけるようにする役割のことを指しています。

見積もり
細分化されたタスクがどれくらいの大きさなのかを考えて可視化する。そして、それを元にスケジュールを引くまでを、今回は見積もりとしています。

見積もり関連で最近失敗したこと

1. 設計タスクを見積もりに含めていなかった

今回、新機能開発を行っていますが、その仕様や設計(DB設計など)を決めるということをタスクを洗い出した時に含めていませんでした。
そこまで大きな機能ではなかったため、そこまで設計に重きを置いていなかったというのが見積もりをした時の考えでした。

しかし、実際には思いの他設計タスクに時間がかかってしまい、コーディングに着手するのが遅れたという失敗でした。

2. 自分がマネージャー的な役割を担っているにも関わらずに、普通に手を動かす時間がある前提でスケジュールを引いてしまった

上記の「マネージャー的役割」に記述したようなことをやっていたのですが、それにも関わらずに普通に手を動かせるという前提でスケジュールを引いてしまいました。

具体的に説明しますと...
自分が「マネージャー的役割」を担っていない状態で1日に1ポイントを消化できるとした時、今回はそうでないため1日に0.5ポイントくらいしか消化できないと考えるべきでした。 しかし、マネージャー的役割を担うことの負担を全く勘定に入れず、「このタスクは3ポイントだから3日くらいで終わるなぁ」という感じでスケジュールを引いてしまいました。

その結果、私が担当するタスクの完了が想定よりも大分遅れてしまいました。

3. スケジュールを引いた時の想定から大きくずれ込んでいることを早めに相談することができなかった

今回の失敗として、これが1番大きい失敗だと思います。上記の1・2のようなことがあり、当初引いたスケジュールよりも遅れてしまっている状態でした。
これには薄々気づいていたのですが、「自分が頑張ればなんとか巻き返せるのではないか」と考えてしまい、早めに相談するということができませんでした。

その後、当初引いたスケジュールとのギャップが大きくなった状態で、ようやく相談してプロダクトオーナーに色々と調整していただくという運びになってしまいました。

4. プロダクトオーナーにとって分かりやすい見積もり表を作ることができなかった

プロダクトオーナーとスケジュールについて話し合う際に、自分が作成した見積もり表は洗い出したタスクが細かくなりすぎていて、中期的なマイルストーンが伝わりづらくなってしまいました。

以下の例のような感じです。 ただ、以下はちょっと細かすぎて実際のタスク表とは少し異なりますが、ニュアンスが伝わればと思います(実際のタスク表を一般化して書くのが難しかった...)。

例1

タスク ポイント
新機能の開発に必要な gem 〇〇を入れて設定する 0.5
〇〇 model を追加 0.5
〇〇 Controller と View を追加 1
CSS を当てる 2
....

例1のような見積もり表では、同じチームメンバー間ではやるべきことが共有できるのですが、プロダクトオーナー目線ではここまでタスクが細かいと中長期的なマイルストーンは把握できないということでした。

このように、プロダクトオーナーや開発チーム以外の人に分かってもらいやすい見積もり表を作ることができませんでした。

失敗から学んだこと

仕様や設計が固まっていない場合は、それを決めるタスクを切って、しっかり時間をとる

今回の仕様決めや設計は、諸事情あり開発チームだけで完結できるものではありませんでした。そのため、仕様設計に関わる人が多ければ多いほど、しっかりとそれらを決める時間を確保するということが必要だと学びました。

また、開発チームで仕様や設計の草案を練ってAさんにレビューしていただき、それを直したものをBさんにレビューしてもらい...というような形で仕様・設計決めをやってしまったことも改善すべき点でした。 仕様・設計に関わる人を一気に集めて、その場で議論して、固めてしまうというやり方が時間短縮のためには必要でした。

マネージャー的な業務をする場合は、自分のコーディングにおける生産性は半分以下だと考えてスケジュールを引くこと

マネージャー的な業務をしていると、思った以上にコーディングに時間が取れなかったり、コーディングしていたとしても集中して取り組むことができませんでした。

そのため、マネージャー的な業務をする場合は、通常時の半分以下の生産性になってしまうと考えてスケジュールを引くことを学びました。

当初のスケジュールの想定通りにいかないと少しでも感じた時点で相談すること

今回の私のように、スケジュールとのギャップが大きくなってしまった段階で、相談するとプロダクトオーナーや他の人も打てる手が限られてしまうということを体感しました。
今回の場合は、社のエンジニアの人にサポートに入っていただいて、なんとかギャップを小さくできました。しかし、早めに相談することで、スコープや納期を調整してもらうという手段も取れたかもしれません。

そのように、時間と共に打てる選択肢少なくなってくるため、少しでも想定通りにいかないと予感した時点で相談するということが大事なのだと学びました。

誰に説明するかを考えて見積もり表を作る

4のように、細かすぎるタスクを切ってしまうと開発チーム以外の人には一気に伝わりづらくなると感じました。 そのため、見積もり表を作る場合は、「誰に見せるもの」「なんのために見せるもの」なのかということを考えて見積もり表を作る必要があると考えました。

具体的には、今回の場合は「プロダクトオーナー」に対して「作るもののスコープや納期を調整するための議論をするため」に見積もり表を見せるという状況でした。

そのため、例1のような細かすぎるタスクの切り方だとマイルストーンが把握しづらく、あまり議論できないということが問題でした。
よって、中項目を作成するなどして、その中項目がどれくらいで終わりそうかということが伝わるようにする必要がありました。

例2

中項目 タスク ポイント
新機能開発のための準備(0.5 pt) 開発に必要な gem 〇〇を入れて設定する 0.5
新機能 A の開発(3.5 pt) A model を追加 0.5
A Controller と A view を追加 1
A 機能に CSS を当てる 2
新機能 B の開発 ... ...

上記のように中項目を設定して、「その中項目がどのくらいで終わりそうか」「その機能はいつまでにできている必要があるか」「だから、スコープ・納期・人員を調整しよう」というような話ができるような見積もり表を作っておくべきでした。

まとめ

今回初めてマネージャー的役割を務めさせていただいて、以下のような失敗をして、学びを得ました。

  1. 設計タスクを見積もりに含めていなかった => 設計タスクを見積もりに含めること
  2. 自分がマネージャー的役割でも、普通にコーディングを進められると思ってスケジュールを引いた => マネージャー的役割をやるのなら、自分の生産性は半分以下であると考えてスケジュールを引くこと
  3. スケジュールから大きく遅れていることが分かった段階で初めて相談した => 遅れることを予感した段階で相談すること
  4. 細かすぎる見積もり表を出してしまった => 誰に見せる見積もり表なのかを考えて、見積もり表を作成する

同じ失敗は2度しないように、今回学んだことを次の機会には生かしていきたいです。

2020年の振り返り

気づけば31日ということで、2020年の振り返りをしていきたいと思います。

この1年の動向

1月 ~ 6月 FJORD BOOT CAMP で勉強

就職を目指して FJORD BOOT CAMP のカリキュラムを粛々と進めていました。 ここで、JavaScript と Vue.js や Action Cable に出会い、「難しい...」と四苦八苦していました。

そして、有難いことに、都内の受託企業に内定をいただきました。

7月 ~ 9月 入社と研修期間

7月から入社して、そこから9月まで研修をしていただきました。
研修では基本的な Rails のアプリを開発していくという形式で進められ、Rails の基本的な書き方を教えていただき、大変勉強になりました。

9月 ~ 12月 初めての実務

研修が9月に終わり、初めての案件にアサインされました。
初めての実務でしたが、社内の方々に色々とご教授いただきながら、なんとかタスクを進めることができました。

学んだ技術

Ruby

基本的なメソッドも覚え始めてきて、少しは自分が考えたように処理が書けるようになってきました。
その上で、今年は Ruby をさらに深く理解したいということで、『メタプログラミング Ruby』を読みました。しかし、内容が難しくてあまり理解したとは言えません。

引き続き Ruby を深く理解していきたいと思っているので、『メタプログラミング Ruby』を読み返したり、『Ruby のしくみ』などの本を読んでいきたいと思っています。

JRuby

実務で触ることになったのですが、こちらはあまり理解して使いこなせているという状態ではありません。

一応、JRuby + Ruby on Rails を使用しての開発〜デプロイまでの基本的な流れは少し分かってきたつもりです。

しかし、JRuby 独自の機能である Java のメソッドをコールするなどの凝った使い方までは、使いこなせるとは言えない状態です。
そのため、もう少し JRuby を理解していきたいという所存です。

Ruby on Rails

FJORD BOOT CAMP のスクラム開発のプラクティスの中で、Action Cable を学んでおりましたが、最初は「分からないことが分からない」状態でした。
しかし、WebSocket がどのようなものなのか、pub/sub モデルがどのようなものなのかを理解していくことで、ようやく Action Cable を少し使える状態になりました。

また、基本的な Rails 力を向上させたいと思い『Rails ガイド』に一通り目を通したり、『パーフェクト Ruby on Rails』を読んだりしました。

小規模のアプリ開発はなんとか進められるとは思うのですが、まだまだ分からないことだらけです。 来年も引き続き実務を通して、Ruby on Rails 力をつけていきたいという所存です。

JavaScript

1月から勉強を始めました。『JavaScript ふりがなプログラミング』、『初めての JavaScript』を読んだり、簡単なメモアプリを作ったりして勉強を進めていきました。 しかし、まだまだ考えた処理をスラスラと書くことができないという状態です。

JavaScript を書いていて「理解せずになんとなく書いている」という部分が多いと感じています。また、基本的な API が頭に入っていないとも感じるため、来年も引き続き勉強を続けていきたいです。

TypeScript

こちらは、実務で触ることになり10月ころから注力して勉強を進めてきました。 『プログラミング TypeScript』や『実践 TypeScript BFFとNext.js & Nuxt.js の型定義』などを読み、簡単なメモアプリなどを作りながら勉強していました。

基本的な書き方は分かったつもりなのですが、ジェネリクスを用いた書き方などは身に付いているとは言えない状態です。

一方で、初めて静的型付けができる言語に触れたのでとても感動しました。そこまで型のメリットを実感できたというわけではありませんが、コンパイルした時にエラーとなるコードが分かるのは素晴らしいと感じました。

Vue.js

1月頃から勉強しています。『これからはじめる Vue.js 実践入門』を読んだり、実際に手を動かしたりしながら、書き方を覚えていきました。

基本的な書き方は理解できたつもりですが、中規模・大規模のアプリ開発はまだまだという感じです。

加えて、Vue3 を目下勉強中ですが、Vue.js を取り巻くツール(webpack・vue-loader...など)の理解がまだまだというのを痛感しております。

バックエンドも難しいですが、フロントエンドの難しさを日々実感しております。

考え方の変化

7月から入社して、実務を通して、先輩方に色々なことを教えていただきました。そのようなことから、自分の考え方が大きく変わりました。

自分もチームの一員であると思えるようになった

これが大きな考え方の変化でした。

9月から実務に入らせていただきましたが、当初は自分が全くチームに貢献できてないことへの罪悪感がありました。そのような罪悪感からか、チームの中で上手く振る舞うことができていないという感覚がありました。
ただ、そのことを社の先輩方に話させていただき、その中で「自分とチームを切り離して考えてしまっていること」に気づかせていただきました。

その気づきがあり、「自分もチームの一員なのだから、自分にとっての利益はチームにとっての利益である」ということを考えられるようになりました。 そのように考えられるようになったことで、チーム内での振る舞い方が以下のように変わってきたという実感があります。

コードレビューで、気になったことや分からないことを率直にコメントできるようになった

今のチームでは PR を相互にレビューをする文化が根付いているため、プログラマー歴10年以上の先輩が私にレビュー依頼をくださることが多いです。

最初は「どのようにレビューをすれば良いのか分からない」という状態でした。
分からないところがあっても「これは自分の勉強不足だ」と考えてしまい、コメントしようと思ってもコメントできないということが往々にしてありました。

しかし、上記のように自分をチームの一員として自覚できたことと、「レビューは対話だから、分からないことや疑問に思ったことはどんどんコメントしていくべき」と教わったことによりレビューに対する考え方が変わりました。

「レビューは個人のコードをチームの共有物にするためのもの。そのためには、対話をどんどんするべき」と考えられるようになり、レビューでコメントを残せるようになりました。

人の時間を奪っていることへの罪悪感が少なくなった

自分に割り振られたタスクを進めていく中で、ハマることが往々にしてありました。そのような時は、チームの方々が声をかけてくださり、モブプロを開いて下さります。しかし、その度に「時間を奪ってしまって申し訳ない」と思ってしまいました。

しかし、ある日「罪悪感を持つよりも、人の時間を活用した分、どのようにしたらリターンを大きくできるか」を考える方が、建設的ではないかと気付きました。
加えて、「そもそも人の時間を奪っているのではなく、そのような時間の使い方をしようと判断したのはチームメンバーなのだから、そのメンバーの判断を尊重するようにと捉えてはどうか」ということを教えていただきました。

そのような気づきがあり、罪悪感が薄れて、チームを頼るということができはじめたように思います。

来年の抱負

Ruby・Rails 力を高めたい

まだまだ Ruby・Rails を理解したとは言えない状態です。読むべきだけども読んでない Ruby・Rails 関連の本もたくさんあります。
そのため、読むべき本を読んでいくという形で勉強を進めていきたいと思っています。

Docker を理解したい

正直なところ、Docker は全く分かりません。そのため、Docker に触れて、少しは使えるような状態になることを目指して勉強を進めていきたいです。

フロントエンド力を高めたい

Vue3系を少しは扱えるようにすることと、React の勉強をしたいと思っています。
また、CSS 力に自信がなく、既存の CSS を少し修正するだけでも時間がかかってしまうという状態です。そのため、ある程度 CSS を自力で書けるという状態を目指して CSS 力をつけていきたいと考えております。

今作成しているアプリをリリースする

最近、「自分のアプリをリリースしました」と言えるようになりたいと思い、密かにアプリを作成しています。
後はフロントエンド関係のタスクが残っているので、2ヶ月以内にはリリースできそうではあるのですが、デザインが苦手なため「どうなることやら...」という気持ちです。

チームでの自分の振る舞いを客観的に見られるようになりたい

9月からの実務では、目の前のタスクをこなすことに精一杯で、自分の振る舞いを客観視できていなかったように思います。
そのため、来年は自分の振る舞いを客観視して、「自分の振る舞いはチームの役に立っているか、チームにとって建設的なものかどうか」という観点で自分の振る舞いを考えられるようになりたいと思いました。

最後に

以上、この1年の振り返りをしてみました。振り返りをしてみると、色々な経験が蘇ってきて非常に濃い経験をさせてもらいました。
特に入社してからは色々なことを勉強させてもらって、そのおかげで、入社前の出来事が遥か遠い昔のことのように感じられました。

このように言語化することで自分の学んできたことや取り組んできたことが、経験として自分の中で消化されたような感覚もあります。

また、来年の抱負がたくさんあって「全部に手をつけられるのか...」と今から心配ですが、なんとかやっていけるように優先順位を考えて取り組んでいきたいところです。

最近学んだ Ruby・Rails のコードを書く時のちょっとしたテクニック

はじめに

2020年7月からプログラマーとして働き始め、それから研修期間の後に9月から案件に入りました。 7月からの5ヶ月弱で、先輩のコードを拝見したり、多くのレビューをいただく中で多くのことを学びました。

今回は、学んだことの中から Ruby・Rails を書く時のちょっとしたテクニックを少し紹介したいと思います。

最近学んだ Ruby・Rails のテクニック

インスタンス変数と nil ガードでキャッシュする

不必要な処理をさせたくない時に、インスタンス変数と nil ガードを使うことでキャッシュすることを学びました。

例えば以下のような last_name メソッドがある時、last_name が呼び出される度に1回1回実行されるため計算量が増えます。

class Person
  def initialize(name)
    @full_name = name
  end

  def last_name
    @full_name.split('_').last
  end
end

つまり、以下では、last_name メソッドが2回 @full_name.split('_').last が実行されます。

kohei = Person.new("kohei_takahashi")
kohei.last_name #=> "takahashi"
kohei.last_name #=> "takahashi"

それが以下のようにインスタンス変数と nil ガードを使うことで同じ Person のインスタンスなら @full_name.split('_').last の処理は1回しか走りません。

class Person
  def initialize(name)
    @full_name = name
  end

  def last_name
    # 以下が変わったところ
    @last_name ||= @full_name.split('_').last
  end
end

kohei = Person.new("kohei_takahashi")
kohei.last_name #=> "takahashi"

Hash のデータを集計やグルーピングしたい

例えば、以下のような hash_1 があるとします。 この時、key の先頭(/ まで、ここではネコやイヌなど)が一致するものの数を集計して、hash_2 のような Hash を作りたいとします。

hash_1 = {
  "ネコ/スコティッシュ・フォールド" => 10,
  "ネコ/アメリカン・ショートヘア" => 5,
  "イヌ/チワワ" => 20,
  "イヌ/柴犬" => 25,
  "トリ/セキセイインコ" => 15
}

hash_2 = {
  "ネコ" => 15,
  "イヌ" => 45,
  "トリ" => 15
}

この時、以下のように Hash をいい感じに使うことで比較的スッキリ書けます。

hash_3 = {}
hash_1.each do |key, val|
  first_key = key.split('/').first
  hash_3[first_key] ||= 0
  hash_3[first_key] += val
end
hash_3

定数を管理するモジュール

Rails のプロジェクトで、例えば以下のように credentials から秘匿情報を読み取っているようなコードがあったとします(basic 認証の秘匿情報を管理しているという設定)。

# application_controller.rb
class ApplicationController < ActionController::Base
  USER_NAME = Rails.application.credentials.basic_authentication[:name]
  PASSWORD = Rails.application.credentials.basic_authentication[:password]

  http_basic_authenticate_with name: USER_NAME, password: PASSWORD
end

以下のような定数を管理するモジュールを作成して、 config/initializers などに置いておくと、スッキリ書くことができます。

# config/initializers/constants.rb
module AppConstants
  BASIC_AUTH_USER_NAME = Rails.application.credentials.basic_authentication[:name]
  BASIC_AUTH_PASSWORD = Rails.application.credentials.basic_authentication[:password]
end

# application_controller.rb
class ApplicationController < ActionController::Base
  http_basic_authenticate_with name: AppConstants::BASIC_AUTH_USER_NAME, password: AppConstans::BASIC_AUTH_PASSWORD
end

最後に

コードを少し綺麗に書くときのテクニックとして、お役に立てれば幸いです。