技育祭にCTO小賀、fluctCTO鈴木が登壇&企業ブース出展します!

こんにちは!人事のさとみん(@satomin35y)です。

サポーターズが主催するエンジニアを目指す学生向けテックカンファレンス「技育祭(ぎいくさい)」にVOYAGE GROUPよりCTOの小賀、fluct CTOの鈴木が登壇します!また、VOYAGE GROUPとしても企業ブースを出展するのでこの場を借りて告知させてください!

技育祭とは?

f:id:satomin35y:20200625115840j:plain

学生の就活支援をするVOYAGE GROUPグループ会社のサポーターズが主催する、エンジニア学生のための日本最大級のオンラインカンファレンス。「技術者を育てる」ことを目的とし、7/4(土)・7/5(日)の2日間開催されます。

落合陽一氏、まつもとゆきひろ氏などIT業界を代表する技術者や、IT企業のCTOなど出演者も豪華です!お申し込みは、6/28(日)までなのでまだの人は要チェックです!

talent.supporterz.jp

7/4(土)15:50〜 『Goライブコーディング』

f:id:satomin35y:20200625095216j:plain

fluct CTO / 「みんなのGo言語」共著者の鈴木健太(@suzu_v)によるライブコーディング。トップエンジニアのコーディング風景が見える貴重な機会かつ、VOYAGE GROUP CTOの小賀による解説付きです!

久々にライブコーディングの機会を頂きわくわくしています!学生の皆さんに楽しんでいただけるよう、なるべく普段の雰囲気で、コーディングの楽しさが伝わる時間にしたいと思っています。技育祭、一緒に楽しみましょう!

7/5(日)13:30〜 『400人以上のインターン生を受け入れ成長させてきたCTOが考える若手エンジニアが成長するコツ』

f:id:satomin35y:20200625095232j:plain

VOYAGE GROUP CTO 小賀昌法(@makoga)が登壇します!エンジニア向けのインターンシップ「Treasure」などの事例を基に若手のスキルアップのコツを公開します!

2006年から毎年学生インターンを受け入れてきました。短期間で驚くほど成長する人を何人もみてきました。ちょっとしたコツを身につけて、成長し続け、生涯現役のエンジニアになりましょう。

7/4(土)・7/5(日) 12:20〜 企業交流ルーム『社内Barがある!?VOYAGE GROUPオンラインオフィスツアー』

f:id:satomin35y:20200625095117j:plain

VOYAGE GROUPのオフィスは航海(VOYAGE)をモチーフに、経営理念の「360°スゴイ」を体現した設計になっています。オンライン上でリアルタイムにVOYAGE GROUPのオフィスメッセージを読み解きながら、私たちと一緒に25分間の航海を体感しませんか? (※チャットでのリアル参加型を想定しております!)

技育祭のお申込みは6/28(日)!

技育祭は6/28(日)までのお申込みとなります!ほかにも興味深いコンテンツがたくさんあるのでぜひチェックください!

VOYAGE GROUP一同、当日お会いできることを楽しみにしています!

talent.supporterz.jp

Privacy Sandboxのイベントを開催しました。 #TechMar

2020年6月17日(水)に電通デジタル、サイバー・コミュニケーションズ、VOYAGE GROUPの3社でTech x Marketing meetup #2 Privacy Sandboxというオンラインイベントを開催しました。

techxmarketing.connpass.com

VOYAGE GROUPからは @makoga が登壇し「ChromiumのPotential uses for the Privacy Sandboxを読む」というタイトルで発表しました。

speakerdeck.com

もともとはタイトルにあるブログ記事の紹介の予定でしたが、それだけだと全体像がつかみにくいということで最初のパラグラフにリンクされているアウトラインとプロジェクトサイトについても紹介しています。

f:id:voyagegroup_tech:20200624215631p:plain

2つめのセッションではサイバー・コミュニケーションズ(CCI)田中さんが「デジマ担当必見!?Privacy Sandboxをデジマ視点で読み解く」を発表しました。

speakerdeck.com

Click Through Conversion Measurement Event-Level API、Aggregated Reporting API、2種類の広告リクエストから表示する広告をブラウザ内で決定する機能であるTURTLE-DOVE、類似する閲覧履歴を持つユーザー同士をブラウザ内でグルーピングするFLoCについてフロー図などを交えて説明しました。

f:id:voyagegroup_tech:20200624220406p:plain

3つ目のセッションでは電通デジタル 神田さんが「Web Privacy Survival Guide」を発表しました。

www.slideshare.net

匿名化、After 3rd party cookie、フィンガープリント、アンチフィンガープリントについて、シーケンス図などを交えて説明しました。

f:id:voyagegroup_tech:20200624221042p:plain

すべてのセッションが終わったあとはオンライン懇親会も開催し、飲みながらPrivacy Sandboxやリモートワークなどについて語りました。

今後もTech x Marketing meetupを開催していく予定です。ご興味ある方は Tech x Marketing - connpass のメンバーに登録ください。

BigQuery定額料金モデルの導入と権限分離 - CTOが聞く Vol.2

VOYAGE GROUPのエンジニアにCTOが「最近は何やってるの?」と聞いていく連載の第2弾です。今回はVOYAGE GROUP全体のインフラを担当しているともかつさんに話を聞いてみました。

目次

定額料金モデルとは

(CTO) 最近は何やってるの?

(ともかつ) BigQueryの料金モデルを定額に変更しました。

(CTO) おー、コスト下がったって言ってたやつね。定額料金モデルってどんな内容なのか教えて。

