ruby-lspを少し便利にするruby-lsp-addons

この記事は

私は、株式会社エニグモでチームのマネージャをしている後藤です。 マネージャ業務の傍ら開発作業を行うこともあります。

この記事はEnigmo Advent Calendar 2024の10日目の記事です。

この記事では多くのRubyを使っている人が、使っているであろうruby-lspを少し便利にする ruby-lsp-addons について紹介しながら、VSCode でどのように活用できるのかについて紹介したいと思います。

ruby-lspとは

ruby-lsp とは、Ruby言語向けに Microsoft が定義している、Language Server Protocolを実装したサーバになります。

Language Server Protocol(以下 LSP) は、プログラム中に定義されたクラス名、関数名、変数名などを取得できるAPIとデータ型を定義したものになります。プログラム中のデータを取得するAPIを定義しておくことで、エディタから対象言語に対応した LSP サーバーを通して 関数定義情報を表示、関数/変数の定義箇所へのジャンプ、関数名/変数名を使った入力補間ができるようになります。

LSP の登場以前は universal-ctags + GNU Global などを用いて同様の機能を実現することが多かったように思いますが、VSCode の登場と合わせて LSP の提案と普及が進みました。その結果、多くのエディタで LSP がサポートされるようになり、最近は補間や関数ジャンプのための機能が LSP に集約されつつあるように思います。

Visual Studio Code(VSCode) の場合は、 ruby-lsp プラグインVSCode に追加することで rubyソース編集次に ruby-lsp を使った関数ジャンプや入力補間が使えるようになります。

ruby-lsp-addons

ruby-lsp ですが、当然ながら ruby の文法しかサポートしていません。ただ、Ruby on Rails の場合はモデルの has_many、blongs_to、validates などなど多数の DSL がありこれらもOUTLINEなどに表示して欲しいところですが、その機能を ruby-lsp に実装するのも違っている気がします。

そんな問題を解決しようとしているのが、 ruby-lsp-addons 機構になります。

公式のドキュメントは Add-ons | Ruby LSP にあります。ただ、公式ドキュメントにもあるように実験的な取り組みのようで、 ruby-lsp のメジャーバージョンが変わると、インターフェイスが変わって addons が動かなくなることもありますし、将来a addons のサポートが終わってしまう可能性があることを心にとめて置いてください。

それでは、

について紹介していきたいと思います。

ruby-lsp-rails

こちらの add-on を使えるようにするには、ruby-lsp-rails gem を追加します。

名前からわかる通り、rails 固有の機能サポートを追加するadd-onになります。 今回は、gitlabhq/gitlabhqのソースの一部を表示しながら、 公式ドキュメントからいくつかの機能を抜粋して紹介したいと思います。

modelのDSLサポート

# frozen_string_literal: true

module Achievements
  class Achievement < ApplicationRecord
    include Avatarable
    include StripAttribute

    belongs_to :namespace, inverse_of: :achievements, optional: false

    has_many :user_achievements, inverse_of: :achievement
    has_many :users, through: :user_achievements, inverse_of: :achievements

    strip_attributes! :name, :description

    validates :name,
      presence: true,
      length: { maximum: 255 },
      uniqueness: { case_sensitive: false, scope: [:namespace_id] }
    validates :description, length: { maximum: 1024 }

    def uploads_sharding_key
      { namespace_id: namespace_id }
    end
  end
end

のようなコードがあると、 VSCodeOUTLINE には以下のように Rails 固有の DSL で定義された項目も表示されます。

コントローラーメソッドからviewへのジャンプ

コントローラのメソッド画面にviewを開くリンクが追加されます。

上の絵の赤枠で囲ったリンクが表示されるようになります。 この、Jump to view をクリックすることで view が表示されます。 地味に、便利です。

その他の機能

公式ドキュメントを読むと、モデルの belongs_to のアソシエーション先にジャンプできる機能、コントローラーのメソッド定義箇所からルーターの定義にジャンプできる機能などがあることになっていますが、上手く機能していないようです。この辺は実験的な取り組みということを温かく見守ることしましょう。

ruby-lsp-rspec

次は、ruby-lsp-rspec です。 こちらも add-on を使えるようにするには、ruby-lsp-rspec gem を追加します。

こちらも名前から想像できる通り、 rspec 向けの機能拡張を行う add-on になります。

以下のような specファイルを開くと、

# frozen_string_literal: true

require "spec_helper"

