「Ruby勉強会~各社の取り組みや課題から学ぶ会~」のイベントレポートをお届けします。今回は4社のエンジニアが集まり各社のRubyに関する事例や知見を発表しましたのでその様子をレポートいたします。画面全体がエラーになることを防ぐ」というテーマで発表させていただきます。本題に入る前に弊社Asobicaについて少しご紹介させていただければと思います。弊社ではコミュニティ運営からロイヤル顧客のデータ分析をワンストップで行い、LTV最大化と新規顧客獲得の効率化を実現するプラットフォーム「coorum(コーラム)」を提供しています。多くの企業に活用いただいており、プロダクトは3つ展開しています。insight」はコミュニティで得られた顧客データと企業が持つ顧客データを統合し分析を行うことが可能です。research」という新しいプロダクトもローンチいたしました。このプロダクトはお客様がなぜその商品を買ったのかを深堀りし、顧客理解を促進させることができる、アンケート機能を持つプロダクトとなっています。ここからは我々がErrorBoundaryの導入に至った経緯についてご紹介します。導入に至った経緯nilチェックが必要なロジックに対して、nilチェックがない状態のソースコードが本番反映されてしまうインシデントが発生してしまいました。※nilチェックのロジックが、本番用branchに適用されていない状態で臨時リリースを当ててしまいました。(hotfix的な扱い)急いでリリースしたいという事情で、feature2の内容をcherry‐pickしてmasterにあてた結果、nilチェックが入っていない状態で反映されてしまい、画面にエラーが出てしまったのです。本当はプルリクのレビューで気づければ良かったのですが、私も含めて誰も気づけませんでした。実際に導入したらどうなるか、コードベースでご紹介します。boundaryというメソッドを用意して、yieldでブロックを受け取ったらそれを評価して、例外が発生したらそれをキャッチしてログに出す、という感じです。boundaryで囲ってあげると、その中でエラーが発生してもその上下にあるものは正常に表示されます。・せっかく実装したErrorBoundyが形骸化しないように、周知していくすべて当たり前のことですが、とても重要なことです。原因をきちんとアウトプットするために文章化したり、同じことを繰り返さないように話し合ったりすることが必要ですね。最後に、弊社Asobicaでは、採用や公式noteでの発信も行っておりますので、そちらもよろしければご覧ください。廣江さん、よろしくお願いします。廣江:今回は「手書きRBSの運用事例」というタイトルで発表させていただきます。みなさんはRBSを書いていますか?おそらく多くの方が書いていないかと思います。RBSを実装やコメントから生成する手段はたくさんありますが、自分で書いている人は少ないのではないかなと思います。今回の発表は、みなさんがRBSを書いてみようと思うきっかけになることを目標としています。私のチームでは現在RBSを手書きで書いていますが、なぜ手書きを選択したのかという理由をまずお話します。※ここでの手書きとは、完全な手書きもありますが、手直しというニュアンスも含めています。前提として、型定義の自動生成については肯定的です。そもそも「型を熱心に書きたくてRubyを選んだ」という人はあまりいないと思います。多くの方が、できるだけ型定義を書かずに、型の恩恵を受けたいと思っているはずです。私もそう思っています。正直に言って、自動生成での型定義の精度はまだまだ低いです...。精度を求めるのであれば、YARDから型生成する方法がありますが、これはRBSの機能の一部でしか利用できず、表現力にも限界があります。Rubyで型のある開発を行うためには、現状においてはRBSを手書きするしかないと考えています。RBSを手書きするということは、当然、そこにコストがかかります。私はできるだけRBSを手書きせずに型の恩恵だけを受けたいと常々思っていますが、そこにコストをかけてでも正確な型を手に入れることによって、組織としてコスト以上のメリットが得られるのではないか...という仮説のもと、今回はRBSを手書きするという選択をしました。一度はやってみたいという興味もありました。正確な型定義では「正確な型定義とは何か」についてご説明させていただきます。下画像に、サンプルでJSONを用意しました。ポケモン3匹分のデータが入っています。これを読み込んでオブジェクト化したあとに、要素の1つをランダムに取得するメソッドがあったとします。そこからTypeProfを使った型を自動生成したとしても、返り値がuntypedなメソッド定義にしかなりません。この状態でコードを書き進めてもuntypedが続きます。そこで、手直しすると下画像のような感じになります。私たちはJSONファイルの中身を知っているので、name、type、height、weightの4つのキーで構成されていることを知っています。それぞれのキーに対応する値がすべての要素で同じであることも知っています。ポケモンのタイプが18種類ということも分かっています。これらをRBSにはめて手直ししたのが下画像です。この状態でコードを書き進めると、得られる値が明確になります。これらの情報が常に分かった状態で開発ができるのは嬉しいことですよね。先ほどの例はJSONファイルを読み込むという極端な例でしたが、実際のRailsの開発においては、paramsの型定義や外部APIを叩いたときのレスポンスボディの型定義などで、似たような問題が起きると思います。これらは多くの場合複雑な構造をしていて、そのまま扱うには厄介です。型定義が正確に行われていれば、簡単に扱いやすくなると思います。(注)paramsの正確な型を書くには、現状のActionController::ParametersのRBSがまだ不十分という問題を抱えています。型検査ではSteepを使っています。Steepでの型検査ではSteepfileを使って設定を書きます。実際に開発で利用している設定は以下になります。いうオープンソースにプルリクを出しやすくする設定なので、今回は無視してもらっても大丈夫です。すると、以下のようにシンプルなデフォルト設定になります。Steepのチェック項目はデフォルトではずらっと以下のような感じになります。この中で、デフォルトの型検査で引っかかると失敗扱いになる項目が以下になります。本当はこれ以上に検査する項目を増やしたいのですが、現状では難しいです。Timeeではこれ以外にメインのシステムがあり、そちらでもRBSを使っています。そこでは冒頭でお話したように、実装やYARDなどから型の自動生成を行っています。メインシステムでは出来るだけコストをかけずに型の恩恵を受けることが目的となっているので、型検査が主目的ではありません。自動生成した型定義をそのままSteepで型検査をすると、多くの検査で引っかかってしまうため、検査は諦めているというのが現状です。現状諦めている検査項目は以下の13項目ありました。デフォルト設定から先ほどの13項目を除外して、残った検査項目は以下になります。これではあまり有意義な型検査は行われていないような気がします。以下の手書きしている場合と比べると、やはり手書きの方がより多くの型を検査できているので、型検査をしたい場合はRBSを手書きしたほうがよさそうです。赤枠のNoMethodの型検査ができていることがチームからも喜ばれています。対するNoMethodエラーがその1つかと思っておりますが、普段の実装でライブラリのMethodでnilを取り入れるようなものに対して、それに気づけるのは大きなメリットだと思います。RBSを手書きすることでより型安全な開発ができて、エラーの可能性にいち早く気づける状態になりました。困りごと当然良いことだけではなく、RBSを手書きすることでコストがかかっていますし、それ以外にも困りごとがいくつか発生します。ライブラリのRBS定義はまだ不十分Steepの型検査は検査項目が多いため、RBSに少しでも不足があると型検査に失敗します。RBS定義が不足しているというのはアプリケーションのRBSもそうですが、外部ライブラリのRBSにも言えます。以下の例では、アクティブレコードのRBS定義が不足しているため、型検査でエラーになっています。これを解消する方法としてgem_rbs_collectionにPRを出す自前のRBS定義を追加するsteep:ignoreコメントを使うという方法があります。私たちのチームでは、gem_rbs_collectionにプルリクを出すことを推奨していますし、そのための手順もドキュメント化されています。まとめ現在までの開発でRBSを手書きすることで、次のような恩恵を受けられました。CodeのSteep拡張機能が超便利になる結論として、RBSを手書きすることに対して支払ったコスト分の恩恵は受けられていると感じています。チームメンバー全員が設計に気を遣うようになったということが喜ばしいですね。今日の発表で、RBSを手書きすることに少しでも興味を持っていただけたら嬉しいです!発表は以上です。8年以上RailsのWebサービスをメンテしてわかったこと西尾:私からは「8年以上RailsのWebサービスをメンテしてわかったこと」について発表いたします。初めに、弊社オシロの紹介を少しさせていただければと思います。オシロはコミュニティづくりのため、誰でも簡単に使えるプラットフォームシステムとコミュニティプロデューサーの伴走サービスを提供しています。コミュニティ専用オウンドプラットフォーム「OSIRO」は、コミュニティに必要なものがオールインワンで提供されるようなプロダクトになっています。当初はアーティストやクリエイター向けのコミュニティをつくるプラットフォームとして開発されたものですが、現在では企業のブランドと顧客をつなげるコミュニティやスキルアップのための相互交流の場など、コミュニティをつくる目的やニーズは多岐にわたるようになりました。言い換えれば、プロダクト開発初期と比べて現在求められる機能は格段に増えているため、OSIROの8年強の開発史は、複雑性をいかに解消していくのか、メンテナンス性をいかに向上させていくかの戦いであったともいえます。それでは、早速本題に入ります。目次サービスクラスについてDataUpdateSccriptについてモデルのコールバックについてまとめサービスクラスについてまず結論から言うと、サービスクラスはいらないと思っています。以下はサービスクラスの例です。サービスクラスが流行ったのかを考えると、以下の背景があったと思います。一方で、サービスクラスにも問題点があり、例示すると以下のようなものがあります。・サービスクラスになんでもかんでも入れがち・必要な抽象化などをしないでとりあえずいれがち・Contorollerにあるコードをそのままコピペしたりしていた・結果的にクラスが分かれているだけで、何もよくなっていない・案件によってサービスクラスのインターフェースが統一されない・.executeなのか.performなのかクラスメソッドetc...以上から、良くないというのがわかりました。Modelとして実装すればいいのでは?いろいろ考えた結果、Modelとして実装すればいいのではないかと思いました。Railsはそういうフレームワークなのでは?・DHHが在籍する37Signalsもそうやっている乱立するModelへの対策RubyのNamespaceを有効活用していく。いわゆる「技術駆動パッケージング」をやらない。部分的に「ドメイン駆動パッケージング」のようなものをやる。・サービスクラスやフォームクラスもこのドメイン・機能で切ったディレクトリにModelとして入れていく。以下の様に、ディレクトリを分けていけばよいと思います。素のRailsだけでもできることはある素のRails機能だけでもわかりやすくする工夫はできます。・ActiveModelなどを活用するとより幅が広がります。それでも厳しい場合には、サービスクラスやフォームクラスを他のディレクトリに作ることを検討すれば良いと思います。弊社のプロダクトでもサービスクラスは残っていますが、最終的にはなくすことを検討しています。DataUpdateSccriptについて続いて、DataUpdateScriptの紹介です。・Forem:(https://github.com/forem/forem)この仕組みだけがOSSで公開されてるわけではないが使いたくて作ってもらったDataUpdateScriptの特徴DataUpdateScriptの特徴として以下が挙げられます。わかりやすいDataUpdateScriptのフローDataUpdateScript自体は非同期でスクリプトを実行して、データ更新などが終了したら、その結果をデータベースに記録します。DataUpdateScript自体にエラーを出す仕組みはありません。DataUpdateScriptの良かったところ本番環境でもタスクの実行漏れがなくなり、結果も記録されるように開発環境ではタスクがRails起動時に実行されるように・タスク実行漏れがなくなってHappy実行したら結果がわかるので使ってないことがわかりやすい・1回実行したら使わないスクリプトの削除が可能モデルのコールバックについて最後に、モデルのコールバックについて紹介します。簡単なPubSubをつくる・ControllerやPOROでPublishする・自作PubSub/ControllerやPOROでPublishする・処理を実行するPubSubのメリット・デメリットコールバックに依存しないことでモデル間の依存関係が減り、疎結合になる。簡単にUnsubscribeすることもできるし、暗黙的に処理が行われることはない。PubSub、Observerパターンの概念を理解する必要がある。調子に乗ると複雑になる。まとめ小〜中規模なプロダクトのうちは素のRubyやRailsの機能で実現できないかを考える。DataUpdateScriptの導入は個人的には一押しです。コールバックはつらいので早めの対策をしましょう。RubyもRailsも全然終わってないし、まだまだ面白いのでぜひ皆さんも楽しんで取り組んでみてください。最後に、OSIROではRubyエンジニアを中心に積極採用中ですので、興味ある方はぜひご応募ください!発表は以上です。栗山:本日は、「食べログのデットコード解析基盤の裏側」というタイトルで発表したいと思います。食べログではアーキテクチャ刷新レベルのリファクタリングが進行しているみなさんの中にも「食べログ」のサービスを利用いただいたことがある方は多いのではないかと思います。Railsにリプレイスされましたがそれ以降はアーキテクチャ刷新レベルのリファクタリングなしに継続運用されています。どのくらい長い期間かというと、この間にWindowsがVistaから11まで4回代替わりしているくらいです。Railsの標準的なモノリシックアーキテクチャは当時の食べログにはマッチしていて、食べログは短期間で大きく成長できました。コードベースが大きくなり、サービスも複雑になった現在の食べログにはマッチしないため、大規模なリファクタリング計画が進行中です。リファクタリング前にデッドコードを削減して認知負荷を下げたい密結合で技術的負債が溜まったアプリケーションというのは散らかった部屋のようなものです。散らかった部屋を見て「よし、部屋を分けましょう!」なんていきなり言いだす人はあまりいません。はいらないものを捨てたり、ホコリや汚れをキレイに掃除したり、収納グッズを買って用途ごとに置く場所を決めたりするなどして、部屋を整理しようと考えるのが自然です。そこでまず、邪魔なものを消してからリファクタリングをしたいと考えました。動的な開発言語なので、デッドコードを静的解析だけで判別するのは難しいです。仕組みがあり「requireされていない」=「不要」という判断ができないそこで今回紹介するデッドコード解析基盤を構築しました。デッドコード解析基盤の概要デッドコード解析基盤は、動的カバレッジ、静的解析、そして両者のマッチングによる長期的なメソッド単位のカバレッジ解析を3つのパートに分けて行うシステムです。動的カバレッジを計測し、次に静的解析を行い、最終的に両者を組み合わせて詳細な解析を行います。以下でtest.rbをサンプルとしてデッドコード解析基盤の動作を説明していきます。稼働サーバーで動的な実行カバレッジを計測して日次集計カバレッジを取得します。動的な実行カバレッジまず稼働サーバで動的な実行カバレッジを計測して日次集計カバレッジを取得します。Rubyの標準ライブラリCovergeを使って実際の動作環境で、どのコード断片が実行されたかを行単位で計測しています。食べログでは実行性能への影響が最も小さいoneshot_linesカバレッジモードを採用しています。https://docs.ruby-lang.org/ja/latest/library/coverage.htmlカバレッジをBigQueryに転送して日次でマージ集計1回のリクエスト/レスポンスごとにファイル名と実行された行番号、リビジョンなどの付帯情報をカバレッジログに出力してfluent-bitでBigQueryに転送しています。ハイライトしているexecuted_linesが実行されたコードの行番号です。このログは断片的で大量なので日次でマージします。動的な実行カバレッジの産物サンプルこうして集計したデータは以下のようなイメージです。この段階では行番号ベースなのでファイルを変更するとずれてしまい、長いスパンで計測するのは難しいです。頻繁に改修がある重要なモデルの中に、滅多に使わない社内管理機能から利用されるコードがあると辛いです。静的なコード解析そこで行番号をメソッドに対応付けて、行番号より変化しづらいメソッド名で実行実績を追跡できるようにします。このパートではリポジトリのコードを静的に解析してメソッド解析情報を取得します。コードをASTに変換するまずカバレッジ計測されたリビジョンのコードをリポジトリからチェックアウトして、RubyVM::AbstractSyntaxTree(標準ライブラリ。SyntaxTree(抽象構文木)に変換します。行番号を名前空間、クラス、メソッドに対応付けるメソッドの開始・終了行のデータが取得できました。動的な実行カバレッジと静的なメソッド解析のマッチング最後に2種のデータをマッチングしてメソッド単位のカバレッジを取得します。メソッド単位の実行カバレッジが取得できました!プロダクションの実行実績がメソッド名で長期間追跡できるようになった食べログでは取得したメソッド単位の実行カバレッジはBigQueryに投入してあるので、検索することが可能となっています。不要なメソッド一覧を出力し、お掃除のためのターゲットリストを取得する、ということも可能です。自身と構文解析仕様が完全に同一なので、解析結果が100%信頼できます。