(ともかつ) 公式サイトには「BigQuery では、処理データの TB あたりオンデマンド料金よりもクエリの固定料金をご希望のお客様向けに定額料金をご用意しています。」と書いてあります。うまく使えばオンデマンド料金より安くなると思います。

f:id:voyagegroup_tech:20200522164838p:plain
クエリの料金 - BigQueryの料金

(CTO) 単価が安いってことは何か制約があるってことだよね?

(ともかつ) クエリ処理に使用するスロットを、500スロット単位で購入する必要があります。ただし効率よくスロットを使えなければ、オンデマンドより高くなります。

(CTO) まあ、ただ単に安くなるなんてうまい話はないよね。

最初は苦戦した

(CTO) どう進めていったの?

(ともかつ) VOYAGE GROUPは多くのプロダクトでBigQueryを利用しています。その中で1番利用料の多いプロジェクトから導入を進めました。

(CTO) お、いきなり1番多いとこから? 少ないプロジェクトからって手もあったんじゃない?

(ともかつ) 一番多いところで導入することがメリットを享受しやすいので。ただ、スロットを使い切る可能性も十分考えられる利用状況だったので、何かあればすぐオンデマンドに切り替えられることを事前に検証し、事業リスクも低いと結論づけて実行に移しました。

f:id:voyagegroup_tech:20200522180101p:plain
料金モデルの選択: オンデマンドと定額

(CTO) なるほどね。順調だった?

(ともかつ) それが、懸念していたようにスロット上限を利用した状態で張り付いてしまい、クエリの実行に遅延が発生しうる状態になってしまって・・・。いったんオンデマンドに戻しました。

(ともかつ) そこから新規のGCPプロジェクトを作成し、処理に時間が掛かるクエリをそのプロジェクトに移設しました。もう大丈夫だろうと改めて定額に切り替え、一旦は問題ないように見えました。

(ともかつ) ところが、アドホックの調査用のクエリで重たいものがいくつかあり、何回か上限に張り付く事象が発生しました。

(CTO) 運用に載せると見えてないものが見えてくるよねw

(ともかつ) はい。。。

管理の一部を権限移譲した

(ともかつ) オンデマンドと定額を切り替える権限はシステム本部(VOYAGE GROUP全体に関わるインフラやCorporateITに責任を持っている部署)だけが持っていました。なぜかと言うと、この権限を渡すと別の部署の設定も変更できてしまうからです。

(ともかつ) とはいえ、1次アラートを受け取ったチームメンバーがすぐ対応できない状況はシステム本部にとっても各部署のチームにとっても良い状態ではないので、なんとかしたいと思っていました。

(CTO) アラートを受け取った人がすぐ対処できるようにしたいよね。

(ともかつ) マニュアルを調べつつ権限の調整と検証をしていると、GCPコンソール画面からはできないけど、API経由であれば細かく設定できることを発見しました。

(ともかつ) これを使うことで各部署は自分たちのリソースだけを操作できるようになりました。

f:id:voyagegroup_tech:20200522175825p:plain
容量コミットメントをワークロード、チーム、および部門に分割

(CTO) 上記画像にあるAssignments単位でオンデマンドと定額を切り替えることができるようになったということか。これはGUIでも設定できると嬉しいやつだ。

今後やっていきたいこと

(CTO) まずは1つの部署で導入し、安定運用までいきました。今後やっていきたいことは?

(ともかつ) 現在の使いかただとスロットにまだ余裕があるので、今後は定額の利用部署を増やしていきます。

(ともかつ) そのためには各部署ごとにスロットの最低限の確保をする必要があると考えています。

(CTO) ある部署が一気に使って他の部署のサービスに影響が出ないようにするには最低保証は欲しいよね。

(ともかつ) そうなんです。そしてそれをやろうとするとまたAPI経由での設定になるようなので、そのあたりを調査、構築、テストしていきます。

(CTO) いいね。

まとめ

(CTO) 料金モデルの変更にあたり、事前の試算から初期導入までを良いスピードで進めたこと、今後の全社展開など、いい話が聞けました。

さて、次回は誰に聞こうかなー


バックナンバー techlog.voyagegroup.com

新卒1年目を終えて、若手エンジニア座談会

こんにちは!VOYAGE GROUPでエンジニア新卒採用を担当するさとみん(@satomin35y)です。

VOYAGE GROUPでは4月に24名の新入社員が入社しました!(うち9名がエンジニア)

今回は、新卒1年目をどう過ごしたのか、2年目で活躍する先輩エンジニアにインタビューをしたのでその様子を座談会形式でお届けします。

お話してくれた2年目エンジニア

f:id:satomin35y:20200512120441j:plain

にっしー(上)株式会社VOYAGE Lighthouse Studio所属。ゲーム攻略メディア『神ゲー攻略』のシステム開発を担当。システムの保守からサイトの機能開発、ライター業務効率改善のためのツール開発など幅広く担当。

まっしゅ(左下)株式会社fluct XDC(Experience Design Center)に所属。フロントエンドを担当。現在、新入社員のOJTも担当中。

しょーた(右下)株式会社fluct 開発本部SREチームに所属。主にAWSやCI/CD、システム監視周りを担当。

ーVOYAGE GROUP(以下VG)に入社しようと思ったきっかけは?

まっしゅ: VGを初めて知ったのは1dayインターンで、その後サマーインターンのTreasureにも参加し出会う人みんながいい人ばかりで、人の部分で魅力を感じ入社を決めました。