RSpec.describe Diffs::StatsComponent, type: :component do
  include RepoHelpers

  subject(:component) do
    described_class.new(diff_files: diff_files)
  end

  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:repository) { project.repository }
  let_it_be(:commit) { project.commit(sample_commit.id) }
  let_it_be(:diffs) { commit.raw_diffs }
  let_it_be(:diff) { diffs.first }
  let_it_be(:diff_refs) { commit.diff_refs }
  let_it_be(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
  let_it_be(:diff_files) { [diff_file] }

  describe "rendered component" do
    subject { page }

    let(:element) { page.find(".js-diff-stats-dropdown") }

    before do
      render_inline component
    end
...

VSCodeOUTLINE に以下のように、 examples のテキストが表示されるため、 spec ファイルの流れを追うのも、修正をするのも楽になります。

また、以下のようにspecファイル上に Run | Run In Terminal | Debug リンクが追加されます。

このRunなどをクリックすることで、rspecのテストを実行できます。また、これらのテスト機構が、 VSCode の テスト機能に統合されているため、 以下のような VSCode 標準の、 Test: Run Test at Curosr などの機能が利用できるようになります。

また、テスト結果についてもGUIで表示られるようになります。

テスト結果がエラーになっているのは、DBなどを構築せずテストだけ実行したためで、gitlab のバグではありません。

使いたいんだけど、プロジェクトの Gemfiles に組み込めないんだけど

VSCoderuby-lsp ですが、インストール時の設定では、プロジェクトに bundle install されているものを使う設定になっていると思います。そのため、プロジェクトの他のgemとの依存関係の問題で使いたくても最新の ruby-lsp が使えない、なんて問題に出会う事があると思います。

そのような場合は、 ruby-lsp などの ruby 開発サポート系ツールだけをインストールしたリポジトリを準備し、bundler でインストールした bin ディレクトリへ PATH を通しておいて、 VSCode 上の設定を変更することで、プロジェクトは独立した形で、 ruby-lsp を使うことができます。

手順は以下のようなGemfileを準備し

# frozen_string_literal: true

source 'http://rubygems.org'


gem 'ruby-lsp'
gem 'ruby-lsp-rails'
gem 'ruby-lsp-rspec'

対象ディレクトリで、

bundle config path vendor
bundle install

しておき、VSCodesettings.json にいかの項目を追加します。

"rubyLsp.bundleGemfile": "${インストールディレクトリ}/Gemfile"

私の場合は、asdf を使ってrubyをいれているのもあってrubocop、erblint などをひとまとめにしたチェック系ツールをまとめたリポジトリを作り、homebrewとasdf用の環境セットアップをするラッパースクリプトを通して、bundle exec するシェルスクリプトから各種ツールを起動するリポジトリを作って使っています。

また、PCが変わっても VSCode の設定が変わらないよう上記リポジトリ

/opt/ruby_tool

へcloneしています。

参考のために、 ruby_toolへのリンクも貼っておきます。頻繁にバージョンを上げたり、ツールを入れ替えたりしているので、ご利用される際は、forkしてお使いください。

最後に

最後までお読みくださりありがとうございました。

明日の記事の担当は 森田 さんです。お楽しみに。

また、株式会社エニグモでは、ツールやエディタのカスタマイズを愛する人エンジニアもそうでないエンジニアも広く募集しております。興味のある方は、以下の求人一覧をご覧ください。


株式会社エニグモ すべての求人一覧

hrmos.co

データアナリストがネガティブなことを伝えなければならない時に気を付けていること

こんにちは、データアナリストの井原です。

この記事はEnigmo Advent Calendar 2024の9日目の記事です。

私は、普段データアナリストとしてエニグモで働いています。データからビジネスの意思決定を行うための示唆を出し、関係者に正しく伝えることが主要な業務です。

最近、チームの勉強会でも話題になったのですが、データアナリストは伝え方って大事だよねという話がありました。その場ではさらっと話が進んだのですが、確かに、データアナリストはいつもポジティブな結果ばかりを伝えるわけではありません。企画者が苦労をしてリリースにこぎつけた施策で、結果も期待されていてという状況で、数字がよくなかった、または、懸念点が上がってきたということも多いです。

この記事では個人のこれまでの経験を振り返って、ネガティブなことを伝えなければいけない時に、どのようなことに気をつけたらよいかを考えてみたいと思います。

この1年を振り返ってみても、結果を伝えにくいと思う案件はいくつかありました。「事前検証ではそれなりに効果が出そうであると(自分が)押した案件が、蓋を開けてみると効果はかなり微量だった。」「これまで重点的に企画が行われてきた施策について、利益性が低そうだと伝えなければいけなかった。」などなど。

そんな自分の経験を通して、どんなことに気を付けるとよさそうか、5つ洗い出してみました。挙げてみた要素はあくまでも個人の意見ではありますし、自分も常にうまくやれているわけではないのですが、自戒もこめて書いていきたいと思います。

1.言いやすい関係性を作っておく

いきなり、そもそもといった内容にはなりますが、元々の関係性はかなり重要な要素だと思います。 これは、言う側だけでなく言われる側の身を想像しても、それまで関係性の薄い第三者から言われるよりは、自身の取り組みを知ってくれている人の方が受け入れやすくなると考えられます。

そのためには、可能な限り、企画の段階でデータアナリストも企画に入っていけるようにするとよいと思います。また、分析の時点から入らざるを得ないとしても、こまめに質問や進捗を伝えるなどして、コミュニケーションを増やしておくといざという時には伝えやすくなります。

巨大な組織だと難しいところもあるかもしれませんが、エニグモは比較的少人数の組織なので、その辺りはやりやすい気はしています。

2.言葉遣いに気を付ける

当たり前ではあるのですが、伝える時の言葉遣いは気を付ける必要があります。特に意識をしていないと、さらっと否定的な言葉が(そこまで否定的だと意識せず)つい出てしまう場合があります。

例えば、「意味がない」とか「ダメですね」のような全否定の言葉は使わないように自分は気をつけます。「この数字だとちょっと厳しいかもしれません」や「想定と違うかもしれませんが、どうですか?」のような、相手と会話していく姿勢を出すだけでも建設的に話せるのではないかと思います。

前項とは反対になりますが、言いやすい関係を作りすぎてしまうと、ぽろっと否定的な言葉も出やすくなってしまう(気軽に話せるので言葉を意識しなくなりやすい)側面もあるので、この辺りはバランスを考えてうまく調整出来るとよいのではないかと思います。

3.最初に「言いづらい結果なのですが」と言ってしまう

これはテクニック的な話ではありますが、会議の最初に言ってしまうと、言う方も聞く方も受け入れる準備が出来るので楽になるのではないかと思います。あるいはタイミングがあれば、「今分析しているのですが、厳しい結果が出るかもしれません」と雑談的に話しておくのもよいでしょう。

根本的にはもう少し他の項目の方が大事ではないかとも思いますが、相手に結果を受け入れてもらうということも、アナリストの重要な仕事の一つです。こういった地味なテクニックを意識しておいて損はないと思います。

4.相手に労いと尊敬の気持ちを持つ

言葉遣いなどに繋がる話ですが、施策を進めているメンバーはその企画に対してかけた労力や思いがあります。その労力や思いに対しては、労いと尊敬の気持ちを持つことが大事です。

相手の企画に対しての思いを知る過程で関係性も作れると思いますし、言葉遣いも下手な言葉遣いはしにくくなると思います。例えば、相手が大切に思っていることに対して、「意味ないよ」などとは、ある程度の良識があれば言いにくいですよね。

5.それでも遠慮はしない、自分の分析結果には自信を持つ

相手を思いやることは大事だと思いますが、一方で、データアナリストとして伝えるべきことは伝えなければなりません。 そのため、最終的には自分の分析結果に対して自信を持つことも大事です。

伝えづらい分析結果が出てきた時、多くの分析者は分析内容を振り返ったり、よさそうな点を見つけようとデータをさらに詳細に区切って細かい分析を行ったりなど、よくあるのではないかなと思っています。しかし、持てる知識をフル活用して分析を行った結果なのであれば、下した結論がネガティブな内容であっても、それはしっかりと伝える必要があります。

伝え方自体は上で書いたように気を付ける必要がありますが、結論はしっかりと相手に伝わるように、準備できればよいと思っています。

おわりに

自分の一年間を振り返りつつ、ネガティブなことを伝えなければならない時に気を付けていること(きたこと)を洗い出してみました。 世の中には、データから新しい知見を発見して、それを活用して成功しました!というポジティブな話が多いと思いますが、現実のデータ分析では、新しい発見やポジティブな結果が確認できることばかりではありません。むしろ、特に目新しくない結果、ネガティブな結果が出てくる方が多いと思われます。

データアナリストという立ち位置は、時に第三者的な視点での意見が求められる職種ですので、データの解釈は冷静に客観的に行わなければなりません。しかし、それを伝えるところまで責任を持つという点では、合理性や客観性以外の人情的なコミュニケーションも必要になってくると思います。

少しでも、今回の話がデータを取り扱う方々の参考になったら幸いです。お読みいただきありがとうございました。

明日の記事の担当はエンジニアの後藤さんです。お楽しみに。


株式会社エニグモ すべての求人一覧

hrmos.co

KamalでRailsアプリケーションを迅速にデプロイする方法

こんにちは!Webアプリケーションエンジニアのレミーです!

この記事はEnigmo Advent Calendar 2024の 8日目の記事です。

Ruby on Rails 8が新しくリリースされ、Kamalという迅速かつ便利なデプロイツールが統合されました。私はこれまでRuby on RailsアプリケーションのデプロイにCapistranoを使用していましたが、Kamalを試してみると、その便利さと簡単さに魅了されました。

この記事では、Kamalを使用してRuby on Rails 8アプリケーションをAWS EC2サーバーにデプロイする手順を詳しく説明します。

Kamalとは?

Kamalは37Signalsのチームによって開発された新しいデプロイメントツールです。このツールを使用すると、デプロイメントプロセスを1つのファイルで定義でき、複雑な手順を簡素化できます。ほとんどのアプリケーションで考慮する必要がない複雑な部分を省略することが可能です。Kamalは、DockerコンテナやTraefikなどのソフトウェアを組み合わせて動作します。セットアップ段階から包括的なソリューションを提供し、Railsアプリケーションを最短時間で本番環境にリリースできます。

Kamalはどのように動作するのか?

Kamalは、サーバー上でDockerコンテナ内にWebアプリケーションを実行し、Traefikを組み合わせてネットワークトラフィックを処理します。新しいバージョンをデプロイする際、Kamalは以下の手順を実行します:

  1. 新しいDockerイメージをビルドする。
  2. 新しいイメージからコンテナを起動する。
  3. 新しいコンテナが正常に動作しているかを検証する。
  4. Traefikを更新して、トラフィックを新しいコンテナにルーティングする。
  5. 古いコンテナを停止する。

特に注目すべき点は、ダウンタイムなしのデプロイメントを実現し、blue/greenデプロイメントをサポートしていることです。

Ruby on Railsアプリケーションのサーバーの準備

ここでは、AWS EC2でサーバーを準備します。

EC2インスタンスの作成

Ubuntu Server 24.04を選択します。

KeyペアはSSHとサーバーへのアクセスに使用します。

セキュリティグループでは、以下のポートを開放してください:

SSH権限の設定

まず、ローカルの公開鍵をコピーします。

cat ~/.ssh/id_rsa.pub

インスタンスを起動したら、先ほど作成したKeyペアを使って、ubuntuユーザーでサーバーにSSH接続します。IPアドレスは新しく作成したインスタンスのものを使用します。

ssh ubuntu@18.182.197.19 -i /path/to/key.pem

次に、ローカルでコピーした公開鍵をサーバーの ~/.ssh/authorized_keys ファイルに貼り付けます。

ssh-rsa AAAABEAAAADAQABAAABgQ...AAAAB3Nzac2EAAAADAQABA Yuto MacBook Pro

これでサーバーの準備は完了です。以降は以下のコマンドでサーバーにSSH接続できます。

ssh ubuntu@18.182.197.19

Ruby on Rails 8アプリケーションの作成

まず、kamaltest という名前でRuby on Rails 8アプリケーションを作成します。

rails new kamaltest

プロジェクトディレクトリに移動します。

cd kamaltest

このアプリケーションは、タイトルと内容を持つ簡単なブログになります。そのため、scaffoldを使用して構築します。

rails g scaffold article title content:text

データベースを作成します。

rake db:migrate

routes.rb ファイルでホームページを設定します。

  root "articles#index"

アプリケーションを起動します。

bin/dev

その後、http://localhost:3000 にアクセスすると、アプリケーションが起動していることが確認できます。

deploy.ymlファイルでKamalを設定する

Ruby on Rails 8ではKamalが既に統合されています。それ以前のプロジェクトにKamalを追加する場合は、Gemfileに kamal を追加し、以下のコマンドを実行します。

kamal init

このコマンドを実行すると、Kamalがアプリケーションをデプロイするための設定を含む config/deploy.yml ファイルが生成されます。

Kamalの基本設定

デプロイのための設定は以下のようになります。

image: yutoyasunaga/kamaltest

servers:
  web:
    - 18.182.197.19 # server IP

proxy:
  ssl: true
  host: kamaltest.sampleapp.net

registry:
  username: yutoyasunaga
  password:
    - KAMAL_REGISTRY_PASSWORD

ssh:
  user: ubuntu
  • image: Docker Hub上に保存されるDockerイメージの場所
  • servers
    • web: サーバーのIPv4アドレス
  • proxy
    • ssl: true に設定するとSSLが自動的に設定されます
    • host: 使用するドメイン
  • registry
    • username: Dockerアカウントのユーザー名
    • password: Dockerアカウントのログインパスワード。セキュリティのため、KAMAL_REGISTRY_PASSWORD シークレットを通じて取得します。
  • ssh
    • user: サーバーにSSH接続する際のユーザー

Kamalのシークレット

機密情報は .kamal/secrets ファイルに配置します。以下はその例です。

# 環境変数からレジストリパスワードを取得
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD

シークレットは環境変数から取得します。例えば、Dockerのパスワードが hogehoge の場合、以下のように設定します。

export KAMAL_REGISTRY_PASSWORD='hogehoge'

以下のコマンドで値を確認できます。

echo $KAMAL_REGISTRY_PASSWORD

Kamalでデプロイを開始

config/deploy.yml の設定が完了したら、以下のコマンドを使用して初回のデプロイを開始します。

kamal setup

このコマンドは以下の処理を行います:

  1. SSHキーを使ってサーバーに接続。
  2. サーバーにDockerがインストールされていない場合は、get.docker.com を使ってDockerをインストール(SSH経由でrootアクセスが必要)。
  3. レジストリにローカルおよびリモートでログイン。
  4. アプリケーションのルートにある標準的なDockerfileを使用してDockerイメージをビルド。
  5. イメージをレジストリにプッシュ。
  6. レジストリからイメージをサーバーにプル。
  7. kamal-proxy がポート80および443でトラフィックを受け入れることを確認。
  8. 現在のGitバージョンのハッシュに一致するアプリケーションバージョンで新しいコンテナを起動。
  9. 新しいコンテナが GET /up リクエストに対して200 OKを返す場合、kamal-proxy にトラフィックを新しいコンテナにルーティングさせる。
  10. 前バージョンのアプリケーションを実行している古いコンテナを停止。
  11. 使用されていないイメージや停止したコンテナを削除して、サーバーの容量を確保。

デプロイが成功すると、次のような結果が表示されます。

Finished all in 70.0 seconds

初回のデプロイは kamal setup を使用しますが、2回目以降のデプロイでは kamal deployのコマンドを使用します。

permission denied エラーの解決方法

初回のデプロイで以下のようなDockerに関連するエラーが発生した場合:

Releasing the deploy lock...
  Finished all in 47.9 seconds
  ERROR (SSHKit::Command::Failed): Exception while executing on host 54.250.243.158: docker exit status: 1
docker stdout: Nothing written
docker stderr: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.47/images/create?fromImage=yutoyasunaga%2Fkamaltest&tag=4985d03bae739286203ce1185efdd4b2c71f90a9": dial unix /var/run/docker.sock: connect: permission denied

次の手順で解決できます。

  1. サーバーにSSH接続します。
ssh ubuntu@18.182.197.19
  1. 以下のコマンドを実行して、現在のユーザーをDockerグループに追加します。
sudo usermod -aG docker $USER && newgrp docker
  1. Dockerが正常に動作するか確認します。
docker ps

以下のような出力が表示されれば成功です。

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

デプロイ後の結果を確認

まず、 https://hub.docker.com にアクセスして、イメージがプッシュされていることを確認します。

Route53を使用してドメインを管理している場合、以下のようにホストゾーン内でレコードを作成し、ドメインをサーバーのIPに向けます。

設定が正しい場合、https://kamaltest.sampleapp.net にアクセスすると、アプリケーションの画面が表示されます。

このように、1分ほどでRuby on Railsアプリケーションをサーバーにデプロイし、ドメインSSLの設定まで完了しました。便利ですね! 🎉

よく使われるKamalコマンド

aliases:
  console: app exec --interactive --reuse "bin/rails console"
  shell: app exec --interactive --reuse "bash"
  logs: app logs -f
  dbc: app exec --interactive --reuse "bin/rails dbconsole"

デフォルトの deploy.yml ファイルでは、以下の4つの便利なコマンドが定義されています。

  • kamal console: Railsコンソールにアクセス
  • kamal shell: サーバー上のコンテナにアクセス
  • kamal logs: サーバーログを確認
  • kamal dbc: データベースコンソールにアクセス

Kamalでのアセットのデプロイ

RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile

Dockerfile にはすでにアセットをプリコンパイルするコマンドが定義されています。そのため、デプロイ時にアセットも自動的に処理されます。

Kamalでの環境変数(Environment Variable)

開発環境の環境変数の設定

開発環境では、dotenv gem を使用します。 公式リポジトリ: https://github.com/bkeepers/dotenv

Gemfileにdotenvを追加 以下のコードを Gemfile に記載します。

group :development, :test do
  gem 'dotenv'
end

環境変数を管理する .env ファイルを作成し、例は以下のように記載します。

TEST_ENV_CLEAR=env_clear_local
TEST_ENV_SECRET=env_secret_local

環境変数が正しくロードされているかをRailsコンソールで確認します。

ENV.select { |key, _| key.start_with?("TEST_ENV") }
=> {"TEST_ENV_CLEAR"=>"env_clear_local", "TEST_ENV_SECRET"=>"env_secret_local"}

本番環境の環境変数の設定

現時点では以下の方法を使用していますが、より良い方法が見つかれば変更する予定です。

本番環境用の環境変数を管理する .env.production ファイルを作成します。

TEST_ENV_SECRET=env_secret_prod

deploy.yml ファイルに環境変数を設定します。

env:
  secret:
    - TEST_ENV_SECRET
  clear:
    TEST_ENV_CLEAR: env_clear_prod
  • clear: deploy.yml ファイルに直接記載される環境変数
  • secret: 機密情報を含む環境変数で、.kamal/secrets ファイルから読み込まれます。

.kamal/secrets ファイルで環境変数を以下のように設定します。

TEST_ENV_SECRET=$(cat .env.production | grep TEST_ENV_SECRET | cut -d '=' -f 2)

上記の設定は、.env.production ファイルから情報を抽出します。 例えば、.env.production に以下の行が含まれている場合:

TEST_ENV_SECRET=env_secret_prod

コマンド cut -d '=' -f 2 によって env_secret_prod が抽出されます。

デプロイ後、Railsコンソールにアクセスして環境変数を確認できます。

kamal console
ENV.select { |key, _| key.start_with?("TEST_ENV") }
=> {"TEST_ENV_SECRET"=>"env_secret_prod", "TEST_ENV_CLEAR"=>"env_clear_prod"}

Kamalは、Ruby on Railsアプリケーションのデプロイを大幅に簡素化し、効率化する強力なツールです。ぜひKamalを使ったデプロイに挑戦してみてください!


株式会社エニグモ すべての求人一覧

hrmos.co

BUYMAのユーザーインタビュー『ユーザーの声からまるわかりBUYMA公式スタートガイドを作った話』

こんにちは!UIUXデザイナーの和田です。
こちらはEnigmo Advent Calendar 2024 の7日目の記事です。

この記事では、定期的に実施しているBUYMAのユーザーインタビューについてご紹介します。
また、ユーザーインタビューで得た気付きをもとに新設した【まるわかりBUYMA公式スタートガイド】についても後述します。

www.buyma.com

1. ユーザーの声を直に聞くメリット

エニグモでは、社内のデータアナリストがBUMYAの利用データを分析して日々サービス改善に努めています。
定量的な分析に加えて、的確なUX改善・向上を進めていくためには、ユーザーさまの声を直にお聞きして、なかなか数値からは解明できない【ユーザーの行動の背景・インサイトをより深く理解することが重要だと考えています。

BUYMAでは、ユーザーさまの声を直にお聞きするために、 定期的にオンライン形式(Zoom)のユーザーインタビューを実施しています。 社内のUIUXデザイナーとデータアナリストがインタビューアーをすることで、ユーザーさまからいただいたご意見やヒントをもとに「すぐに詳細な分析をおこない、施策化して改善を進められる」ようにしています。

2. BUYMAのユーザーインタビュー

BUYMAでは、ユーザーさまを対象に実施しているユーザーアンケートの中でインタビューにご協力いただける方を募っています。 毎回とても多くの方に、インタビューへのご協力に前向きな回答をいただいており、全国男女問わず幅広い年代の方にご参加いただいています!
ユーザーインタビューの内容についてご紹介します。

🌟 2-1.ユーザーさまご自身について

ユーザーさまご自身についてのお話を広くお聞きしています。 主に、『普段の生活スタイル』や『どのような「価値観・嗜好」をお持ちなのか』といった点や、BUYMAとの接点になりそうな「ファッション情報にふれる機会」「お買い物傾向」に関するお話などを伺っています。

🌟 2-2.サービス利用に関するヒアリング

ユーザーさまが普段BUYMAを『どのように利用して』『どのように感じていらっしゃるか』、実際の利用エピソードをもとにプラスとマイナスの両側面から率直な話をお聞きできるようにしています。

BUYMAの利用をはじめたきっかけ
BUYMAを使う理由(BUYMAの価値) ←→ BUYMAを使わなくなった理由(離脱理由)
BUYMAの気に入っているところ ←→ いまいちだと感じるところ、残念なところ
BUYMAを安心して利用できるポイント ←→ BUYMAに不安を感じるポイント
BUYMAでのよかった・満足できたエピソード ←→ BUYMAでのマイナスエピソード

BUYMAにおいて価値を感じていただいているポイント
→ より多くの方にその価値を広げていけるように
BUYMAにおける課題・ペイン
→ 課題の背景を理解して適切な改善につなげていけるように

上記のように、プラス・マイナスそれぞれの気付きをもとに、UX改善・向上にむけた検討を進めています。

🌟 2-3.利用を見せていただく(行動観察)

実際にアプリやサイトを利用している様子を画面共有いただいて、どのようにBUYMAをご利用いただいているか見せていただきつつ、お話を伺っています。
例えば、ユーザーさまの普段の利用(検索や商品閲覧・比較検討など)に即したシナリオをベースに、どのような操作をどのような手順でされているのか、利用の中の些細なつまずきやペインがどういったところにあるか、などを操作いただきながらヒアリングを進めています。

🌟 2-4.サービスに関する認知・理解に関するヒアリング

BUYMAサービス・機能における「認知(知っていたか)」や「理解(理解できていたか)」についてもお聞きしています。 『どこで認知・理解したか』『どのように解釈していたか』なども率直にお聞きすることで、【対象サービス・機能の露出や導線が十分であるか】など、改善のヒントをいただけるようにしています。

🌟 2-5.検討中の施策に関するコンセプトヒアリング

検討中の施策に関するプロトタイプをZoomミーティングの中で投影させていただきながら、率直な印象やご意見をお伺いしています。
施策を進める前に、初見の印象やニーズにマッチしているかなどを確認することができ、よりよいかたちに細かな調整をかけることができます。

BUYMAのUXリサーチについては、昨年の記事でもご紹介しておりますので、ぜひこちらもご覧ください。

tech.enigmo.co.jp

3. ユーザーの声から「気付くこと」「改めてわかること」がある

ユーザーにとっての「ペイン・課題」「BUYMAの価値」など、ユーザーの声から「気付くことができること」「改めてわかること・理解が深まること」がたくさんあります。

🌟ユーザーにとっての「ペイン・課題」に関する気付き

BUYMA利用におけるつまずきがどんなタイミングで起こっているのか、「BUYMA利用におけるペイン・課題」がどのくらいUXに影響を及ぼしているか、など、ユーザーインタビューを通じて具体的に理解を深めることができます。また、サービス提供側として想定していた捉え方と、ユーザーさまの捉え方が異なっていたり、思った以上に浸透していない情報についての気付きを得ることもあります。

🌟ユーザーにとっての「BUYMAの価値」に関する気付き

一方で、ユーザーさまにとって「BUYMAの価値」がどのように認識されていて、ユーザーさまのまわり(ご家族やご友人)に浸透しているか、などの気付きを得ることもできます。

以下では、ユーザーインタビューを通じて、改めてBUYMAの価値だと再認識することができた機能についてご紹介します。

世界中からさがす|リクエスト一覧

BUYMAには『リクエスト』という機能があります。
世界中にいるBUYMAの出品者に、ほしい商品を探してもらうことができる機能ですが、 インタビューでは、この機能を利用して購入経験のある方は「リクエスト機能の満足度がとても高い」ことがわかりました。

「どこを探しても見つからなかった国内完売商品がほしくて、
リクエストを利用してみたら見つかって購入できて本当に嬉しかった!」
「ずっと探していたバッグがなかなか見つからず、中古は状態がいいものがなく諦めかけていたけれど、リクエストを使って購入することができて非常に満足。」

また、インタビューの対象者の中でリクエスト機能を使ったことがある方は、複数回リクエストを利用しており、 「探したいと思うアイテムをリクエストして世界中から探してもらう」体験を気に入ってくださっている傾向がありました。

今後は、「探したいアイテムがあるけれどリクエスト機能を知らない・使ったことがない方」に、 このプラスの体験をより広げていけるように検討を進めていければと考えています。

4. ユーザーの声からまるわかりBUYMA公式スタートガイドを作った話

ユーザーインタビューが終わったあとは、まとめ作業を行って主要な気付き(BUYMAの価値、ペイン・課題、仮説検証の結果など)を社内に共有しています。
そこから、UX改善・向上につなげる施策化をおこなっています。
今回は、ユーザーインタビューで得た気付きからコンテンツ作成を行った事例を紹介します。

まるわかりBUYMA公式スタートガイド

4-1.まるわかりBUYMA公式スタートガイド新設の経緯

インタビューの中では、BUYMAに関する分からないことや不安なことについてもお伺いしています。 その中で、以下のようなご質問・ご意見をいただくことがありました。

BUYMAの運営会社って日本企業なんですか?」
BUYMAの商品ってなんで安いんですか?」
BUYMAでほしい商品があるけれど購入して大丈夫なのか不安があります。」

このようなご質問・ご意見をいただいたことをきっかけに『BUYMAを安心してご利用できると判断するための情報の重要性』について改めて認識することができました。
その後、社内で検討を進め「BUYMAとはどういったサービスなのか」「安心して利用できるのか」などの疑問や不安を解消いただくために、【まるわかりBUYMA公式スタートガイド】というページを新設することになりました。ぜひこちらもご覧ください!

www.buyma.com

インタビューの中でBUYMAに関する分からなさや不安があるとお話されていたユーザーさまから、 いろいろと会話させていただいたインタビューの最後に 「思い切ってインタビューに参加してみてよかった。これまでより安心して買い物ができそう。」 とおっしゃっていただいたことがありました。

ユーザーインタビューを通して、ユーザーさま1人1人に向き合うことの大切さを実感するとともに、 UX改善・向上に向けて一歩ずつ前進するためのエネルギーをいただけていると感じています!

4-2. BUYMAユーザーの声をよりたくさんの人に広げたい

ユーザーインタビューの中で、積極的にご利用いただいているユーザーさまは、「まわりのご家族やご友人もBUYMAをご利用いただいている方が多い」という傾向が見えてきました。
一方で、BUYMAでのお買い物を躊躇される理由として、「まわりにBUYMAを利用している人がいない」といった方が多くいらっしゃいました。

そこで、『どんな人がBUYMAを利用しているかイメージがつくことで親近感がわくようにするとよいのではないか』という仮説から、 BUYMAをご利用いただいているユーザーさまのお声をBUYMAのコンテンツとして掲載させていただくことにしました。

🌟 BUYMAユーザーが感じるBUYMAのよさ

BUYMAの利用者が「どんなところにBUYMAの魅力を感じてくださっているのか?」より多くの人に知っていただくために、BUYMAを使う理由についてユーザーさまのご意見をピックアップしています。

BUYMAユーザーに聞く|なぜBUYMAを使ってるの?

🌟 BUYMAユーザーが安心して利用できる理由

BUYMAを安心して利用してくださっている利用者が「どうしてBUYMAを安心して利用できると判断してくださったのか?」より多くの人に知っていただくために、BUYMAを安心についてお聞きした際のご回答をピックアップしています。

BUYMAユーザーに聞く|安心して利用できてる?

www.buyma.com

BUYMAについてより深く知っていただき、安心してご利用を続けていただけるように、今後もBUYMAのことをもっと深くわかりやすい情報を発信していければと考えています。


明日の記事の担当は・・・
BUYMAサイトの開発と運用保守をされているレミーさん】です!
お楽しみに!


株式会社エニグモ すべての求人一覧

hrmos.co

コスト削減と精度維持を両立!類似画像検索システムの内製化成功事例(システム編)

こんにちは、データサイエンティストの髙橋です。業務では企画/分析/機械学習モデル作成/プロダクション向けの実装/効果検証を一貫して行っています。

この記事では類似画像検索システムの内製化にあたり、システム面での課題をどのように解決したかについて紹介します。内製化の背景や機械学習部分などについては以前作成した記事で説明しており、この記事はその続きとなります。

この記事は Enigmo Advent Calendar 2024 の 5 日目の記事です。

内製化の目的・事業インパク

内製化の目的は、弊社が運営する CtoC EC サイト BUYMA において、商品名寄せで利用している他社製の類似画像検索システムの精度を保ちながらコストを削減することでした。既に内製化後のシステムに移行しており、同等の精度を維持しつつ年間数百万円規模(約8〜9割)のコスト削減を見込んでいます。

また、名寄せ以外にも類似画像検索システムを利用している施策があり、その移行も進めることでさらなるコスト削減の可能性があります。さらに、他社製の類似画像検索システムではコストが高く断念していた EC サイト上での画像起点のレコメンドや、画像による類似商品検索機能なども検討出来るようになりました。

より詳細な説明については前編である機械学習編の記事を参照ください。

類似画像検索のシステム化における課題

類似画像検索のシステム化における課題として、非常に大規模なデータを現実的な時間で処理する必要がありました。機械学習編の記事に記載した類似画像検索の各ステップに対して、毎月処理するデータ量は以下の通りでした。

ステップ 毎月処理するデータ量
商品画像をダウンロードし、物体存在箇所をセグメンテーションして切り抜き 約 200 万画像
切り抜いた画像の Embedding 計算 約 1000 ∼ 2000 万画像
切り抜いた画像・Embedding ファイルを GCS (Google Cloud Storage) にアップロード 約 2000 ∼ 4000 万ファイル
Embedding による画像同士の類似度計算 数十〜数千億の組合せ
画像ハッシュによる画像同士の類似度計算 数十万の組合せ

単純に単一のサーバー上で各ステップを実行する方法では、全体で1ヶ月以上かかる見込みであり、毎月定期的に処理を行うのは現実的ではありませんでした。

また、データ量以外の課題として、弊社では機械学習基盤として Vertex AI Pipelines を利用していましたが、今回のシステムはその基盤上に実装できない課題がありました。先述した規模のデータを処理するにはそれに特化した複数の GCP (Google Cloud Platform) サービスを組み合わせる必要がある一方で、現行の基盤は VM インスタンス上で Python コードを実行する用途を想定していたためです。そのため、複数の GCP サービスを連携して毎月スケジュール実行するアーキテクチャを作る必要性がありました。

システム化における各課題の解決策

前述した課題をどのように解決したかを説明します。ただし、前提として類似画像検索の各ステップのうち、以下は1つのかたまりとして処理することにしました。

  • 商品画像をダウンロードし、物体存在箇所をセグメンテーションして切り抜き
  • 切り抜いた画像の Embedding 計算
  • 切り抜いた画像・Embedding ファイルを GCS にアップロード

理由は、セグメンテーションおよび Embedding 計算の両方に GPU が必要であったため、また GCS とのデータのやり取りに時間がかかることから、「商品画像ダウンロード > セグメンテーション > Embedding 計算 > アップロード」の一連のステップを同一メモリ上で行いたかったためです。

「商品画像ダウンロード > セグメンテーション > Embedding 計算 > アップロード」の高速化

Dataflow という並列分散処理が行える GCP サービスを利用することで高速化を実現しました。Dataflow とは、GCP が提供するマネージドのバッチ・ストリーミングデータ処理サービスであり、並列分散処理により大量のデータを効率的に処理することが可能です。

今回 Dataflow を選択するにあたり、Ray on Vertex AIも候補に上がりました。いずれも並列分散処理は実現できそうでした。Ray はその構文がネイティブの Python に近く、既存の Python コードに大きな変更を加える必要がなさそうだったため、実装コストが低そうに見えました。一方で、Dataflow は Apache Beam の構文を理解し覚える必要があり一定の学習コストが伴いそうでしが、社内で利用実績があり困ったときに既存の資産を参考に出来そうであったため Dataflow を選択しました。
実際に、 Dataflow の実装で困ったときに他プロジェクトでのソースコードを参考にして解決することができ、この選択は正解であったと考えています。また、 Apache Beam の構文もそれほど複雑ではなく、初期の学習コストは多少ありましたが慣れれば実装に大きく手間取ることはなかったです。

約 200 万枚の画像に対して、並列分散処理無しでは約 30 日かかる見込みでしたが、Dataflow により約 56 時間に短縮することができました。 Dataflow の設定は

  • ワーカーマシンタイプ: n1-highmem-4 (vCPU 4 、メモリ 26 GB)
  • ワーカー数: 4
  • GPUnvidia-tesla-t4

としました。ハイメモリのマシンタイプを利用した理由は、メモリ枯渇でジョブが途中で停止してしまうことがあったためです。

Embedding 同士の類似度計算処理の高速化

Vertex AI Vector Search というサービスを利用しました。Vertex AI Vector Search とは、GCP が提供するマネージドサービスで、膨大な数の Embedding 同士の類似度計算を高速に行うことができます。

Vertex AI Vector Search を利用することで約 1700億個の Embedding の組合せを約 8 時間で処理することができました。単一のサーバー上で処理した場合の処理時間は見積もっていませんが、おそらくこの規模のデータをこの速度で処理するシステムを作るにはそれなりの工数がかかったと思います。

Vertex AI Vector Search の設定としては、Algorithm type 、 Shard size はそれぞれデフォルト値である tree-AH 、 Medium で速度やコストに問題がなかったためそのままとしました。 Approximate neighbors count は値を変えて実験したところ検索速度に大きな違いが生じました。具体的には、 約 1 千万件の 768 次元のベクトルに対して類似度計算を 100 回行い、処理速度の統計量を算出したところ以下の通りでした。

num_neighbors mean (sec) std (sec) min (sec) max (sec)
10 0.031 0.013 0.021 0.076
100 0.189 0.034 0.146 0.266
1000 0.218 0.034 0.17 0.297
10000 0.519 0.149 0.39 1.109

上記実験結果より可能な限り低い値にすることで処理時間が大きく短縮できそうでした。今回の用途では 1 画像に対して同一と検知される見込み画像数は数件程度であったため、それをカバーできる 10 としました。

画像ハッシュ計算処理の高速化

このステップでも Dataflow を利用しました。

約 27 万件の画像の組合せに対して、並列分散処理無しでは約 54 時間かかる見込みでしたが、Dataflow により約 45 分に短縮することができました。 Dataflow の設定は

  • ワーカーマシンタイプ: n1-standard-1 (vCPU 1 、メモリ 3.75 GB)
  • ワーカー数: 120

としました。Dataflow を採用したことで、ワーカー数を自由に変更することができ、120 ワーカーで並列分散処理を容易に実現できました。

複数 GCP サービスを連携させてスケジュール実行するアーキテクチャ作成

Cloud Composer という Airflow のマネージドサービスを利用しました。Cloud Composer は、GCP が提供するワークフローオーケストレーションサービスで、複数のクラウドサービスを連携してスケジュール実行することができます。

今回 Cloud Composer を選択するにあたり Cloud Workflows も候補に上がりましたが、 Airflow の社内での利用実績が豊富であったため Cloud Composer を採用しました。こちらでも困ったときに他プロジェクトの既存のソースコードが参考になる場面が多く、また移行前の類似画像検索システムの一部で Airflow を利用しており、既存システムの理解がスムーズに出来たメリットもありました。

以下のようなフローで Dataflow や Vertex AI Vector Search などを連携し、類似画像検索の各ステップを実行するシステムを Cloud Composer で実装しました。

Cloud Composer 処理フロー

開発生産性や保守性を向上させるために、 Dataflow では各ステップごとに Docker Image と Flex Template を、 Cloud Batch でも Docker Image を利用しました。これにより各ステップを独立に開発・テスト・デプロイ出来るようにしました。この部分の詳細については、別途機会があれば記事として執筆する予定です。

実装時の工夫

ここでは、システム実装時の工夫を2つ紹介します。同じようなシステム構成の実装をされる方の参考になれば幸いです。

Cloud Composer からの GCP サービス呼び出し方法

Cloud Composer から 各 GCP サービスを呼び出すに当たり、Dataflow には Airflow に専用のクラスが存在しましたが、 Vertex AI Vector Search には存在しませんでした。そこで、 GCPREST API例:インデックス作成 API)を呼び出すことでリソースの作成や作成状況のポーリングを行うクラスをカスタムで実装しました。具体的には Airflow の Sensor クラスを利用して以下のようなイメージで実装しました(あくまでソースコードのイメージとして簡易化したものであり、実際のものとは異なります)。

