GitHub で記事の管理と公開を一元管理したいと考えて、以下にブログを移転しました。
今後は、新しいブログの方に記事を書いていきたいと思います。 詳しくは以下の記事を御覧ください。
GitHub で記事の管理と公開を一元管理したいと考えて、以下にブログを移転しました。
今後は、新しいブログの方に記事を書いていきたいと思います。 詳しくは以下の記事を御覧ください。
最近、ChatGPT に自前のドキュメントを読み込ませる方法を調べて、実際にコードを書いたりしてみたので、その中で得られた知識をまとめた記事になります。 自分用のメモとして、雑多に書いていますので悪しからず。
Fine Turning と Prompt Design の2つの方法があります。
{"prompt": "<prompt text>", "completion": "<ideal generated text>"} ...
以下の文章を読んだ上で、次の質問に答えてください。 # 読み込む文章 koheitakahashi の信念は「選択と集中」である。 # 質問 koheitakahashi の信念は?
> koheitakahashi の信念は「選択と集中」です。
自前のドキュメントを予め 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
最近、既存のフォームオブジェクトのリファクタリングを考えることがありました。 そのときにふと「ネストしたリソースを扱うフォームの実装方法にはどのような選択肢があるのか」という疑問が浮かびました。
この記事では、上記の疑問について調べた内容、それぞれの選択肢について実際に手を動かしてみた所感をまとめています。 同じようなケースに遭遇した方の参考になれば幸いです。
この記事は以下の方々を対象としています。
以下のサンプルコードは 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
フォームの要件は以下としました。
次にサンプルコードを交えた実装方法の説明しますが、元となるコードに対して、各種選択肢による実装を PR で見れるサンプルのリポジトリを作成しましたので必要に応じてご参照ください。
ちなみに元となるコード(main ブランチ)は scaffold し、Shop・Book に必要な関連、バリデーションを追加した状態です。
https://github.com/koheitakahashi/form-sample-app/tree/main
ネストしたリソースを扱うフォームの選択肢には以下のようなものがあります。
accept_nested_attributes_for
を使うこれらについて、サンプルコードとともに所感を述べます。
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 では特定のコンテキストでバリデーションを実行できます。これを使えば、上記のケースには対応できます。
しかし、関連するフォームや、そこで扱うモデルが多くなってきた場合に、必要なコンテキストが多くなることが予想されます。
フォームオブジェクトとは Rails のデザインパターンの一つです。View(ユーザー入力を受け取るフォーム)と Model の間にフォームに関する責務を負った間接層を導入するというパターンです。 このパターンの導入により、「ユーザーの入力値の加工・検証」などのフォーム独自の責務をカプセル化できます。そのため、特定のフォームに関する処理はそのフォームに対応するフォームオブジェクトにまとめることができます。
この実装による、サンプルコードの全体は以下です。 https://github.com/koheitakahashi/form-sample-app/pull/2/files
重要な箇所は app/forms/shop_form.rbです。これが今回導入したフォームオブジェクトです。
このフォームオブジェクトでは、以下をやっています。
フォームに関する処理をまとめる置き場ができたことにより、accept_nested_attributes_for
で挙げた問題点を解消できました。
一方で、accept_nested_attributes_for
に比べてコードの記述量が多いことがデメリットとして挙げられます。関連するモデルの更新だけではなく、フォームオブジェクトに関連するモデルのエラーオブジェクトを格納することも自前で実装する必要があります。
また、フォームオブジェクトの実装は自由度が高いため、複数人で開発する際には実装に差異が出やすいように思いました。例えば、以下のようなところが実装が分かれそうです。
a について、サンプルコードでは ShopForm の shop
というキーワード引数を渡したとき、そして、Book のパラメータに id が含まれていた場合は編集画面用の処理が走るようになっています。
しかし、ShopForm の引数に edit
のようなキーワード引数を用意して、編集画面の処理を走らせたい場合は ShopForm.new(edit: true)
などのように呼び出すように実装することも可能です。
b に関しては、サンプルコードでは、books_attributes=
が呼ばれたタイミングで、必要な Book モデルのインスタンスを生成しています。しかし、ShopForm が initialize されたときに、インスタンスを生成することも可能です。
このように、実装が別れやすいポイントがあるので、複数人で開発をしていた場合にフォームオブジェクト間でインターフェースや、内部処理が分かれることになるということが起こりそうです。内部処理の差異の問題は深刻ではないかも知れません。しかし、インターフェースが異なるなどはフォームオブジェクトを使う際に混乱してしまうため、開発者間で実装のルールを作っておくなどの対応は必要だと思いました。
フォームオブジェクトを自前で書くこともできますが、実装しやすくするための 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 の他には reform があります。trailblazerというフレームワークの一部になります。
この実装による、サンプルコードの全体は以下です。 https://github.com/koheitakahashi/form-sample-app/pull/4/files
ポイントとしては以下です。
property
・collection
などの DSL が生えて、それを使ってフォームオブジェクトを実装するvalidate
などのメソッドが生える#save
を実行することで、ネストしたモデルを一括で作成・更新することができるreform で用意されている DSL を使うことで、少ない記述量でフォームオブジェクトを実装することができます。そのため、少ないコードでフォームオブジェクトを実装できることがメリットでしょう。
一方で、DSL の使い方が ActiveModel と違うため戸惑いがありました。そういう意味では、DSL に慣れる必要があるデことがメリットとして挙げられます。
ここまで、ネストしたリソースを扱うフォームの実装方法の選択肢を挙げて、それらの実装例と所感を見てきました。これらの選択肢は、まとめると以下のような対比構造になります。
accept_nested_attributes_for
かフォームオブジェクトかaccept_nested_attributes_for
かフォームオブジェクトかaccept_nested_attributes_for
accept_nested_attributes_for
に比べてコードの記述量が多いここまで、ネストしたリソースを扱うフォームの実装方法をまとめました。個人的には以下の順序で実装を考えると思います。
accept_nested_attributes_for
で実装できないかを考えるRails で実装されている機能を使えば事足りるのであれば、それを使いたいと考えているからです。
また、フォームオブジェクトを使った場合でも、ある程度のルールがあった方がフォームオブジェクトの読みやすさや改修しやすさがあると思うため、そのルールを持ち込む意味で yaaf を導入を検討したいです。また、reform は色々やってくれる印象があるのですが全容を把握しきれていないです。そのため、全容を把握しやすい yaaf の方が好みでした。
正月三が日も終わり、大分出遅れてしまった感がありますが、2021年のふりかえりと2022年の抱負を書きます。
2020年の振り返り の記事では、2021年の抱負を以下のように書いていました。
Ruby・Rails 力を高めたい
Docker を理解したい
フロントエンド力を高めたい
今作成しているアプリをリリースする
チームでの自分の振る舞いを客観的に見られるようになりたい
これらの目標を達成できたのかをふりかえります。
これは書籍やgemを読むことで、コードの書き方について自分の引き出しを増せたと思います
一方で、設計力や開発速度はまだまだだです。設計力についてはコードリーディングしても、設計パターンをインプットすることができませんでした。開発速度については、コードを書く量がまだまだだと思うので、意識的にコードを書いて早さを上げていきたいです。
総じて、抱負としていた目標は達成できましたが、インプットをアウトプットに繋げる力を磨いていきたいと考えました。
書籍を読み、Docker tutorial に取り組みました。また、個人開発したアプリでも Dockerfile を書きました。そのため、Docker の基本のキは把握できたと思います。抱負としていた目標は達成できました。
抱負としたのは以下です。
個人開発したアプリでは、CSSフレームワークを使わずにデザインを当てたことで、CSSに対する苦手意識がなくなりました。また、Vue3 を導入して動くものが作れたため、こちらも目標は達成できたと思います。
React については、勉強できなかったため目標未達成です。
12月に以下のリリースブログを書いて、アプリをリリースできたため目標達成です。
音楽をシェアしやすくするアプリ SoundLinks をリリースしました
「自分の振る舞いはチームの役に立っているか、チームにとって建設的なものかどうか」を判断して、チームの役に立ちたいという目標でした。
仕事ではチームビルディングを考えて、実践していました。その結果、色々整備できてチーム開発がしやすくなったように思えます。
もちろん私一人の力ではないですが、チームの役に立つことを考えて、アクションを起こせたことから目標は達成できたと思います。
Tomcat・IIS の環境に Rails アプリをデプロイするための手順を学びました。また、IISの仮想ディレクトリを使って認証の設定方法についても学びました。
CSS について苦手意識がありました。そこで、CSS設計完全ガイド を読み、個人開発したサービスでCSSを書いたことで、苦手意識が少なくなりました。
Ruby で綺麗なコードを書くために最近勉強していたこと で書いたことに取り組み、良いコードの引き出しを増やすことを意識しました。
特に、gem のコードリーディング会を同僚と始め、Sinatra・Sorcery・Pundit・Octkit・ActiveSupport を読みました。知らないメソッド、テクニックを知ることができ、メタプログラミングへの理解が深まりました。
一方で、gem の全体像を捉えて設計を理解し、設計パターンの引き出しを増やすということはあまりできませんでした。この点はコードリーディングの仕方を工夫して、設計の引き出しを増やすことを意識していきたいです。
また、開発速度はまだまだなので、素早くコードを書けるようにコードを書く時間を増やしたいです。
Web API の設計や OpenAPI の書き方について学びました。
API設計については、API利用者が達成したいゴールを元にリソースを決めて、レスポンスを構造化するという基本的な設計手順や考え方は理解することができました。
一方で、設計したAPIは利用者が限られるクローズドなものだったため、パブリックなAPIの設計やセキュリティを意識した設計については理解できたとは言えません。
OpenAPI の基本的な書き方については理解できたと思います。一方で、Grape・committee-rails などの gem やツールは使いませんでした。そのため、OpenAPI を Rails で便利に扱うためのツール・gemの使い方などは習得できていません。
上述したように書籍を読んで、Docker tutorial に取り組み、個人開発したアプリでDockerfile を作成したため、基本のキは理解できたと思っています。しかし、浅い理解なので、使いこなせるように深く理解していきたいです。
ECR・ECS・ALB・Route53・RDS などを使用してアプリを開発しました。しかし、体系的に理解できているとはいえず、使いこなせているわけではないため、深く理解していきたいです。
仕事でSQLを書く機会が多かったため、この際SQLをしっかり読み書きできるようにしようと思い意識的に勉強していました。
書籍を読み、手を動かして、それなりに読み書きはできるようになったと思います。一方で、パフォーマンスを意識したSQLを記述することについては、まだ自信がありません。
非技術的なことについて、学んだことが多かったので列挙します。
2022年は「理解したつもりの(あるいは、なんとなく使っている)概念、技術を深く理解する」ことを目標にしたいと思います。
2021年はDocker・AWSなど、扱える技術の幅を広げた1年だったように思います。一方で、それらを使って動くモノはできたけど、それがどんな理屈で動いているのかという根本の仕組みを理解せずになんとなく使っている状態です。それは Docker・AWS に限らず、Ruby・Rails、UNIX・Linux のファイルシステム。REST・オブジェクト指向などの概念なども含みます。
そのような理解したつもりになっている、あるいは、なんとなく使っている技術や概念を深く理解することを目指したいです。そのための取り組みとして、その概念・技術についてのレポートのようなブログ記事を定期的に書くなどの試みを考え中です。
この1年は色々失敗を重ねた1年だったと思います。ただ、社内の方々や懇意にしているエンジニアの方々のおかげで、なんとか生き残ることができました。大変感謝しています。
チームのリーダーを務める、チームビルディングをするなどは、自分にとってはチャレンジでした。その中で大小様々な失敗をしましたが、その分学んだことは多かったです。
また、個人開発したアプリをリリースできたことがとても嬉しかったです。開発期間は長くかかってしまいましたが、0からリリースまでやりきることができて達成感があります。
まだまだできないこと・知らないことばかりです。一方で、できたこと・知ったことも沢山あった1年でした。2022年も引き続きなんとか生き延びていきたいです。
上記のことを学ぶために以下の本を読みました。
koheitakahashi と申します。2019年からFJORD BOOT CAMPに入会し、2020年7月からWEBエンジニアとして受託開発会社に務めています。
今回、FJORD BOOT CAMPに在学していた頃から開発を続けていたWebアプリをリリースしました。
SoundLinksという、音楽をシェアしやすくするWebアプリです。この記事は、そのWebアプリの紹介と開発過程のまとめです。
SoundLinksは、楽曲を共有したい時に、音楽プラットフォームごとに楽曲のリンクを探すのが面倒という問題を解決したい、楽曲を共有したい人向けの音楽プラットフォーム横断検索サービスです。
ユーザーは、楽曲のタイトルを入力すると各音楽プラットフォーム毎の楽曲リンクを取得することができます。
これは、自分でそれぞれの音楽プラットフォーム内を検索してリンクを取得する場合とは違って、一度に複数の音楽プラットフォームのリンクを取得できることが特徴です。
リポジトリのURL: https://github.com/koheitakahashi/sound_links
アプリのURL: https://sound-links.com
Spotify、AppleMusic、KKBOX、YouTubeMusic...など、音楽プラットフォームが多様化しています。そのため、仲間内のSlackやDiscordで「この曲を共有したい」ときに気軽に楽曲を共有できない問題があると思いました。
例えば、自分はSpotifyを使っているが、楽曲を共有したい相手はAppleMusicを使っている場合です。Spotifyの楽曲リンクだけ共有しても、共有された側のAppleMusicユーザーは自分が使っているプラットフォームで共有された曲を聞くことができません。
この問題を回避しようと思ったら、相手が使っているプラットフォーム内で共有したい楽曲を検索しなければいけませんが、これでは気軽に楽曲を共有することができません。
そのような楽曲をシェアしづらい問題を解決するために、SoundLinksをリリースしました。
以下のキャプチャのように、音楽プラットフォームを横断検索できます。
そして、各プラットフォームごとの楽曲リンクをワンクリックでクリップボードにコピーすることができます。
クリップボードにコピーされる内容の具体例は以下です。楽曲名・アーティスト名・各プラットフォームのリンクがコピーされます。
リライト(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はリリースに至るまで、大きく分けて以下の過程がありました。
実際にどのようなことをしたのかを以下に書きます。
エレベーターピッチとは、自分が作りたいサービスを30秒で伝えられるように明確化した文章です。
上記の SoundLinksとは
の段落で書いている文章を作りました。
エレベーターピッチを作ることで、自分がこれから作りたいサービスの本質は何なのかということが明確になりました。それが文章化されていることで、実装したい機能を思いついた時に、実装するかどうかの判断がしやすくなりました。
具体的には「ユーザーログイン機能」「楽曲をお気に入り登録する機能」を思いつきましたが、このサービスは「シェアしやすくすることが目的」だと立ち戻ることができ、これらの機能は実装しない判断ができました。
ペーパープロトタイプとは、アプリの画面・UIを紙などに起こしたものです。実際に以下のようなものを作成しました。
これを作成したおかげで、どのような画面が必要なのか、どのようなUIが適しているのかなどの検討ができました。また、画面のゴールがイメージできたことで実装の見通しが立ち、リリースまでに実装しなければいけない機能・画面が洗い出しやすくなりました。
このアプリが実現できるかどうかを判断するために、以下のことを調査・検討しました。
上記を調べた結果、スクレイピングは避けた方が無難という結論にいきつきました。そして、Spotify・AppleMusic・KKBOX・YouTubeがAPIを公開していたため、それらの4つのサービスに対応することとしました。しかし、YouTubeは後述の理由によりサポートを断念します。
技術選定について、Railsを採用・Herokuにデプロイすることを当初は考えていました。それまでRailsを勉強していたということと、フロントエンド側では複雑な処理をしないことから、JavaScriptのフレームワークを導入する必要はないという判断をしたためです。
しかし、開発を進めていく中でVue.jsを勉強したくなり、途中でVue.jsを導入しました。
開発上のタスクの管理は、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 の書き方については手探りで、これで良いのかという懸念がありますが、動くものができました。
Herokuにデプロイして、身近な方々へ告知して実際に使っていただきました。フィードバックをいただき、実際に自分でも継続して使ってみて、デザインが大きな課題だと分かりました。
当初のデザインは以下のスクショです。
デザイン面の課題を細分化すると以下の2つだと捉えました。
そのため、バージョン1.0.0では上記の課題が解決できるように対応していきました。
バージョン1.0.0の開発でやったことは大きく分けて以下の2点です。
前述したように「何ができるかが分かりづらい」「ユーザーが使ってみようと思えないデザインではない」ことが課題でした。そのため、「何ができるかのメッセージを強調する」「ユーザーが触ってみようと思える最低限のデザインレベルをクリアする」ということを目標にデザインを再度練り直しました。
デザインについては、これまで体系的に勉強したことがありませんでした。そのため、この際簡単な本を読んで基本を抑えようと思い、以下の本を読みました。
ページのデザインを再度練り上げるために使ったツールは Figma です。
また、ファビコン・ロゴ・アイコンの作成は Canva を使いました。
そして、最終的には以下のようなデザインに落ち着きました。
より詳しくデザインの変更を見たい場合は以下をご覧ください。
https://github.com/koheitakahashi/sound_links/releases/tag/v1.0.0
ユーザーの使い勝手を向上させるという目的ではなくて、自分の勉強を目的として以下のことに取り組みました。
上記のどちらも経験が少なかったため、今回ある程度学んでみて「SPAとAWSの触りは知っている」という状態になりたかったという動機です。
合わせて Docker の基本も勉強したいと思い、以下のようなインプットをしました。
そして、Docker を使うなら ECR・ECS の構成が便利だと聞き、その構成を採用しました。こちらは体系的に学んだというわけではありませんが、以下の記事を参考にさせていただきました。その中で、分からないところは公式リファレンスを見て解決していきました。
最終的には以下の構成となりました。
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でお知らせください。
最近、綺麗なコード(自分の中では変更しやすいコードという意味)とは、どのようなコードなのかということが分からなくなっていました。
また、コードレビューの中で「これは自分の好みを押し付けているだけではないだろうか」と感じることがありました。
どこまでが一般的に言われる保守しやすいコードなのか、どこまでが自分の好みなのかという線引きが分からなくなってきていました。
そのため「綺麗なコード(保守しやすいコード)とはどのようなコードなのか」を再確認するために、ここ2ヶ月ほど集中的に勉強していました。
そこで、同じような悩みを持っている方の参考になればと思い、どのようなことを勉強していたのかをまとめました。
そもそも、オブジェクト指向に則って考えられないと適切なクラス設計はできないと考えて『オブジェクト指向設計実践ガイド』を再読しました。
本書は過去3回ほど読んだのですが、その時は理解できていなかった部分が多かったです。
今回は写経しながら読み進めたこともあり、ようやく「依存 = 変更する際の摩擦」であることを実感を伴って理解できました。
それにより「クラスが他のクラスの情報を知りすぎている(つまり依存度合いが大きい)」という状態を検知でき始めてきました。
今までは臭うコードを見ても「どこが良くないのか、どのようにすれば良くなるのか」を言語化できていませんでした。
そこで、リファクタリングについて学びたいと思い『リファクタリング Ruby エディション』を読みました。
本書では「どのようなコードが臭うコードなのか。そのような臭うコードには、このリファクタリングの手段が使える」といった、リファクタリングのレシピが明確に示されており大変勉強になりました。
本書を読んだことで、リファクタリングをより言語化して考えやすくなった感覚があります。
望ましいとされるスタイルルールを知るために「Ruby Style Guide」を読みました。
Rubocop のデフォルトに従っていれば、このスタイルを逸脱することはないですが、そもそもどのようなルールに従っているのかを把握するために読んでみました。
「自分の好みを押し付けているかも知れない」と感じていた部分が Style Guide には明記されていました。
そのため、「自分の好みの書き方なのか、共通認識を得られている書き方なのか」の線引きができ始めてきました。
良いコードの引き出しを増やすために以下の3点に取り組みました。
問題への解決方法がパターン化されているものは、それを学んだ方が早いと思い『Ruby によるデザインパターン』を読みました。
「あのコードは、このパターンを使っていたんだ」ということが発見できて面白かったです。
また、パターンを知ってることで問題に直面した時、解決方法までの道のりをショートカットできそうだと思いました。
メソッドを知っていれば、綺麗に書けたという場面が数えきれないくらいあるので Ruby リファレンスマニュアルの「組み込みライブラリ」まで一通り読みました。
Enumerable#partition
など知らなかったけれども、役立つメソッドを知ることができてよかったです。
Sinatra を読んで、今は Sorcery を読んでいる最中です。
Sinatra の throw catch
を使った処理の抜け方や、Sorcery の Adapter の考え方など勉強になりました。
まだまだ読んだコードの絶対量が足りないので引き続き読んでいきたい所存です。
上記のようなことをインプットしたからといって、すぐ綺麗なコードを書けるわけではないとは思っています。
特に自分の場合はコードを書いた量・読んだ量がまだまだ足りないので、インプットしたことをコードに反映して試行錯誤していくことが必要なのだと感じました。
この記事の内容を大まかにまとめると以下になります。
ActionController::Liveを使っていてstream.close
されたら、cookieの値を変更するという処理を実装しようとしたのですが、cookieの値が変更できないという問題に直面しました。
そもそも、今回はチャンク形式転送エンコーディングという方式でレスポンスを返していたのですが、その仕組みは最初にクライアントにレスポンスを送って、そのレスポンスボディに逐次データを書き込んでいるというものでした。
そのため、レスポンスを最初に送っているため、レスポンスヘッダーが変更不可能な状態になります。よって、データを送りはじめたら、それ以降同一レスポンス内ではcookieは変更できないということでした。
フロントエンドが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回ほどの頻度でcookieを監視する
FileDownloadsController#create
で、ダウンロードが完了したら、cookieにdownloading=0
などの値を入れる
フロントエンドは、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」などで検索しても、中々情報がヒットしなかったので、同じような問題に直面した方の参考になれば嬉しいです。
年明けから従事しているプロジェクトで、初めて開発チームのマネージャー的な役割を少しだけ担うこととなりました。
社の方々に多大なサポートをいただきながら、自分なりに考えてなんとかやってはいるものの、マネージャー的役割が初めての経験ということもあり色々と失敗しました。
この失敗を次に生かすために、失敗したこととそこから学んだことをまとめたいと思います。
ちなみに、以下のような意味合いで言葉を使っています。
マネージャー的役割
プロダクトオーナーから開発しているアプリの方向性を聞き、それをチームが取り組めるタスクに細分化して、それを見積もる。
他のチームとの窓口的な役割を担い、開発チームが開発を進めていけるようにする役割のことを指しています。
見積もり
細分化されたタスクがどれくらいの大きさなのかを考えて可視化する。そして、それを元にスケジュールを引くまでを、今回は見積もりとしています。
今回、新機能開発を行っていますが、その仕様や設計(DB設計など)を決めるということをタスクを洗い出した時に含めていませんでした。
そこまで大きな機能ではなかったため、そこまで設計に重きを置いていなかったというのが見積もりをした時の考えでした。
しかし、実際には思いの他設計タスクに時間がかかってしまい、コーディングに着手するのが遅れたという失敗でした。
上記の「マネージャー的役割」に記述したようなことをやっていたのですが、それにも関わらずに普通に手を動かせるという前提でスケジュールを引いてしまいました。
具体的に説明しますと...
自分が「マネージャー的役割」を担っていない状態で1日に1ポイントを消化できるとした時、今回はそうでないため1日に0.5ポイントくらいしか消化できないと考えるべきでした。
しかし、マネージャー的役割を担うことの負担を全く勘定に入れず、「このタスクは3ポイントだから3日くらいで終わるなぁ」という感じでスケジュールを引いてしまいました。
その結果、私が担当するタスクの完了が想定よりも大分遅れてしまいました。
今回の失敗として、これが1番大きい失敗だと思います。上記の1・2のようなことがあり、当初引いたスケジュールよりも遅れてしまっている状態でした。
これには薄々気づいていたのですが、「自分が頑張ればなんとか巻き返せるのではないか」と考えてしまい、早めに相談するということができませんでした。
その後、当初引いたスケジュールとのギャップが大きくなった状態で、ようやく相談してプロダクトオーナーに色々と調整していただくという運びになってしまいました。
プロダクトオーナーとスケジュールについて話し合う際に、自分が作成した見積もり表は洗い出したタスクが細かくなりすぎていて、中期的なマイルストーンが伝わりづらくなってしまいました。
以下の例のような感じです。 ただ、以下はちょっと細かすぎて実際のタスク表とは少し異なりますが、ニュアンスが伝わればと思います(実際のタスク表を一般化して書くのが難しかった...)。
例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 の開発 | ... | ... |
上記のように中項目を設定して、「その中項目がどのくらいで終わりそうか」「その機能はいつまでにできている必要があるか」「だから、スコープ・納期・人員を調整しよう」というような話ができるような見積もり表を作っておくべきでした。
今回初めてマネージャー的役割を務めさせていただいて、以下のような失敗をして、学びを得ました。
同じ失敗は2度しないように、今回学んだことを次の機会には生かしていきたいです。
気づけば31日ということで、2020年の振り返りをしていきたいと思います。
就職を目指して FJORD BOOT CAMP のカリキュラムを粛々と進めていました。 ここで、JavaScript と Vue.js や Action Cable に出会い、「難しい...」と四苦八苦していました。
そして、有難いことに、都内の受託企業に内定をいただきました。
7月から入社して、そこから9月まで研修をしていただきました。
研修では基本的な Rails のアプリを開発していくという形式で進められ、Rails の基本的な書き方を教えていただき、大変勉強になりました。
研修が9月に終わり、初めての案件にアサインされました。
初めての実務でしたが、社内の方々に色々とご教授いただきながら、なんとかタスクを進めることができました。
基本的なメソッドも覚え始めてきて、少しは自分が考えたように処理が書けるようになってきました。
その上で、今年は Ruby をさらに深く理解したいということで、『メタプログラミング Ruby』を読みました。しかし、内容が難しくてあまり理解したとは言えません。
引き続き Ruby を深く理解していきたいと思っているので、『メタプログラミング Ruby』を読み返したり、『Ruby のしくみ』などの本を読んでいきたいと思っています。
実務で触ることになったのですが、こちらはあまり理解して使いこなせているという状態ではありません。
一応、JRuby + Ruby on Rails を使用しての開発〜デプロイまでの基本的な流れは少し分かってきたつもりです。
しかし、JRuby 独自の機能である Java のメソッドをコールするなどの凝った使い方までは、使いこなせるとは言えない状態です。
そのため、もう少し JRuby を理解していきたいという所存です。
FJORD BOOT CAMP のスクラム開発のプラクティスの中で、Action Cable を学んでおりましたが、最初は「分からないことが分からない」状態でした。
しかし、WebSocket がどのようなものなのか、pub/sub モデルがどのようなものなのかを理解していくことで、ようやく Action Cable を少し使える状態になりました。
また、基本的な Rails 力を向上させたいと思い『Rails ガイド』に一通り目を通したり、『パーフェクト Ruby on Rails』を読んだりしました。
小規模のアプリ開発はなんとか進められるとは思うのですが、まだまだ分からないことだらけです。 来年も引き続き実務を通して、Ruby on Rails 力をつけていきたいという所存です。
1月から勉強を始めました。『JavaScript ふりがなプログラミング』、『初めての JavaScript』を読んだり、簡単なメモアプリを作ったりして勉強を進めていきました。 しかし、まだまだ考えた処理をスラスラと書くことができないという状態です。
JavaScript を書いていて「理解せずになんとなく書いている」という部分が多いと感じています。また、基本的な API が頭に入っていないとも感じるため、来年も引き続き勉強を続けていきたいです。
こちらは、実務で触ることになり10月ころから注力して勉強を進めてきました。 『プログラミング TypeScript』や『実践 TypeScript BFFとNext.js & Nuxt.js の型定義』などを読み、簡単なメモアプリなどを作りながら勉強していました。
基本的な書き方は分かったつもりなのですが、ジェネリクスを用いた書き方などは身に付いているとは言えない状態です。
一方で、初めて静的型付けができる言語に触れたのでとても感動しました。そこまで型のメリットを実感できたというわけではありませんが、コンパイルした時にエラーとなるコードが分かるのは素晴らしいと感じました。
1月頃から勉強しています。『これからはじめる Vue.js 実践入門』を読んだり、実際に手を動かしたりしながら、書き方を覚えていきました。
基本的な書き方は理解できたつもりですが、中規模・大規模のアプリ開発はまだまだという感じです。
加えて、Vue3 を目下勉強中ですが、Vue.js を取り巻くツール(webpack・vue-loader...など)の理解がまだまだというのを痛感しております。
バックエンドも難しいですが、フロントエンドの難しさを日々実感しております。
7月から入社して、実務を通して、先輩方に色々なことを教えていただきました。そのようなことから、自分の考え方が大きく変わりました。
これが大きな考え方の変化でした。
9月から実務に入らせていただきましたが、当初は自分が全くチームに貢献できてないことへの罪悪感がありました。そのような罪悪感からか、チームの中で上手く振る舞うことができていないという感覚がありました。
ただ、そのことを社の先輩方に話させていただき、その中で「自分とチームを切り離して考えてしまっていること」に気づかせていただきました。
その気づきがあり、「自分もチームの一員なのだから、自分にとっての利益はチームにとっての利益である」ということを考えられるようになりました。 そのように考えられるようになったことで、チーム内での振る舞い方が以下のように変わってきたという実感があります。
今のチームでは PR を相互にレビューをする文化が根付いているため、プログラマー歴10年以上の先輩が私にレビュー依頼をくださることが多いです。
最初は「どのようにレビューをすれば良いのか分からない」という状態でした。
分からないところがあっても「これは自分の勉強不足だ」と考えてしまい、コメントしようと思ってもコメントできないということが往々にしてありました。
しかし、上記のように自分をチームの一員として自覚できたことと、「レビューは対話だから、分からないことや疑問に思ったことはどんどんコメントしていくべき」と教わったことによりレビューに対する考え方が変わりました。
「レビューは個人のコードをチームの共有物にするためのもの。そのためには、対話をどんどんするべき」と考えられるようになり、レビューでコメントを残せるようになりました。
自分に割り振られたタスクを進めていく中で、ハマることが往々にしてありました。そのような時は、チームの方々が声をかけてくださり、モブプロを開いて下さります。しかし、その度に「時間を奪ってしまって申し訳ない」と思ってしまいました。
しかし、ある日「罪悪感を持つよりも、人の時間を活用した分、どのようにしたらリターンを大きくできるか」を考える方が、建設的ではないかと気付きました。
加えて、「そもそも人の時間を奪っているのではなく、そのような時間の使い方をしようと判断したのはチームメンバーなのだから、そのメンバーの判断を尊重するようにと捉えてはどうか」ということを教えていただきました。
そのような気づきがあり、罪悪感が薄れて、チームを頼るということができはじめたように思います。
まだまだ Ruby・Rails を理解したとは言えない状態です。読むべきだけども読んでない Ruby・Rails 関連の本もたくさんあります。
そのため、読むべき本を読んでいくという形で勉強を進めていきたいと思っています。
正直なところ、Docker は全く分かりません。そのため、Docker に触れて、少しは使えるような状態になることを目指して勉強を進めていきたいです。
Vue3系を少しは扱えるようにすることと、React の勉強をしたいと思っています。
また、CSS 力に自信がなく、既存の CSS を少し修正するだけでも時間がかかってしまうという状態です。そのため、ある程度 CSS を自力で書けるという状態を目指して CSS 力をつけていきたいと考えております。
最近、「自分のアプリをリリースしました」と言えるようになりたいと思い、密かにアプリを作成しています。
後はフロントエンド関係のタスクが残っているので、2ヶ月以内にはリリースできそうではあるのですが、デザインが苦手なため「どうなることやら...」という気持ちです。
9月からの実務では、目の前のタスクをこなすことに精一杯で、自分の振る舞いを客観視できていなかったように思います。
そのため、来年は自分の振る舞いを客観視して、「自分の振る舞いはチームの役に立っているか、チームにとって建設的なものかどうか」という観点で自分の振る舞いを考えられるようになりたいと思いました。
以上、この1年の振り返りをしてみました。振り返りをしてみると、色々な経験が蘇ってきて非常に濃い経験をさせてもらいました。
特に入社してからは色々なことを勉強させてもらって、そのおかげで、入社前の出来事が遥か遠い昔のことのように感じられました。
このように言語化することで自分の学んできたことや取り組んできたことが、経験として自分の中で消化されたような感覚もあります。
また、来年の抱負がたくさんあって「全部に手をつけられるのか...」と今から心配ですが、なんとかやっていけるように優先順位を考えて取り組んでいきたいところです。
2020年7月からプログラマーとして働き始め、それから研修期間の後に9月から案件に入りました。 7月からの5ヶ月弱で、先輩のコードを拝見したり、多くのレビューをいただく中で多くのことを学びました。
今回は、学んだことの中から Ruby・Rails を書く時のちょっとしたテクニックを少し紹介したいと思います。
不必要な処理をさせたくない時に、インスタンス変数と 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_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
コードを少し綺麗に書くときのテクニックとして、お役に立てれば幸いです。