しょーた:きっかけはSunriseというインターンで、VGがアドテク方面で強いインフラ基盤を持っていることを知りました。学生時代はインフラを触ってはいなかったけれどVGでなら楽しくインフラに取り組めそうと思い入社しました。

にっしー:僕は2人の経歴を足した感じですが(笑)、1dayとTreasureとSunriseに参加しVGのクルーと関わる機会が多く、会社の事業に興味があったというよりは「いい人ばかりしかいない」という人の部分に魅力を感じて入社しました。

voyagegroup.com voyagegroup.com

※1dayインターンは今年は現時点で開催予定はありません。

ー入社前と入社後でギャップはあった?

全員:ギャップかー…(悩む)

まっしゅ:いい意味でのギャップならありました!入社前は、たとえば管理画面の担当になれば管理画面だけという風に、ずっと1つの領域を任されるのかなと思っていました。でも実際に入社すると、やりたいことをどんどん任せてもらえ、バッチや広告配信のJavaScript、管理画面などfluctにあるもの全般を半年間で触りました。現在は、新サービスの「DATA STRAP」に関わるなど幅広く携わっています!

しょーた:僕は思ってたより、みんながちゃんと仕事をしているというギャップがありました(笑)

にっしー:それ言おうと思った(笑)

しょーた:入社前からカジュアルにコミュニケーションをしてくれたので仕事もそのイメージだったのですが、いざ仕事になるとみんなしっかり考えて仕事をしてるという、いいギャップがありました。

にっしー:僕もほぼ同じなんですが、フランクにコミュニケーションを取る人が多いイメージでしたが、議論の場ではみんなしっかりと自分の考察を持ってくるのが印象的でした。 開発のアプローチを取るときも明確な理由を求められることが多く、ユーザーやプロダクトを中心にして考える必要があるんですが、逆にロジックがしっかりしていれば年次関係なく受け入れてくれる点で、組織構造のフラットさにも驚きました。

しょーた:たしかにそう思う!

にっしー1年目でも筋が通った考えであれば自分の意見が通る雰囲気があるよね。

しょーた:僕はまっしゅを見ていて思うことがあって。fluctは歴史が長いサービスなので技術的にも過去の遺産があって凝り固まってるのかなと思っていたんですよ。でも、まっしゅは新しいことをしているし、fluctの開発自体も新しい技術をどんどん取り入れる雰囲気が強いです。そこも入っていい意味でのギャップでした。

まっしゅ:たしかにもっとレガシーだと思ってた(笑)

ーまっしゅは新しいことをしてるの?

まっしゅ:fluctは2010年に開発が始まったので管理画面のフロントだと古いjQueryだけかと思っていたんですが、モダンなReactも使い始めています。最初は知見がある人がいればReact化したいという感じだったんですが、ちょうど僕が触った経験があったのでReact化していくことも採用され、さらに新サービスのDATA STRAPではモダンな技術のみでの開発に挑戦しています。

にっしー:Reactをメインで書けるのがまっしゅだけの状態でよくそっちに意思決定できたよね。柔軟ですごい。

まっしゅ:たしかに。fluctの中でもフロントメインの人がいないのは課題としてずっとあって、ある日のキックオフ会でfluct CEOのもっちーさんに「まっしゅがフロントチームのリーダーね!1人だけど!」と誰もいないなら自分でやってみようの精神で指名されたのが始まりだった(笑)

全員:すごい(笑)

ー1年目で成長したなと感じるポイントは?

まっしゅ人に聞かず自分で意思決定ができるようになった部分ですね。最初の3ヶ月は、自分のやりたいことがあっても周囲の人に都度伺いを立てていたんですが、1on1の振り返り面談で上司に進め方を相談したら「もっと自分で意思決定していいよ」とアドバイスをもらい、それからは自分で決めてその理由を周囲に伝える姿勢に切り替えました。DATA STRAPでも、自分が1番知っている立場で決める人は自分しかいないので、意思決定の部分で成長できたと思います。

全員:いい話だ…!

にっしー:僕はWebメインの事業部なんですが、入社前まではアプリメインにしか開発してこなかったんです。なので、1年間でJavaScriptの開発力がかなりついたなと思います。また、ユーザーやプロダクト中心に考える姿勢も1年間で育ってきたなと感じます。

しょーた:僕もにっしーと似てるんですが学生時代はサーバーサイドをメインにしてたので、AWSやLinuxの知識はない状態でSREチームに入りました。インフラをメインにやりたい気持ちは強い一方で、業務上どうしてもインフラの知識がないと話し合いにすらならないことも最初は日常的に起き、なので最初は知識の吸収をひたすら行いました。今では管理画面のフロントのCI/CDをまるっと作り変えることができ成長した気がします。

まっしゅ:2年分の負債を取り除いてくれたやつね!最初は僕がやろうとしたけど自分の知識では無理で、しょーたに助けを求めたら全部作り変えてくれました(笑)

しょーた:まっしゅの痕跡も消して作り変えました(笑)あとは試験環境も作ったり、監視周りもできるようになりましたね。

ーどうやって1から知識をつけていったの?

しょーた:問題に対してなにが問題かをひたすら調べ、調べる中でわからないことが出てくるとその瞬間にまた調べ、という繰り返しで進んでいきました。

にっしー:しょーたは今年Treasureのインフラ講師にも選ばれたもんね!すごいよ! voyagegroup.com

しょーた:初めてで不安もあるけど頑張ります(笑)

ー最後に、2年目で挑戦していきたいことは?