from typing import Any, Dict

from airflow import models
from airflow.decorators import task
from airflow.sensors.base import BaseSensorOperator


@task(task_id="create_resource")
def create_resource(args1: int, args2: str):
    """GCP のリソースを作成する(Vertex AI Vector Search のインデックスなど)。

    Args:
        args1 (int): 引数1。
        args2 (str): 引数2。
    """

    # call_create_resource_api 関数は別途実装。内部で GCP の REST API を呼び出す。
    response = call_create_resource_api(args1, args2)
    return response


class ResourceSensor(BaseSensorOperator):
    """GCP のリソース操作の状況をポーリングする。

    Attributes:
        poke_task_id (str): ポーリング対象のタスクID。
        args1 (int): 引数1。
        args2 (str): 引数2。
    """

    def __init__(self, *, poke_task_id: str, args1: int, args2: str, **kwargs):
        """GCP のリソース操作の状況をポーリングするクラスを初期化。

        Args:
            poke_task_id (str): ポーリング対象のタスクID。
            args1 (int): 引数1。
            args2 (str): 引数2。
        """
        super().__init__(**kwargs)
        self.poke_task_id = poke_task_id
        self.args1 = args1
        self.args2 = args2

    def poke(self, context: Dict[str, Any]) -> bool:
        """
        リソース操作の状況をポーリングして、完了したかどうかチェックする。

        Args:
            context (Dict[str, Any]):
                Airflow のコンテキスト。どのような値が格納されているかは以下参照。
                https://airflow.apache.org/docs/apache-airflow/stable/templates-ref.html

        Returns:
            bool: リソース操作が完了したかどうか。
        """
        # 作成したリソース情報(IDなど)を取得。
        response = context["ti"].xcom_pull(task_ids=self.poke_task_id)

        # call_get_resource_status_api 関数は別途実装。内部で GCP の REST API を呼び出す。
        status = call_get_resource_status_api(response, args1=self.args1, args2=self.args2)

        # status = {"done": True} のような値と仮定
        return status["done"]


