Chromeでのインターン生活を振り返る
今年の夏にGoogle JapanのChrome(ブラウザ)チームで2ヶ月間インターンをしました。去年の夏も行ったので今年で2回目です。
今年のインターンはどんな感じだったのか、年末なので(?)ゆるく振り返ってみようかと思います。
インターンが決まった経緯
私は去年もGoogle STEPプログラムの一環でMapのチームでインターンをしていましたが、そのときにreturning offerを頂いて再びインターンができることになったので戻りました。今回は普通の(STEPでない)インターンとして参加しました。
returning offerを頂いた後、今年の4月ごろにインターンでの希望分野やチームを聞かれたのですが、Chromeを希望しました。 去年いたMapチームが外国人比率が高めだった一方、Chromeは日本人エンジニアが多めなチームの1つだったので、両方の環境を体験してみたかったことと、 やはりChromiumがオープンソースであるのが魅力的だったことが理由です。
その後、ホストマッチングで無事にChromeチームに配属されることになり、ChromeのService Workerを担当するチームでの9週間のインターンが始まりました。
プロジェクトの内容
ChromiumはOSSでプロジェクトの内容も自由に話すことができるので、軽く説明してみたいと思います。
TL;DR
ChromeのService Workerのアップデートにおいて、旧来はregisterしたスクリプトに変更があった場合しかアップデートが起こらなかったところを、 そのスクリプトからimportScript()で呼び出されるスクリプトのみに変更があった場合もアップデート処理を呼べるようにするというプロジェクトに取り組みました。
Service Workerについて
Service Workerとは、Webページが開かれていない間もスクリプトを走らせることを可能にするようなJavaScriptの機能で、Chromeでは確か2013年ごろに実装が始まりました。 Webリソースをキャッシュしてオフラインでの閲覧を可能にしたり、タブを開いていない間でもプッシュ通知を送ることができるようにしたりなど、アプリケーションのような機能をWebで使うことができるPWA(Progressive Web Apps)の主要技術として最近注目されています。
Service Workerの動き
register
Service Workerを使うにあたって、Web開発者はその挙動を記述したスクリプト(main scriptと呼びます)を用意し、それをnavigator.serviceWorker.register()
というメソッドを使って登録させます。下記の例ではsw.js
というのがmain scriptになります。
<!-- main.htmlとか --> <script> navigator.serviceWorker.register('sw.js'); </script>
こうすることによって、ページの初回訪問時にそのページのService Workerがブラウザに登録され、次回以降の訪問時にはそのページを管理するService Workerを使った処理がなされるようになります。
register後のページロード
登録済みのService Workerによって管理されているページへのアクセスが発生したときの挙動をみてみましょう。
アクセスしようとしたページを管理するService Workerが登録されているとき、ブラウザは通常のようにネットワークリクエストを投げるのではなく、そのService Workerに対してfetch eventというものを発行します。fetch eventを受け取ったService Workerの動きはmain scriptでの実装次第ですが、よくある例では、予めブラウザのストレージにページのリソースが保存されるようにしておき、それらを読み込んできて返すということをしています。
このような場合、保存されているリソースをリモートに合わせて適切なタイミングでアップデートする必要があります。Chromeでは、該当ページに対するfetch eventの処理が終わった直後にアップデートの必要があるかどうか(=リモートにあるリソースの内容と手元にあるものの内容が異なっているかどうか)をチェックし、必要があると判断した場合はアップデートのルーチンを引き起こします。
もっと詳しく知りたい方は以下の資料なども参考にしてください :) qiita.com
問題だった点
main scriptは普通のJavaScriptファイルなので、次のように他のスクリプトを importScripts()
することが可能です。
// sw.js (main script) importScripts('foo.js');
このような場合のアップデートチェックで、 sw.js
には何も変更がないが foo.js
には変更があった、というような場合、全体としては変更があるわけなので、アップデートをするのが自然に思えます(そしてW3Cのspecificationでも要求されています)。
しかし、ChromeのService Workerの実装には、たとえfoo.js
に変更があったとしても、 sw.js
に変更がなかった場合はアップデートが起こせないというバグがありました。これを直して、main scriptからimportScripts()
で呼ばれる全てのリソースに対してアップデートチェックをする、というのが今回の私のプロジェクトでした。
難しかった点
最初にプロジェクトの内容を聞いた時は別にそんなに難しくないんじゃないかと勝手に思っていましたが、
importScripts()
で読み込まれるリソースを静的に解析できない- アップデートが必要な時(リソースに変更がある時)以外はスクリプトを実行してはいけない
といった理由から、新しい版で必要とされる全てのリソースを知ることが不可能だったこと、 また、それまでのアップデートチェックの実装を利用することができず、一から作る必要があったことから、思いの外難しかったです。
それなりに大きいプロジェクトだったので、実装を始める前にどのようなアプローチで実装するのかを説明するドキュメント(design doc)を書く必要があったのですが、 これも私の担当だったので、メンターの方やチームの方と議論をしながら書きました。 design docが完成して実装の方針が立つまでにも色々な紆余曲折があり、それらは以下のスライドで説明されているので、興味がある方は読んでみてください。
(※最終プレゼンテーションで使ったスライドのうち、公開できる部分のみを残したものです)
9週間でやったこと
プロジェクトの進捗やその他どのようにして過ごしたかを、時系列で振り返っていこうと思います。半ば自己満足のために書いているだけのいわば日記みたいな内容になっているので興味のない人は読み飛ばしてくださって構いません、、、
第1週(7/30~)
初日にオリエンテーションがありましたが、昨年と同じだったので早々に切り上げて自分の席に行き、環境構築をしていました。 初日にはChromiumをビルドして動かすところまでできていた気がします。去年のチームではGoogle Maps iOSを最初にビルドするまでに2日くらいかかっていた気がするのでChromiumはビルドが簡単で良いなと思いました。
机の様子はこんな感じでした。 確か16コアのデスクトップマシンに24inchくらいのモニター2枚をいただきました。キーボードは私物です。 Chromiumは大きいのでそれなりにコアを積んだマシンでないとビルドが重すぎて人権がないようです。 gopherくんのぬいぐるみはメンターさんのものでしたが、インターン期間中はずっと私の机の上にいてくれました。
最初の週だったので、まずはChromiumのワークフローに慣れようということで、小さなバグを与えてもらってそれに取り組んでいました。
Googleはしょっちゅう何かしらの社内イベントをやっているのですが、この週も早速金曜日に年一度のChromeチームの遠足的なものがあり、一日がかりで飯能に行きました。 遠かったです。 ビールの製造所が併設しているレストランに行って、クラフトビールの製造過程の見学をしたり中東料理をいただいたりしました。 お酒が苦手なので美味しいビールを美味しく味わうことはできませんでしたが、お料理は堪能できました。
submitしたパッチ
- 1158092: ServiceWorker: Make NextEventId() thread-safe
- メンターの方が偶然見つけたバグをついでに直したものです
- 1156334: ServiceWorker: Use TRACE_EVENT_WITH_FLOW for tracing fetch events
- 1159939: Use TRACE_EVENT_WITH_FLOW for tracing ServiceWorkerContextClient events
- Starter bugへのパッチ。chrome://tracingでService Worker関連のイベントを可視化する際に、関連するイベントの間に矢印を貼ってわかりやすくしようというものでした。
第2週(8/6~)
メインのプロジェクトのためにコードを読んでいました。Service Workerの実装は膨大かつ複雑で理解に時間がかかったので多分この週はこれしかしていないです。
学校の某実験の最終課題(オセロ)の締め切りもあり、少し忙しかったです(いうほど真面目に取り組みませんでしたが)。
submitしたパッチ
なし
第3週(8/13~)
先週コードを読んでいて気づいたバグがあったので、それを直していました。 Chromium本体のコードの修正はたった1行でしたが、テストを追加することになり、それに時間がかかっていました。 元々のコードが結構わかりにくかったので、それを修正するためにたくさんコメントを入れたり、テストの形式を変えたりなどしていました。 そのおかげでパッチを出すのに1週間くらいかかってしまい、readabilityは難しいなぁと思いました。
メインのプロジェクトのdesign doc(新しい機能の詳細な実装計画)も多分この頃に書き始めました。
submitしたパッチ
なし(この週に取り組んでいたものは翌週出しました)
第4週(8/20~)
design docを書いて、チームの他の人に見てもらっていました。 当初の実装方針では、例外的な場合においてW3Cの定めているService Workerのspecificationに違反してしまう可能性があることが指摘され、 それを適切に対処するかどうかでチーム内で意見が分かれたのですが、最終的にはspecificationに違反しないが実装がより複雑になる方針に変えることになりました。
実装も多分この頃から始めていました。
あとは、火曜日にService Workerチームの人たちと西麻布(六本木から近い)にある前衛的なスペイン料理のレストランに連れて行っていただきました。 Googleではインターンが来たとき、チーム全体で外食などをしてWelcome Lunchをする風習がある(費用も会社から負担される)のですが、 これも私と、先週からチームに来ていたもう1人のインターンの方のWelcome Lunchとしての企画でした。 ランチなのにwelcome drinkが出てくるようなとても洗練されたレストランでした。美味しかったです。
submitしたパッチ
- 1172222: [ServiceWorkerCacheWriter] fix update of bytes_compared_
- 先週取り組んでいたバグの修正とテストを週の始めにようやく出しました
第5週(8/27~)
この週の月曜日に、Googleのエンジニア全体で江ノ島にバーベキューに行く一日がかりのイベントがありました。 去年も参加して、折角海の近くに来たのに暑すぎて砂浜には出ずひたすら海の家で過ごすということをしていましたが、今年も同様でした。
プロジェクトの方は実装がある程度進んで、部分的に動くものもできてきたので、ようやくパッチをレビューに出すことを考えるようになりました。 このときすでに手元での変更が600行くらいになっており、1つのパッチにまとめるには大きすぎたので、細分化を試みようとしつつも結構苦戦していました。
思うにChromiumのような大きなプロジェクトでは、単純なことをするのにも様々な箇所への変更を加えなければならないので、 実装をしていて普段の感覚でキリが良いと思えるポイントまで中々到達できず、あとでパッチを分けるという事態になってしまったのだと思います。 そういった箇所で普段の開発との感覚のずれみたいなのがあり、少し難しいなと思いました。
前週からマウンテンビューのオフィスから東京にやってきている社員さんがいて、よくlld(リンカ)などについてのお喋りをしたりしていました。
submitしたパッチ
- 1192571: Add ServiceWorkerImportedScriptUpdateCheck flag
- メインプロジェクトのためのflagを足しました。今回実装した機能のon/offをchrome://flagsから操作することができるようにするものです。
第6週(9/3~), 第7週(9/10~)
細分化して綺麗にしたパッチをようやくレビューに出し、いくつかmergeさせるなどしていました。 インターン期間の終わりも近づいてきたのに残された実装が山のようにあったので結構焦り始めていました。
9/13には、Chromeが今年で10周年を迎えることを記念して、Chromeに初期から関わっているエンジニアのお話を聞いたりご飯を食べたりする、少し大きめのイベントがありました。 正直仕事のほうをやっていきたいという気持ちでしたが...
We hit double digits! Thanks to all of you for making #GoogleChrome’s last 10 years so awesome. pic.twitter.com/grBZusVmel
— Google Chrome (@googlechrome) 2018年9月4日
submitしたパッチ
- 1198651: Add pause_when_not_identical option for ServiceWorkerCacheWriter
- 1215458: Add ServiceWorkerUpdateChecker
- 1214975: Add ServiceWorkerCacheWriter::Resume() for paused cache writers
- 1220854: Add ServiceWorkerSingleScriptUpdateChecker
- いずれもメインプロジェクトのためのものですが、後にくるメインのパッチで使われる予定のメソッドを追加したり、新しく追加するクラスを輪郭だけ追加したりといった脇役な感じのパッチが多いです
第8週(9/17~)
前週の終わりに作り始めたプロジェクトの中で最も重要で大きなパッチ(crrev.com/c/1224610)を少しずつ見てもらいながら、ユニットテストを書き始めました。 新しい機能に対するテストなのでテスト用のクラスを一から書こうとしていたわけでしたが、自分の実装のデザインがユニットテストがしにくいデザインになっていたことに書き始めてから 気づくようになりました。テストがしにくいデザインというのはつまり大概の場合はつまり使い方が直感的でないクラスやメソッドを作っているということな気がするので、 テストを書くとこういう良いことがあるんだなぁという気づきを得たような気がします(ほんまか)。
ただ、今回のプロジェクトではたまたま「テストが書きやすくなるデザイン」と「意味的にわかりやすいデザイン」が食い違うような箇所があって、 どちらを取るべきなのかかなり迷ったようなこともあったので、一概には言えない難しさも感じました。
水曜日にはChromeのVersion 69のリリースを祝ってケーキを食べるイベントがあり、Chromeの非公式キャラクターであるどーもくんのケーキを食べました。 かわいいですね。
金曜日には学科に新しく入ってくる2年生の歓迎会があり、次の週には学校が始まる予定だったので、もう夏休みも終わるんだなぁと一人でしみじみしていました。
submitしたパッチ
なし
第9週(9/24~)
インターンもついに最終週だったので、木曜日にある最終プレゼンに向けてのスライド作りを隙間時間にしつつも、先週から続けているパッチの修正に時間を割いていました。 プロジェクトがキリの良いところで終わるか終わらないかの瀬戸際だったのでかなり焦っていて、本来ならプレゼンの準備にもっと専念していても良い時期でしたが、 ギリギリまでパッチのsubmitのために時間を使おうと粘っていました。
学校が火曜日から始まりましたが、この週だけは授業を欠席してインターンに行っていました(最初からその予定でした)。 Chrome内の隣のチームの同じ大学のSTEPインターンの方々は先週にインターンを終えていなくなっていたので、少し寂しい感じがしました。
木曜日にインターンの最終成果発表があり、Chromeチームの方々ほぼ全員に集まっていただいた前で30分間の英語でのプレゼンを行いました。 発表の数分前までスライドを直していましたし、喋る練習をする時間が直前の2時間くらいしか取れなかったりしたので、かなりバタバタしたプレゼンでしたが、 複雑なプロジェクトの内容を長々と説明していたらいつの間にか時間をほぼ使い切っていたという感じで終わりました。
submitしたパッチ
- 1237693: Remove unused variable in ServiceWorkerNewScriptLoader unittest
- 1237634: [ServiceWorker] Fix the timing of state transition in NewScriptLoader
- コードを読んでいて気づいた小さなバグを修正しました。
- 1224610: Implement ServiceWorkerSingleScriptUpdateChecker
おわりに
気の向くままに書いていたら中途半端な日記のようになってしまいました。人に見せる内容でもないのでAdvent Calendarとかには貼らずに静かに公開したいと思います。
インターン期間中お世話になったみなさま、ありがとうございました。