まっしゅ:技術面では、SSRとPWAですね。DATA STRAPでも最初からSSRをやってみたいと提案はしていたのですが、SEOを気にしないプロダクトの性質やデプロイフローが難しくなることもあり、自分でプロダクトを立ち上げるのが初なので今回は見送りにしてました。ただ、プロダクトが無事ローンチできたので、次の挑戦としてはちょうどいい課題かなと思っています。

にっしー:Webの知識はついてきた実感があるので、次はAWSやGCPのクラウド周りの知識をキャッチアップしたいです。あとは、課題を解決するための自分の手札を増やすために使える技術の幅も広げていきたいですね。最近はNext.jsを勉強中です。

しょーた:インフラ知識のインプットはできてきたので、次はアウトプットの比率をあげて会社に対して貢献していきたいです。技術的には、最近GCPの勉強を始めたので使っていきたいのと、グローバル展開がきているのでAWS上でのUSなどのマルチリージョン化に取り組みたいです!

まっしゅ・しょーた・にっしーありがとうございました!

動的解析を利用し、実働6日でレガシーコードを1/3削った話(Perl編)

こんにちは!株式会社VOYAGE MARKETINGで働くエンジニアの yopidax です。
約20年ほど続くサービス、ECナビの技術的負債の返済に取り組んでいます。

ecnavi.jp

今回は直近で、レガシーコードを大量に削除したので、そのアプローチをご紹介したいと思います。

目次

解析の対象と抱える課題

ECナビを長年支える、Perlで書かれたバッチが対象です。コードはGitLabのリポジトリで管理されていて、規模をまとめるとこんな感じです。

  • ファイルの数
    • バッチ関連全体 : 3,315
    • うち、Perlファイル(.pm, .pl) : 1,111
  • Perlバッチの数(crontab調べ)
    • 255

ECナビを長年支えるバッチですが、レガシー故に多くの課題を抱えています。

  • テストコードが書かれてない、あっても継続的なテストが行われないため、壊れている
  • crontabの設定のみ削除され、放置されたコードが大量に残っており、生きているコードを把握できない
  • 20年前から継ぎ足しの開発、近年あまりメンテナンスされておらず、知っている人が少ない
  • 現役で事業貢献している機能も多く、全てリライトするよりもうまく活かし続けたい

放置されたコードが多く、何をするにも足枷となるため、まずは「実行されないコードを削除し、生きているコードだけにする」ことで、メンテナンス性の向上と、現状を把握できるようにすることから始めました。

アプローチ

対象ファイル数が多いので、効率よく対処するために、まずは母集団を機械的に一括して減らすことを初手としました。その後で、必要に応じて手作業で個別に対応する想定です。
PerlはLL(lightweight language)であり静的解析が苦手なため、動的解析によるアプローチを選択しました。しかし、動的解析を行うにも、都合の良いPerlモジュールを見つける事が出来なかったので、自作する事にしました。

自作にあたっては、汎用的にはせず、今回の制約条件を活用しました。

  • Perlの利用箇所がcrontabキックに集約されていること、キック方式が統一されていること
  • crontabの実行サイクルが1ヶ月以内に1度でも実行されるのが、3件除いて全てであること

元々、バッチ以外でもPerlを利用していたので、管理画面での操作をトリガーに実行されるケースもあったのですが、過去の取り組みで大半が削除されていたことから、crontabキックへ集約されていることを把握していました。方式に関しては、crontabの設定一覧を調査し、統一されている事を知りました。

解析は、本番環境で実施しました。開発環境だと、データの過不足によりバッチが動作せず、意図するログを収集できない可能性があります。

以下の手順で行いました。

  1. 実行されるファイルを洗い出す
    • Perlの特殊変数 %INC をログとして吐き出す仕組みを追加する
  2. 実行されないファイルを知る
    • GitLabで管理されるPerlファイルを git ls-files ‘*.pm’ 等で出力し、ログと突き合わせる事で、簡単に実行されないファイルを洗い出すことができます

1. 実行されるファイルを洗い出す を詳しく説明します。

実行されるファイルを洗い出す

Perlの特殊変数 %INC を用いて、実行されるファイルを洗い出すことにしました。 %INC は、モジュール名とモジュールのファイルパスの組み合わせを保持します。

以下のようなモジュールを作成し、実行時に読み込まれた全てのモジュールを出力できるようにしました。

ログを出力するモジュール

package IncModuleLogger;
use constant LOG_PATH  => "/hoge/load_module_logger/logs/";
END {
    my $file = LOG_PATH . $ENV{MODULE_LOGGER_OUTPUT_FILE_NUMBER};
    open my $fh, '>>', $file or die;
    print $fh "$0 -> $0\n";
    while (my ($key, $value) = each(%INC)) {
        ## perlのコアモジュール、ライブラリは除外。perl5ディレクトリ以下にある。
        if ($value !~ 'perl5') {
            print $fh "$key -> $value\n";
        }
    }
    close $fh;
}
1;

バッチの実行タイミングや処理するデータの有無により、読み込むモジュールが変わるケースがあります。1バッチ辺りが依存するモジュールを網羅的に収集できるよう、ログは同一のファイルに上書き保存しています。
%INC には、Perlコアモジュールやサードパーティのライブラリも含まれます。Cartonによる依存管理をしていたため除外しました。しかし、実行の起点となるファイル名は含まれないので、ログとして残すために $0 を利用しています。

実行

コマンド

before
common.sh perl ecnavi.pl
after
common.sh 55 perl ecnavi.pl

common.sh に追記