with models.DAG(
    "dag_name",
    schedule_interval="0 0 * * mon",
) as dag:
    create_resource_task = create_resource(args1, args2)
    wait_create_resource_task = ResourceSensor(
        task_id="wait_create_resource",
        poke_interval=60 * 10,
        timeout=3600 * 3,
        poke_task_id="create_resource",
        args1=args1,
        args2=args2,
    )

    create_resource_task >> wait_create_resource_task

ここで、 Airflow のコンテキストを利用してポーリング時に必要なリソース情報(ID など)を Sensor クラスで取得するようにしました。

Vertex AI Vector Search での類似度計算時にリトライ

Vertex AI Vector Search で類似度計算を行う際に、Exponential backoff アルゴリズムによるリトライ処理を入れるようにしました。理由は、実際に運用していると Vertex AI Vector Search の呼び出し時に google.api_core.exceptions.InternalServerError: 500 Failed to call Service Control Check.google.api_core.exceptions.Unknown: None Stream removed というエラーが稀に発生することがあったためです。Vertex AI Vector Search による類似度計算は1回の定期実行あたり約 8 時間かかるため、途中で停止するとリトライにかかる時間が大きいという問題がありました。

Exponential backoff アルゴリズムによるリトライは backoff ライブラリを利用し、リトライ対象のエラーは google.api_core.exceptions.ServerError としました。ソースコードを見ると今回発生した google.api_core.exceptions.InternalServerErrorgoogle.api_core.exceptions.Unknown がこのクラスの子クラスであり、他の子クラスも GCP 側のサーバーエラー起因のものであるためリトライする方が良いと判断したためです。

