TECH PLAY

株式会社モバイルファクトリー

株式会社モバイルファクトリー の技術ブログ

222

こんにちは、エンジニアの id:mp0liiu です。 非常に遅くなってしまいましたが、昨年の7/4にPerlの最新安定バージョンである5.42がリリースされたので新機能や変更点についてまとめます。 source::encoding プラグマが追加され、デフォルトで有効に スコープ内のソースコードに期待する文字コードの指定をするプラグマ source::encoding が追加されました。 指定できるのは ascii と utf8 のみです。 use source::encoding 'ascii' するとスコープ内のソースコードに非ASCII文字が存在している場合、コンパイルエラーが発生するようになります。 use source::encoding 'utf8' は use utf8 と同等です。 v5.41 以降の feature bundle 1 ではデフォルトで use source::encoding 'ascii' が有効になります。 従来では次のように use utf8 していないのに非ASCII文字を扱うようなコードは、エラーになることなく意図していない挙動をしてしまうことがありました。 say length "あいうえお" ; # 本当は5文字だが, use utf8 していないので 15 が出力される use source::encoding 'ascii' することで、そのようなコードはコンパイルエラーになるので事前に気づくことができるようになります。 use v5.42 ; # use source::encoding 'ascii' も有効になる say length "あいうえお" ; # コンパイルエラー: Use of non-ASCII character 0xE3 illegal when 'use source::encoding "ascii"' これからは日本語など非ASCII文字を使うコードはきちんと use utf8 してから書くようにしましょう。 なお、 source::encoding 'ascii' はコメントやPODでも非ASCII文字があるとコンパイルエラーになるので注意が必要です。 __DATA__ , __END__ セクション以降に書く分には問題ありません。 any, all 演算子の追加 List::Util の any, all と同じ挙動をします。 演算子として実装されているためコードブロックのスタックフレームが作られずより高速に実行できるとのことでしたが、ベンチマークをいろいろとってみたところ大きいパフォーマンスの差はないものの、コードブロック内の処理やリストの要素数によってどちらの方がパフォーマンスがよいかが変わってしまいました。 なのでこだわる場合は自分で該当部分のパフォーマンスを計測することをおすすめします。 参考までに List::Util と関数と演算子とでそれぞれベンチマークをとったので参考にしてみてください。 keywordのほうがパフォーマンスが良い場合(any) Benchmark: running keyword_any, list_util_any for at least 1 CPU seconds... keyword_any: 2 wallclock secs ( 1.13 usr + 0.00 sys = 1.13 CPU) @ 495.58/s (n=560) list_util_any: 1 wallclock secs ( 1.11 usr + 0.00 sys = 1.11 CPU) @ 355.86/s (n=395) Rate list_util_any keyword_any list_util_any 356/s -- -28% keyword_any 496/s 39% -- keywordのほうがパフォーマンスが良い場合(all) Benchmark: running keyword_all, list_util_all for at least 1 CPU seconds... keyword_all: 1 wallclock secs ( 1.11 usr + 0.00 sys = 1.11 CPU) @ 503.60/s (n=559) list_util_all: 1 wallclock secs ( 1.04 usr + 0.00 sys = 1.04 CPU) @ 358.65/s (n=373) Rate list_util_all keyword_all list_util_all 359/s -- -29% keyword_all 504/s 40% -- List::Utilのほうがパフォーマンスが良い場合(any) Benchmark: running keyword_any, list_util_any for at least 1 CPU seconds... keyword_any: 2 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 16.04/s (n=17) list_util_any: 1 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 17.92/s (n=19) Rate keyword_any list_util_any keyword_any 16.0/s -- -11% list_util_any 17.9/s 12% -- List::Utilのほうがパフォーマンスが良い場合(all) Benchmark: running keyword_all, list_util_all for at least 1 CPU seconds... keyword_all: 1 wallclock secs ( 1.05 usr + 0.00 sys = 1.05 CPU) @ 16.19/s (n=17) list_util_all: 1 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 17.92/s (n=19) Rate keyword_all list_util_all keyword_all 16.2/s -- -10% list_util_all 17.9/s 11% -- ベンチマークに使用したコードはこちら use v5.42 ; use Benchmark qw( timethese cmpthese ) ; use List::Util (); use utf8 ; binmode STDOUT, ':encoding(UTF-8)' ; my @ary = ( 1 .. 1000000 ); say "keywordのほうがパフォーマンスが良い場合(any)" ; cmpthese( timethese(- 1 , +{ keyword_any => sub { use experimental qw( keyword_any ) ; any { $_ == 500 } @ary ; }, list_util_any => sub { List::Util::any { $_ == 500 } @ary ; }, }) ); print " \n " ; say "keywordのほうがパフォーマンスが良い場合(all)" ; cmpthese( timethese(- 1 , +{ keyword_all => sub { use experimental qw( keyword_all ) ; all { $_ < 500 } @ary ; }, list_util_all => sub { List::Util::all { $_ < 500 } @ary ; }, }) ); print " \n " ; say "List::Utilのほうがパフォーマンスが良い場合(any)" ; cmpthese( timethese(- 1 , +{ keyword_any => sub { use experimental qw( keyword_any ) ; any { $_ == @ary / 2 } @ary ; }, list_util_any => sub { List::Util::any { $_ == @ary / 2 } @ary ; }, }) ); print " \n " ; say "List::Utilのほうがパフォーマンスが良い場合(all)" ; cmpthese( timethese(- 1 , +{ keyword_all => sub { use experimental qw( keyword_all ) ; all { $_ < @ary / 2 } @ary ; }, list_util_all => sub { List::Util::all { $_ < @ary / 2 } @ary ; }, }) ); print " \n " ; レキシカルなメソッドを宣言できるようになった my sub のように my method でスコープ内でのみ呼び出すことのできる、レキシカルなメソッドを宣言できるようになりました。 また、レキシカルメソッドを呼び出すための演算子 ->& も追加されました。 use v5.42 ; use experimental 'class' ; class Point { my method hoge { say "hoge" ; } method wrap { $self -> &hoge ; } } Point->new->wrap(); # hoge $self->&method は method($self) の糖衣構文です。 同じクラス内でもスコープが違えば呼び出すことはできないですし、継承先のクラスから呼び出すこともできません。 switch 機能とスマートマッチング演算子の削除が無期限の延期に Perl5.38 で非推奨となり、5.42で削除予定だったswitch 機能(given-when構文)とスマートマッチング演算子は削除が無期限に延期となり、これらを使っても実験的機能であることの警告は発生しないようになりました。 switch機能はデフォルトでは無効になっており、個別に有効にするか、v5.34 までの feature bundle で有効になります。 v5.35 以降の feature bundle では無効になります。 { use v5.10 ; given ( 100 ) { when ( $_ % 2 == 0 ) { print " $_ is even" ; } default { print " $_ is odd" ; } } } { use v5.36 ; given ( 100 ) { # syntax error when ( $_ % 2 == 0 ) { print " $_ is even" ; } default { print " $_ is odd" ; } } } { use feature 'switch' ; given ( 100 ) { when ( $_ % 2 == 0 ) { print " $_ is even" ; } default { print " $_ is odd" ; } } } スマートマッチングはデフォルトで有効ですが、 smartmatch 機能として有効/無効を切り替えられるようになりました。 v5.40 までの feature bundle では有効になっており、v5.42 以降では無効となります。 { use v5.41 ; print 'A' ~~ [ 'A' .. 'D' ] ? 'Included' : 'Not included' ; # syntax error } { use feature 'smartmatch' ; print 'A' ~~ [ 'A' .. 'D' ] ? 'Included' : 'Not included' ; } あくまで後方互換性のためのことを考えると削除が難しかったので残しておいている、という感じがするのでこれからswitch機能やスマートマッチングを多用するコードを書くのはおすすめできないです。 特にスマートマッチングはオペランドごとの挙動を覚えるのが難しいのでやめておいたほうが良いでしょう。 switch機能は直接条件式を記述するなどスマートマッチングを利用しないように使う限りにおいては使用しても問題ないかなと思いますが、 だとしても代替として match構文 が提案されており、実験的実装も作られ後々実装される可能性があるので、今まで通りコードを書くのが一番無難かなと思います。 フィールド変数の attribute :writer が追加 クラス構文のフィールド変数の値を更新する setter を自動生成する attribute が追加されました。 スカラ変数のみ対応しています。 class Point { field $x :writer :param; field $y :writer :param; } my $p = Point->new( x => 20 , y => 40 ); $p->set_x ( 60 )->set_y( 100 ); # writerはインスタンス自身を返すのでメソッドチェーンも可能 引数を指定すると指定した名前で setter を生成します。 Moose系のクラスビルダーのアクセサと違ってフィールド名と同名のアクセサで getter / setter 両方として使えるようにできないので注意が必要です。 パッケージの区切り文字としてのアポストロフィを無効にできるようになった Perlではパッケージ名の区切り文字に :: を利用しますが、Perl4の頃は ' を利用しており、その互換性を保つためPerl5になってからもずっと ' をパッケージ名の区切り文字として利用できるようになっていました。 ' をパッケージ名の区切り文字として利用することは Perl5.38 で非推奨となり、 Perl5.41.3 で一旦削除されましたが、議論の後にデフォルトでは復活し、プラグマで有効/無効を切り替えられるようになりました。 以下のように apostrophe_as_package_separator 機能として有効/無効を切り替えられるようになっています。 use feature 'say' ; use POSIX; use feature 'apostrophe_as_package_separator' ; say $ POSIX' VERSION ; # $POSIX::VERSION と同じ no feature 'apostrophe_as_package_separator' ; say $ POSIX' VERSION ; # コンパイルエラー apostrophe_as_package_separator は use v5.42 で無効になります。 use v5.42 ; say $ POSIX' VERSION ; # コンパイルエラー chdir が CORE:: 名前空間に追加された コア関数と同名の関数がパッケージ内に定義されている際、曖昧さを避けて呼べるよう CORE:: にいくつか組み込み関数が追加されていってるのですが、その流れの1つかと思われます。 二項演算子で左項が否定されるのが不自然な場合に警告が発生するように !$x < $y のようなコードがあったとして、比較演算子で左項を本当に否定したいことはまずなくて、通常は条件式全体を否定したい場合が多いと思います。 そのような場合に警告が発生するようになりました。 ! $x < $y # 警告が発生: Possible precedence problem between ! and numeric lt (<) 拘束演算子(=~など)、 cmp 、 <=> 以外の比較演算子、isa演算子でこの警告が発生するようです。 このような場合は否定された演算子を使うか、かっこで優先順位を明示するか、優先順位の低い論理否定演算子 not を利用するようにしましょう。 $x >= $y !( $x < $y ) not $x < $y builtin モジュールの indexed 関数で配列のインデックスと値の組のリストを生成し、2変数のforループでイテレーションするコードのパフォーマンスが改善 Perl5.40で追加された、組み込み関数を提供する builtin モジュールの indexed 関数を利用することで、配列のindexと要素の列挙が楽に書けるようになっていました。 use v5.40 ; my @array = qw( red blue green ) ; for my ( $index , $value ) (indexed @array ) { say " $index => $value " ; } ただし、これは配列のインデックスと値のリストを実際に生成する点など、通常の for (@array) のようなループ文と比べて効率的でないコードとなっていました。 5.42からは内部的には配列のインデックスと値のリストを実際に生成するのではなく、通常の for (@array) と同じ方法で配列をイテレーションするようになりました。 まとめ source::encoding プラグマが追加されことは影響が大きそうで、 use utf8 していないころに書かれたコードは見直したほうがいいかもしれません。 その他は今回も細かい改善点が多いといった感じですが、Perlに足りなかった機能が追加されたりPerl4のころの構文を無効にできるようになったりと確実に過去のバージョンより使いやすくなっています。 次のバージョンでは名前付き引数が追加されるなど、大きな変更がありそうで楽しみです。 この記事では書けなかったこともあるので詳しいことが気になった方は 公式ドキュメント もぜひ読んでみてください。 use v5.42; のような構文のことです。Perlでは後方互換性を保つため古い機能は無効にできるように、新しい機能は有効にできるようになっていますが、 feature bundle はそういった各機能を、バージョンごとに推奨されるものをまとめて有効/無効にしてくれるプラグマになっています。 ↩
アバター
はじめに こんにちは。駅メモ!開発チームの id:wang です。今回は駅メモのサービスのインフラ環境の EC2 インスタンスをx86_64からAArch64へ移行したため、その話をしようと思います。 なぜARM 単刀直入にいうと、コスト削減のためです。駅メモ!のサービスではAWSの Intel/AMD が載ったインスタンス(x86_64)を使っていました。しかし、公式によるとAWSが開発したAWS Gravitonが搭載されているARMインスタンス(AArch64)が同じスペックのx86_64より20%ほど費用対効果が良いため、移行を検討していました。 そして、全面移行から3ヶ月が経過しました。昨年同月のコストと比較した結果、年間で約20%のEC2コスト削減を実現できる見込みです。 事前検証 まず、ARM移行の是非について、社内で検証しました。考慮すべき問題は主に二つありました。一つ目は会社のサービスがARMインスタンス上で問題なく稼働できるかということです。二つ目は移行することで本当にコストを削減できるかということです。 一つ目については、自社サービス本体のみならず、本番環境と同一の条件で開発を継続できるよう、社内の開発ツール等の検証も行いました。この段階で、ARMインスタンス上で動作しないモジュールの有無を確認し、互換性のないモジュールが発見された場合の対応策についても検討しました。 二つ目に関しては、社内のベンチマーク環境でx86_64とARMインスタンスの性能を直接比較し、費用対効果の検証を実施しました。検証の際は、精度の高い判断を行うため、複数のスペック(インスタンスサイズ)を用いて比較を行っていました。 また、最終的な目的はコスト削減であるため、単なるアーキテクチャ間の比較にとどまらず、x86_64の新旧世代やサイズ変更による性能変化(縦比較)についても並行して検証しました。 検証の結果、以下の2点が明らかになりました。第一に、現行のx86_64と同等のスペックを持つARMインスタンスにおいては、同等のパフォーマンスを維持しつつ、コストを5%削減可能であるという結果が得られました。第二に、現行より1ランク上位のARMインスタンスを検証したところ、コストは4%上昇するものの、パフォーマンスが20%向上することが確認されました。 以上の結果を踏まえ、今回は「現行より1ランク上位のARMインスタンス」への移行を決定しました。インスタンス単価はわずかに上昇しますが、20%の性能向上により、稼働させるインスタンス総数を最適化(削減)できるため、最終的なインフラコストを最も大きく抑えられると判断したためです。 計画 社内状況により本格的な移行作業は最初の検証から1年後となりました。これに合わせ、移行の3ヶ月前から準備を再開したのですが、この1年でインフラ環境に大きな変化が生じていました。 まず、OSが Ubuntu 20.04 から 24.04 へアップデートされ、さらに既存の x86_64 インスタンスのスペックも引き上げられていました。検証当時のデータが現在の環境にそのまま適用できないと判断し、改めてパフォーマンスなどを再検証を行うこととしました。 また、1年前の検証段階では「ARM非対応のモジュール」がいくつか発見されていました。しかし、当時はそれらが近いうちに廃止される予定だったため、移行時には問題にならないと判断し、特段の対応は見送っていました。ところが、この1年で状況が一変します。諸般の事情により該当モジュールは引き続き不可欠な機能として利用継続となったため、急遽ARM環境での互換性を確保するための改修、あるいは代替手段の検討という、想定外の対応を迫られることとなりました。 これらの予期せぬ課題に対し、他チームとも密に連携しながら代替策や改修方針を改めて検討し、柔軟に対応を進めました。 移行の3ヶ月前から余裕を持って準備と現状確認に着手していたことが功を奏し、スケジュールを大きく乱すことなく、確実な移行へと繋げることができました。 実際の移行 検証段階では、サービスが問題なく動作することを確認しましたが、本番環境への全面移行を即座に行うには慎重を期す必要がありました。 そのため、まずは段階的な移行プロセスを採用しました。第一段階として、開発を主管するエンジニア数名にARMベースの開発環境を提供し、日常的な業務において予期せぬ不具合が発生しないかを一定期間観測しました。 続いて第二段階として、本番環境に数台のARMインスタンスを限定的に投入し、実際のトラフィック下での挙動をモニタリングしました。これらのプロセスを経て、十分な安定性が確認できた段階で、最終的な全環境のARM移行を完了させました。 移行結果 コスト削減以外にも、移行によって大きな副次的メリットが得られました。 CI/CD環境の高速化 『駅メモ!』チームでは、CI/CD環境としてAmazon EC2を用いたセルフホスト型のGitHub Actionsを構築しています。この実行環境をARMインスタンスへ変更したところ、CIの実行速度が約30%向上しました。 イベント運用時の安定性と効率化 『駅メモ!』では、レイドイベント等の高負荷時にパフォーマンスを維持するため、インスタンス数を増強しています。ARM移行後は、従来よりも少ない増加台数で負荷に対応可能となり、インスタンス数の推移もより安定しました。 まとめ 今回のARM移行では、ベンチマーク環境が重要な役割を発揮しました。本来同じスペックの移行だけを考慮していましたが、1ランク上のスペックのパフォーマンス検証もできたことにより、より広い選択肢が視野に入りました。そのおかげで、最終的により費用対効果が良いインスタンスを選択したことで、インフラコストの削減に成功しました。 また、事前に余裕のある移行スケジュールを設定したことで、今回のような前提条件の変化や想定外の技術的課題が生じた際にも、立ち止まることなく素早く計画を調整し、柔軟に対応することができました。この経験から、大規模な環境移行においては、余裕を持った移行計画の策定こそが重要であると改めて実感しました。
アバター
はじめに こんにちは。駅奪取チームの id:kawa-mf です。 今回は、駅メモチームから駅奪取チームへ異動して、業務領域がどのように変化したのかについて、記述しようと思います。 前提としてなのですが、使用している言語(Perl, Vueなど)や環境といったものはほとんど同じで、チーム独自のものは少ないです。 1つ大きな差があるとすると、駅メモチームは人数が多く、駅奪取チームは人数が少ないことです。 今回の記事では、チームによって、どれくらい業務内容が変化するのかを書いて、これから入社を考えている人の参考にしてもらえると嬉しいなという温度感くらいで書いていこうと思います。 これ以降は、駅メモチームと駅奪取チームのことは、駅メモと駅奪取と記述させていただきます。 変わったこと 1つのプロジェクトに対する開発エンジニアの人数 駅メモ 2~5人 駅奪取 基本的に1人 上記はコーディングを進める人が一人という意味です レビューなどは、チームメンバーが行ってくれます 期限などが近い場合はヘルプに入って、一時的に3人になったりはします 一つのプロジェクトに対し、割り当てられる人数からもわかるように、駅メモのプロジェクトは比較的大きく、駅奪取は周年開発のようなものでも普通くらいの大きさのように感じます。 駅奪取だと、相談などはいつでもできる環境は整っていますが、1つのプロジェクトに対して、1人のエンジニアがコーディングを行います。 チーム開発的な複数人で進めるといった経験を積むことは難しいですが、1人で進める上で良い点もあります。 開発をする上で、何が必要かなどの見積もりから全て行う必要があるので、コーディング部分以外の知識も身につけることができます。 今回、駅奪取14周年で追加した新ニャッシュの都市ニャッシュを開発したのですが、初めての見積もりを行いました。 見積もりでは、他のニャッシュのスキルの詳細を詳しく知らなかったので、その部分についても調査しながら進めたこともあり、少し時間がかかってしまいました(+1日程度)。 見積もり以降の実装については、基本的に1人で行いました。 今回は、そこまで不具合が発生しなかったのですが、不具合が発生した場合は実装した人が一番の理解者のはずなので、その人が対応にあたることが多く、責任も重くなっていると感じました。 AWS周辺の作業 駅メモ 開発チームが見るのは、フロントエラーやサーバーエラーなど メンテナンスやDBのエラーなどは、インフラチームが担当 駅奪取 全て担当 駅奪取だと、人数が少ないためインフラの業務も行う必要があります。 こちらは普段の業務で変わった部分が多いです。 例えば、毎週の負荷分析で今までよりも見る範囲が広くなったことや、CloudWatchアラームをTerraform経由で追加するなどです。 ページごとのデザインについて 開発における流れとして、デザイナーが画面のデザインを考える → エンジニアやディレクターを交えて、デザインが問題ないのかを考える → エンジニアがコードに落とし込む(落とし込む際にもより良いデザインを思いつけば、デザインを調整する) というのが通常の流れだと思います。 駅メモだと、その流れに沿って開発が行われていましたが、駅奪取には専属のデザイナーが不在です。 基本的には、デザインもチームで考えて、考えたものをデザイナーの方に添削していただくみたいな形が多いです。 これまで、デザインについて、深く考えたことがなかったのですが、この辺りについても考える必要があります。 配属されてすぐに既存のページの表示改善を行いました。 しかし、変更方法が悪く、ユーザーからの問い合わせもあり、最終的にはチケットの要件から変更しました。 軽微なデザインを追加/変更するのみでも難しいということを実感しました。 個人的な感想なのですが、配属されてから1年ほど経ちますが、デザインを考える部分は全く慣れないです... 駅メモと駅奪取を比較し、良い点と残念な点(駅奪取視点) 良い点 AWSでエラー確認などを含めてなのですが、対応する範囲が広くなったことで、より周辺部分について知識を得ることができた点 残念な点 駅メモだと複数人で開発していることもあり、チーム開発という感じがするのですが、駅奪取だと個人開発の感覚から抜け出せない点 駅奪取チームで個人的にやりたいこと 普段の開発では経験するのが少ない、インフラ周りの作業 ユーザーのニーズに合わせた良さそうなデザイン設計 モバファクでは中途採用・新卒採用ともに絶賛募集中です。 会社の情報については、モバファクブログでも発信しています。 技術好きな方、モバファクにご興味をお持ちの方は、ぜひご応募ください! ・モバファクブログ: https://corpcomn.mobilefactory.jp/ ・採用サイト: https://recruit.mobilefactory.jp/
アバター
駅奪取チームの id:konakawa です。 以前駅奪取で、デプロイ戦略に起因して、特定ケースにおいてサーバのファイルのタイムスタンプが巻き戻ってしまうことがありました。 これにより、キャッシュバスティングをすり抜けて古いキャッシュが混ざってしまい、不具合の原因となってしまいました。 本記事では、この問題について概説し、とった対応を含めて紹介します。 同じようなインフラ構成のサービスの役に立てば幸いです。 問題概要 デプロイ戦略について 駅奪取では、最新のファイルが以下の2通りの経路で各 EC2 インスタンスへ届けられる仕組みとなっています。 デプロイ時: デプロイ用のサーバーから、rsync でファイル一式を配布 メタデータも同期される オートスケール時: AMI から起動したインスタンスが git pull を実行 Git は内容が同一のファイルは更新しないため、その場合はメタデータが更新されない これにより、後述するような特定ケースにおいてはファイルのメタデータの異なるインスタンスが混在するような構成になっていました。 問題となる時系列 本プロダクトで、あるときのフロントビルド結果の変遷として以下のようなものがありました。 ある時点でのビルド結果(以下 A とする)が存在する 変更が入ったビルド結果(以下 B とする)がデプロイされる 上記変更を revert したビルド結果(以下 A' とする)がデプロイされる 単に変更とそのリバートを順に反映しており、ありふれた経緯かと思います。 この経緯では A と A' は、内容は同一ですがファイルのタイムスタンプが異なるものになります。 以上の手順を踏んだとき、インスタンスが持つビルド結果は デプロイ時点で起動していたインスタンス: A' オートスケールで上がってきたインスタンス: AMI の持つビルド結果が A の場合、更新されず A のまま という状態が混在することになります。 キャッシュ このプロダクトでは、キャッシュバスティングのために、静的ファイルの URL をそのファイルのタイムスタンプ(mtime)を元に生成します。 すなわち、ファイルの更新があった際は URL が更新されるため、古いキャッシュを掴み続けることがない仕組みです。 しかし前述のように、オートスケールで起動したインスタンスでは、ファイルが A 時点のタイムスタンプを持ったままになります。 そうしたインスタンスから返される HTML には、 A のタイムスタンプを元にした静的ファイルへの URL が埋め込まれます。 CDN やブラウザのキャッシュは、このタイムスタンプ情報を含む URL をキーに紐づきます。 ここで、例えば以下のようなシナリオで、古い A の URL に新しい B の内容をキャッシュすることがあります。 A のタイムスタンプを使った URL へのリクエストが発生し、そのキャッシュの TTL が切れていればオリジンへリクエストを行う。 このとき B が最新である期間であれば、オリジンから B のファイルが返され、A の URL に B の内容がキャッシュされる状態になる。 ブラウザでキャッシュを掴んでいる場合、If-Modified-Since によりキャッシュの更新確認を行う。 キャッシュのタイムスタンプとオリジンのファイルのタイムスタンプを比較して、後者の方が新しければオリジンのファイルを使うが、 オリジンでも巻き戻りが起こっていれば、ブラウザで掴んだキャッシュを使用することになる。 この状態でパスに用いているタイムスタンプの巻き戻りが起きると、キャッシュバスティングが正しく機能しなくなります。 すなわち、最新のファイルの内容が A' であるにも関わらず、古い B の内容が使われてしまいます。 この結果、ビルド結果に A(') のものと B のものが混在することになり、不具合が発生する原因となってしまいます。 対策 FSx 化やあるいはイミュータブルインフラ化など、インフラ側の仕組みを変えることで、今回の mtime 巻き戻りのような状態の不一致が起きなくなります。 これらは本件以外にも有効ですが、比較的重い対応になります。 これらを導入するまでビルドの反映をリバートできないというのは不便なため、少なくとも本件の対応としてはこれらは見送りました。 オートスケールで上がってくるインスタンスが最新の情報を取得する方法を git pull から rsync に揃えるというのも1つですが、rsync で配る元のサーバが生きていることに依存してしまうため、これも避けたいです。 そこで、ファイルの名前自体に内容ハッシュを付加する対応をしました。 これであれば非常に軽量に入れられ、かつ前述したところの A と B はファイル自体が違うため、キャッシュが混じることもなくなります。 まとめ インスタンスの状態を更新するのに git pull を用いていたが、ファイルの内容が変わらない場合はメタデータが更新されない タイムスタンプの巻き戻りが発生することで、キャッシュバスティングがあっても古いキャッシュが混じる パスやクエリパラメータでなく、ファイル名レベルで変更を反映することで解決できた ここまでご覧いただきありがとうございます。 本記事が似た問題に遭遇した方などの参考になれば幸いです。 モバファクでは中途採用・新卒採用ともに絶賛募集中です。 会社の情報については、モバファクブログでも発信しています。 技術好きな方、モバファクにご興味をお持ちの方は、ぜひご応募ください! ・モバファクブログ: https://corpcomn.mobilefactory.jp/ ・採用サイト: https://recruit.mobilefactory.jp/
アバター
概要 こんにちは、駅メモ!開発チームエンジニアの id:hayayanai です! 11/14-15に開催された YAPC::Fukuoka 2025 へ参加してきました。今回はそのレポートです。 「レポートを書くまでが YAPC」とのことで、社内ドキュメントとして共有したものを手直しして、このブログにも投稿しておきます。 yapcjapan.org 講演を聴いたり会場を見て回ったりして、業務で活かせないかな〜と考えたことを書き残しています。 概ね講演メモ→感想という感じの順番で書いてます。LTについてはいくつか聴きましたが、気になったものを少しピックアップして書いてます。 参加を決めた理由 会社のスポンサーチケットがあった YAPC行ってみたかった モバファクの社員が元々結構参加していて、興味があった 福岡行きたい Perl あまりわからないけど、ちょっと詳しくなりたいな 他のカンファレンスとYAPCの雰囲気の違いを感じてみたかった Day 1 オープニング パッションを感じるオープニングトーク なぜ強調表示できず ** が表示されるのか — Perlで始まったMarkdownの歴史と日本語文書における課題 by hkws GPTのレスポンスで、強調表示できたりできなかったりする カギカッコやスペースで強調されたりされなかったり CommonMarkの仕様に由来 CommonMarkはまだv0だけど、実態はv1みたいな感じ Markdownの歴史 ** のオリジナル→CommonMarkの変化 Markdownには強調と強い強調がある strongタグに変換 →emタグに変換の順で処理 **や*のネストを処理に困って、left/right-flankingの導入をした 分かち書きする言語でバグる 対策は考えられてるけど、CJK friendlyなParser/Rendererを利用しないといけない 感想 Markdown、文章の改行周りや箇条書きの方言も多くて大変だなと思ってた。 自分が使うMarkdownパーサーはどんな解釈ルールで解釈されてるか、確認したほうが良さそう。 CJKを意識した実装になってないものを使ってたら意図しない表示の原因になるかも。 エディタとレンダラーで同じパーサーを使っている場合は問題なさそうだけど、異なるパーサーを使っている場合はさらに注意が必要そう。 Introducing RFC9111 by 小山健一郎 参考書籍: Web配信の技術 今回は共有キャッシュの話 HTTPキャッシュの実装は参照してるRFCが色々ある FastlyはRFC9111 CloudflareはRFC7234/RFC2616 RFC7234が多め 新しく作るなら、廃止されているのでRFC9111が良いだろう ミドルウェアがたくさんある どのリクエストをキャッシュしていいかというのもRFC9111に書かれている Tests for HTTP Cachesを使うとローカルでテストができる CDNのキャッシュの実装を見ると良い 感想 開発者ツールのNetworkタブ見るときも、no-cacheの挙動とか覚えておくと良さそう? キャッシュの利用を考えるとき、できるだけユーザーに近い側のキャッシュを使うというのは改めて大事だなと思った。 AIコーディングの弱点、やっぱりプログラミングは人間が(も)勉強しよう by きしだ なおき Transformerの解説 最近は変数の入力を大きくしても性能がほぼサチっている 電気や人間が供給できる文章数に限界 チャットで人間による評価が入ってきてパフォーマンス向上(GPT3.5〜) Function Calling LLMから外部関数を呼び出すように MCP 一旦知識を吐き出させて、ユーザー入力と知識を投入させると精度が出やすい Reasoning AIの計算 3桁までの数字は1トークン LLMはユニットテストが書けるものは賢くなる 非機能要件は大体苦手になる 長いコンテキストの対応 1Mトークン対応と謳っているが、性能が出るのは30Kトークンくらい どんどん遅くなっていく なぜ同じ失敗が続くのか 間違い同士で注目して、大事な情報だと認識してしまう コンテキストを汚さずコンパクトに。 汚れたときにコンテキストから消し去るのは、coding agentが頑張っている部分 壁打ちと実際に作業させるチャットを分けると良い ダメな情報はダメなものとして認識してくれない 否定文を上手く理解してくれないのと同じ プログラミングの話 感想 「失敗を繰り返さないようにログとして持たせる」vs「失敗のログが重視されてしまって誤りを繰り返される」のバランスが大変そう。 ダラダラ書いてると人間とAIどちらにも優しくないコード書きがちだから、コードのユーザビリティを意識していきたいなと思った。 最近はAIが人間のプログラミング学習を支援してくれる機能もあって人間の勉強もしやすいよなと思っている。研修に活きてきたりするんだろうか。 「データ無い!腹立つ!推測する!」から「データ無い!腹立つ!データを作る」へ ― ゼロからデータを作り、チームで育てられるようにするまで SNSアカウント一覧の中にCPANのIDもある データは必要になってから重要性に気づく 推論は… 人間がわからないものは機械にもわからない 推論せずに済むなら推論しないほうが良い データを作る 利用できる状態まで、データのソースを整理する 辞書作りの例 DuckDBを利用 スプレッドシートからはじめよ ツールのフロントエンドとしてデファクト 前処理など、データ作りは大変 SPOF回避や、マイルストーン設定が大事 手順は変えてもいいが、一貫性は持たせないといけない 感想 データが欲しくなったときに、そもそもデータが無いことに気づくのあるなと…。 (本題とは逸れるけど)論理削除フラグみたいなbooleanを、delete_fgじゃなくてdeleted_atで持つテクニック、ちょっと前にXで見たけどわかりやすそうだなと思った。 DuckDBが各種プログラミング向けにライブラリがあるよと紹介されたとき、Perlにもあるんだろうか…?と疑ってしまったけれど、ちゃんとあるようだった。 大規模OSSに一人で立ち向かうには WebKitのスクリプトはPerl/Ruby/Pythonで書かれている 高校生からOSSに貢献 Prettierは人手不足でメンテナーに WebKit, JavaScriptCoreの話 WebKit開発者のランクの話 fix typoだけじゃなくて、数年単位で責任持ってやっていく 可能な限り時間を捻出して、集中してやる 最も大事 コードを読む(AIに読ませる)→手を動かしてデータを見る プロジェクト内の常識に沿って書けるようにする 他の貢献者の活動を見る 寝よう コードを書く気が起きないときは、AIに書かせる 自分の助けのためにAIを使うのではなく、書かせる部分の性能について もっと進化してほしい このパッチと似た最適化を別のところに適用させたりはうまくいく 感想 PerlもWebフロントエンドのOSSに比べれば人手不足だろうなという気がするので、Perl書けるエンジニアとして何らかの貢献してみたいなと思った。 プログラミング言語の勉強の話、知らなかった言語がスラスラ読み書きできるようになるのは楽しかったよなぁと、昔の記憶が思い起こされた。 他のコントリビューターのコードを見るのは、OSSに限らず、複数人で開発しているリポジトリなら大事だよなと感じた。 「バイブス静的解析」でレガシーコードを分析・改善しよう by hitode909 Perlの静的解析の話 プロジェクト固有の静的解析ツールをAIに作らせて、AIの信用できない部分をカバー 静的解析は構文木をパースするけど、バイブス静的解析はコードベース内の記法のみ扱う PPI等ではなく、正規表現で作る 言語で扱える全ての記法に対応する必要ないよね 偽陰性は、デッドコードが残るだけだから問題なし 偽陽性は不具合のもとで危ない 確認、実行は人間の目に留めておくと良い デッドコード削除のカスタムコマンドを作っておくと便利 useしてないのにメソッド呼び出しするコードの改善 正規表現でやってたのを、AIにPPI版を実装してもらう PPI版が遅すぎるから、同じ結果になるように正規表現の方を改善 感想 「デッドコードの一生」の図が、この通りにやったことあるな〜となって、どこも一緒なんだなという気持ち。 use忘れでメソッド呼び出しとか、どこでも問題になるんだなぁ…😭 LT 「文字列→日付」の落とし穴 標準のモジュールで変換する場合も、バリデーションは手前でしないと意図しない変換になる場合がある 感想 結構トラップな挙動…。 高速化&コスト半減!? GitHub Actionsのサードパーティーマネージドランナーの比較 by occhi みんなCIは高速化したい ランナー GitHub ホステッド セルフホステッド サードパーティ←この話 感想 サードパーティランナーがGitHub ホステッドのランナーより安くなる理由ってどこから来るんだろうか 2ヶ月で新規事業のシステムを0から立ち上げるスタートアップの舞台裏 新規事業の立ち上げ: トップダウン 再現性の考察 リサーチは人力 SRE含めエンジニアは5人 あらゆるものが無いところから 技術スタックは買えない(Ruby on Rails) ユーザーストーリーマッピング Spec-Driven Development AIにPR作ってもらう 仕様書等もコミットした 1週間に40回デプロイ AIの0→1は瞬発力がすごい AIが散らかしたコードをきれいにした 感想 RailsでAIの恩恵受けづらいの、やはりPerlと似たようなところがあるのかなと思った。 【お楽しみ】10分トークN連発 by YAPC::Fukuoka チーム (40分) 幕間CMを支えている技術 React + GitHub Pages or Vercelでやる Window Management APIでフルスクリーンを制御する display:none でできるだけローカルキャッシュを使う ネットワークが不安定なことの対策 感想 てっきりOBSとか使ってるのかと思ってたけど、手作りで丁寧に作られていた 後天的Perl Rubyを先天的に獲得 Perlの . を文字列結合からメソッド呼び出しにできた 感想 これはPerl? それともRuby? クイズ思い出した できるもんなんだなぁ log 対数ではない Perl/Ruby界隈はLTSVが主流 Goはslogが来て構造化ログのトレンド? JSON Linesがいい感じ Claude CodeのログもJSON Lines thinkingのログ見たり テスト・静的解析のログをsub agentにさせて、コンテキストを食いつぶすのを避ける試みをしてる ハルシネーションには注意 iframeを許可するようにheaderで許可すると良い 感想 テスト・静的解析のログをsub agentにさせるの面白そう。自動でやってくれる未来も来そう 頑なに再代入しない! 理由 if文の中で場合分けして変化していくコード…。わかりにくい 読みやすさ・バグのリスク低減・保守性UP jsの例 letの時点で、変数の流れを追いかけることになる constで即時実行関数にする。などで対応 デメリット検証 実行時間比較 IIFE > 関数を作る >= 再代入でフラグ設定 OSSで実践した node-lambda 値が不変で安心 関数をつくることに意識が向きやすい ユニットテストが書きやすくなるメリット バグが生まれにくいというところにもつながる 感想 jsの即時実行関数みたいなことを、たまにperlでもdoやsubでやったりするので、わかりみが深かった。 発表の経過時間とスライドの進み具合がウサギとカメでわかるの良いなと思った Rabbit というものらしい? LT 銅鑼で発表が打ち切られるの面白い プロジェクトの空気を読んで開発してくれるPerlのAIツールがほしい by kobaken 最近のPerlは初学者やAIに優しくなってる 古いものと混ざってわかりづらい 本を書いて、初学者やAIにも優しくした 感想 新しいバージョンに移行したときとか、適宜情報をアップデートしていくのは人間・AIどちらにも大事だよなと思った Pythonを"理解"しているコーディングエージェントが欲しい!! by nikkie Linter で指摘する 修正するところはsub agent に任せている 感想 修正をsub agent に任せるのはなるほど〜となった。 基盤モデルのアーキテクチャを改造してみよう - 時系列基盤モデルのマルチモーダル拡張事例の紹介 by himura467 離脱する時期や、一度落ちたら戻らないから、その手前でアラートを出せないかをやってみた 感想 ゲームの離脱防止に繋がるかも? Day 2 起床後、ホテルで急なお仕事をやっつける。ちょっと遅刻して会場入り 旧から新へ: 大規模ウェブクローラのPerlからGoへの置き換え by motemen 感想 20年の歴史のあるプロダクトを移行するって判断がすごい。 Perl実装に切り戻す判断をあらかじめ決めておくのも大事だよなと思う。 やっぱりPerl→Goでリソース減るんだなぁ。 gRPC/OpenAPIエンドポイントをPerl側から呼び出して共有←良さそう。 OpenTelemetry対応、Perlでやるよりは楽だろうから言語移行のメリット出て良さそう。 Perl の生きのこり Perlの歴史の話 CGI mod_perl PSGI/Plack Interface/実装 の関係 複雑なアプリケーション 環境構築問題 Carton コードの問題 CIで問題検知 複雑化するプロダクトにPerlはどう立ち向かってる? プログラミング言語の変化 perlはbackward-compatibilityだけではなく、bugward-compatibilityも重視 でも、OOPは変化した v5.42は組み込みで class がついてくる try catchもある スマホ対応による変化 ChatCPT, AIによる変化 新しい書き方を推し進める動機づけ experimentalでないなら try catchはおすすめ Try::Tinyはデファクトだけどハマりどころがある 感想 後方互換性を保ちながら、時代に合わせて発展していったのがPerlなんだなと再認識 機密情報の漏洩を防げ! Webフロントエンド開発で意識すべき漏洩ポイントとその対策 モダンなフロントが漏洩させやすい理由 テンプレートエンジンとの違い ViewライブラリでUIを組む 特にSSRも当たり前 Nextでif文使って出し分けるだけだと、キャンペーン情報の存在が漏れる 対策 サーバーから情報をfetch フレームワーク特有の仕組みを使う 漏洩の有無を調べる方法 grep 開発者ツールのNetworkパネル テクニック CIでもgrep Taint API(React) 実行時エラーでページを見られなくできる GraphQLでdata fetch スキーマに書かれたfieldしか取れなくなる resolverの実装コストとトレードオフ 感想 Taint API、Nuxtにも無いかな〜 モダンなフレームワーク、バックエンドとフロントエンドの境界が曖昧な感じで、それが漏洩に繋がりやすいのかなと思った。 読む技術・書く技術・伝える技術 - 15年続けて分かった持続可能なオープンソース開発 by azu 15年くらいオープンソース開発 アウトプットではなく、アウトカムを目指している 実際の影響・成果 時間が必要 燃え尽きまでに段階がある 技術的依存を増やし、心理的負荷を減らす 技術的依存はコントロール可能 心理的負荷はコントロール不可 JSer.infoの話 新しい情報を数千から自動収集 人間のキュレーション さらに興味を持って見てもらう 将来的には自動化される未来もあるかも 13記事溜まったら公開で、心理的負担の減少 textlintの話 No core rule 自分でルールを選んでね コアにルールを持つと、ルールのissueがコアに集まる=心理的負担増 ユーザーがプラグイン(ルール)を選ばないといけないのがデメリット ユーザーがAIになって難しさが変化 JavaScript Primerの話 書籍版とウェブ版がある ウェブ版は常に公開されているから、完成へのハードルが下がる 章を書くときに、Design Docを作ってから書き始める Design DocからAIにドラフトを書かせて第一歩とする 既知の言葉で未知を説明する サンプルコードの実行結果があっているかをテストで確認 コントリビューターを増やす 変化への対応 著者を増やして心理的負担を減らす 報酬も出す 100人以上 Stale issue/PRの自動閉鎖とか、心理的負担の低減に役立つ 技術的依存は交換(更新)可能だからいい感じ 更新を継続するにあたって、習慣化はどれくらい大事だと感じていますか? 勝手に習慣、癖になっている 感想 JSer.infoはよく読んでいるけれども、inputの部分がとても大きいことを始めて知った。 ただその中で、技術による自動化を組み込んでいるのが面白いなと思った。 textlintもよく使っていて、技術記事に特化したルールやAIっぽさを消すルール等、自分で目的に応じて調整できるのが合ってるなとは思う。 JavaScript Primerも新人時代に読んだ記憶があって、最新の内容を学べて良かったなと思った。 裏側でかなりの人や技術が働いているんだなと StaleなIssueやチケットを、ちゃんと閉じていくのは大事なんだろうなという気持ち 探求の技術 週1でブログを書いている なぜ探求するのか おもしろさが原動力 追いかけるのではなく、楽しむ 報酬や評価を目的(外発的動機づけ)にするよりも、自分の興味や好奇心から(内発的動機づけ)のほうが、長期的に続きやすい アウトプットは自分のため 外からの評価より、自分の学びを目的にする 他人へ説明するために書くことで学習効果が生まれる 誰かが読むかもという意識 習慣の力 意志力に頼らず行動できる 歯磨きをやるぞ!ってやるのは少ない。習慣になってるから 技術ブログを書くぞ!ではなく、習慣化している きっかけ→ルーティン→報酬→きっかけ 習慣化を根付かせた例 歯磨きを習慣化させたアメリカの例 きっかけの創出 すぐできるような行動。(歯に舌で触れる) 即座に報酬を得られないと習慣化が難しい 虫歯がなくなって健康よりも、ミントで爽快感を得ることをアピール 技術探求を習慣化するには 情報収集チャネルを用意 X, はてなブックマーク, RSSリーダー, 会社のSlackチャンネル ルーティンを決める 習慣が途切れることを嫌に思わせる (損失回避バイアス) すぐに取りかかれるように環境を用意しておく 報酬を用意 いいねやアクセス数は報酬にしない 外発的動機づけになってしまう 自分でコントロールできるものが良い 記録を残す工程は自動化する 課題 情報過多・キャッチアップの限界 アウトプットの質と量のジレンマ どっちを優先すべきか わかりやすい記事が書ける理由 自分が一番わかっていないから 技術ブログを書くのは、自らの学習の手段 精通してから書くのではなくて、逆 試行錯誤しながら理解を含めていくライブ感 躓きやすいポイントを押さえられる 結論ファーストであるかは必ずしもそうではない 上司への報告ではないから コンテキストファーストで、導入部で読者の前提知識を揃える Stack Overflowがこの構成 Situation->Complication->Question (状況→複雑化→疑問) テンプレートを用意 導入部 課題定期 解決策 実装例 結論 参考文献 登壇経験が文章力を高めた AIに技術記事を書かせるのはどうか 学習の機会は失ってしまう 壁打ち相手として文書構成を検討 ラバーダッキング効果 文章校正 いきなり編集は任せない。出力フォーマットを指定しつつ、指摘のみにしておく 自分の知識を増幅する道具とすると良い 感想 自分がXで情報収集しているのも、普段の癖を活かせてるから良いのかなと思った 技術広報を推進するチームのメンバーとしては、各々の開発環境に技術ブログ執筆用のリポジトリをクローンしておいてもらうのが良いのかもと思った きっかけ作りに良いかも 執筆までの第一歩を下げられそう 今後の業務で使いそうなので、また読み直したい LT ghqの秘密 ghqが対応してるバージョン管理システムの話 感想 GoogleもPerforceだったんですねぇ 今はPiper 伝統的日本企業のソフトウェアエンジニアになって無双しよう! toCサービスたくさん 内製開発組織がない YAPC参加で認知拡大 感想 カンファレンスでスポンサーして認知拡大させるのは良さそう 企業スポンサーってそういう目的もあるよねと グッズでサンリオほしい 自社IPのグッズ良いなとなった スポンサーブース 全部回れました コンプリート景品の煎餅はお土産に ブースの方とも色々話せた 時間によっては登壇者の方も 懇親会 クラフトビールを出している店が有名らしいと聞いた ピーチのビールを飲んだ。美味でした 聞いてみた感じPerl書いたことない人や、使ってない会社も多めだった Ruby(on Rails)やPHP書いている人は結構見かけた 学生支援制度は素晴らしい パックマンルールで立っている方がちらほらいて、スッと会話に混ざりやすかった YAPCのセッションやAI、PerlやRubyは皆の共通の話題として話が広がりやすかった印象 登壇していた方や学生さんとも話せて良い学びになった 色んな人と話して、今後の業務に活かせそうなアイデアがたくさん浮かんだ 既に社内で提案してみたり もっと話したい! 全体の感想 以前お世話になった方にも久しぶりにご挨拶できてハッピー 福岡に住んでいる同僚や知り合いにも会えてハッピー Slidoで思いついたことをサクッと質問できるので楽だった lintエラー対応をsub agentにやらせてコンテキストを節約するの流行ってそう 社内にシェアしていきたい AIの話は多め 開発でAI使ってる話が多め。プロダクトに組み込むAIの話もあった Perl詳しくないから理解できるか不安だったけど、知らない人にもわかるような導入から始まるセッションが多くてわかりやすかった モバファクはPerlコミュニティ的には話せるネタを持ってたりするのかもしれない? 他のカンファレンスよりもホームのような雰囲気で参加できた 来年は東京。次は前夜祭から参加したい モバファクでは中途採用・新卒採用ともに絶賛募集中です。 会社の情報については、モバファクブログでも発信しています。 技術好きな方、モバファクにご興味をお持ちの方は、ぜひご応募ください! モバファクブログ: https://corpcomn.mobilefactory.jp/ 採用サイト: https://recruit.mobilefactory.jp/
アバター
駅奪取チームの id:kimkim0106 です。 モバファクでは全社での Gemini の導入、エンジニアへは GitHub Copilot と Cursor を導入など、AI を活用した生産性向上に継続的に取り組んでいます。 駅奪取チームにおいても、積極的に AI 活用を行っており、以前からコードレビュー業務の改善として PR-Agent を導入しています。 tech.mobilefactory.jp ですが、既存のツールにも問題点があったため、Claude Code を活用できないかと考え、検証を行いました。 プライベートでの開発に Claude Code をすでに使っているメンバーがいたことも理由の 1 つです。 新規であったり小規模なプロジェクトで活用できることは分かっていたものの、規模が大きく複雑なプロジェクトでの活用はやってみないとわからなかったため、検証を行うこととなりました。 検証内容 検証は、当初は 6 月から 2 か月間でしたが、効果があったため延長し、現在も検証を継続しています。 以下のような内容を検証しました。 どのような業務で活用できるか 今までのツールでも、単純な作業の代替はできていましたが、複雑なタスクをこなすことができるかを検証しました。とくに、大規模かつ長期運営プロダクトでの活用というのは業務でないと検証できないことです。 とくに、駅奪取は 2011 年からサービスを開始しており、14 年近く運営を続けている長期運営プロダクトです。 長期運営プロダクトでは、さまざまな技術的な変遷や歴史的な経緯があり、そうした複雑なコードベースでの活用というのは業務でないと検証できないことです。 費用対効果 導入するにあたっては、やはり効果があるという事実が後押しになるかと思います。 以前 GitHub Copilot の検証時も、検証時のアンケートから費用対効果がわかり、導入へとつながっています。 今回も、Claude Code 導入に当たってかかる費用に対する効果について、目標を定めました。 ただし、短期的には効果がなかったとしても、不具合が減るなどで中長期的に効果が出る可能性も考えていました。 また、AI コーディングツール導入によって逆に生産性が下がっているという記事も見かけたので、ここについてはしっかり検証してきたいと思っていました。 www.itmedia.co.jp Max 契約するメリットがどれぐらいあるか Pro プランや 100 ドルの Max プランだと、すぐにリミットに達してしまうため、できる限り多く検証できるように 200 ドルの Max プランにしました。 また従量課金の場合、上限がないため最大の費用が予想しづらかったのも理由の 1 つです。 ccusage を使えば従量課金だった場合のコストがわかるので、どれぐらい使っていたかを調査しました。 github.com 検証結果 業務でも活用することができ、また費用対効果もあったため、引き続き使用することとなりました。 業務での活用例 長期運営プロダクトの複雑なコードベースであっても、Claude Code を十分に活用することができました。 MCP サーバーがあれば、ドキュメントやチケットの情報も参照できるため、他のツールよりも自走力が高かったです。 モバファクで使っている Backlog や DocBase はいずれも公式で MCP サーバーが用意されています。 grep で置き換えるには複雑なものも、Claude Code にさせることができ、工数を削減することができました。 たとえば、ライブラリのバージョンアップでまとめて変更が必要な場面等で活用ができました。 タスク状況にもよりますが、普段のコーディング業務の大半を Claude Code にさせることができ、1 週間ほとんどコードを自分で書かないということもありました。 時間にも余裕が生まれたため、ユーザーには影響はないものの警告のログが出ている箇所の修正や、コミットフックの改善など、第二領域的なタスクにも着手ができるようになり、将来的な負債を防ぐことにも繋がりそうです。 ただしすべての業務で活用できるわけでなく、駅奪取チームでは定常業務の効率化は難しいことがわかりました。 すでに効率化ができていたのと、Google Workspace などの MCP 連携していないツールにある情報を参照する必要のあるタスクであったりするためです。 費用対効果 当初の検証期間である 2 か月間で、費用対効果があることを確認できました。 1 スプリント内で、エンジニアの消化するストーリーポイント(SP)が増加したうえ、検証前に設定した目標 SP を超える事ができました。 興味深いのは、Claude Code の性能低下が話題になっていた時期に SP が下がっていた点です。 見積もりの誤差も考えられますが、性能低下が業務にも影響があったのではと考えています。 Max 契約するメリットがどれぐらいあるか 従量課金に比べて、コスト削減が大きく、Max プランは必須とも言えるかと思います。 多い人は、1 か月で 600 ドル相当の使用をしていました。 ネクストアクション 引き続き、業務での活用を広げていきたいと思っています。 現在、Claude Code Action の検証も行っており、昨年導入した PR Agent を代替できないかというのを考えています。 Claude Code の場合、より広い範囲のコードを参照できるため、より深いコンテキストを必要とするレビューも可能になるのではないかと考えています。 また、他チームでの Claude Code の検証開始をきっかけに、チーム横断での情報共有の場として「AI 定例」ミーティングも生まれました。 今後も Claude Code に限らず AI コーディング周りの知見を全社で高めていきたいと思っています。 モバファクでは中途採用・新卒採用ともに絶賛募集中です。 会社の情報については、モバファクブログでも発信しています。 技術好きな方、モバファクにご興味をお持ちの方は、ぜひご応募ください! ・モバファクブログ: https://corpcomn.mobilefactory.jp/ ・採用サイト: https://recruit.mobilefactory.jp/
アバター
概要 参加を決めた理由 聴いた講演 オープニング キーノート webpack 依存からの脱却!快適フロントエンド開発を Viteで実現する Storybook 駆動開発で実現する持続可能な Vue コンポーネント設計 昼食 生成AI時代のWebアプリケーションアクセシビリティ改善 Inside Vitest: テストフレームワークアーキテクチャの詳細解説 Vue で 3D を楽しむ Vue.jsでつくる実験映像 AI駆動で進める依存ライブラリ更新 ─ Vue プロジェクトの品質向上と開発スピード改善の実践録 ライトニングトーク アウトプットから始めるOSSコントリビューション〜eslint-plugin-vueの場合〜 個人でデジタル庁のデザインシステムをVue.jsで作っている話 React Nativeならぬ"Vue Native"が実現するかも?新世代マルチプラットフォーム開発フレームワークのLynxとLynxのVue.js対応を追ってみよう chocoZAPサービス予約システムをNuxtで内製化した話 Introducing Vite DevTools Vue Quiz スポンサーブース 懇親会 感想 概要 こんにちは、駅メモ!開発チームエンジニアの id:hayayanai です! 2025/10/25 に開催された Vue Fes Japan 2025 の参加レポートです。 講演を聴いたり会場を見て回ったりして、業務で活かせないかな〜と考えたことを書き残しています。 スライドから得られた情報のメモと、感想が混じっています🙇 入口のオブジェ Vue Fes Japan 2025 - Vue Fes Japan 2025 - タイムテーブル 参加を決めた理由 ふと技術カンファレンスに行ったことないなと思い、どんな感じなのか興味が出ました。 イベント後に公開される資料を見ることはありますが、リアルタイムで聴くのとはまた違うかなと。 そんなときにVue Fesの開催情報を見かけました。 駅メモ!のフロントエンドでVue.jsを使っているのもあり、トレンドや社外の情報を知る良い機会になると考えました。 チームのSlackで情報を共有したところ、興味のあるエンジニアが数人集まったため、皆で参加することにしました。 さらに、自身が技術広報を推進するチームのメンバーでもあるので、技術イベントでスポンサーがどんな取り組みをしているかを見たいなと思いました。 聴いた講演 オープニング カッコいいカウントダウン映像でスタート Vue製ではなく 自作レンダラ だそう 来場者が800人超えとのこと いくつか部屋があるからかそこまで人数多いとは感じなかった 全ての発表で英語と日本語で文字起こし+同時翻訳がある キーノート Babel, gulpは古いという話 プロダクトから剥がしたいなと強く思った ナウいツールたちの話 Rust, Go製のツールが強くて高速 そしてVite Plus デファクトになってほしい気持ち ただエンタープライズ向けには有料だったり 今はOSS中心のツールを利用しているわけですが、開発部分で商業ツールに依存するのはどうだろう?という雑談をした Perforceは有償ですけど業界によってはGit同様に利用されているのだから、値段次第で選択肢には入ってきそうという気持ち リリースされたら試したいけど、先に古いものの整理か アーリーアクセスちょっと気になってる webpack 依存からの脱却!快適フロントエンド開発を Viteで実現する 10年という歴史があるところも含め、自分が関わっているプロジェクトと似た課題があったような印象 MPAで100以上のページが存在 HTMLテンプレートに対してcreateAppでVueコンポーネントをマウントするところ ViteのBackground Integration など、設計的にも今後に活かせそうな部分が多いと感じた ベンチマーク指標は以下とのこと ビルド時間 開発サーバー起動時間 HMR反映時間 Storybook 駆動開発で実現する持続可能な Vue コンポーネント設計 既にストーリーを作成する運用があった状態 ストーリーを書くのを実装の前に持ってきたという変更 ストーリー書くときは、コンポーネントはスケルトンでpropsのみ メリット props ファーストで整理される 実装忘れや認識ズレが消える 表の情報と、裏のロジックが整理される 設計やデータの流れが実装前に整理されそうなのが良さげに感じた。 昼食 お弁当などは無いので近くで食べることに (お弁当が出るタイプのカンファレンスもあるということを知る) 会場のビル 内でナンとダルカレーを頼んだ 土曜日だからかレストラン全然空いてなかった 😇 ナンとダルカレー 生成AI時代のWebアプリケーションアクセシビリティ改善 WCAGについて JISにも対応がある ちょっと古いから、今から対応するならWCAG 2.2を参考にすればOK デジタル庁のガイドブック も参考になる コード生成手法の比較 指示なし Zero-Shot: 配慮するように指示 Few-Shot: 正誤コード例を提示 Self-Criticism: 生成したコードをレビューし1回修正 LLM > 人間でアクセシブルなコードが作れる 特に指示しないほうが良い結果になった Few-Shotは一番悪かった AIに悪い例与えると逆に悪いコード書くみたいなこと、たまによくある。 過剰にアクセシビリティ情報を適用して違反が増えたことも 過剰なaltとか自分も見覚えあったので共感 WAI-ARIA対応 MCPサーバーを作ったとのこと https://github.com/yamanoku/aria-validate-mcp-server Chrome DevTools MCPより、Playwright MCPのほうがアクセシビリティのチェックには現状有用 Linterでもチェック可能 eslint-plugin-vuejs-accessibility Markuplint Inside Vitest: テストフレームワークアーキテクチャの詳細解説 Vitestの中身の話 Browser Modeだと実際にブラウザ上でテストが動くのを見られる Vitest v4でVRTもできるようになったし、どんどん便利になってる印象 isolationやpoolの話 今のところデフォルト設定で速度は困ってないなと 今後高速化考えるなら、今のうちに他のオプションで動くような書き方にするのもアリかも?と思ったりした Vue で 3D を楽しむ スライドに合わせて投票ができて、画面に反映される仕組み リアクションも送ることができて、上から絵文字が降ってくる 😄 投票画面 Vueで3D扱うなら…という紹介があって、TresJSでの実際の例に進んだ Understand 3D Scenes in Vue 型など開発する上で色々問題があったけれど… glTFのオブジェクトの例 それを解決するツールキットを作ったとのこと。 GitHub - toddeTV/gltf-type-toolkit: This plugin generates type-safe glTF file representations in TypeScript and optimizes the loading and bundling of models in web projects, while being bundler-agnostic (Vite, Rollup, Webpack, esbuild, Rspack, ...). デモの中で実際にFirefox, Chromeの違いが出ていて、動作確認の重要性がわかった 影やライティングなど、細かい部分で使い方は把握する必要がありそう パフォーマンスやバンドルサイズが気になるところ 基本2Dなプロジェクトに、ちょっとリッチな表現を行うために3Dを導入するとかあり? 聴講者とコミュニケーション取れる感じの発表、流行っていく? Vue.jsでつくる実験映像 映像の制作ツールをVue(PWA)で作っている UIツールも作っている 直接Vueを使う場面もある 目的達成のための手段としてのVue(プログラミング)の例をたくさん見られた カメラ活用含め、ブラウザやnodeで触れられるAPI増えてるなという印象 映像作ると聞いてProcessingで考えが止まっていたけど、もっと色々できるなと気付かされた AI駆動で進める依存ライブラリ更新 ─ Vue プロジェクトの品質向上と開発スピード改善の実践録 プロジェクトの進め方 AI時代の新人育成 ペアプロでどんなプロンプト書いてるか見るとか AIの活かし方 など、結構盛りだくさんだった Composition API化が全120ファイルで2週間 昔一度検討して実行しなかったことがあるけど、やってみて駄目だったらブランチごと捨てるというのも良いなと思った 最近のツールなら膨大な作業も完遂しやすそう 現在Claude Code使ってるのもあって、 tsumiki フレームワークが導入しやすそうだったので取り入れたいなと思う ライトニングトーク 下記以外にもいくつか聴きましたが、気になったものを少しピックアップ アウトプットから始めるOSSコントリビューション〜eslint-plugin-vueの場合〜 きっかけって大事だなと OSSで一個バグ見つけてるの思い出したから、PR作ってみようかなと思った 個人でデジタル庁のデザインシステムをVue.jsで作っている話 React+Tailwindしか存在しないと思ってたので、Vueのみで作られてるものがあるのは学び デジタル庁のデザインシステムを使っておけば、大きな間違いは無いよなという謎の信頼を持っている React Nativeならぬ"Vue Native"が実現するかも?新世代マルチプラットフォーム開発フレームワークのLynxとLynxのVue.js対応を追ってみよう React Nativeが羨ましいと思っていたので、Vueでnativeが書けるならとても欲しい 今後が気になるところ https://github.com/rahul-vashishtha/lynx-stack/tree/lynx-vue-implementation/packages/vue chocoZAPサービス予約システムをNuxtで内製化した話 戻るボタン対応の話が、共感の嵐だった あれは大変…。 自前で履歴管理のスタックを用意したとのこと Introducing Vite DevTools 満員で入れず。そんなことあるんだ Vue Quiz 大敗北 WatchEffectとflushオプションは何かに使えるかも? 自分の経験したプロジェクトもそうだけど、reactiveじゃなくてref使ってる方が多いみたい? スポンサーブース 主に昼休憩で回ったからか結構混んでた。 時間の関係でくじ引きまでできず…。 見かけた出し物: Vueクイズ バグを見つけよう! くじ引き ウェブアプリ Xのフォローやポストで何かもらえるよ系 2次元コードだけじゃなくて、NFCタグでXアカウントに飛べるところも シール貼ってアンケート 付箋書いてボードに貼るタイプ ビラ配布 スポンサー繋がりで気付いたこととしては、企業Tシャツ着て講演聴いてる方もいらっしゃったなと。 頂いたラムネを食べながらこのドキュメントを書き上げました。 懇親会 0回戦敗退。 チケットは早めに取ろう。 Vue FesのXもフォローしたし来年は大丈夫なはず…! 感想 Viteとその周辺がトレンド スポンサーしている会社は記憶に残った 自分はインプットもアウトプット、どっちも足りてない もう少し体系的に学んでいきたい 登壇できるまでの心理的・技術的ハードルを超えていきたい 会場が東京駅周辺で便利 オフラインの技術イベント初参加だったけれど、しっかり楽しめた メモを取る端末どうしようかなという悩みは生まれた 登壇者と話す時間も確保されていて、色々質問できて良かった オンラインでも質問できることはあるけど、直接話せるのはやはり良い モバファクでは中途採用・新卒採用ともに絶賛募集中です。 会社の情報については、モバファクブログでも発信しています。 技術好きな方、モバファクにご興味をお持ちの方は、ぜひご応募ください! モバファクブログ: https://corpcomn.mobilefactory.jp/ 採用サイト: https://recruit.mobilefactory.jp/
アバター
駅メモ!開発基盤チームの id:xztaityozx です。 今回は駅メモ!で利用している GitHub Actions の監視について書こうと思います。 前提 駅メモ!チームでは CI/CD 環境として Amazon EC2 を用いた Self-Hosted な GitHub Actions を構築しています。Webhook をトリガーに EC2 インスタンスが起動されるため、開発者は特に意識することなく CI/CD を利用することができるようになっています。 しかしながら、ノーメンテナンスで運用できるというわけではありません。日々 EC2 で使うイメージ(AMI)や IaC の更新、ワークフローの修正・改善などを行っています。CI/CD が速いことは開発者それぞれに大きなメリットとなるため、パフォーマンス改善は特に重要なタスクです。 こういった運用を行う上で重要になるのが、GitHub Actions の監視です。ワークフローの実行時間や成功率などを継続的に監視することで、問題の早期発見や改善効果の把握が可能になります。 監視の構築 先述の通り、GitHub Actions のパフォーマンス測定のためにワークフローの実行時間、EC2 インスタンスが起動してからジョブが開始されるまでの時間、ワークフローの成功率などを収集しなければなりません。これらの情報は GitHub Actions に関する Webhook のペイロードを収集・解析することで得ることができます。例えば、 workflow_run はワークフローの実行に関する情報を提供してくれます。 // 例 { " action ": " requested ", " workflow_run ": { " id ": 123456789 , " created_at ": " 2025-05-01T12:00:00Z ", " updated_at ": " 2025-05-01T12:05:00Z ", " status ": " requested ", // ... } , } action プロパティはざっくり言うとワークフロー全体の状態を示している値です。 requested , in_progress , completed の 3 種類が存在し、それぞれの状態へ移行したときに Webhook が送られます。ここからワークフロー実行がリクエストされた時間、開始された時間、完了した時間がわかるため、ワークフローの実行時間やキューイング時間を計算できます。 他にも workflow_job イベントの workflow_job.steps プロパティから、各ステップごとにどれくらい時間がかかったかを知ることができます。 // 例 { " action ": " completed ", " workflow_job ": { " steps ": [ { " name ": " Checkout ", " status ": " completed ", " conclusion ": " success ", " number ": 1 , " started_at ": " 2025-05-01T12:00:10Z ", // 開始時間 " completed_at ": "2025-05-01T12:00:20Z" // 完了時間。↑との差分がステップの実行時間 } , { " name ": " Run tests ", " status ": " completed ", " conclusion ": " success ", " number ": 2 , " started_at ": " 2025-05-01T12:00:21Z ", " completed_at ": " 2025-05-01T12:04:50Z " } ] } } これらの情報を計算するためには、一旦ペイロードを保存し、後で解析するようにします。駅メモ!チーム内にはこのような仕組みが既にあります。それは以前の記事で紹介した開発メトリクスのダッシュボードです。 tech.mobilefactory.jp 詳しくはぜひ上記の記事を読んでいただきたいのですが、簡単に説明すると、API Gateway + Lambda + S3 でデータを収集し、EC2 上の Elasticsearch + Grafana で可視化を行うというものです。Elasticsearch へのデータ投入は EC2 上で行っています。 Elasticsearch にデータを載せてしまえばあとは好きなように可視化するだけです。作成されたダッシュボードは週 1 回のペースでチーム全体に共有され、改善点があれば都度対応しています。 まとめ 今回は GitHub Actions の監視ダッシュボードを構築している話を書きました。これにより、CI/CD のパフォーマンスを継続的に監視し、問題・異常・改善の効果を把握しやすくなりました。私自身も活用の場面がかなり増えてきています。 今後はまだ収集できていない情報(CPU, メモリ利用率など)の追加、ダッシュボードの確認の仕方など、改善を続けていきたいと思います。また何か知見があったら記事にしたいと思います。 余談 開発メトリクスダッシュボードの記事とこの記事の両方を読むと時系列がややおかしいので補足です。 実は GitHub Actions の監視ダッシュボードは開発メトリクスのダッシュボードを作成する前に作られていました。実験的に実装したものでしたが、ある程度うまく動いていたことから開発メトリクスダッシュボードの構築時に参考にされました。 今回、開発メトリクスダッシュボードの出来が良かったこと、複数のダッシュボードがあると管理が面倒ということで、古い実装を廃止し開発メトリクスダッシュボードに統合したという背景でした。
アバター
対象のエラー XcodeからアプリをApp Store Connectにアップロードする際、以下のエラーに遭遇することがあります。 Invalid XXX icon. The XXX icon in the asset catalog in 'XXX.app' can't be transparent or contain an alpha channel これは配信しようとしているアプリのアイコンに透明度情報(アルファチャンネル)が含まれている際に発生するエラーです。 このエラーはValidate Appを実行することで検知できます。 Validate Appで検知できると何が嬉しいのか 自分が所属するチームでは以下のようなリリース作業フローを採用していました。 アイコン変更 → 動作確認 → レビュー → Distribute App このフローでリリース作業をする場合、最後のDistribute Appの段階でエラーが発生するとアイコン修正をしてやり直す必要があります。 また、特定の日時にアイコン変更をリリースしたい場合、レビュー通過からDistribute Appまで期間が空き、エラーに気づく頃には時間の余裕がなくなっているかもしれません。 以下のようにアイコン変更の直後にValidate Appを実行することでエラーを事前に検知し、手戻りを防ぐことができます。 アイコン変更 → Validate App → 動作確認 → レビュー → Distribute App Validate Appの実行方法 Xcodeのメニューから Product > Archive を選択し、アプリをアーカイブする アーカイブが完了すると表示されるOrganizer画面で、対象のビルドを選択し Validate App ボタンをクリックする 表示されるダイアログに従って、Validate を実行する アイコンにアルファチャンネルが含まれている場合、この段階でエラーが検出されます。 この際、アルファチャンネル以外の項目もチェックされるため内容によっては無視しています。 例えばApp Store Connectにすでにアップロード済みのバージョンでValidate Appを実行すると以下のようなエラーが出ますが、画像のチェックだけしておきたいケースでは無視で問題ありません。 おわりに 今回はアルファチャンネルエラーがValidate Appで検知できることを紹介しました。 XcodeのValidate Appが具体的にどのようなチェックをするのか公式ドキュメントに記載されていないため、チーム内で検証した内容を記事にしてみました。 では、良いXcodeライフを!
アバター
駅奪取チームでエンジニアをしている id:kebhr です。 大きな git リポジトリで git コマンドを実行した際、OOM Killer によって git プロセスが強制終了される問題に遭遇しました。その原因と対処法について共有します。 TL;DR git maintenance の自動実行が原因で OOM が発生する場合は、以下のコマンドで無効化できます。 git config --local maintenance.auto false 背景 私たちのチームでは、画像や JS/CSS などの静的ファイルを専用の EC2 インスタンス (t3a.small) から配信しています(以降、「static インスタンス」と呼びます)。これらの静的ファイルは GitHub 上のリポジトリで管理されており、画像ファイルを含むため、リポジトリのサイズは大きくなっています。 static インスタンスは静的ファイルの配信に特化しており、通常は nginx が動作するだけなので、コストを抑えるために t3a.small(メモリ 2GB)という小さなインスタンスタイプを選択しています。 運用フローは以下の通りです: インスタンス起動時:ユーザーデータで GitHub から最新のリポジトリを git fetch 運用中:本番環境への変更を rsync で同期(git は使用しない) 問題の発生 2025 年 4 月頃から、static インスタンスの起動時に git プロセスが OOM Killer によって強制終了される事象が発生するようになりました。 Apr 9 10:05:02 ip-10-55-5-153 kernel: [ 268.544103] Out of memory: Killed process 1965 (git) total-vm:3838224kB, anon-rss:1392756kB, file-rss:536kB, shmem-rss:0kB, UID:5000 pgtables:3928kB oom_score_adj:0 原因の調査 当初、git fetch が失敗していると考えましたが、実際には git fetch は正常に完了していました。そのため、別のプロセスが原因と考えました。 開発環境で再現させながらプロセスツリーを監視したところ、git fetch の後に自動実行される git maintenance のサブプロセスが OOM の原因であることが判明しました。 2246 ? Ss 0:00 /usr/lib/git-core/git maintenance run --auto --no-quiet --detach 2247 ? S 0:00 \_ /usr/lib/git-core/git gc --auto --no-quiet --no-detach 2251 ? S 0:00 \_ /usr/lib/git-core/git repack -d -l --cruft --cruft-expiration=2.weeks.ago --keep-pack=pack-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.pack 2252 ? Sl 1:56 \_ /usr/lib/git-core/git pack-objects --local --delta-base-offset .git/objects/pack/.tmp-2251-pack --keep-true-parents --honor-pack-keep --keep-pack=pack-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.pac git maintenance について git maintenance は、リポジトリへの書き込み操作(fetch、commit、merge、rebase など)の後に自動的に実行される最適化処理です。リポジトリのパフォーマンスを維持するために、不要なオブジェクトの削除やパックファイルの再編成を行います。 しかし、大きなリポジトリでは pack-objects プロセスが大量のメモリを消費し、メモリ容量の小さいインスタンスでは OOM を引き起こす可能性があります。 対処法 git の maintenance.auto オプションを false に設定することで、自動実行を無効化できます。 git config --local maintenance.auto false この設定により、git fetch などの操作後も git maintenance は実行されなくなります。 まとめ メモリ容量が限られた環境で大きな git リポジトリを扱う場合、git maintenance の自動実行が OOM の原因となることがあります。特に、今回のケースのように「起動時に一度だけ git fetch を実行し、その後は git を使用しない」という運用では、maintenance.auto を無効化しても実用上の問題はありません。 ただし、この対処法は以下の条件下でのみ有効です: git fetch や git pull を定期的に実行しない環境 リポジトリの更新を git 以外の方法(rsync など)で行う環境 通常の開発環境や、定期的に git 操作を行う環境では、メンテナンスの無効化は推奨されません。その場合は、メモリに余裕のあるインスタンスタイプへの変更や、定期的な手動メンテナンスの実施を検討してください。 モバファクでは中途採用・新卒採用ともに絶賛募集中です。 会社の情報については、モバファクブログでも発信しています。 技術好きな方、モバファクにご興味をお持ちの方は、ぜひご応募ください! ・モバファクブログ: https://corpcomn.mobilefactory.jp/ ・採用サイト: https://recruit.mobilefactory.jp/
アバター
はじめに 駅奪取チームの id:konakawa です。 モバイルファクトリーでは、前年度と今年度の新卒で行う新卒同期勉強会というものがあります。 この会は参加者を何人かずつのグループに分けて行うのですが、その組み合わせについて 毎回同じ人と一緒になっている気がする 職種が偏ってしまうことがある 社会人年数も偏らないようにしたい といった偏りの問題を抱えていました。 これまでは人力で頑張って組み合わせを作っていましたが、上記条件を可能な限り満たす組み合わせを考えようとすると非常に手間がかかります。 去年は AI に組み合わせの作成をお願いしましたが、偏りの問題をうまく解決することは難しそうでした。 今回はこの会の組み合わせを CP-SAT ソルバー の力を借りて作成した話になります。 問題について この問題は Social Golfer Problem (SGP) と呼ばれる問題に非常に似ています。 SGP とは、簡単には以下のような問題です。 何人かのゴルファーが、複数週にわたって定期的にゴルフをプレイする。各週、全員がいくつかの同じ人数のグループに分かれてプレイする。このとき、同じペアのゴルファーが 2 回以上同じグループでプレイしないようなスケジュールを組みたい。 SGP は SAT (satisfiability problem, 充足可能性問題) と呼ばれる問題に帰着できることが知られており、SAT は NP 完全であることが示されています。 すなわち、SGP は 解を見つけることは多項式時間でできるかわからない 解の候補が実際に解かどうか検証することは多項式時間でできる ような問題です。 「解の候補が実際に解かどうか検証することは多項式時間でできる」ため、この「解の候補」の探し方を上手くやれば、現実的な時間で解の 1 つが見つかるかもしれません。 一方、新卒同期勉強会の組み合わせを考える問題は、これに制約を加えて拡張したものと捉えられそうです。 今年度は、満たしたい制約を以下のように定めました。 任意のペアが同じグループになる回数を均等にする グループの全員が同じ職種にならない 25 卒が 3 人以上同じグループにならない 任意の 3 人組が 2 回連続で同じグループにならない 任意の 2 人組が 3 回連続で同じグループにならない 「開催回」「参加者」「グループ」をそれぞれインデックスにもち、ある開催回である参加者があるグループに属するかどうか表す真偽値型配列 assignments[times_meeting][member][group] を用意してやれば、上記の制約は全て assignments を用いた線形な不等式で書くことができます。 例えば、任意の 3 人組が 2 連続で同じグループにならないことは # 参加者1・参加者2・参加者3が1回目と2回目の勉強会でグループ1にならない assignments[1][1][1] + assignments[1][2][1] + assignments[1][3][1] + assignments[2][1][1] + assignments[2][2][1] + assignments[2][3][1] < 6 , # 参加者1・参加者2・参加者3が1回目と2回目の勉強会でグループ2にならない assignments[1][1][2] + assignments[1][2][2] + assignments[1][3][2] + assignments[2][1][2] + assignments[2][2][2] + assignments[2][3][2] < 6 , ... のような線形不等式で表すことができます。 真偽値の変数のみからなる不等式制約を充足する解が存在するか という問題を 0-1 ILP (Integer Linear Programming) と呼びます。 任意の SAT の問題は 0-1 ILP に帰着できることが知られており、 SGP を真偽値の線形不等式制約で拡張した今回の問題は 0-1 ILP に属すると言えそうです。 0-1 ILP も NP 完全であることが知られており、すなわち上手く解の候補を探すことができれば現実的な時間で解が見つかるかもしれません。 制約プログラミング このような問題を解決するプログラミングパラダイムとして、制約プログラミング (CP, Constraint Programming) が存在します。 制約プログラミングは、問題を変数とその変数の制約という形で記述し、その制約を満たす解を見つける手法です。 記述した制約を専用のソルバーに渡すと、「上手く解の候補を探す」部分をやってくれます。 今回は Google が提供している CP-SAT ソルバー を使用しました。 CP-SAT ソルバー は C++, C#, Java, Python に提供されていて、今回は Python で制約プログラミングを行いました。 CP-SAT ソルバーを用いた制約プログラミングでは、比較的直観的に制約を記述していけます。 先ほど線形不等式の例で取り上げた、任意の 3 人組が 2 連続で同じグループにならない制約を用いて、コード例を記載します。 from ortools.sat.python import cp_model model = cp_model.CpModel() TOTAL_MEETINGS = 10 TOTAL_GROUPS = 3 GROUP_SIZE = 4 # name は仮名 MEMBERS = [ { 'name' : 'ito' , 'role' : 'planner' }, { 'name' : 'kato' , 'role' : 'developer' }, { 'name' : 'kobayashi' , 'role' : 'designer' }, { 'name' : 'nakamura' , 'role' : 'planner' }, { 'name' : 'saito' , 'role' : 'developer' }, { 'name' : 'sato' , 'role' : 'designer' }, { 'name' : 'suzuki' , 'role' : 'planner' }, { 'name' : 'takahashi' , 'role' : 'developer' }, { 'name' : 'tanaka' , 'role' : 'designer' }, { 'name' : 'watanabe' , 'role' : 'planner' }, { 'name' : 'yamada' , 'role' : 'developer' }, { 'name' : 'yoshida' , 'role' : 'designer' }, ] TOTAL_MEMBERS = len (MEMBERS) # 論理変数: assignments[meeting_idx][member_idx][group_idx] = その回でそのメンバーがそのグループにいるかどうか assignments = [[[ None for _ in range (TOTAL_GROUPS)] for _ in range (TOTAL_MEMBERS)] for _ in range (TOTAL_MEETINGS)] for meeting_idx in range (TOTAL_MEETINGS): for member_idx in range (TOTAL_MEMBERS): for group_idx in range (TOTAL_GROUPS): assignments[meeting_idx][member_idx][group_idx] = model.NewBoolVar(f 'a_{meeting_idx}_{member_idx}_{group_idx}' ) # 任意の3人組が2連続で同じグループにならない for member_i in range (TOTAL_MEMBERS): for member_j in range (member_i + 1 , TOTAL_MEMBERS): for member_k in range (member_j + 1 , TOTAL_MEMBERS): # 連続するミーティングをチェック for meeting_idx in range (TOTAL_MEETINGS - 1 ): for group_idx in range (TOTAL_GROUPS): # 今回のミーティングで3人が同じグループにいる current_meeting_trio = model.NewBoolVar(f 'trio_m{meeting_idx}_g{group_idx}_{member_i}_{member_j}_{member_k}' ) model.AddBoolAnd([ assignments[meeting_idx][member_i][group_idx], assignments[meeting_idx][member_j][group_idx], assignments[meeting_idx][member_k][group_idx] ]).OnlyEnforceIf(current_meeting_trio) model.AddBoolOr([ assignments[meeting_idx][member_i][group_idx].Not(), assignments[meeting_idx][member_j][group_idx].Not(), assignments[meeting_idx][member_k][group_idx].Not() ]).OnlyEnforceIf(current_meeting_trio.Not()) # 次回のミーティングで3人が同じグループにいる next_meeting_trio = model.NewBoolVar(f 'trio_m{meeting_idx+1}_g{group_idx}_{member_i}_{member_j}_{member_k}' ) model.AddBoolAnd([ assignments[meeting_idx + 1 ][member_i][group_idx], assignments[meeting_idx + 1 ][member_j][group_idx], assignments[meeting_idx + 1 ][member_k][group_idx] ]).OnlyEnforceIf(next_meeting_trio) model.AddBoolOr([ assignments[meeting_idx + 1 ][member_i][group_idx].Not(), assignments[meeting_idx + 1 ][member_j][group_idx].Not(), assignments[meeting_idx + 1 ][member_k][group_idx].Not() ]).OnlyEnforceIf(next_meeting_trio.Not()) # 両方が同時にTrueになることを禁止 model.AddBoolOr([ current_meeting_trio.Not(), next_meeting_trio.Not() ]) このような形で一通り制約を記述して、ソルバーに解を探してもらうと、数秒から 1 分程度の現実的な時間で上記制約を全て満たす組み合わせを見つけることができました! === 第1回 === - グループ1: yamada (developer), yoshida (designer), watanabe (planner), kato (developer) - グループ2: tanaka (designer), ito (planner), saito (developer), kobayashi (designer) - グループ3: suzuki (planner), takahashi (developer), sato (designer), nakamura (planner) === 第2回 === (以下省略) 制約の追加 この方法であれば途中で条件が変わっても、制約を追加して再度問題を解くことで解決することができます。 例えば、初回の勉強会を実施した後に、トータルの開催回数が増えたとします。 この場合、「初回の組み合わせが特定のものである」という制約を追加した上で、開催数を増やして再度プログラムを実行することで、既に開催されたものを考慮した上で全体の偏りをなくした組み合わせを見つけることができます。 また、司会やタイムキーパーの回数を均等にするなど、更に制約を追加して拡張することもできそうです。 まとめ 毎年組み合わせの作成が課題になっていた勉強会について、制約プログラミングで問題を解決し、最適な組み合わせを見つけることができました。 制約プログラミングの手法であれば、将来的な拡張も行える上、機械に組み合わせ作成を任せるので時間もかかりません。 似たような問題に遭遇したら、ぜひ制約プログラミングのことを思い出してみてください!
アバター
こんにちは、駅メモ!チームの id:charines です。 今回は駅メモ!のデータ管理におけるフィクスチャ関連の改善の事例を通じて、駅メモ!チームの改善業務への取り組みを紹介します。 課題の背景と目的 駅メモ!ではゲームに必要なマスターデータをフィクスチャファイルとして管理しており、開発環境での書き出しと本番環境での読み込みによって日々データの更新を行なっています。 しかしサービスも開始 10 年を超え、定期開催されるガチャやイベントを始めとしたデータが肥大化していき、フィクスチャの書き出しがタイムアウトするなど更新業務に支障をきたす場面が増えてきていました。 これを解決するために、今後更新予定のない古いデータをアーカイブし、読み書きの対象外とする実装を行いました。 実装 核となる実装はシンプルで、フィクスチャ管理に利用しているライブラリの処理に、管理対象のレコードを限定するような処理を追加しています。 具体的には Perl のモジュール DBIx::Fixture::Admin のクラスを継承し、アーカイブ機能のための追加の実装を行いました。 DB 上のデータを取得する処理を含むプライベートメソッド _dump_yaml において、 + my $active_record_conditions = $config->{active_record_conditions}; + if (exists $active_record_conditions->{$schema}) { + $sql .= ' WHERE ' . $active_record_conditions->{$schema}; + } my $rows = $dbh->selectall_arrayref( $sql, +{ Slice => +{} } ); のように config ファイルから取得した条件をクエリとして差し込むことで、アーカイブ範囲でないレコードのみが対象となるようにしています。 (クエリ文字列を直接連結しているためプロダクトに使うには安全でない実装ですが、今回は社内システム向けの実装なので簡易的なものにしています。) config ファイルには +{ # `テーブル名 => 条件` の形で記載する active_record_conditions => +{ # 10 と 25 は常設のガチャ gacha => '`id` >= 1234 OR `id` IN (10, 25)' , }, } のように、一定以上新しいものや常設されているガチャをクエリの形式で記載しています。 この実装によって、単純なフィクスチャファイルの書き出しでは最も重いテーブルで 7.0 秒ほどかかっていたものを 3.7 秒ほどに短縮し、より複雑な処理が伴う箇所では 10 分以上かかっていたものを数秒まで短縮することでタイムアウトの問題を解決できました。 駅メモ!チームにおける改善タスクへの取り組み 駅メモ!チームにおけるデータの更新はエンジニアよりもプランナーが中心に行うことも多く、今回の改善タスクもプランナーの困りごとを技術的に解決した例の一つでした。 このように駅メモ!チームではエンジニアでは気づきにくい業務改善を取りこぼさないようにするために、プランナーとエンジニアが一緒に参加する改善タスクを共有するための場を設けたり、時にはエンジニアが普段プランナーが行う業務を代わって行い、エンジニア視点でないと気づきにくい改善点を探すなどの取り組みをしています。 他にもプランナーの業務効率化をいつまでにどの程度達成するか具体的に目標を定めるなど、チームとして積極的にエンジニア以外が対象の業務改善も進めることで、よりスムーズにミスのないコンテンツを作成できるよう継続的に取り組んでいます。 おわりに 今回は改善タスクとして取り組んだフィクスチャファイルのアーカイブ機能実装について、具体的な実装からチームとしての改善タスクの取り組み方までを紹介させて頂きました。 この記事が読者の皆様の技術的課題の解決や、改善タスクの取り組み方への参考の一つになれば幸いです!
アバター
駅メモ!チームでエンジニアをしている id:stakHash です。 開発活動に関わるデータを収集し、開発生産性を測るためのメトリクス(便宜的に「開発メトリクス」と呼びます)を可視化するための仕組みを作りました。 その目的や設計などについてまとめました。 どんなものを作ったのか なぜ作ったのか どう作ったのか どう使っているのか まとめ どんなものを作ったのか 開発生産性を測るためのデータを簡単に収集し、可視化するためのダッシュボードです。 画像はごく一部ですが、このようなグラフの形で各種メトリクスを見ることができます。 GitHub PR 関連のダッシュボード例 2025 年 6 月 16 日現在では、以下のようなメトリクスを可視化したダッシュボードがあります。 テスト所要時間の推移や、特に時間のかかっているテスト ビルド・デプロイ所要時間の推移 コード品質の推移 なぜ作ったのか GitHub リポジトリなどをベースに開発メトリクスを集計するような SaaS もありますが、今回は内製することにしました。 理由は大きく 2 点あります。 「スモールスタートしたかった」 こと、 「既に部分的にデータが収集されていた」 ことです。 Four Keys をはじめとした開発メトリクスは広く認知を得ていますが、駅メモ!チーム内では活用事例が少ない状態でした。新機能を追加するプロジェクトの範囲で計測されることがあったり、個人がスクリプトを書いて部分的に計算したりすることはありましたが、継続的に計測されているものは限られていました。 長期的な計測を改善活動に結びつける仕組み・文化づくりが必要だったため、最初から金銭的コストをかけて SaaS を使うという選択肢は取りづらかったのです。まず MVP を作成し、必要に応じて SaaS の導入を検討しよう、という流れになりました。 そんな中、自動テストの運用に関わるメトリクスは既に継続的に計測されていました。例えば GitHub Actions がランナーを掴むまでの時間、成功率、中断率などです。 この仕組みを参考にしつつ、より包括的なデータを収集できる仕組みを作ることにしました。 どう作ったのか 構成は非常にシンプルです。全て AWS 上でホストしています。 構成図 集計対象のデータは様々なワークフローに散らばっているため、どのような環境でも送信しやすいように HTTP リクエストで受け付けます。 リクエストボディは以下のような形式です。 { " type ": " metrics-type ", " payload ": { " key1 ": " value1 ", " key2 ": " value2 ", ... } , } 受けたリクエストの type を元に分類し、まず S3 バケットに格納します。 ここではまだデータの整形や集計は行わず、 payload をほぼそのままログの形で保存します。分析が進んでいく中で、求める集計方法は変化していくことが想定されるためです。生のデータを保持しておくことで、集計方法が改善されても再集計すれば良く、対応が簡単です。 S3 バケットに集められたログを可視化する仕組みは、 Elasticsearch と Grafana を利用しました。 類似のダッシュボードツールは他にも Redash や、検索エンジンとバンドルされた OpenSearch ダッシュボード等もありますが、今回は社内で利用実績のあった Grafana にしました。 Grafana ではブラウザ上からグラフィカルにダッシュボードを作成できます。 作成したダッシュボードは JSON 形式で出力できるので、これを Git 管理し、インスタンス起動時に terraform で構成しています。 ID の類やバージョン情報には、ダッシュボードを作成・編集する際に自動で割り当てられるものもあります。これらはスクリプトを書いて JSON から削除しておくと、 terraform で扱うときに無駄な差分が減って管理しやすいです。 resource "grafana_dashboard" "dev_metrics" { for_each = toset ( [ "dashboard-name" , ... ] ) config_json = file ( "$ { path.module } /dashboard-models/$ { each.value } .json" ) } 弊社では各エンジニアに利用する開発環境として EC2 インスタンスを提供しているので、今回は簡単のため Elasticsearch と Grafana をホストする EC2 インスタンスを用意しました。 EC2 インスタンスを起動したタイミングで、 S3 バケットに蓄積したログを取得・集計し、 Elasticsearch にロードします。 前回の起動時から未集計のデータが大量に追加されていると集計に時間がかかってしまうこともありますが、現在の運用上そこまで大量に積まれないため、これで問題ありません。 どう使っているのか 週に 1 回前後 EC2 インスタンスを起動し、最新のデータを取り込んで各種メトリクスの推移を確認しています。 直近 1 週間で大きな変化はないか、もしあればその原因は何が考えられるか、すぐに改善できるものか否かなどを検討し、必要に応じて改善タスクとして登録します。 こうして登録されたタスクは、他の改善タスクと合わせて毎週優先度や担当チームが割り当てられます。 定期的にチェックすることで、例えばランダムに失敗するユニットテストを見つけて修正したり、ESLint などのルールを調整したりといった改善に繋げられています。 その他にも、何か改善を行った際の効果測定にも利用することがあります。 まとめ 開発生産性を向上させ、ひいてはプロダクトを改善すること目的に、AWS の各サービスや Grafana を利用し、開発メトリクスを集計するダッシュボードを作りました。 もし「これから集計したいけど、SaaS の導入は難しい」というようなシーンがありましたら、設計の参考になれば幸いです。
アバター
はじめに こんにちは。駅メモ!開発チームの id:k-nishioka です。今回は、駅メモ!開発チームの 1 ユニットが 5 年間にわたって取り組んできた開発手法についてお話ししたいと思います。 アジャイル開発にスクラム開発の要素を取り入れながら続けてきた運用について、 最新のスクラムガイド(2020 年版スクラムガイド)を参照しながら行った取り組みについてご紹介します。 スクラムガイド 私たちのチームでの運用方法 私たちのチームは現在 11 人のメンバーから構成されています。 スクラムガイドによるとチームサイズは通常 10 人以下が推奨されています。 今回は、そのような状況でどのようにスクラムを効果的に運用してきたかについてまとめました。 デイリースクラムの実施 まず、日々の進捗確認としてデイリースクラムを活用しています。 進行を当番制で毎朝 15 分デイリースクラムを行い、チーム全員で進捗や課題を共有しています。 水曜日のスプリント切り替えサイクル スプリントは 1 週間ごとのサイクルで運営しており、毎週水曜日は次のスプリントへの重要な切り替え日となります。この日にはまず、1 時間半のスプリントレトロスペクティブを実施します。 その後の休憩を挟んで 1 時間はスプリントプランニングに充てられます。このプロセスでは、あらかじめスクラムマスターが選定したタスクに対し、プランニングポーカーを使って見積もりを行います。 また今後の開発スケジュールに関してはスプレッドシートで管理し一覧性を高めています。これと Backlog というタスク管理ツールを併用しており、これらが自分たちのプロダクトバックログになっています。 私たちはまとまった時間でスプリントレビューを実施していません。 スプリントレビューの目的はスプリントの成果を検査し、インクリメントを提示することでフィードバックや協力を引き出し、リスクを減らしプロダクトの価値の最適化を進めることです。 スプリントレビューの時間を細かく分割してタスクとして切り出し、動作確認の時間を確保しています。 スプリントレトロスペクティブでの議題 Figjamというオンラインホワイトボードツールを利用しリアクションの多いものや各個人が優先して話したいものを議題としています。 ここでは失敗だけでなく成功例も拾い、その両方からネクストアクションを立てるように意識しています。 私たちのアジャイル開発におけるメリット 1. 責任感の向上 各メンバーが交代でデイリースクラム・レトロスペクティブの進行役を務めることで、全員が進行を理解し責任感が生まれ、スムーズな運営が可能になっています。 2. モチベーションの向上 改善ばかりが話題にあがりがちな振り返りですが、成功例を共有することでチーム全体の士気が高まり、モチベーションを向上させています。 3. ミーティング時間の短縮 チーム規模が大きくなるにつれ、ミーティング時間が増加しやすいです。 そこで定常的な業務が多い私たちのチームではスプリントプランニングなら短縮できるのではないかと考えました。 実際にスクラムマスターがプロダクトオーナーと協議を事前に行い、タスクをあらかじめ選定しておくことで時間の短縮を図っています。 4. プロジェクトリスクの減少 タスクを細かく分割し、ゴールを明確に定めたうえで進行しているため、万が一大きな問題が発生した際も早い段階で差し戻しができ、早期のリカバリーを可能としています。 各タスクのゴールについては、定常的なものに関してはモブプロなどで定期的見直し、差し込みに関してはそのフローをあらかじめ決めておくことで明確にしています。 5. 効率的な優先順位付け プロダクトバックログを用いて期限や終了要件を設定することで、タスクの前後関係やその期限が把握できるため、優先順位が明確になっています。 私たちのチームでは差し込みのタスクが発生することもあります。 差し込みタスクの対応をするために、スプリント内の落とすタスクの選択・担当の変更を素早く決定することができます。 私たちのチームでの課題 スクラム開発を通じて様々な成果を上げてきましたが、いくつかの課題にも直面しています。 ミーティング時間の増加 私たちのチームでは、レトロスペクティブを月あたり 6 時間行っています。この時間はフルリモートでのコミュニケーションを促進する貴重な機会である一方、スクラムガイドの 3 時間を超える値となり作業時間が減少する要因となっています。 そこで進行を一定期間固定することによって、振り返りの質を落とすことなく時間の短縮を図っており、現在月あたり 1 時間の短縮に成功しています。 振り返りの質の不安定さ レトロスペクティブの進行を当番制にしているため、各回の振り返りの質にばらつきが生じます。特に、経験の浅いメンバーが進行を担当する際には、学びの機会となるものの進行スキルの向上が必要とされています。 こちらも進行を一定期間固定することで学びをすぐに活かせるよう図っています。 さいごに 今回はサイズが大きいアジャイル開発の運用についてまとめてみました。 現在も改善を続けており、またよりフレームワークに沿ったスクラム開発も実施予定のため、 いずれそれも記事にできると良いかなと考えています。 少しでも読まれた方のお力になると嬉しいです!
アバター
こんにちは、駅メモ!開発チームエンジニアの id:hayayanai です! 駅メモ!のフロントエンド開発では、Linter として ESLint や Stylelint、それらの Vue 関連のプラグインを導入しています。 これらの開発支援を利用していく中で、既存の ESLint ルールのエラー文だけを変えたいという話が上がりました。 一般的なエラー文を出すよりも、チームとしての方針を明記した方が対応しやすいということです。 一から ESLint のカスタムルールを作る方法については、調べてみると既に多くの情報がありました。 一方で、既存のルールを上書きする方法の情報は少なく、実装に苦戦しました。 そこでこの記事では、既存のルールのエラー文を上書きしたカスタムルールを作る方法を紹介します。 サンプルプロジェクト 今回は例として no-magic-numbers のルールを上書きします。 エラー文を日本語で出力しつつ、どういう風に直して欲しいかを記載するように変更します。 サンプルを ESLint Online Playground で見る 環境 ESLint v9.25.1 Flat Config を使用 全体の構成 . ├── eslint.config.mjs ├── rules │ ├── index.mjs │ └── no-magic-numbers.mjs └── src └── example.js rules/index.mjs import noMagicNumbers from "./no-magic-numbers.mjs" export default { rules: { "no-magic-numbers": noMagicNumbers, }, } rules/no-magic-numbers.mjs /* 上書きしたいルールの RuleModule を取得する */ import { Linter } from "eslint" const linter = new Linter({ configType: "eslintrc" }) // "flat" だと getRules の呼び出しでエラーになる const noMagicNumbersRuleModule = linter.getRules().get("no-magic-numbers") console.log(noMagicNumbersRuleModule) /* 上書きして export する */ export default { ...noMagicNumbersRuleModule, meta: { ...noMagicNumbersRuleModule.meta, docs: { ...noMagicNumbersRuleModule.meta.docs, url: "https://tech.mobilefactory.jp/", // 社内のドキュメント等に置き換えると便利 }, messages: { ...noMagicNumbersRuleModule.meta.messages, noMagic: "マジックナンバーは禁止です: {{raw}}。`MAGIC_NUMBER_` から始まる定数を使用してください", }, }, } 参考: console.log(noMagicNumbersRuleModule) の出力結果 { meta: { type: 'suggestion', docs: { description: 'Disallow magic numbers', recommended: false, frozen: true, url: 'https://eslint.org/docs/latest/rules/no-magic-numbers' }, schema: [ [Object] ], messages: { useConst: "Number constants declarations must use 'const'.", noMagic: 'No magic number: {{raw}}.' } }, create: [Function: create] } eslint.config.mjs import myRules from "./rules/index.mjs" /** @type {import('eslint').Linter.Config[]} */ export default [ { plugins: { "my-rules": myRules, }, rules: { // "no-magic-numbers": "off", // 既存のルールが有効になっているなら off にすると良い "my-rules/no-magic-numbers": "error", }, }, ] src/example.js let a a = 3.14 a = 299792458 解説 大まかに以下のような流れでルールの上書きを実現しています。 既存のルールを上書きするために no-magic-numbers の RuleModule を取得 取得した RuleModule の中の、カスタマイズしたい部分を変更 ルールとして export eslint.config.mjs でカスタムルールを import して有効化 (必要に応じて既存のルールを off にしてエラーが重複しないようにする) ポイントとしては以下の通りです。 Flat Config を使用していれば、カスタムルールのために別途プラグインの package を作るなどの手間が無くて簡単 RuleModule の export のされ方はプラグインによって異なるので、実装を読みつつ調整する必要あり meta.messages の key( messageId ) はルール毎に異なるので、 console.log 等で確認しながら対応するとスムーズ カスタマイズ後の動作確認 コマンドラインで実行 % npx eslint src/example.js 2 : 5 error マジックナンバーは禁止です: 3.14 。 `MAGIC_NUMBER_` から始まる定数を使用してください my-rules/no-magic-numbers 3 : 5 error マジックナンバーは禁止です: 299792458 。 `MAGIC_NUMBER_` から始まる定数を使用してください my-rules/no-magic-numbers ✖ 2 problems ( 2 errors, 0 warnings) Cursor(VSCode) 上の表示 Cursor 上の出力結果 my-rules/no-magic-numbers のリンクをクリックすると、 rules/no-magic-numbers.mjs 中の url に指定したページへ飛ぶことができます。社内ドキュメントなどをリンクとして設定すると便利だと思います。 Cursor に修正を指示すると、上書きした後のエラー文を読んで適切に提案してくれました。画像は claude-3.7-sonnet での結果です。 Cursor に修正を指示した結果 まとめ ESLint v9 の Flat Config 環境で、既存のルールの検出方法をそのままに、エラー文を上書きしたカスタムルールを作る方法を紹介しました。 エラー文章に対応方針を書いておくと、どう直して欲しいかをエンジニアや AI に伝えやすくなります。 皆さんもぜひ試してみてください。 冒頭、実装時に苦戦したと書きましたが、執筆時 GPT に聞いたらそれっぽい方針を示してくれました…。今後頼りになるケースが増えそうで、ワクワクしてきますね! 参考 https://eslint.org/docs/latest/extend/custom-rules https://eslint.org/blog/2022/08/new-config-system-part-2/#from---rulesdir-to-runtime-plugins
アバター
こんにちは。駅メモエンジニアの id:kawa-mf です。 アワメモ公式サイトで、Nuxt2からNuxt3に移行しました。 しかし、Nuxt3をAWS Serverlessにデプロイする際にLambdaの制限容量を超えてしまい、ECRを利用することで解決したので、こちらについて書いていきます。 経緯 移行以前より、Lambdaの制限容量である250MB中、240MB以上を使用していました Nuxt2からNuxt3から移行するにあたり、Lambdaの制限容量である250MBを超えてしまいました 元の構成は以下のようになっていました Nuxt3へ移行するにあたり、既存のパッケージの更新や置換を行いました 結果、Lambdaの制限容量を超えてしまいました (Nuxt2の時から、残り数MBみたいな状態が続いていて、制限容量を超えるのは時間の問題でした...) 容量を圧迫した主な原因としては、依存しているパッケージの更新などです Lambdaの制限容量が超えてしまい、回避策として候補に上がったものについて AWS Serverlessの利用を中止し、AWS Amplifyに移行する AWS Serverlessのv4から有料になるので、別のものに移行したいということから、候補に上がりました しかし、弊社の運用ポリシーの観点から、使用することができませんでした Elastic Container Registryの導入 元の構成を大きく変える必要がなかった 弊社の別のプロジェクトで導入経験があった 今後の拡張性を考えて、ECRを導入することにしました ECRを導入後の構成 上記の問題を解消するためにECR (Elastic Container Registry) を導入しました 導入した結果、以下のような構成に変わりました 元々、S3にはビルド後のファイルや画像などが配置されていました その全てをECRに移したので、S3は廃止し、ECRからこれらの情報を取得するように変更しました 実際に変更したコード アワメモ公式サイトのデプロイでは元から、CodeBuildを使用していました 今回は、CodeBuildからDockerのビルドも追加で行うように変更しました 以下では、Serverless.yml、Dockerfile、buildspec.ymlの変更点について説明します Serverlessの調整 変更前 functions: handler: name: function-handler handler: .nuxt/dist/serverless.handler # memorySizeなどの他の設定 変更後 functions: handler: name: function-handler package: packageType: Image image: 'ECRのurl:latest' # latestを指定 url: true 上記には記述していないのですが、S3を使用しなくなったので、それに関連するものを削除しました 他にも、esbuildの導入、functions内のeventsやCacheBehaviorsの調整といった細かい修正は行なっています Dockerfileの追加 こちらはECRの導入にあたり、新しく作成しました nuxt.config.tsで出力先のディレクトリは、lambda-distに変更しています FROM public.ecr.aws/lambda/nodejs:18 as base RUN npm install -g yarn@1.22.21 ENV NODE_ENV $NODE_ENV # ----- builder FROM base AS builder WORKDIR /work COPY ./package.json ./yarn.lock ./ RUN yarn install # Copy remaining files for build COPY . . # Build the application RUN yarn build # ----- app FROM base WORKDIR ${LAMBDA_TASK_ROOT} COPY --from=builder /work/lambda-dist ${LAMBDA_TASK_ROOT}/lambda-dist COPY --from=builder /work/.nuxt ${LAMBDA_TASK_ROOT}/.nuxt COPY --from=builder /work/node_modules ${LAMBDA_TASK_ROOT}/node_modules CMD ["lambda-dist/server/index.handler"] buildspecの調整 一部抜粋したものです ( $ から始まるのは環境変数です) phases: pre_build: commands: - aws ecr get-login-password --region ECRを置いているリージョン | docker login --username AWS --password-stdin $ACCOUNT build: commands: - yarn - docker build -t $ECR_IMAGE_NAME . # dockerビルド - docker tag $ECR_IMAGE_NAME:$ECR_TAG $ACCOUNT/$ECR_IMAGE_NAME:$ECR_TAG # ECRへpush post_build: commands: - docker push $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$ECR_IMAGE_NAME:$ECR_TAG - yarn global add serverless@3.38.0 - sls deploy --verbose - aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/*" まとめ AWS Amplifyは運用ポリシーの関係上、使用することができなかった ECRの導入 Lambdaの制限容量を気にする必要がなくなった 弊社で導入実績があったため、導入に際してセキュリティなど考えることが少なかった 今後は、Nuxt2からNuxt3へ移行するにあたり苦戦した点や、AWS Serverlessのv4から有料になるにあたり、対応したことについては機会があれば書こうと考えています。 ※ 一部の記述を訂正しました (2025/05/14 更新)
アバター
こんにちは、駅メモ!開発チームエンジニアの id:maeken2010 です 🙌 今回は ChatGPT と Raspberry Pi 5 を活用して自宅に NAS を構築した経験を共有します。ホームサーバー・NAS も初めてでしたが ChatGPT と一緒に構築ができました。 Raspberry Pi 5 先日、Raspberry Pi 5(以下、ラズパイ)を購入しました。ラズパイは言わずと知れたミニ PC です。YouTube でラズパイと電子ペーパーを組み合わせて天気ステーションを作成する動画を偶然視聴し、自分も挑戦したいと考えてラズパイと電子ペーパーを購入しました。 しかし、予定していた電子ペーパーの到着が遅れ、しばらくラズパイが手持ち無沙汰な状態に。このままラズパイがインテリアになるのは勿体なく、活用する方法を模索した結果ホームサーバーとしての利用を思いつきました。そこで目指したのが NAS の構築です。丁度家に余っていた HDD もインテリアになっていたので、再利用することにしました。 NAS とは? NAS(Network Attached Storage)とは、ネットワーク接続型ストレージのことを指します。PC やスマートフォンから、ファイルブラウザを通じてアクセスできるようにするためのものです。手元に余っていた HDD をラズパイに接続し、試行することにしました。 ラズパイとHDD NAS 構築の試行錯誤 ネットでググれば、ラズパイで NAS を構築するための情報は数多く見つかります。しかし、その過程で微妙な問題に直面しました。HDD は2台ありそれぞれ容量も違うので構成はどうするべきか、フォーマット形式はどれを選ぶべきか、バックアップの必要性、Mac の TimeMachine として使う方法など、自分のユースケースに最適な解決策が必要でした。 ChatGPT の活用 そこで、ChatGPT を使うことにしました。ChatGPT も言わずと知れた AI ツールです。ChatGPT を使うことで、上で挙げたユースケースに合う提案をしてくれることを期待しました。結果は予想以上に便利でした。 プロンプトについては特別なことはぜず、作業しながら疑問に思った事や躓いた事が出てきたら随時 ChatGPT へ質問しました。 カスタマイズされた提案 ChatGPT は、私のユースケースに合わせたカスタマイズした提案を行ってくれました。一度選択した情報を覚え、そのコンテキストに基づいて最適なアドバイスを提供します。 柔軟な対応力 設定作業を進める中で、同じ質問を何度も行うことがありましたが、その都度必ず回答してくれます。曖昧な聞き方をしても、コンテキストを踏まえた上で私が求める回答をしてくれるのは非常に助かりました。 信頼性の確認 ただ、ChatGPT が誤った情報を提示することも時折あるため、提案を受け入れる際には自身でもしっかりと確認を行う必要がありました。 例えば、CUI 上でのパーティション作成の操作手順ですが、この手順ではうまくいきませんでした。おそらく fdisk の仕様が OS によって異なるのかもしれません。今回は私物の Mac のディスクユーティリティでパーティション作成をしました。 プロジェクト機能 プロジェクト機能を活用することで、チャットログを体系的に管理できます。これにより、複数のチャットを 1 箇所にまとめることができます。 さらに、プロジェクトに対して ChatGPT の振る舞いを指示することができます。「ラズパイを使ってホームサーバーを構築しようとしている」ということを指示することで、プロジェクト上で新しくチャットを開始するとその指示を踏まえて回答してくれるようになります。 ちなみに、何も指示していない場合、なぜかテキストエディタは nano を使おうしてくるので、個人的に好きな vim を指定しました。 まとめ 今回初めて NAS を構築しましたが、ChatGPT のおかげで意外と簡単に進めることができました。 Mac からの接続も問題なく、TimeMachine でのバックアップも可能になりました。 新しいことに挑戦するときって、ちょっとした不安や疑問がつきものですが、信頼できるツールがあると気持ちが楽になりますね。 ChatGPT は技術的なサポートをしてくれるだけでなく、まるで相棒のように一緒にプロジェクトを進めてくれました。特に、カスタマイズされた提案があるおかげで、自分のペースで安心して作業を進められました。 「ChatGPT と一緒なら、新しいことに挑戦しやすい」と思いました。これからも ChatGPT を活用しながら、新しいチャレンジを楽しんでいきたいと思っています。次はどんなプロジェクトに取り組もうか、今から楽しみにしています 💪
アバター
はじめに モバファク 24 卒エンジニアの id:knj-mf です。 記事が出る頃にはすっかり 2025 年となってしまいましたが、仕事を始めて最初の年というのはかなり大きいものでした。 今では仕事にもある程度慣れてきましたが、就職前や入社直後の時期では、新卒としては働くことに関して漠然と「よく見えないなあ」という感覚があったことを覚えています。 そこで、5 人のモバファク新卒エンジニアそれぞれの視点で「何を通じて」「何を見て」「何を考えたか」を直に伝えることに価値があると考えました。 本記事では、新卒エンジニアが入社からの半年間で経験したことについて、5 人全員の視点でレポートしています。 これから入社を考えている人、また新入社員を受け入れる人にとっても「新卒から見える世界」を伝えられると良いなと思っています。 また、技術ブログはアウトプットの意味も大きく、我々の経験も兼ねています。win-win となることを期待しています。 24 新卒エンジニアの配属 (執筆時点) 24 卒エンジニアの 5 人 はそれぞれ業務範囲の異なる、別のチームに配属されました。この記事の執筆時点で下記のチームの配属になっています。 駅奪取チーム (konakawa) 駅奪取について、インフラからフロントエンドまで広範に携わる 駅メモ!・開発チーム・EVENT (oshima) 駅メモ!のイベント関連 駅メモ!・開発チーム・DUEL (yang) 駅メモ!のバトル関連 駅メモ!・開発チーム・SCALE (kinjo) 駅メモ!全体の体験拡大 駅メモ!・開発基盤チーム・native (r-hayashi) ストアで配信されるアプリ関連 書くこと 5 人の新卒エンジニアがそれぞれセクションを持ち、それぞれの視点・それぞれの書きたい流れで、本配属後について書いていきます。 形式を厳密に定めることはせず、各自の考え方を元にした書きたいこと・伝えたいことを優先しています。 新卒メンバー全体での新人研修、またはエンジニア全体での新人研修については今回の記事では触れません。 同じモバファクといえど、配属が違えば仕事の内容も変わってきます。新人研修以降それぞれの業務内容や体験が分岐してからのレポートということにしました。 新人研修についての記事はこちらです。 https://corpcomn.mobilefactory.jp/archives/7546/ https://tech.mobilefactory.jp/entry/2024/11/06/160000 駅奪取 konakawa の半年間 私は駅奪取チームに配属されました。 駅奪取チームは駅メモ!チーム全体と比べると少人数のチームで、企画側も開発側も個々人が手広くタスクを行う体制になっています。 私も配属してから幅広く業務を任せていただき、様々な経験をさせていただいています。 ここではその中の主要なものを取り上げて紹介し、また、チームや会社の雰囲気についても私の受けた印象を紹介したいと思います。 新人タスク 共通の新人研修を終え、駅奪取チームに配属されて最初は、ゲームそのもので遊んでみたりコードを簡単に追ってみたりする新人課題を行い、プロダクトを一通り知るところから始まりました。 その後は「新人向けタスク」という形で比較的易しいタスク、プロダクトの文脈が浅く取りかかりやすいタスクや、ユーザに直接影響の出ない管理画面の改修などのタスクがまとめられており、そのタスクを進めていくことで、チームでの業務に少しずつ慣れていくことができました。 コーディングの際は周辺のコードの書き方を模倣しつつ進めていましたが、駅奪取は 13 周年を迎えたプロダクトで歴史が長いため、現在では非推奨とされている書き方のコードが残っていたりして辛かったのを覚えています。 そういったものは適宜リファクタリングしながら進めていましたが、なぜ非推奨なのか、なぜ A ではなく B の書き方の方が良いのか、先輩方に助言を頂きながら試行錯誤することは、最初のステップとして良い学びの機会となりました。 そういった機会があったからこそ、未来の誰かが困らないようなコードを書こうという意識を今は強く持つことができています。 また、新人向けタスクを進める中で表示や仕様など企画側の方と相談する必要がある機会は多々あり、チーム内でのコミュニケーションという点でも必要な経験を積むことができました。 総じて、チームの受け入れ体制として、階段の一段目がしっかり用意されていて、経験が浅いところからでもジョインしやすい印象がありました。 プロジェクトへの参加 新人タスクを進めて業務に慣れてきた頃、駅奪取で新イベント形式の開発が進んでおり、そのプロジェクトのサポートに入らせていただきました。 新人タスクのように文脈が浅めで単発のものでないタスクに取り組むのはそのタイミングが初めてで、かつ、既にあるイベントの仕組みに変更を入れる内容でそれまでよりも込み入っていたので、はじめは想定よりも時間がかかったりして苦戦した記憶があります。 13 年続く巨大なコードベースに変更を加えるというのに慣れていなかったのも大きかったように思います。 すでにある仕組みの上に新しい仕組みを入れようとすると考慮漏れは起きがちになるので、不具合を起こさないよう、加えようとしている変更の影響範囲を調べたり、なるべく安全な変更にしたりすることに苦心していました。 ここで、そもそも変更しやすい仕組みにすること、負債が出ないような実装をしておくことを意識しようと身をもって学習できました。 現在進めているプロジェクトでもこの経験から、拡張性を持たせた実装を考えるように意識しています。 今振り返ると、早い段階でプロジェクトに参加する機会をいただき、そこで経験を積めたことは自身のスキルアップに大きく寄与したと思います。 そういった機会があれば、すなわち、やらなきゃいけなくなった時が、結局一番成長するんだなあと思いました。 コミュニケーションについて 私がモバファクに入社するにあたり不安だった点は、フルリモート環境でのコミュニケーションが上手くいくかでした。 学生の時にリモート中心の環境にいて、必要最低限のやり取りしかなく、質問や相談がしづらかったり、そもそもメンタルに良くなかったりという経験がありました。 それがあってモバファクではどうなるか心配していましたが、実際にはその懸念は杞憂に終わりました。 入社前後共に「心理的安全性」という言葉はよく耳にしていて、実際にその面でのサポートも十分にされていました。ランチ会や雑談の場を設けるなど、取り組みはさまざまなところで見られます。 少なくとも、フルリモートでもコミュニケーションを円滑にしようという共通認識があるだけで、かなり安心できるのではないかと思います。 また、質問などをする際の心理的障壁も小さい環境だと思います。 駅奪取チームでは、メンターさんと 1 対 1 の朝会・夕会が設けられていて、そこで雑多な話も質問もできる環境になっていました。業務に慣れていない時期でも、こうした質問しやすい場で些細な疑問も払拭することができました。 チームや業務に慣れてくるとオープンな場でも質問をしやすくなってくるかと思いますが、その状態まで自然な流れで移行できて、チームに入りやすい印象でした。 最後に 私はここまでの経験から、チャレンジングなタスクをする時が最も成長するように思いました。 駅奪取チームは大きくないチームなのもあって、フロントエンド・バックエンドのコーディングに加えてインフラ周りも見る機会があるので、様々なことに挑戦できる環境です。 現在は自分がメインで担当するプロジェクトにも挑戦させていただいています。 今後も積極的に幅広いタスクを引き受け、力をつけていきたいです。 また、様々なタスクに挑戦できる一方で、いきなり物凄いハードルを越えなきゃいけない訳ではなく、そこに至るまでのステップが整備されている環境であるとも思いました。 心理的にも取り組みやすく、新人でも参画しやすい環境なのではないかと思います。 駅メモ!開発 (EVENT) oshima の半年間 10 年の歴史がある駅メモ!では、多種多様な形式のイベントが存在します。 駅メモ! EVENT チームのエンジニアは、それらイベントの改修や、新規開発を行うのが主な業務です。 遊撃隊業務 駅メモ! EVENT チームに配属され、まず初めに携わったのが「遊撃隊」の業務でした。 これは日々の開発業務や、ユーザーからの問い合わせから発生する細かいタスクを拾っていく業務で、やること自体は軽微な修正だったり、直近のイベントの動作確認、分析用のデータを SQL クエリで抽出するなどが多いですが、 対応箇所はイベントの実装から広く浅くという感じで、何も知らない状態からイベントの仕様や実装を把握するのに適している業務と言えます。 機能開発に携わる 遊撃隊業務を経て、ある程度業務に慣れてきたところで、とある機能の開発を先輩エンジニアから引き継いで担当しました。 規模は小さいですが、開発する機能はユーザーと、イベントのコラボ先の関係者とのやりとりで使用される部分で影響範囲が普段より広く、そのやりとりのフローと駅メモ!の画面やシステムの挙動が一致しているかどうかは普段以上に気にかけていました。 もちろん実装面もそうですが、特に意識したのは、本機能に関わっていたプランナー・エンジニアとのコミュニケーションです。 遊撃隊業務の時から、社会人の基礎である「報連相」は意識して取り組んでいましたが、本機能については先述の通り何か手違いがあれば大きな影響を及ぼしかねない機能です。 そのため、実際に機能の仕様を考えているプランナーと、自分、また引き継ぎ元の先輩エンジニアとの間で、仕様の認識にズレがないか、また実際に自分が実装した内容についても、挙動が想定通りかを頻繁に slack 上で確認していました。 その後本機能がリリースされ、実際にユーザーに使用されはじめて数ヶ月経っていますが、特に問題が発生していないのは適宜コミュニケーションを取っていたおかげかなと感じています。 リモート環境について 入社前はリモート環境だとコミュニケーションや、タスク進行が大変そうという懸念がありましたが、配属から半年ほど経ち、規模の大きいチーム開発にも参加した上で振り返ると、これについては全く問題なかったなと感じています。 コミュニケーション面では、各プロジェクトの規模に応じて週に数回〜毎日夕会や、場合によってはエンジニアのみで集まる朝会を開いて進捗確認や相談をすることで、仕様やデザイン、設計について認識にズレが発生することを防いでいます。 タスク進行面では、ボードツールにタスク毎に設計を書き起こすことでロードマップ化し、エンジニアがそれぞれ対応するタスクを自ら決めるという形を取っています。そのため、「今、誰がどんなタスクをやっているか」「開発の進捗はどれくらいか」が可視化されていて、一目で見やすい状態になっています。 これらアクションは先輩方が過去のリモート環境での経験を元に改善していった結果のもので、そのお陰もあって滞りなくタスクが進められています。そういった理由から、リモート下でのコミュニケーションや、タスク進行については全く問題に感じたことは無いです。 「エンジニア」の領域は広い また、入社前のエンジニアのイメージと大きく変化があった部分として、エンジニアが担当する業務は何も設計やコーディングだけでなく、特に EVENT チームでは、イベントに関するデータ抽出や分析業務、仕様や実装に関する調査業務などもあり、業務が多岐に渡っているという点があります。 そして、そういった業務ができる機会は意外と多く存在していて、自分から「やってみたい」と声を挙げれば任せてくれる環境にあります。 自分自身、開発業務以外のスキルも幅広くつけていきたいと考えているのと、データを見たり分析したりするのは好きなので、最近はそういったタスクを積極的に取るようにしています。 勿論、技術基盤の改善など技術面を深掘りしたタスクも自ら課題提起して取り組むことができる環境です。 そう言った意味では、トライできる業務が幅広く存在するし、その中で自分に合ったタスクを見つけて追求していける点が良いところかなと思っています。 最後に 駅メモ!EVENT チームの一員としての半年間を振り返ると、面白い業務がたくさんあって飽きないな、という感想を持っています。 駅メモ!の一部を作っているという実感もありますし、実際に駅メモ!で遊んだ時にその一部を目にすると感慨深い気持ちになります。 また、それ以外にもイベント関連のデータ抽出・分析業務、仕様や実装の調査業務といった開発以外のタスクも対応することができて、これはこれで普段の開発とは大きく異なる作業でやっていて新鮮に感じます。 そして、入社前に感じていたリモート環境下でのコミュニケーションや、タスク進行などについての不安は特に感じることはなく、寧ろ作業環境を自分で作れる分、リモートワークの方がタスクに集中しやすいのかなとも思っています。 今後については、まだ EVENT チームが対応する業務について全て経験したわけではないので、挑戦したことのないタスクに挑戦し続けるとともに、その中で特に興味を持った分野のタスクを深掘りして、よりできることを増やしていきたいなと考えています。 駅メモ!開発 (DUEL) yang の半年間 入社研修を終えた後、DUEL チームに配属され、この新しい環境でのスタートに心躍らせると同時に、期待と不安が入り混じった気持ちでいっぱいでした。 この半年間、チームの定常業務に触りつつ、新機能の開発もしたことで、いろいろな経験を得ました。 さて、この半年間経験した印象的なことを紹介させていただきたいと思います! 配属後研修も引き続き 入社研修では技術研修の部分もありましたが、それは今後使用することのあるプログラミング言語や開発フレームワークなどの基礎知識の学習に限られており、実際の業務開発とは関連が多くないと思いました。配属前は、実際の業務開発に対して不安を感じていましたが、配属後にも新人向けの研修が続きました。 この研修では、実際にゲーム内に小さな機能を追加することを通じて、ゲームプロジェクトについて初歩的な知識を得ました。また、実践を通じて入社研修で学んだ内容を再度強化することができました。これにより、今後の実際の業務内容について少し理解が深まり、プロジェクトのコードに徐々に慣れていくことで、当初の不安も消えていきました。もちろん、研修中のコードはチームの先輩たちがレビューしてくれました。このプロセスで、他のメンバーとのコミュニケーションが徐々に増え、自分自身がチームの一員として少しずつ溶け込んでいることを感じました。 チームの定常業務から実際業務を着手 配属後の研修が終わった後、チームの定常業務の開発に取り組み始めました。最初の開発内容は、実際には練習のためのもので、過去のでんこを再開発することでした。最初は、以前の研修と同じように、自分一人で開発ドキュメントに従って進めるものだと思っていましたが、今回の形式は非常に異なり、初めて「ペアプログラミング」という手法に触れることになりました。先輩と MTG で対面しながら直接コミュニケーションをとりつつ開発を行うというものでした。最初は少し気まずく感じることもありましたが、開発内容に集中することで、しばらくその感覚を完全に忘れてしまいました。 実際、その後の業務で私はこの方法がモバファクで広く使われていることに気付きました。特に難しい問題に直面したときには、すぐに誰かを見つけて一緒に解決することができます。分からないことがあったり、アイデアが浮かんだ時にすぐに相談できるし、フィードバックを得られるので、この方法は私にとって非常に効率的でした。特に新人にとっては、何もかもが不慣れな段階を素早く乗り越えるのに非常に役立ちました。助けてくれたモバファクのメンバーたちに心から感謝しています。 新機能の開発 定常業務以外にも、この半年間で 2 つの新機能の開発に参加しました。定常業務では新しい内容の開発も行いますが、既存のフレームワーク内での変更や設定の追加に限られることが多いです。しかし、新機能の開発は大きく異なります。新機能の企画案の共有から始まり、実際の機能が完成してプレイヤーの前に登場するまでのすべての段階に参加することができ、多くの技術に限らない知識を得ることができました。たとえば、プロジェクトから作業タスクへのブレイクダウンや各部分の工数見積もり、API などのドキュメント作成、バックエンドとフロントエンドの開発、他のチームメンバーとの共同作業など、多くのことを学びました。 モバファクのエンジニアは、ほとんどバックエンドからフロントエンドまでの開発ができます。フロントエンドの開発の経験が全くなかった私にとって、最初の新機能開発では多くのフロントエンドの経験を積むことができました。その後すぐに参加した 2 つ目の新機能の開発では、フロントエンド部分の開発における抵抗感が明らかに前回よりも少なく感じました。もちろんフロントエンドの開発は単に機能を実現するだけではなく、デザイン面も関係しているため、デザインチームのメンバーと何度も確認や修正を行う必要があります。この面では非常に多くの時間を費やしましたが、ここをしっかりと磨かなければ、プレイヤーに完成度の高い作品を提供することはできません。半年間にわたる新機能開発を通じて、フロントエンド開発の業務についてより深い理解を得ることができました。 大きなチャレンジ 自分はこれまで実際のフロントエンド開発を一度も行ったことがありませんでしたが、ティザーサイトを開発するというタスクがあったとき、自分を試すいい機会だと思い、これを通じてこの分野の知識と経験を補いたいと考え、ぜひこのタスクをやらせてほしいと申し出ました。 今振り返ると、これはほぼ CSS だけを書くプロジェクトでした。体系的に CSS を学んだことがなかった私にとって、まるで謎解きのようなものでした。そのため、このサイトの開発は、同時にパズルを解くようなプロセスでもありました。ドキュメントを何度も調べたり、AI に質問したりすることで、一歩一歩作業の進捗を進めていきました。 進捗の都合で、最終的にサイトの開発を自分一人で完了させることはできず、少し残念でした。しかし、先輩たちの手厚いサポートのおかげで、サイトは予定通りのタイミングで無事に公開することができました。今回のタスクを通じて、開発の技術だけでなく、先輩たちの行動を観察する中で、プロジェクト管理や特殊な状況の対応方式についても学ぶことができました。この経験は、チームの一員として今後活動していく上で、技術以上に貴重な知識となりました。 最後に 振り返ってみると、この半年間は私にとって非常に価値のある時間でした。 技術面だけでなく、問題解決やチームとの協力といった、多方面でのスキルを磨く機会に恵まれました。特に、未経験の分野にチャレンジし、困難を克服する経験は、自信を育むと同時に、自分に足りないものを見つめ直す貴重な機会でもありました。 以上、貴重な経験と知識をいただいた半年間のご紹介でした! 駅メモ!開発 (SCALE) kinjo の半年間 新卒エンジニア全体・駅メモ!配属エンジニアでの新人研修の後、SCALE チームでの仕事が始まりました。 初めはチームに慣れるまで KAIZEN と呼ばれるタスクを進め、その後から実際の開発に取りかかるようになりました。 1. 小規模開発 まず初めに携わった開発プロジェクトは、比較的小規模な新規機能開発でした。 エンジニアとしては自分ひとり、他はプランナーの方とデザイナーの方という構成で進めることになりました。 とはいえ、最初の仕事なので自分にはチームの先輩エンジニアの方がひとり付いて見てくださることになりました。 自分の中では、エンジニアには「既に決められた仕様やデザインをコードに落とし込んでいくことがメイン」のようなイメージを持っていましたが、実際は「開発にあたってコード周辺を何とかする役割」であって、開発メンバーとして、他職種の方と逐次連携を取りながら進める必要があることに気付きました。 コードを追っていく中で仕様漏れがあることに気付くことがあればそれを相談したり、「この仕様は実装が煩雑になりすぎるな...」という場面ではオミットできないか相談したりしました (プロダクトに必要なものであるので外せない、というものも当然あります)。 エンジニア内々では「この処理はライブラリの方に任せています」で話しを終わらせてしまうところも、ちゃんとライブラリではどういう処理をしているのかについて把握して伝える必要がある場面もありました。 実際の作業内容としてはバックエンドでは API エンドポイントを 2, 3 程度定義して対応するロジックを書き、フロントエンドからそれを呼んで、結果に応じて出すダイアログを実装する、程度の小さいものでした。 新人の初めての開発ではあるのでスピード感はなかったのですが、基本的な開発フローを見ることができました。 小規模開発でしたが、複数の職種が集まって動くチームでの開発を経験できました。 2. 中規模開発 (チーム開発) 上述のプロジェクトを終え、次のプロジェクトは中大規模開発への途中参加でした。 駅メモ!の既存の機能に大幅な変更を加え、複数の概念をひとつにまとめる...といった難度高めのプロジェクトです。 途中で一度プロジェクト自体を中断して別の開発を進めていたり、中断前後でメンバーが変わっていたりと、内容だけではない文脈の難しさもあるプロジェクトでした。 エンジニアの人数としては、先にプロジェクトに参加していた 2 人に自分が 1 人として加わる形で、適宜 1~3 人程度追加で作業に参加することもありました。 まず仕様書を読むことになったのですが、それ自体が 17,000 文字弱ある長大なもので、細かい画面遷移までみっちり書いてあり「プランナーはこんなところまで把握し、仕様を決めているのか」と驚きました。 デザインも同様に、細かいところまでほとんど実際の画面と同じように作られており、こちらもすごいものでした。 実装や仕様の全体を把握しきるまで、2 ヶ月程度かかりました。 開発に参加した初期の時点では、概要を把握していなかったために何度か手戻りが発生するタスクもありました。 開発では仕様を元に開発タスクを切り出し、チケットで管理する形式で進めていました。このタスクをチケットに切り出すというのは結構難しく、「想定していたより実装が煩雑であった」や「仕様の抜け漏れに気付き追加の工数が必要になった」など見積りと話が大きく違ってくることもありました。 特に自分が持ったタスクのなかで工数が膨らんでしまったものとして、管理画面 (中の人がお知らせの設定を入れたりする場所) の実装がありました。実際に使う人にヒアリングをしながら作業をしたのですが、色々なこと (perl のテンプレートから vue への移行など) が重なって当初の予定の何倍もの時間がかかってしまいました。 参加初期こそ難しさを感じましたが、プロジェクトに慣れてくると、バックエンドのクエリ最適化からフロントエンドのちょっとしたアニメーションまでさまざまなタスクを取ることになり、勉強になることも多かったです。 3. その他 (プロダクトと開発チーム) 駅メモ!は既に 10 周年を迎えるゲームであり、機能もすべて把握することが難しいほどある大規模なソフトウェアです。 駅メモ!開発において、自分が担当するような業務ではフロントエンド・バックエンドともに実装に関わることになります。 プロダクト特有の事情に関して幅広く知っている必要があり、まだまだ実装に際しては過去の実装や経緯の調査している時間も長かったり、先輩エンジニアに「こういった実装は過去に例があるか」「この実装が入ったときはどのようなプロジェクトのためで、どのような意図があったのか」を質問しながら進めています。 また、10 年もの期間があるとさまざまな開発負債が残ってしまうものですが、KAIZEN という取り組みによって負債を緩和しようとする土壌もあります。 今は他の開発があるから手が付けられないけど、これは KAIZEN に回して覚えているうちに直しておこう、といったことができるのは心理的安全性に繋がっていると思います。 負債は避けられないもので、ライブラリの互換性の無いバージョンのために大規模な移行がおこなわれたりするものです。なんとか開発を止めずに、コンフリクトを抑えながら移行しようとした結果難しい構成になってしまっている箇所も多々あります。 負荷が高そうな一方で、個人的にはこういった点は分かりやすい改善点であり、チーム全体に改善提案をする機会ともなって、学習のモチベーションにも繋がっています。 開発は止まるものではなく、コードやテストなどは物凄い勢いで増えていきます。 一方、開発チームの雰囲気としては「このモジュールを試験的に導入してみたい」や「新しく規約としてこういったルールを定めたい」などの提案はかなり受け入れる姿勢を持っています。 普段の開発プロジェクト外でも KAIZEN やチームへの提案という動きができる環境なので、作業が並列することもありますが、こういった運用などを考えるのが好きな人もハマるような場所だなと思います。 最後に 自分がチーム配属から経験した、小規模開発・中規模開発・その他について振り返りました。 他にも、技術広報のグループに参加するといった機会もありましたが、自分はエンジニアとしては上記のような経験をさせてもらいました。 駅メモ!開発基盤 (native) r-hayashi の半年間 新卒エンジニア全体の新人研修を終えた後、私は駅メモ!開発基盤の native チームに配属されました。チームへの配属後は、新しい学びの連続でした。 ネイティブアプリ開発の学習 native チームはその名の通り、Android や iOS といったネイティブアプリの開発・運営を担当するチームです。新人研修ではネイティブアプリ開発に触れなかったため、配属後には Android と iOS に関する開発について新たに学び始めました。 native チームでは研修形式ではなく、公式の教材を活用した自習形式が主な学習スタイルでした。Android においては、最新技術を習得できる「 Compose を用いた Android アプリ開発の基礎 」を学習しました。このチュートリアルでは、Kotlin の文法学習に始まり、シンプルな画面表示から、複雑な画面遷移のアプリ作成などを学びました。また、非同期処理を用いてインターネットからデータを取得する方法も習得しました。 同様に、 iOS については「 SwiftUI の公式チュートリアル 」を利用して学習を進めました。こちらでは、1 つのアプリを作成する過程で、SwiftUI を用いて静的な画面の作成やデータの状態による動的な画面作成方法などを学びました。 学習ペースは自身で調整でき、私は約 1 ヶ月で学習を進めましたが、わからないことがあればすぐに質問できる環境が整っていたため、不安なく学びを続けることができました。Android と iOS の最新の実装方法を習得しましたが、日々の業務では従来の技術がまだ多く使われているため、学んだことを活かし、最新技術への移行も進めていきたいと考えています。 本格的に業務開始 学習が進む中で、native チームの業務を少しずつ教えてもらえるようになりました。最初は簡単なタスクから始まり、アプリアイコンの更新や BGM 追加などを担当しました。一連の作業を見学した後、実際に同様のタスクが発生した際に挑戦する形でした。最初は、正確に覚えているか、上手にできるかという不安もありましたが、豊富なドキュメントが用意されており、それらを確認しながら作業を進められたので安心して取り組むことができました。 業務に慣れてきてからは、非推奨・廃止予定の実装方法に対する警告への対応や、コードフォーマッターが適用されていない箇所の修正など、開発効率の向上に貢献するタスクや、UX を考慮した機能改善に積極的に取り組みました。これらはすべて自ら「やりたい」と声を上げて始めたものであり、問題を見つけて解決する貴重な経験となりました。 学習が一段落した後は、より重要なタスクとして、アプリの新バージョンのリリースやライブラリ、Kotlin のバージョンアップなどを担当しました。これらの更新作業は安定した運営の要であり、開発基盤チームの重要性を改めて実感する良い機会となりました。学生時代のハッカソンでのアイデア重視の開発とは異なり、安定した運営を意識した開発は初めてで、とても新鮮な経験でした。 チームでのコミュニケーション 私が配属された native チームは、他のチームに比べて少人数ですが、静かで落ち着いた雰囲気の中、気兼ねなく話しやすい環境があります。チームメンバーの休みが重なると一人で業務をこなす日もあり、その場合には他チームからの依頼に少し緊張することもありますが、これは慣れの問題で、むしろさまざまなことに対応できるようになる良い機会だと捉えています。 日々の業務の中では、朝と夕方にミーティングが設定されています。この時間に進捗の共有や相談、その他重要な情報の共有を行うことで、全員が日々の進行状況をしっかり把握できるだけでなく、迅速にフィードバックを受けることができます。また、業務中に生じた疑問や相談内容は、ミーティング以外にも伝言用の Slack スレッドでやり取りできます。内容が複雑で対面の方が適している場合にはオンラインミーティングを利用します。これらのおかげで、何かあっても孤立せずにすぐに助けを求められるという安心感があります。 さらに、Slack でオープンな作業スレッドを作成し、進捗や感じたことを書き留めておくことで、先輩から「〇〇で悩んでいたみたいだけど大丈夫?」といった気配りをいただくこともあります。このように、非公式な形であっても進捗を共有できる場があることで、チーム内での支援を得やすくなっていると感じます。 このような体制の中で業務に取り込むことで、コミュニケーションに対する不安はほとんどなく、安心して業務に専念できています。 最後に この半年間を振り返ってみると、「自ら動くこと」の重要性を強く感じています。native チームでは新人研修で扱わない言語や開発環境を使用し、学習も主に自習形式で進めていくため、積極的に学び続ける姿勢が何よりも大切であると実感しました。 さらに、「やりたい」と思ったタスクを自ら提案し、挑戦することができます。相談することで適切なレベルのタスクを提案してもらえることも多く、自分の成長に合わせて新しい知識やスキルを習得する機会を得られると感じています。また、コミュニケーションについて特段の心配はなく、何か心配なことがあればすぐに相談できる環境が整っているため、安心して新たな挑戦を続けることができます。 このような環境で働くことで、自ら動き、新しい機会を掴むことの大切さを実感し、今後のキャリアでもこの姿勢を持ち続けていきたいと思っています。これからも多くのことを学び続け、さらに成長していきたいと考えています。 さいごに モバファク 24 新卒エンジニア 5 人の視点から、入社後半年間に経験したことや学んだことについてまとめました。 メンバー全員で自分の章を担当するという形式でブログ記事としましたが、配属が違うメンバーのエピソードを見ることができ、新卒内でも互いに刺激になりました。 これから新卒エンジニアとしてモバファクに入社される方、または新卒エンジニアをこれから迎え入れる方々にとっても参考になる内容になったかなと思います。 記事を読んでいただきありがとうございます! モバファクでは中途採用・新卒採用ともに絶賛募集中です。会社の情報については、モバファクブログでも発信しています。 技術好きな方、モバファクにご興味をお持ちの方は、ぜひご応募ください! モバファクブログ: https://corpcomn.mobilefactory.jp/ 採用サイト: https://recruit.mobilefactory.jp/
アバター
はじめに 駅メモ!チームでエンジニアをしている id:wgg00sh です。 この記事では、駅メモ!内で地図クライアントとして使用している mapbox-gl-js を使うにあたって工夫した点などを紹介していきます。 【✨新機能リリース✨】 6/1 12時頃より、アプリ版駅メモ!にて「タイムラインと地図の切替機能」をリリースしました🎉 本機能では地図を見ながらチェックインが行えたり、 地図上でレーダーの対象駅や駅の属性等を確認することができます💪 詳細はお知らせよりご確認ください♪ #駅メモ #駅メモ10周年 pic.twitter.com/vneZv27AVU — 駅メモ!公式 (@ekimemo) June 1, 2024 駅メモ!では、2024 年 6 月に、「タイムラインと地図の切替機能」(以降:タイムライン地図、本機能)をリリースしました。 本機能の実現にあたって、苦労した点やその解決方法を書いていきます。 本記事で扱う内容 この記事では、主に Mapbox GL JS の以下の機能・プロパティに関する話をします。 icon-allow-overlap , text-allow-overlap icon-ignore-placement , text-ignore-placement symbol-sort-key 機能の概要説明 はじめに、本機能についての説明をします。 通常のタイムライン タイムライン地図 駅メモ!はスマートフォンの位置情報を取得して、最寄り駅にアクセスして遊ぶゲームです。タイムライン地図では画像右側のように現在地や周辺の情報を地図上に表示しながらプレイすることが可能になります。 これにより、従来よりも直感的に駅の情報を確認しながらプレイすることができるようになりました。 実現にあたって直面した問題 ここからは、いくつか本機能の実現にあたって苦労した点をその解決策と合わせて説明します。 addLayer() を使って複数の画像を組み合わせたアイコンを描画したい場合 今回のタイムライン地図機能では、駅メモ!の遊びである「駅の収集」と「駅の取り合い」の両方に焦点を合わせて駅の情報を描画しています。 タイムライン地図で描画したいアイコンの内容 タイムライン地図で使用している駅アイコンはこのようなものです。 このアイコンの描画に必要な情報として、3 つの要素に分解することができます。 駅属性 アクセス状況 駅名 駅属性は、ゲーム内で駅の取り合いをするにあたって影響するパラメータで、4 つ (heat、eco、cool, および廃駅にのみ適用される「属性無し」) のうちいずれか 1 つを必ず持ちます。 アクセス状況は、ユーザがその駅にアクセスしたかの状態で、4 つ(未アクセス、当月未アクセス、当月アクセス済み、当日アクセス済み)のいずれか 1 つの状態になります。 駅名は、その名前の通り駅に割り振られた名称で、何かしらの文字列になります。 アイコンの描画手法について ここで苦労したのが、アクセス状況と駅属性を両方含むアイコンを描画する点です。 Mapbox GL JS で地図上の特定の座標にアイコンを描画するには、大きく 2 つの手法があります。 レイヤー機能を用いて描画する方法 HTML マーカーを地図上に配置する方法 本機能では、描画されるアイコンの数は非常に多くなりうるため、HTML マーカーを用いることでパフォーマンスの悪化が懸念されます。 そのためレイヤー機能で描画する選択肢しかありませんでした。しかし、この場合描画できる内容に大きな制約がかかります。 symbol レイヤーが表示できる内容 前述のリンク先に記載されていますが、 symbol レイヤーでは同一レイヤーの各要素に対して、「1 つの文字列」と「1 つの画像」を組み合わせて描画します。(文字列や画像自体はプロパティを参照できるので要素ごとに異なるデータを渡せます) symbol レイヤーを用いてデータを描画する場合のサンプルは下記のようになります。 map . addLayer ({ id : "stations" , source : "stations" , type : "symbol" , layout : { "text-field" : "{name}" , // 駅名 "icon-image" : [ "get" , "icon-key" ] , // 画像 } , }) 1 つのアイコンには 1 つの画像までしか利用することができません。 そのため「駅属性・アクセス状況それぞれに対応する画像を用意し、それら 2 つの画像の組み合わせを用いて 1 つのアイコンを描画する」といったことはできませんでした。 この問題を解決する為に、今回 2 つの案を試しました。 案 1: レイヤーを 2 つ用意する 案 2: 2 つの画像を組み合わせた単一の画像をあらかじめ用意しておく 案 1: レイヤーを 2 つ用意する 駅属性画像とアクセス状況画像をそれぞれ用意(4+4=8 種)して、2 つのレイヤーでそれぞれ描画してみます。 map . addLayer ({ id : "stations" , source : "stations" , type : "symbol" , layout : { "icon-anchor" : "center" , "icon-image" : [ "get" , "element" ] , "icon-size" : 0 . 5 , "icon-offset" : [ 0 , -60 ] , // 駅名 "text-field" : "{name}" , "text-anchor" : "top" , "text-offset" : [ 0 , 0 . 5 ] , } , }) map . addLayer ({ id : "stations2" , source : "stations" , type : "symbol" , layout : { "icon-anchor" : "center" , "icon-image" : [ "get" , "status" ] , "icon-size" : 0 . 5 , "icon-offset" : [ 0 , -15 ] , "symbol-sort-key" : [ "get" , "sort-key" ] , } , }) このように、ほとんどの場所で正しく表示されなくなってしまいます。 通常、Mapbox GL JS の symbol レイヤーは,アイコン・テキスト同士に対して衝突検出を行い、重なっていれば一方を描画しないようにします。 これは、 icon-allow-overlap 、 text-allow-overlap がそれぞれデフォルトで false になっているためです。 (参考: https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/) この機能のおかげで、描画すべきアイコンが膨大な場合にも適切な見やすさを保つことができ、描画負荷も抑えられます。 この仕組みを無効にするには、それぞれのレイヤーに icon-ignore-placement: true , text-ignore-placement: true を付与します。 map . addLayer ({ id : "stations" , source : "stations" , type : "symbol" , layout : { // 他の設定 "icon-ignore-placement" : true , "text-ignore-placement" : true , } , }) map . addLayer ({ id : "stations2" , source : "stations" , type : "symbol" , layout : { // 他の設定 "icon-ignore-placement" : true , "text-ignore-placement" : true , } , }) 非常に見づらくなってしまってしまいました。 これは、 icon-ignore-placement 、 text-ignore-placement を付与したことで、同じ駅の属性・アクセス状況は重ねて描画できるようになったものの、他の駅との重なりも無視されるようになってしまったためです。 駅数が少ない地域では、駅同士が密接することはなくこれでも問題ないかもしれませんが、都心部では画像のとおりに上手く描画できません。 そのため、このアプローチは不十分と判断しました。 案 2: 2 つの画像を組み合わせた単一の画像をあらかじめ用意しておく もう一方のアプローチは、あらかじめ 2 つの画像を組み合わせて 1 つの画像として書き出しておくものです。 cool かつ、アクセス済み heat かつ、今月未アクセス 属性無しかつ、本日未アクセス このように、 属性4種 x アクセス状況4種 = 16種 の画像をあらかじめ作成しておきます。 type Station = { name : string lat : number lng : number element : string status : string } [ "heat" , "cool" , "eco" , "none" ] . forEach (( element ) => { [ "unaccessed" , "unaccessed_month" , "unaccessed_today" , "accessed_today" , ] . forEach (( status ) => { map.loadImage( `/img/station_element/ ${ element } _ ${ status } .png` , ( error , image ) => { if (error || !image) { console .error( "Failed to load image" , error) return } map.addImage( ` ${ element } _ ${ status } ` , image) } ) } ) } ) const getIconKey = ( station ) => { return ` ${ station.element } _ ${ station. status} ` } map.addSource( "stations" , { type : "geojson" , data : { type : "FeatureCollection" , features : stations. map (( station : Station , index ) => { return { type : "Feature" , geometry : { type : "Point" , coordinates : [ station.lng, station.lat ] , } , properties : { name : station. name , "icon-key" : getIconKey(station), } , } } ), } , } ) map.addLayer( { id : "stations" , source : "stations" , type : "symbol" , layout : { "icon-anchor" : "center" , "icon-image" : [ "get" , "icon-key" ] , "icon-size" : 0.5 , "icon-offset" : [ 0 , - 30 ] , "text-field" : "{name}" , "text-max-width" : 10 , "text-size" : 14 , "text-anchor" : "top" , "text-offset" : [ 0 , 0.5 ] , } , } ) このようにすることで、間引きにも対応して複数画像を組み合わせたアイコンを描画することができました。 案 2 の懸念点 今回のケースでは、存在する画像の組み合わせが、16 通りと多くはないため全て予め作成しておく形で対処しましたが、ケースによってはそれが難しい場合もあるかもしれません。 例えば、ここに 4 通りのパラメータが 1 つ・2 つと増えれば全部で 64 通り・256 通りとなり、画像の生成が自動化できない場合は作成に掛かる作業コストも大きく、全て loadImage() で保持する場合メモリの圧迫も心配になってきます。 その場合、解決策としては 実際に使われる組み合わせのみ読み込む 画像の作成を実行時に動的に行う といった工夫が必要になってくると思われます。 今回は画像の数が少数かつ予め決まっていたため、全パターンを作成する方法を採りました。 駅が密集している場合に、優先して描画される駅を指定したい Mapbox GL JS では、駅が密集しているとアイコンの描画がある程度自動的に間引かれるという話を先にしました。 タイムライン地図機能では、ライブラリのデフォルトの優先度ではなく、ユーザのプレイ状況に応じて優先的に描画する駅を選択しています。 具体的には、ユーザの現在地付近の駅や、大規模な駅などは間引かれづらくなるようにしています。 例として、「山手線の各駅を優先して間引く・間引かない」という 2 つの条件で、同じ描画範囲の画面を用意してみました。 山手線をなるべく間引く 山手線を優先して残す 後者には、東京・品川・池袋など、主要な駅が間引かれずに残っていることがわかると思います。 const TARGET_STATION_IDS: number [] = [ xxx, yyy, ... ] // 山手線の各駅のID map.addSource( 'stations' , { type : 'geojson' , data : { type : 'FeatureCollection' , features : stations. map (( station : Station , index ) => { return { type : 'Feature' , geometry : { type : 'Point' , coordinates : [ station.lng, station.lat ] } , properties : { 'sort-key' : TARGET_STATION_IDS. includes (station. id ) ? 0 : 1 , } } ; } ) } } ); map.addLayer( { id : 'stations' , source : 'stations' , type : 'symbol' , layout : { 'symbol-sort-key' : [ 'get' , 'sort-key' ] , } , } ); 実際の駅メモ!内では、このコードのように特定の駅だけ優先度を上げる形ではなく、ユーザのプレイ状況に応じて複数の観点から優先度を決定しています。 おわりに タイムライン地図機能の実装にあたって、描画周りでの工夫を紹介しました。 描画内容・順序を工夫することで、ユーザにとって見やすい地図を提供することができました。 今回の開発を通して、Mapbox GL JS にこんな機能があったのかと学びもあり多く、工夫次第でさまざまな表現が可能であることを実感しました。 参考 Mapbox GL JS symbol Mapbox GL JS でカスタムマーカーを追加する Optimize map label placement
アバター
はじめに 駅メモ!開発チームエンジニアの id:kaidan388 です。 駅メモ!のフロントエンドは Vue で書かれており、およそ 1500 コンポーネントあります。 Vue2 が EOL を迎えるに際して、これをどう Vue3 に移行するかが問題になりました。 具体的には以下の 2 点をどう達成するか、というのが問題になります。 普段の機能開発を止めずに、Vue3 移行を進めたい 普段のリリースを止めずに、Vue3 のリリースをしたい 駅メモ!開発チームでは、途中メンバーの交代もありつつですが、基本的に 3 名で 1 年半かけて、上の要件を満たしつつ Vue3 へ移行を完了しました。 この記事では、いかにして Vue3 化を完了したか解説しようと思います。 技術的な難しさについてはすでに多くのブログで語り尽くされているように思うので、ここでは、チームの運用やその他特別な工夫について語ります。 具体的には以下のような工夫を行いました。 差分を可能な限り小さくする スプレッドシートを用いた、作業の見積もり及び進捗の可視化 実装に詰まったらすぐに相談できる環境の用意 Vue2/Vue3 緊急切り替えボタンの作成 はじめに 移行に際しての問題点 差分を可能な限り小さくする スプレッドシートを用いた、作業の見積もり及び進捗の可視化 実装に詰まったらすぐに相談できる環境の用意 Vue2/Vue3 緊急切り替えボタンの作成 まとめと今後の展望 移行に際しての問題点 まずは、移行開始時点で駅メモ!のフロントエンドがどのような状況にあり、移行に際して何が問題になったかを、簡単に整理します。 まずは、なんといっても、コンポーネントの多さが問題になりました。 フロントエンドの実装の大部分は Vue2 で書かれており、コンポーネント数は 1500 ほどありました。 この数の多さにより、以下の 3 点が問題として現れてきました。 作業工数が大きくなる 工数見積もりが難しい 移行作業の進捗管理が難しい また、アプリの歴史の長さからくる、実装の読みづらさも移行作業の障壁でした。 元々駅メモ!のフロントエンドは Angular で書かれており、それを Vue2 に移行したという経緯があります。そのため、駅メモ!に初期からあるコア機能の実装は、Angular と互換を保つために、独自の実装が多く含まれています。Vue3 は破壊的変更が多く、この互換性のための実装がそのままではうまく動かないケースが多々ありました。 その他にも、10 年前と今とで設計思想が微妙に変わっていたり、初期の実装には社内で作った独自のフレームワークが使われていたりと、歴史の長さからくる実装の読みづらさは様々な場所で障壁として現れてきました。 いかにして普段の機能開発への影響を抑えるか、という点も問題になりました。 正確な見積もりではないにしても、1500 コンポーネントあることを考えると半年〜1 年以上は時間のかかるプロジェクトになるだろうというのは、駅メモ! 開発チーム内でも先にわかっていました。これほどの長期間駅メモ!の新機能開発を止めることは、当然できません。移行の実装から反映まで、Vue3 化に関わらない作業への影響を最低限に抑える方法を考えることも、問題になりました。 ここまでの問題を整理して、以下に箇条書きします。 作業工数が大きくなる 工数見積もり・進捗管理が難しい 古い実装が読みづらい 普段の開発への影響を最小限にしたい これらの問題にどのように対応したか、このブログ記事で紹介していきます。 差分を可能な限り小さくする 作業工数の大きさに対応するため、移行時の差分は必要最低限にするという方針で作業を行いました。 話を始める前に、まずは、移行計画の変遷について説明させてください。 駅メモ!のフロントエンドは、Vue2 が使われていること以外にも様々な問題を抱えている状態でした。 例えば、全体的に使われているパッケージが古く、それらを更新する必要があります。他にも、ビルド時間が長い、ファイル構成がまちまちで統一感がない、など解決すべき課題は数多くあります。 それらを加味し、以下の記事で紹介した移行計画が立案され、これを実行しました。 tech.mobilefactory.jp ただ、結論としては、必要な工数が現実的でないとわかったため、この方針は却下されることになります。 実際の作業時間について説明すると、まずは、作業の合間でいくらかの中断もあったのですが、パッケージを切り分けるなどの移行の下準備に半年かかりました。 その後、試しに駅メモ!のチュートリアル画面の移行を行ったのですが、60 コンポーネントの移行に 3 ヶ月ほどの時間を要してしまいました。これは、Vue3 移行と一緒に、sass を node-sass から dart-sass に置き換えようとしたことが主な要因で、css をほぼ全て書き直す必要が生まれたからです。 作業に慣れることでいくらかのスピードアップは期待できますが、流石に、60 コンポーネントに 3 ヶ月かかる見積もりでは、1500 コンポーネントの移行を完了することは現実的でなさそうです。 というわけで、一緒に解決したかったフロントエンドの様々な問題は一旦諦め、Vue2 を Vue3 に置き換えることだけに集中して作業を行う方針に決まりました。 具体的には、Vue3 のマイグレーションガイド通りに作業を行い、レガシーコードとの兼ね合いを考えるときも負債には目をつぶり可能な限り差分を小さくする、という方針です。 方針転換後は作業スピードも目に見えて向上しました。 プロジェクトが始まったのが 2023 年の 4 月ごろ、方針が変わったのが 2024 年の 3 月で、実際に Vue3 化が完了したのは同年 9 月の末になります。 つまり、チュートリアルの移行完了(諸々合わせると 80 コンポーネントあります)までに 1 年かかり、駅メモ!本体の 1500 コンポーネントの移行完了までに 7 ヶ月かかったことになります。 方針転換とはすなわち、過去 1 年分の進捗をなかったことにする決断なので、当時はかなり悩みました。ただ、体感として、方針変更後は作業完了の目処が立つようになり、ゴールが見えることで気持ち的にも楽に作業できるようになったように思います。 作業の見積もりや作業の計画の大事さを身をもって体験した業務だったと思います。 スプレッドシートを用いた、作業の見積もり及び進捗の可視化 工数見積もり・進捗管理の難しさに対応するため、Google スプレッドシートを用いて進捗の管理を行いました。 Vue3 化の作業は、締め切りは明確には決まっていませんでした。 これは、作業量の多さから正確な作業完了時期を考えることが難しく、締め切りを作っても守れるかどうかは未知数となるためです。 しかし、締め切りがないと、つい作業ペースが遅くなってしまい、Vue3 化の反映がいつまでもできなくなるのではないかという懸念があります。 その問題を解決するために使われたのが、Google スプレッドシートです。 下図は、実際に進捗管理で使っていたスプレッドシートです。 Vue3進捗管理バーンアップグラフ 別のシートに、作業しなければ行けないコンポーネント名が 1500 列並んでいて、移行が終わって親ブランチにマージするタイミングでその別シートに完了のフラグと日付を記録するようにしていました。その作業により、上記のバーンアップグラフが描画されます。 今回の Vue3 プロジェクトでは、9/30 を作業完了の目安として理想の線を描画、そしてできるだけいつもその線を上回る状態を維持することを目指して作業を行いました。 途中でその想定より速度が出るとわかったので、1 日 14 コンポーネント作業することを目処に理想の線を引き直したりもしています。 また、Vue3 移行では週に 1 回のペースで振り返りを行いました。その振り返りでも、このグラフの傾きを一定にする、もしくは今より傾きを大きくするために、障壁になっているものは何かという視点で議論をしました。このブログで書いている工夫も、この振り返り会から生まれてきたものです。 目にみえる進捗があることで、チーム内はもちろん、チーム外に向けても進捗状況が共有しやすく、結果健全に作業を進められたのではないかと思います。 実装に詰まったらすぐに相談できる環境の用意 古い実装が読みづらい問題に対応するため、朝会の後にすぐ相談できる環境を用意していました。 前提として、モバイルファクトリーでの勤務はフルリモートになっています。 難しい実装にぶつかってうまく理解できなくなった場合は、まず Slack で声をかけ、誰かに Google Meet に参加してもらって相談する、という流れをとります。 普段はこの順序で問題は起きないのですが、Vue3 移行では古い実装を読むたび頻繁に行き詰まります。頻度も高いので声もかけづらく、相談がしづらい状況が生まれてしまいました。 これを解決するために、Vue3 移行チームでは、毎日の朝会の後お昼休みに入るまで、朝会の Google Meet の部屋に残ってもくもく会をしていました。 もくもく会とは、通話は繋いだまま基本的に黙って作業し、困ったことがあったら相談する、という会です。 これを行うことで、朝会の後少し雑談し、そしてそのままもくもく会で実装の行き詰まっている点を相談する、という流れを作ることができました。作業に行き詰まる時間も短くなり、進捗も上がるようになったと思います。 相談する側の心理としても、わざわざ声をかけて人を募るより朝会の後自然な流れで話す方が、些細な問題でも気軽に相談でき、相談しやすくなったように思います。 Vue2/Vue3 緊急切り替えボタンの作成 普段の開発への影響を最小限にするため、 Vue2/Vue3 緊急切り替えボタンを作成しました。 これは特に、反映時に、Vue3 以外の開発への影響を下げるための工夫です。 移行作業は、Vue3 移行のための親ブランチを作成して、そこから子ブランチを切り、ディレクトリ単位で進めました。 移行作業はその要領で普段の開発と並行して行えたのですが、問題は反映です。 Vue2 でも動く破壊的変更ではない差分は先に反映したりしたのですが、それでも最終的に 1300 ファイルほどに差分があるプルリクを反映する必要がありました。 また、反映は 3 日間で終える必要がありました。これは、反映期間中はできるだけ Vue3 以外のデプロイを止めて、不具合が起きてもすぐに対応できる状況を作ろうと、チーム内で相談した結果です。 これらを踏まえ、以下のような作戦で反映を行いました。 サーバ上に、Vue3 の差分を含まないビルド成果物(以下 Vue2 ビルドと記述します)と、Vue3 の差分を含むビルド成果物(以下 Vue3 ビルドと記述します)の、両方を配置する Vue2 ビルドと Vue3 ビルドの成果物の切り替えは、管理画面の操作からデプロイなしで行える 3 日のうち、最初の 1 日で様子を見ながら Vue3 ビルドを受け取るユーザの割合を上げる。2 日目で 100%にする。3 日目は様子見と不具合対応 万が一クリティカルな不具合が出た場合、チームと相談しつつ、管理画面から Vue2 ビルドに切り替える このうち特に便利だったのは、Vue2 ビルドと Vue3 ビルドの成果物の切り替えを管理画面からの操作でデプロイなしで行えるようにした点です。 これがあることで、もし、駅メモ!が遊べなくなるようなクリティカルな不具合を出したとしても、 5 分以内に切り戻せるので、心理的に安心して作業を進められました。 なお、実際には、この切り替え機能で Vue2 に戻すことはありませんでした。 反映時、軽微な表示の崩れは複数見つかりましたが、ゲーム体験にクリティカルに響くような不具合はなかったからです。これは、社内で 3 ヶ月ほどかけて入念に動作確認していたことと、どの程度の不具合であれば修正を後日に回してよいかをチーム内でよく握り合わせていた成果かと思います。 まとめと今後の展望 駅メモ!という 1500 コンポーネントある巨大な Vue2 アプリの Vue3 移行について、チーム運用やその他特別な工夫について話しました。 具体的には、以下の 4 点を紹介させていただきました 作業工数が大きくなってしまうので、差分を可能な限り小さくする 工数見積もり・進捗管理が難しいので、スプレッドシートを用いて作業の見積もり及び進捗の可視化をする 古い実装が読みづらいので、実装に詰まったらすぐに相談できる環境を用意する 普段の開発への影響を最小限にしたいので、Vue2/Vue3 緊急切り替えボタンを作成する これらの工夫により、7 ヶ月ほどで、1500 コンポーネントある Vue2 アプリを Vue3 に移行完了することができました。 今度は、駅メモ!のフロントエンドのさらなる改善に努めたいです。 Vue3 化が終わったとはいえ、古いパッケージが使用されていたり、Angular と Vue の実装が混じり合っている部分があったりと、駅メモ!のフロントエンドには多く問題があります。 チーム内で行っている改善の仕組みを用いて、これらに取り組んでいきたいです。 tech.mobilefactory.jp また、Vue3 化が終わって明確に良かった点として、駅メモ!のフロントエンドの全容を把握しているエンジニアがチーム内に生まれた、という点が挙げられます。 今まで、駅メモ!のフロントエンドは歴史の長さと実装の多さから、その全容を把握できるエンジニアがいない状況でした。結果、何か改善を入れようと思ってもそれで問題が生じないかの判断が難しく、踏ん切りがつかない場面が多々ありました。 今回の Vue3 化で、駅メモ!の実装の全体を把握しているエンジニアがチーム内に生まれ、結果改善の取り組みも進めやすくなったように思います。 エンジニアの生産効率性をさらに上げられるよう、今後も改善を継続したいです。
アバター