export PERL5OPT="-I$/path/to/ecnavi/logger -MIncModuleLogger"
export MODULE_LOGGER_OUTPUT_FILE_NUMBER="$1"

既存のバッチは全て、共通のシェルスクリプトで環境変数設定などの事前処理を行っていました。こちらに PERL5OPT を追加し、作成したモジュールを読み込ませることで、既存のPerlコードを変更すること無く追加できました。他にも、バッチとログファイルを後から紐付けて集計しやすいように、バッチ毎にユニークな番号を環境変数として渡し、ログファイル名としました。

ログのサンプル

/path/to/ecnavi/batch/ecnavi.pl -> /path/to/ecnavi/batch/ecnavi.pl
Error.pm -> /path/to/ecnavi/lib/error/Error.pm
Mysql.pm -> /path/to/ecnavi/lib/db/Mysql.pm
Ecnavi/Model.pm -> /path/to/ecnavi/common/ecnavi/Model.pm
IncModuleLogger.pm -> /path/to/ecnavi/logger/IncModuleLogger.pm

モジュール名 -> モジュールパス で出力しています。 各モジュール内部でのモジュール検索パス指定によっては、出力されるモジュールパスが一部相対パスになるため、注意が必要です。/hoge/fuga/piyo/../dump.pm など。今回はモジュール名がほぼユニークだったため、こちらを解析に利用しました。

いざ、大量削除

1ヶ月間のログ収集で、252/255バッチ分のログを収集できたので、これを元に実行されないファイルを洗い出し、削除を行いました。削除の際に気をつけたことをいくつか挙げます。

Perlファイルをgrepする

削除対象のパッケージ名やモジュール名からgrepし、関連ファイルの洗い出しを行いました。ymlやiniなど、動的解析では検知できないファイルがあるからです。

結局、grepするなら動的解析を行った意味が無いのでは?と思いますが、動的解析なしですと、1111ファイル全てをgrepする必要があります。解析を行うことで対象を絞り込む事ができるため、作業量は大幅に削減されます。また、今回のケースでは、削除対象が同じパッケージにまとまっている事が多かったため、全体で50回ほどのgrepで済みました。

リリース単位を細かくする

例えば、1MRの対象を、特定のディレクトリ/同系のライブラリ/クラス単位にするなどです。

テストが全て通ったからヨシッ!と言いたいところですが、冒頭で述べた通り、大半はテストが無いか壊れている状況でした。 幸い、冪等性が担保されたバッチが多くリカバリが容易であること、デプロイ環境が整備されており、何かミスがあった場合にすぐに戻せることから、リリースの単位を小さくすることで影響範囲を最小に留めるという判断をしました。

結果

手を動かした日数と削除量、ログ収集が1ヶ月で済んだことを踏まえると、かなりコストパフォーマンスの良いアプローチでした。

工数

  • ログ収集の仕込み
    • 2日
  • 収集
    • 1ヶ月
  • 解析と削除
    • 4日

実績

Perlファイルだけですと、1/3ほど削除する事ができました。また、リリース単位を細かく(MRで12くらい)したことが効いたのか、無事故で作業を終えることが出来ました。今回行った動的解析が信頼できることもわかりました。

  • バッチ関連全体
    • 807(3315 - 2508)ファイル
  • Perlファイル(.pm, .pl)
    • 408(1111 - 703)ファイル

まとめ

レガシーシステムを改善する前段階として、現状の機能を必要十分に絞り込み把握するために、今回の作業を行いました。削除を行うことで、問題の分母を大きく、手間を掛けず減らすことができます。感想ですが、動的解析により、コードが実行されないことが保証されているため、安心感を持って削除できました。また、一括で消せる爽快感もあります!!

とはいっても、上記の課題を抱える中で、コード量を削減していく手段に銀の弾丸はありません。様々な手段を組み合わせながら、段階的に減らしていくと進みやすいです。実際我々も、他にもアレコレしました。

また、このアプローチは言語問わず実現できる方法です。例えばPHPですと、OPcacheを元に解析、なんてこともしました。

今回の作戦があなたのプロジェクトのレガシー改善の一助になれば幸いです。他の方法もあれば、教えて下さい!

[PR]

株式会社VOYAGE MARKETINGでは、一緒に働く仲間を募集しています!

hrmos.co hrmos.co

合わせて読みたい

techlog.voyagegroup.com

2年目エンジニアが新卒に贈る成長のコツ

こんにちは、VOYAGE MARKETING新卒2年目エンジニアのかいくんです。
去年、ビジネス視点を持ったエンジニアになるためにVOYAGE GROUPに入社しました。入社後は新設されたアプリチームに配属してもらい、アプリ開発未経験ながら気合でアプリを開発してきました!

今回は僕が入社後に学んだ成長のコツを紹介していきます。

目次

仕事内容

僕が開発しているアプリはECナビアンケートです。
アンケートに回答するとポイントが貯まって、現金やAmazonギフト券などに交換できるサービスです。

ECナビアンケート

ECナビアンケート

  • EC Navi Apps
  • ライフスタイル
  • 無料
apps.apple.com

このアプリはディレクター2人、エンジニア3人、デザイナー1人の計6人で開発しています。

仕事はミーティングから始まります。ミーティングではチーム全員で何に取り組むかについて話し合います。 エンジニアやデザイナーであっても売上やユーザー動向を把握して、機能や案件の提案を行っています。 取り組む内容が決まったら仮説を立てて検証を行います。 そして検証結果を元にまたミーティングを行うという流れで進めています。