以下が実際のソースコードのイメージです。(簡易化したものであり、実際のものとは異なります)。

from typing import List

import backoff
from google.api_core.exceptions import ServerError
from google.cloud.aiplatform import MatchingEngineIndexEndpoint
from google.cloud.aiplatform.matching_engine.matching_engine_index_endpoint import (
    MatchNeighbor,
)


# エラーが解消するまでの待ち時間が不明なため、リトライの最大時間は1時間半とした。
# 類似度計算は1回の定期実行あたり約 8 時間かかるため、1時間半の待ち時間は許容する。
@backoff.on_exception(backoff.expo, ServerError, max_time=5400)
def find_neighbors(
    index_endpoint: MatchingEngineIndexEndpoint,
    deployed_index_id: str,
    queries: List[List[float]],
    num_neighbors: int,
) -> List[List[MatchNeighbor]]:
    """デプロイされたインデックスで与えられた Embedding に対して近似最近傍探索を実行。

    Args:
        index_endpoint (MatchingEngineIndexEndpoint): Vertex AI Vector Search のインデックスエンドポイントクラス。
        deployed_index_id (str): インデックスのデプロイID。
        queries: List[List[float]]: Embedding のリスト。
        num_neighbors (int): 近似最近傍探索で取得する Embedding 数。

    Returns:
        List[List[MatchNeighbor]]: 類似度トップ `num_neighbors` の id と類似度のリスト。
    """
    return index_endpoint.find_neighbors(
        deployed_index_id=deployed_index_id, queries=queries, num_neighbors=num_neighbors
    )