f:id:Kai-kun:20200428172125p:plain:w400

2年目エンジニアが新卒に贈る成長のコツ

事業を理解しよう

提案や方針に対して意見を言うには、事業の理解が必要です。

僕は新卒エンジニアであっても提案や方針に対して意見が言える環境にいました。 そのため「バンバン意見を言って活躍するぞ!」と気合を入れてミーティングに参加していました。 しかし、ミーティングで意見を言うのは簡単ではありませんでした。

それはチームが置かれている状況、業界やユーザーの動向など事業についての理解がなかったからです。 例えば、売上増加を目標としたミーティングでは何をすれば売上が伸びるのかわからず、全く意見が言えませんでした。

そんな僕が事業を理解するために行った2つの取り組みを紹介します。

1. 表・グラフ・ログを確認する

サービスの開発や運用に携わっている方は業界やユーザーの動向を知るために、日々何かの表やグラフ、またはログを確認しています。これらを確認すれば事業の理解を深められます。

例えば僕のチームのディレクターは、スプレッドシートに売上項目を分類して、項目ごとにユーザー1人当たりの売上の目標と実績をまとめています。 このスプレッドシートを確認すると、影響が大きい売上項目や、目標と実績が乖離している売上項目がわかります。

このような売上に関する情報があれば、売上増加を目標としたミーティングで、より売上に影響のある提案ができたり、目標と実績の乖離について質問して議論を活発化することができたと思います。

表・グラフ・ログを確認して事業の理解を深めましょう。

2. 目標と指標が選ばれた背景を知る

仕事には目標があります。そして目標が達せられたか判断するために指標を定めています。 目標と指標が選ばれた背景を知れば、事業の理解を深められます。

例えば僕のチームでは、ユーザー1人当たりの売上増加を目標としています。そしてアプリリリース後の最初の指標は「長期にわたってアプリでポイントを獲得しているユーザー数の増加」でした。

最初の指標を「長期にわたってアプリでポイントを獲得しているユーザー数の増加」としていたのは、アンケート回答による売上は少ないため、長い間サービスを利用してくれなければユーザーを獲得するためにかかった費用が回収できないからです。 このような背景を知っていれば、売上増加を目標としたミーティングで、直接売上を伸ばすための提案だけでなく、ユーザーを獲得するためにかかっている費用を下げるといった違った視点で意見が言えたかもしれません。

目標と指標が選ばれた背景から事業の理解を深めましょう。

優先度を決めて取り組もう

やりたいことがたくさんあるのに対して、やれることはとても少ないのです。 何が必要なのかを判断して優先度を決めて取り組みましょう。

優先度を決める時は、指標やサービス価値に対する影響の大きさを考えると良いと思います。 指標を達成するためには影響の大きい課題から取り組みます。 またサービスでエラーが起きた場合は、サービス価値に及ぼす影響の大きさで取り組む優先度を判断します。

入社したての頃の僕は「すごい新卒だと思われたい」「良いサービスを作りたい」「コードを綺麗にしたい」という気持ちに溢れていました。 そのため「コードが微妙だから新しい書き方にリファクタリングしたい」「放置されているエラーを直したい」といった感じで、目についたものに片っ端から取り組もうとしていました。

しかし片っ端から仕事に取り組むのはよくありませんでした。 取り組める仕事は限りなくあり、終わらないからです。 また無駄な仕事に時間を使って、必要な仕事ができないのは本末転倒です。 先輩にはよく「それは今やらなくて良いよ」と注意されていました。

特に優先度を決める必要を感じたのはアプリのリリース前です。 アプリをリリース予定日までに使えるようにするため、サービス価値に影響の大きい機能を優先的に実装しました。 例えば、会員登録機能やアンケート回答機能です。 デイリーコンテンツや広告といったサービス価値への影響が比較的小さいものは、初回リリース時の実装から省きました。

影響の大きさから何が必要か判断して、優先度を決めて取り組みましょう。

コードレビューで質問しよう

コードレビューで成長するコツは、なぜそのようにコードを書いたのか質問することです。

できるエンジニアの設計には意図があります。 それは責務の分け方、状態や依存の管理の仕方、将来の拡張や運用を反映した設計など様々です。
コードレビューをする際は、そのコードが問題なく動くかを確認するだけではなく、なぜそのように書いたのか?と疑問を持ち、開発したエンジニアと議論を交わすことで成長できると思います。

取り組む課題が大きい、開発期間が短いといった時に、新卒の自分にできる仕事がないこともありました。
「できる仕事がなくて悔しい!でも成長しなければできないまま。」
「やることがなくて、どうしたら良いのかわからない。」
そんな辛い時期もあります。
そういう時は先輩のコードレビューを通じて成長できました。

例えば、どれだけコードを作り込むかはその時々で判断するべきだと学びました。 すぐに削除されるコードであれば、無駄にクラスを分ける必要はないでしょう。if文を追加するだけの最低限の実装とテストで良いかもしれません。またコードが今後どのように改修されるかわかっていない場合は、理解しやすいようにシンプルに実装した方が良いと思います。間違って抽象化されたコードは分かりづらく改修しにくくなってしまうからです。

このようにコードレビューから様々な設計を知り、新たなコードの書き方を身に付けられます。

まとめ

今回は僕が入社後に学んだ成長のコツを3つ紹介しました。

  • 提案や方針に対して意見を言うには事業を理解している必要があります。事業に関する数値や、目標と指標の背景から事業の理解を深めましょう
  • やりたいことがたくさんあるのに対して、やれることはとても少ないです。影響の大きさから優先度を決めて仕事に取り組みましょう。
  • できるエンジニアの設計には意図があります。コードレビューの時はなぜそのように実装したか質問して吸収しましょう。