実際にこのリトライ処理を入れたことで、その後の運用時に同じエラーが発生することがありましたが、無事にリトライされることで途中で停止せずに実行完了していました。

まとめ

本記事では、類似画像検索システムの内製化におけるシステム面での課題と、それをどのように解決したかについて説明しました。大規模なデータを毎月現実的な時間で処理しなければならない課題を、複数の GCP サービスを組み合わせて高速化することで解決しました。また、複数の GCP サービスを連携してスケジュール実行する必要がある課題を、 Cloud Composer を利用して実装することで解決しました。

今後は他の機能への応用を検討しています。本記事が類似のシステムを構築されている方々の参考になれば幸いです。

明日の記事の担当は UI/UXチーム の飯沼さんです。お楽しみに。

追記:2025/01/21 第48回 MLOps 勉強会で登壇しました!

第48回 MLOps 勉強会にてこのブログの内容を発表いたしました。勉強会ページには配信アーカイブも掲載されておりますため、ご興味ある方はご覧いただけますと幸いです。

speakerdeck.com


エニグモでは一緒にデータを利用したサービス価値向上を実現していただけるデータサイエンティストを募集中です!世界178ヶ国に1100万人超の会員を有し、出品数は630万品を超えるBUYMA には膨大なデータが蓄積されており、データ活用の余地はまだまだあります。ご興味ある方はカジュアル面談からでもお話できますと幸いです。

他の職種も絶賛募集中です!

株式会社エニグモ すべての求人一覧

hrmos.co

効率もモチベーションも爆あがり!Neovimという最強エディターについて

こんにちは!フロントエンドエンジニアの張です!

この記事はEnigmo Advent Calendar 2024の3日目の記事です。

エンジニアは日頃のタスクを対応するためにいろんなツール駆使していますが、絶対になくてはいけないツールと言いますと、やはりテキストエディター一択だと、私は思っています。

そのテキストエディターですが、実は何種類もあって、側から見ると、どれも同じように見えるかもしれませんが、毎日何時間もそれで仕事をするエンジニアにとって、自分に合うエディターを選ぶことは実は仕事の効率とモチベーションに大いに関わっています。

本記事では、ここ約一年半、私が毎日使っている最強エディターNeovimについて、ご紹介します。

Neovim とは

もしかしたらNeovimを知らない方も多いかもしれませんが、それがエンジニアなら誰しも一回は使ったことがある、名高い Vim エディターをリファクターしたものです。 そのため、NeovimVim の特性と独自の機能を備える、モダンなコマンドラインエディターになっています。

特性

  • Vim から受け継いだもの
    • ベースが Vim になっている
      • Vimfork して開発したもの
      • Vim motion
      • Vim command
    • 反応速度が早い
    • ミニマル
  • 独自の特性
    • Lua でコンフィグ可能
    • LSPのネイティブサポート

強み

Neovim について簡単に説明しましたが、それは一体ほかの主流のエディターと比べて、何の強みがあるでしょう。

Vim Motion

Vim Motionというのは Vim、あるいはNeovim上で使える、カーソルを移動させたり、テキストを編集したり、できるショットカットです。 Motionを使いこなせると、マウスなしでテキストを高速で編集、ナビゲートすることが可能になります。 それは日々大量なテキスト、あるいはコードを処理するエンジニアにとって、とても貴重なスキルです。

実例

個人的に最も使っているNormal Mode下の基礎Motionが以下となります

Motion 機能
[count] h カーソルを(count行)左に移動させる
[count] j カーソルを(count行)下に移動させる
[count] k カーソルを(count行)上に移動させる
[count] l カーソルを(count行)右に移動させる
[count] w カーソルを次/ count個後の単語に移動させる
[count] b カーソルを前/ count個前の単語に移動させる
i insert modeに移行して、カーソルを左に一行移動させる
a insert modeに移行して、カーソルを右に一行移動させる
d [motion] motionの終着点までの文字を削除する
x カーソルの位置の文字を削除する
y [motion] motionの終着点までの文字を複製する
p yで複製した内容をペーストする

Text object selection を利用したMotion

Motion 機能
ciw カーソルが位置する単語を丸ごと削除して、insert modeに移行する
diw カーソルが位置する単語を丸ごと削除する
dap カーソルが位置する段落を削除する
yap カーソルが位置する段落を複製する

使えるMotionは実際まだまだありますが、量が多いのもありますし、公式ガイドを参考した方が的確なので、ここでは割愛させていただきます。

効率的で楽しい

以上の実例を見ると、Vim/ Neovim が高速なテキスト編集を実現できることがわかると思いますが、それは実は私にとって、一番大事なメリットではありません。 では、一番のメリットは何でしょうか!

楽しい!

そう!楽しくテキストを編集できること自体が一番のメリットだと思います。 よく見ますと、Vim Motionってゲームのコンボに見えないでしょうか? Vim/ Neovim なら一日中コードを書いても退屈することはありません! それがエンジニアのモチベーションを引き上げて、効率を改善できることを私はこの一年半ですごく実感できました。

PDE(Personalized Development Environment)

Neovimは一部のエンジニアから、PDE、つまり個人的開発環境とも呼ばれています。 それはNeovimのコンフィグ自由度がとても高くて、ユーザーがそれを思い通りに自分好みにできるからです。 具体的に言うと、UIの表示、文字を打つ時のエフェクト、ユーザー入力への反応など、その気になれば全部自分で実装できます。 結果として、ユーザーは自分のワークフローにピッタリな開発環境を構築できて、開発の効率を大幅に上げられます。

効率改善以外、PDEはユーザーのモチベーションの向上にもつながると思います。 なぜかと言うと、エンジニアが仕事のタスクをPDEで対応することで、自分が設計したツールをテストできるからです。 開発が好きなエンジニアなら、それだけで仕事へのモチベーションが大幅に上がるでしょう。

多彩なプラグイン

近年、開発を支援したり、ワークフローをスムーズにしたり、できるプラグインがプログラミングエディターの必要不可欠な要素だと思われる傾向が強まりつつあります。 当然、プログラマーのためのエディターであるNeovimにも充実したプラグインエコシステムがあります。 ここでは、重点的に、何個か人気なプラグインについてご紹介します。

Telescope

TelescopeはNeovimコアの機能を利用した、高性能なLuaFuzzy Finderです。 簡単に言うと、指定した範囲以内のコンテンツをキーワードで検索するツールです。 例えば、コードベース内のファイルを大まかな文字列で検索したり、特定な文字列がコードベースのどこに記載されているかを探したり、することが可能です。

それだけでも結構便利なツールなんですが、もっとすごいのはTelescopeの拡張性です。Neovimと同様に、Telescopeは拡張性に重きを置いてデザインされました。 そのため、Telescopeの既存機能を利用して、新しい機能を作成することも比較的に簡単にできます。それに、純Lua製のプラグインであるため、Luaでカスタマイズするのもとても便利です。 もちろん、原作者と他のコミュニティーメンバーによって、もうすでにたくさんの拡張機能が作られたので、まだ自分で機能を作成したくないユーザーでも充実した既存機能を利用できます。