入社からの1年はあっという間でした。 様々な経験をさせてもらい、たくさんの学びがありました。 そして経験や学びの分だけ、成長や挑戦の機会がありました。

答えのない課題に対して、チームで考え、行動していくのは非常に面白いです。 失敗を恐れず、引き続き頑張っていこうと思います。

ニッチすぎる!?知られざる広告JavaScriptの世界

こんにちは。雨宮(@rail44)です。
普段はヨーヨーやポケモンに興じるかたわら、株式会社fluctで広告配信システムの開発を担当しています。

fluctは広告業界ではSSP(Supply-Side Platform)と呼ばれる立ち位置で、インターネットメディアの収益の最大化にフォーカスした事業を行っています。
私たちのシステムを使うと、広告によるマネタイズが面倒な運用無しに出来る。といったイメージです。

この記事では、自分が直近で担当をしている広告の配信スクリプトと、普段注目されづらいその裏側について書いていきたいと思います!

広告タグの構造

さて、webページに広告を表示したい場合、アプリケーションはHTMLで記述されているため、広告もHTMLタグの形でお渡しすることになります。
(※fluctではモバイルアプリや動画プレイヤーへの広告配信も行っており、それらの場合はHTMLではない納品形態になっています)

シンプルな物で一例を示すと以下のようなHTMLタグになっています。

<!-- A: 表示領域 -->
<div class="hoge"></div>

<!-- B: 広告配信スクリプト -->
<script type="text/javascript" src="https://pdn.adingo.jp/p.js" async></script>

<!-- C: 広告表示コマンド -->
<script type="text/javascript">
  var fluctAdScript = fluctAdScript || {};
  fluctAdScript.cmd = fluctAdScript.cmd || [];
  fluctAdScript.cmd.push(function (cmd) {
    cmd.loadByGroup("hoge-group")
    cmd.display(".hoge", "hoge-unit");
  });
</script>

タグの構造をざっくり分類すると、
A: 表示領域となるdiv要素
B: 外部のJavaScriptを読み込むためのscript要素
C: IDを用いて広告の読み込みや表示を指示するscript要素
といった作りになっています。

ここで、途中で読み込んでいる、B が広告配信スクリプトと呼んでいるJavaScriptとなります。
サーバーへの広告リクエストの発行、広告のレンダリングやスタイリング、各種イベントのトラッキングなどが責務となっています。

さて、このJavaScriptをガシガシ開発していくわけなんですが、広告以外ではなかなか遭遇しないような制約が数多くあるので紹介していこうと思います。

広告ならではの制約

動作するブラウザが限定できない

一般的なwebサービスではターゲットとなるユーザーから、動作環境を想定した上で開発が行われます。
たとえば、スマホユーザーが主体であればiOSのSafariとAndroidのChromeをサポート範囲とすれば大部分をカバーすることができます。

しかし広告システムとなると、様々なwebメディアへと導入されることになるので、広告配信スクリプトはサポート範囲として最大公約数を目指すことになります。

実際、現在のfluctでは、数バージョン前のInternet ExplorerやSafari、つまりPromiseやArray.prototype.forEachが無い世界でも動作するような作りになっています。

Polyfillは(素直には)使えない

動作するブラウザを広くするためには、Polyfillを用いて未実装な機能を補完するという解決策が多く用いられます。
今日ではbabelからcore-jsを使う方法が一般的でしょうか。 [参考]

ところが、これも広告配信スクリプトでは工夫をして導入する必要があります。
なぜなら、広告を表示したいwebアプリケーションへ既にPolyfillが導入されていた場合に、衝突してしまう危険性があるためです。

エラー処理を緻密に行いたい

Polyfillが使えないのと同じような理由で、ランタイムエラーの検知をするのにwindow.onerrorのような手法は使えません。
自分達のシステムとは関連しない、メディア側や導入されている別の広告プロダクトのエラーも検知してしまうためです。

そのために、エラーが発生してもそれをthrowするのではなく、ScalaでいうEitherやRustでいうResultのような構造を用いることが優先されます。
また、想定外のランタイムエラーを検出するためにスクリプトの全体をtry-catch句でラップすることになりますが、window.setTimeoutのような、一旦ブラウザのランタイムへコンテキストが戻ってからコールバックされるような物はcatchができないことに注意する必要があります。

try {
    window.setTimeout(() => {
        throw new Error("hoge"); // Unable to catch!!
    }, 0);
} catch (e) {
    console.log(e);
}

ですが、すでに述べた動作させたいブラウザやPolyfillの制約によりPromiseは使えません!

広告の表示は出来るだけ速くしたい

速くしたい、というのは通常のアプリケーションでもあたり前な話なのですが、広告についてはより顕著です。
多くの広告がインプレッション(表示回数)やクリックされた回数を元に利益の計算がなされるため、表示が遅くユーザーの目に留らないようでは無意味となってしまいます。

メディアの動作とは互いに影響し合わないようにしたい

広告の表示が遅れてしまっても、メディア自体の表示が遅れないように。もしランタイムエラーを起こしてもメディアが動作しなくなることは避けなければなりません。
また、メディア側で用いているフレームワークによって広告表示が出来なかったり、といった事を避ける必要があります。
しかも、ここでもフレームワークの種類やバージョンに始まり、メディアの作りは千差万別です。

以上から、スクリプトの全体的な動作を非同期的に行う必要があります。
先ほどの広告タグの作りにも、その一端が現れているのが分かるでしょうか。

とはいえ、書き味はモダンにしたい

var Ad = function (hoge) {
    this.hoge = hoge;
};

Ad.prototype.load = function () {
    ...
};

なんて2020年に書きたくないんじゃ!!(突然の発狂)
言語オタクとしてはプロトタイプベースの言語はとても可愛いゲがあって良いんですが、チームでメンテナンスをしていくことを考えると辛すぎます。

非同期エラー処理をするにしても、

function setTimeoutWithCatch(cb, errCb, msec) {
    window.setTimeout(function () {
        try {
            cb(e);
        } catch (e) {
            errCb(e);
        }
    }, msec);
}

とか作りたくないんじゃ!

どう戦うか

これまでの制約をまとめると、広告配信スクリプトの要件はこんな感じになってしまいます。

Promiseが無く、Polyfillも使えない環境下で非同期に動作する、読み込み/実行が高速なJavaScript
(日々の開発の負担は減らしたい)

:sob:

この要件への立ち向い方は色々ありそうですが、現在のところ、

  • 最低限のPromise風のsyntaxが使えるオブジェクトを手製
  • そのPromiseLikeなオブジェクトを使ったモジュールローダーを手製
  • 手製のモジュールローダーを使うようなJavaScriptの成果物となるよう、バンドラであるRollupを使ったビルドシステムを整える

などといった取り組みを行っています。

Promise風のオブジェクト

今回のケースでPromiseが使えるようになると何が嬉しいかというと、コールバック関数をラップする、といった辛さのあるエラーハンドリングの回避が出来る点が最も顕著でした。
そこで、それなりの大きさがあるfull-featuredな代替実装を使うのではなく、素朴な .then/.catchを実現できることのみにフォーカスした、小さなPromise風オブジェクトを実装しPonyfill的に運用しています。

Ponyfill

import { PromiseLike } from "./promise"

function delay(msec: number): PromiseLike<void> {
  return new PromiseLike((resolve) => {
    setTimeout(() => resolve(), msec);
  });
}

のように、Promise風オブジェクトを使う部分で明示的にimport文を書くことで、モジュール内のスコープでのみその恩恵を受けることができるようになります。
また、このPromiseLikeには .catch がなされていない例外のスローが起きた時のハンドリングを実装しているので、想定外のランタイムエラーをログ収集基盤で検知することも実現できました。

(なお、TypeScript自体に PromiseLike<T> 型が存在していて、import無しだとそちらと見做されてしまう問題には後から気付き、対応予定というのはあります……)

モジュールローダー

AMD形式のモジュールを動的に読み込むモジュールローダーも自前で実装しています。
モジュールローダーへ求められる要件としては

  • Internet Explorerで動作する
  • 用いるPromiseの実装を上記の物に切り替えることのできる
  • グローバルではなく名前空間を持たせてモジュールの定義や展開ができる
    • これもPolyfillと同様、Webメディアのモジュールローダーとの衝突を避けるため

といった物で、このようなモジュールローダーは自分で書かなければ存在しませんでした。
AMDの仕様へ準拠しつつ、広告商材の配信頻度からimportする可能性の高いモジュールはエントリポイントとなる単一のJavaScriptへバンドルし、そうでない物は外部モジュールとしてビルドする、といった私たちのビジネスの構造に特化した実装をしています。

ビルドシステム

ここまで見てきた通り、広告スクリプトは通常のWebアプリケーションの構成とは大きく異なっています。様々なWebページからロードされ、その機能が利用されるといった特徴から、アプリケーションというよりライブラリに近い特性を持っていると言えます。
そこで、Webアプリケーションのバンドラとしてデファクトスタンダードと言えるWebpackではなく、npmモジュールのビルドでよく用いられているRollupを採用しています。

また、モジュールの分割の制御や独自のモジュールローダーの採用の必要から、RollupのCLIを直接使うのではなく、Rollupをライブラリとして利用するビルドスクリプトを記述してビルドを行っています。
言及が遅れましたが、TypeScript、ESLint、Terserといったモダンなツールチェインが使えるような環境も実現できました。

まとめ

ニッチな話題が続きましたがいかがでしたでしょうか。

class構文、const/let、Promise、async/awaitなど近年のJavaScriptの進化は目覚ましく、類を見ない先進的な構文を持った言語になりつつあります。
その書き味や開発のしやすさを維持しつつ、上記のような制約を満たすためのビルドシステムやモジュールシステムを作り込むのは、大変ですがエンジニアとして非常に魅力的な課題です。
また、Web上のトラフィックで少なくない割合を占める、広告の表示で高速化や省サイズを実現することはインターネットそのものに寄与することのできる取り組みでもあります。

今回の記事では割愛していますが、CIとLambda@Edgeを組み合わせた検証環境などのトピックもあるので、別の機会があればまだまだ話が出来そうです!

株式会社fluctでは、こんなニッチなJavaScriptで広告をよりよくする仲間を募集しています!

株式会社fluct SSP開発本部 ソフトウェアエンジニア | 株式会社VOYAGE GROUP
株式会社fluct SSP開発本部 シニアソフトウェアエンジニア | 株式会社VOYAGE GROUP
株式会社fluct SSP開発本部 ソフトウェアエンジニア(沖縄勤務) | 株式会社VOYAGE GROUP
株式会社fluct SSP開発本部 クライアントサイドエンジニア | 株式会社VOYAGE GROUP