私がよく使っている機能をリストアップすると、以下となります。

  • builtin.buffer
    • 開いたバッファの中からファイルを検索する
  • builtin.find_files
    • コースベース内のファイルを検索する
  • builtin.live_grep
    • コースベース内で特定の文字列やパターンを検索する

詳細に興味がある方はぜひ公式GitHubページをご覧ください!

LSP(Language Server Protocol) 関連プラグイン

NeovimVimの違いの話になると、ビルトインのLSPサポートの有無が必ずといっていいほど話題に出されます。実際、LSPのサポートを目当てでNeovimに移行したユーザーも結構います。

では、LSPとは一体何なのでしょうか?

一言で言うとMicrosoft社が開発した、エディター/ IDEとLanguage Serverの間のコミュニケーションを可能にするプロトコルです。 Visual Studio Codeなどのエディターがコードのオートコンプリート、定義元への移動、コードの診断(Diagnostic) ができるのもLSPのおかげです。(LSPは元々Visual Studio Codeのために作られています)

詳細はこちらの公式サイトで閲覧できるので、興味がある方はぜひご確認ください。

前にも話した通り、NeovimにもそのLSP機能を搭載されています。そして、それを中心に作られたプラグインもたくさんあります。それらも利用すれば、簡単かつ自由度が高いLSP設定ができるため、 NeovimでのLSP体験はいろんなエディターの中でもトップクラスです。例えば、Language Serverから受信した情報に基づいて、画面上にコードに関する情報を表示するのも簡単にできます。

複雑だと思うかもしれませんが、以下のLSPプラグインを利用すれば、大体のLSP設定ができます。

  • nvim-lspconfig
    • いろんなLanguage Serverのデフォルトコンフィグを提供する
  • mason.nvim
    • Neovim用のLanguage Serverパッケージマネージャー
      • 簡単に主流のLanguage Serverをインストールできる
  • mason-lspconfig
    • nvim-lspconfigmasonを繋げるもの

LSPを管理する mason.vim

今ではLSPを利用するエディターも結構増えましたが、設定の手軽さ、自由度、エコシステムの大きさのため、Neovimを選択するユーザーもまだまだ増えています。

おわりに

以上が私が約一年半Neovimを使って、未使用の方にも知ってほしいNeovimの見どころでした。 まだまだ説明しきれてないことが多いですが、Neovimが素晴らしいプログラミングエディターであることが伝われたら嬉しいです! 文章を読むだけではピンと来ないかもしれないので、時間がある時、ぜひ、Neovimを使ってみてください!

明日の記事の担当は 採用広報担当 の戸井さんです。お楽しみに。

おまけ

初心者が自分で0からNeovimを始めるのは結構ハードルが高いので、自分が最初に使ったリソースを幾つか共有したいと思います。

  • Neovim公式
  • kickstart.nvim
    • Neovimのコア開発者の一人が開発したNeovimコンフィグ
      • cloneしたらすぐ使える
      • コードの説明がとても詳しくて、初心者に優しい
  • vimtutor
    • Vim Motionを勉強できるcli tool
    • vimをインストールすれば、一緒にインストールされる
      • Unix-like のシステムでは大体デフォルトでインストールされている

開発エンジニアがDatadog Summit Tokyo 2024に参加した感想

こんにちは!Webアプリケーションエンジニアの川本です!
最近はBUYMAの出品者向けのチームでパフォーマンス改善に取り組んでおります。

この記事はEnigmo Advent Calendar 2024の 2日目の記事です。

少し日が経ってしまいましたが、2024年10月16に開催された「Datadog Summit Tokyo 2024」に参加してきました。

www.datadoghq.com

直近の業務でパフォーマンス改善に取り組む機会が多かったのですが、その際にオブザーバビリティーの向上を支えてくれる、Datadogに興味を持ちました。開発エンジニアにとってもこれらを活用できると視野が広がると感じ参加を決めました。

印象に残ったセッション

Datadogダッシュボードで見える化する、新たなビジネス価値創造のチャンス

www.datadoghq.com

このセッションでは、リリースした機能が意図通りに使われているかを分析し、期待されるスコアを可視化するダッシュボードの活用例が紹介されました。ダッシュボードから課題を発見し、改善につなげることでビジネス価値を創造していくという内容でした。

特に印象に残ったのは、エンジニアがビジネス観点を持つ重要性です。 ダッシュボードを作成するためには、案件の目的や必要なデータを理解する必要があり、それがビジネス観点を養うきっかけになると感じました。

弊社でも新機能をリリースする際に、エンジニアがダッシュボードを準備し、ビジネス側と連携しながら価値創造に貢献していきたいです。

開発者の生産性向上

www.datadoghq.com

このセッションでは、Datadogを活用した開発生産性向上に関するパネルディスカッションが行われました。

最も印象に残ったことは、Wantedly社の市古さんがおっしゃっていた「オブザーバビリティは開発を加速させる」という点です。

www.wantedly.com

大規模リファクタリングやライブラリのバージョンアップ等を行った際の影響範囲はとても広いので、ステージング環境でなるべく確認しようとしても、全てを完璧に把握することは難しいことが多いかと思います。そのため、本番環境にリリースする際はどうしても慎重になりスピード感が損なわれてしまいます。

しかし、オブザーバビリティを向上させると、問題が発生しても素早く発見・対応できるという安心感が得られます。これにより、適切なリスクを取れるようになり、レビュー工数の削減や認知負荷の軽減につながるため、スピードと品質の両立が可能になるという内容はとても納得できました。

弊社で運営しているサービスの「BUYMA」も歴史の長いサービスで、これから大規模リプレイス、大規模リファクタリング、ライブラリの大幅なバージョンアップを控えております。その際に今回学んだことを活かして、オブザーバビリティを向上させながら開発生産性を維持しながら問題と向き合っていきたいです。

導入したいDatadogの機能

今回のサミットを通して様々なDatadogの機能について知ることができました。 その中でも自社で導入したいと考えている機能は Datadog Continuous Profiler です。

www.datadoghq.com

Datadog Continuous Profilerとは?

Datadogの公式ページでは以下のように紹介されております。

Datadog Continuous Profiler を使用すると、最小限のオーバーヘッドでスタック全体にわたって本番環境でのコードパフォーマンスを分析できます。コードプロファイリングを利用して、アプリケーションで最もリソースを消費するメソッドまたはクラスをすばやく検出および最適化できます。これにより、コードの効率が向上し、クラウドプロバイダーのコストが削減されます

メソッド単位でどれだけ時間がかかっているかや、CPU時間、メモリ使用量等のリソースの消費量が可視化されるので、パフォーマンスのボトルネックを発見するのに最適です。

導入したい理由

この機能を導入したいと思ったのは、弊社で運用されはじめて長年経過した機能のパフォーマンス改善に取り組んだことがきっかけでした。

私はボトルネックを見つけるのにまず該当するコード全体を読んでいたのですが、これは根拠のない推測をしているだけで効率が悪かったなと反省しております。

そういった際に Continuous Profiler で計測してコードレベルで可視化してボトルネックになっている箇所を明確にし、修正の目処を立てるといったことができていればより効率的かつ効果的なアプローチがとれていたなと感じております。

おわりに

今回のイベントを通してDatadog、オブザーバビリティに関して新たな知見を得ることができたのと同時によりこの分野に興味を持つことができました。

今回ワークショップには参加できなかったので Datadog Learning Center で興味のある講座を受講して実際に手を動かしてDatadogに関する知見を深めていこうと思います。

最後に、帰り際にいただいたDatadogのかわいいグッズも大切に使わせていただきます!

運営の皆様、素晴らしいイベントをありがとうございました!

明日の記事の担当は フロントエンドエンジニア の張さんです。お楽しみに。


株式会社エニグモ すべての求人一覧

hrmos.co