ピクシブの春インターンに参加しました
2025-03-05 から 2025-03-14 の間、ピクシブ株式会社が開催していた『PIXIV SPRING BOOT CAMP 2025』の pixiv ウェブエンジニアリングコースに参加していました1。
上記の募集ページから一部抜粋すると、こんな感じのコースです:
pixiv 開発で直面している課題に対して、型システムに基いた精密な静的解析ツールを活用した型付けの強化やルール作成、リファクタリングなどの課題に挑戦してもらいます
静的解析ツールまたは Rector を用いて、型システムおよび構文木に基いた静的解析に挑戦します。 特定のプログラミング言語だけでなく、プログラミング言語そのものに強い興味がある方を歓迎します。PHP の経験はあると望ましいですが、必須ではありません
というわけで、PHP の静的解析とかそのあたりをやってきました。
参加しようと思ったわけ
Twitter で、メンターがツイートしていたのを見たのがきっかけです。
ツイートを読み込み中...
厳密には、興味を持ったきっかけはこの ↑ ツイートではなく、2024 年夏インターンの募集ツイートだったのですが、こちらは応募しそこねてしまいました2。なので、春に同じようなコースの募集が行われているのを見つけて、改めて応募したというわけです。
僕は中学生の頃から長らく C を触ってきました。変数に型が付いているのが当たり前という価値観が育っていたわけです。そんな中、高専に入学してすぐの頃、Discord の Bot を作るために Python を触り、数値型を引数に取る関数に文字列を渡していたことに気付かず、意図した挙動をしないことに悩まされていました。原因に気付いた当時のツイートがこれです:
やっとできた・・・ファイルから読み込んだ関係で変数の型が str だったけど、発言させるチャンネルを取得するのに使う関数の引数は int 限定だったから発言されなかったっぽい
やっぱ型なくても変数宣言できる言語はあれだな・・・https://twitter.com/Watasuke102/status/1272853053473841152 (凍結されたアカウントによるツイート3)
その後も、動的型付け言語を触るたびに似たような経験をする羽目になってしまいました。このような経験から、開発体験が、プログラミング言語……というより型の有無によって大きく変わるという気付きを得たのですが、これが当時の僕にとっては衝撃でした。当たり前ですが、開発体験は良いに越したことはありません。メモ帳でもコードは書けますが、シンタックスハイライトがあったほうが嬉しいし、LSP 等で補完などの開発支援機能を使えたほうが嬉しいですよね。という流れで、徐々に開発体験を向上させてくれる型システムに対して興味を持つようになっていきました。まだ真面目に勉強していませんが。
また、長らく TypeScript に触れている経験(と、JavaScript に苦しめられた経験)から、動的型付け言語に対して静的解析などを行うことによって開発経験が改善される、というのは身を持って体感しています。そういうわけなので、PHP という動的型付け言語に対して静的解析などを行うこのコースに興味を持ち、応募しました。
それから、僕が個人的に pixiv やピクシブ百科事典を使っていたこと、ピクシブのインターンやイベントに参加している人を Twitter の TL でよく見かけていたことから、ピクシブという会社そのものに興味があったのも要因です。
応募
ピクシブのインターンに参加したい理由、コースの志望理由、アピールしたい開発経験などを書いて出します。2025-01-14 締め切りで、僕は当日の 20 時ごろに提出しました。翌日には面接の日程を尋ねるメールが来て、01/30 に選考結果が通知されたという感じです。
面接
面接では、静的/動的型付け言語にまつわる質問が多かった記憶があります。同じ静的型付け言語である Rust と TypeScript は何が違うのか、動的型付け言語が用いられているのは何故だと思うか、等です。後者はうまく答えられませんでしたが、Python を例に挙げてライブラリが豊富であること、書き手が(型を明示しなくて良い等の点で)動的型付け言語を好んでいることが要因としてあるのでは、みたいな回答をした気がします。
また、面接の後半にはコーディングテストがありました。課題としては、ログが与えられ、それをフィルタリングする的なやつでした。面接前までは「なんやかんやでいちばん慣れてて言語機能が強い TypeScript で書こうかなあ、必要に応じて Rust か C++を使おう」と思っていたのですが、問題があからさまに(?)シェル芸だったので、シェルスクリプトで書きました。マジで予想してなくてちょっと面食らいましたが、いちおうそれっぽい解を書くことは出来ました。
最後の方に「このコードの問題点はどこでしょうか?」と聞かれたのですが、変数の命名以外に何も思い浮かびませんでした。なんと意図しない行にマッチしてしまう可能性があるコードを書いてしまっていたようです。前述の質問にうまく答えられなかった点も含めて、感触としては微妙だったのですが、選考を通過できていて良かったです。コーディングテストはコードを書く過程を見るものである、みたいな話はよく聞きますし、当日にもそれっぽい説明を受けはしましたが、まあ正しいコードを書けた時よりは不安になるものですよね。
やったこと
インターン期間中は、PHP の静的解析を行うツールである「PHPStan」と、自動的にリファクタリングを行うツールである「Rector」に関するタスクに取り組みました。
PHPStan
静的解析で型情報を表示したりしてくれるツールです。
早速ですが以下のコードを見てください。
<?php declare(strict_types = 1);
/**
* @param false|string $a
*/
function f($a): void {
if ($a === false) {
\PHPStan\dumpType($a); // false
} else {
\PHPStan\dumpType($a); // string
}
}
関数の上に書かれているブロックコメントは PHPDoc 形式です。関数の引数である $a
の型が false
あるいは string 型であると指定しています。そして、コメントでも書いていますが、if で $a
が false
であることを確かめたら、その if ブロック内では $a
が false
であると教えてくれます。そうでない場合、つまり else ブロックでは $a
は string であると教えてくれます。
割と TypeScript っぽいですね。しかし、PHPStan のすごいところは、以下のような型推論4が出来る点です:
<?php declare(strict_types = 1);
$a = ['a' => 10, 'b' => 100];
\PHPStan\dumpType($a);
// => array{a: 10, b: 100}
\PHPStan\dumpType( array_sum($a) );
// => 110
array_sum
は配列の要素を合計する関数です。引数として渡されている配列の中身が静的に決定できるので、array_sum
の実行結果を静的に決定して、その結果を型として利用できるんです。すごくないですか?
何故こんな事ができるのかというと、PHPStan が array_sum
に対して静的解析を行うコードを書いてくれているからです。PHPStan には、動的 (Dynamic) に関数 (Function) の戻り値の型 (ReturnType) を調べる拡張機能 (Extension) を追加することができ、 DynamicFunctionReturnTypeExtension
インターフェースを実装したクラスとしてこれを作成することができます。例えば、 PHPStan による、array_sum
に対する拡張機能の実装が以下です。
というわけで……
社内ライブラリに対する PHPStan 拡張機能の実装
……この見出しにあるとおりのタスクが与えられました。社内で使われている PHP の関数に、戻り値の型を推論する PHPStan 拡張機能を追加するというやつです。
けっこう難しかったです。上に貼った PHPStan のソースコードを見てもらえたら分かるかもしれませんが、例えばある変数 $a
が ConstantArray である、すなわちキーが分かっている配列であるということは、$a->getConstantArrays()
が返却する配列が空ではない(count($a->getConstantArrays()) > 0
)ということです。どういうこと??
そもそも PHP について詳しくなかったので、ConstantArray とは?というところからわからなくて、大変でした(まあこれについては、PHP で一般に使われる用語ではないっぽくて、PHPStan の内部実装で使われる用語らしいという雰囲気があります)。最終的にメンターとペアプログラミングっぽいことをして書きながら理解を進めていきましたが、内部実装のドキュメントが全然ないことも踏まえると、PHPStan のことを知っている人が近くにいない状態で PHPStan のコードを理解できる気はしないですね。
PHPStan の Issue 探索
前述のタスクが終わったら、次に PHPStan の Issue を起点として本体の実装を眺めるというタスクが与えられました。
まず見たのは以下の Issue です:
以下のようなコードで、戻り値の型が静的に決定できず、広すぎる型になってしまう、というものです。
Playgroundhttps://phpstan.org/r/e8216080-38be-4654-b09d-1ffda02e1f34
これは Issue に貼られていた playground です。いちおうここにもコードを貼っておきます(return の上にコメントを追記しています)。
<?php declare(strict_types = 1);
class Reproduction
{
const TYPE_XXX = 'xxx';
const TYPE_YYY = 'yyy';
const TYPE_ZZZ = 'zzz';
/**
* @return array<'a'|'b'|'c'|'d',Reproduction::TYPE_*>
*/
public function main()
{
$list = [
'a' => Reproduction::TYPE_XXX,
'b' => Reproduction::TYPE_YYY,
'c' => Reproduction::TYPE_ZZZ,
'd' => Reproduction::TYPE_XXX,
];
$keys = ['a', 'b', 'c', 'd'];
$found = false;
foreach ($keys as $key) {
if ($list[$key] === Reproduction::TYPE_XXX) {
// The first matched key is kept and subsequent matched keys are rewritten.
if (!$found) {
$found = true;
} else {
$list[$key] = Reproduction::TYPE_ZZZ;
}
}
}
// list => array{
// a: literal-string&non-falsy-string,
// b: literal-string&non-falsy-string,
// c: 'zzz',
// d: literal-string&non-falsy-string
// }
return $list;
}
}
$list
は、関数の PHPDoc で示されているように「 'a'|'b'|'c'|'d'
をキーとして、 Reproduction::TYPE_*
を値とする array 」であってほしいのですが、ループ内で上書きされる余地のある c
キーに対応する値の型のみ 'zzz'
になっていて、ほかは literal-string&non-falsy-string
になっています。このせいで、関数の PHPDoc に書かれている戻り値の型と整合性がない、というエラーが発生します。
このバグの面白いところは、** $found
の型が bool であると明示する(直前の行に /** @var bool $found */
を追加する)だけで、エラーが発生しなくなる**という点です。内部構造を把握したうえで見ると納得なのですが、パッと見た感覚ではかなり非自明ではありませんか?ぜひ原因を考えてみてください。
ちょっと話が逸れるので、答えは折りたたみ内に書いておきます。ちなみにこの記事で一番時間をかけているのがこのセクションです。インターン終了からこの記事が投稿されるまでに10日以上を要しているのもこいつのせいです。暇なら読んでください。
上記のコードについて説明する前に、とりあえずこのコードにおける \PHPStan\dumpType($a)
の出力を考えてみてください。
<?php declare(strict_types = 1);
$a = 1.0;
while(true) {
$a /= 2.0;
\PHPStan\dumpType($a);
}
$a
の初期値は分かっていますし、代入演算子の右辺も分かっているので、$a
のとりうる値が静的に決定できる……わけではないですよね。無限ループなので、$a
の値は 0.5, 0.25, 0.125, ... と変わっていき、すべてを列挙することは出来ません。にもかかわらず静的解析で逐一計算をしていたら、解析がいつまで経っても終わりません。
というわけで、PHPStan は一定回数以上ループ処理を試行して、それでもスコープ内にある変数の型が固定できなければ、変化する変数の型を「一般化」(generalize) するという処理を行います。わかりやすいのが以下の例です:
<?php declare(strict_types = 1);
// 1回ループ
$a = 1.0;
for ($i=0; $i<1; $i++) {
$a /= 2.0;
}
\PHPStan\dumpType($a); // 0.5
// 2回ループ
$a = 1.0;
for ($i=0; $i<2; $i++) {
$a /= 2.0;
}
\PHPStan\dumpType($a); // float
ループが 1 回の場合は $a
の値が定まりますが、2 回の場合は float になっています。PHPStan は、 2 回目のループが終了したタイミングで、現在のスコープをループ開始前のスコープと比較して、変数の型が変化しているかどうかを確かめます。
実装は以下のあたりです:
do-while 文の最後にこんな処理がありますね。
if ($bodyScope->equals($prevScope)) {
break;
}
if ($count >= self::GENERALIZE_AFTER_ITERATION) {
$bodyScope = $prevScope->generalizeWith($bodyScope);
}
$count++;
GENERALIZE_AFTER_ITERATION
は 1
と定義されています。$count
が 0-indexed なので、ループ処理を 2 回試行して、それでもスコープが固定されなければ、変数を generalize するという処理が行われるわけです。
さらに、以下のコードを見てください。
<?php declare(strict_types = 1);
$list = [
'a' => 0,
'b' => 1,
'c' => 2,
'd' => 0,
];
foreach ($list as $i) {
\PHPStan\dumpType($i); // 0 | 1 | 2
if ($i === 0) {
// 処理1
} else {
// 処理2
}
}
それはそうという気もしますが、foreach 内での $i
は、配列 $list
に入っている値の列挙になりますよね。そして、 PHPStan が foreach を解析する際は、$i
の値を逐一変更しながら検査しているわけではないようです。つまり、 $i
はループ試行ごとに 0, 1, 2, 0 と変わっていくわけではなく、ずっと 0 | 1 | 2
のままらしいです。
このせいで、 if の条件が静的に決定できなくなっています。このような状況において、PHPStan は、各 if 文において、その条件式が true である時と false である時の双方を検査します。「処理 1」と「処理 2」の双方が検査されるわけですね。
では、先程の issue で貼られていたコードをもう一度見てみましょう。
<?php declare(strict_types = 1);
class Reproduction
{
const TYPE_XXX = 'xxx';
const TYPE_YYY = 'yyy';
const TYPE_ZZZ = 'zzz';
/**
* @return array<'a'|'b'|'c'|'d',Reproduction::TYPE_*>
*/
public function main()
{
$list = [
'a' => Reproduction::TYPE_XXX,
'b' => Reproduction::TYPE_YYY,
'c' => Reproduction::TYPE_ZZZ,
'd' => Reproduction::TYPE_XXX,
];
$keys = ['a', 'b', 'c', 'd'];
// この↓PHPDocの有無で結果が変わる
/** @var bool $found */
$found = false;
foreach ($keys as $key) {
if ($list[$key] === Reproduction::TYPE_XXX) {
// The first matched key is kept and subsequent matched keys are rewritten.
if (!$found) {
$found = true;
} else {
$list[$key] = Reproduction::TYPE_ZZZ;
}
}
}
// list => array{
// a: literal-string&non-falsy-string,
// b: literal-string&non-falsy-string,
// c: 'zzz',
// d: literal-string&non-falsy-string
// }
return $list;
}
}
なんとなくわかりましたか?つまり、PHPDoc で $found
の型が bool であると示したとき、以下のように処理が行われます:
- ループ処理 1 回目:
- 最初の if 文について、
$list[$key]
を静的に決定できないため、true / false 双方のケースを検査する。つまり、条件式が true であるとみなして処理を行う if (!found)
について、$found
は bool 型だから(静的に決定できないから)、true / false 双方のケースを検査する- true の時、
$found
の型は変化しない(既に bool だから) - false のとき、
$list
は代入によって型が変化する。$key
が静的に決定できないため、$list
の型はarray{a: 'xxx'|'zzz', b: 'yyy'|'zzz', c: 'zzz', d: 'xxx'|'zzz'}
となる
- true の時、
- 最初の if 文について、
- ループ処理 2 回目:
- 最初の if 文について、 条件式が true であるとみなして処理を行う
if (!found)
について、$found
は bool 型だから、true / false 双方のケースを検査する- true の時、
$found
の型は変化しない(既に bool だから) - false のとき、
$list
の型は変化しない(既に変化しているから)
- true の時、
- スコープが変化していないため、変数の generalize は行われない
PHPDoc がない場合(つまり Issue に貼られたコードそのままの場合)、 $found
の型は false となり、以下のように処理が行われます:
- ループ処理 1 回目:
- 最初の if 文について、
$list[$key]
を静的に決定できないため、true / false 双方のケースを検査する。つまり、条件式が true であるとみなして処理を行う if (!found)
について、$found
は false だと静的に決定できるから、 条件式が true のケースのみ検査する$found
は、 true が代入されることによって bool 型となる(true | false
は明らかに bool ですよね)
- 最初の if 文について、
- ループ処理 2 回目:
- 最初の if 文について、条件式が true であるとみなして処理を行う
if (!found)
について、$found
は bool 型だから、 true / false 双方のケースを検査する- true の時、
$found
の型は変化しない(既に bool だから) - false のとき、
$list
は代入によって型が変化する。$key
が静的に決定できないため、$list
の型はarray{a: 'xxx'|'zzz', b: 'yyy'|'zzz', c: 'zzz', d: 'xxx'|'zzz'}
となる
- true の時、
$count
が 1 であるにもかかわらず、スコープが変化しているため、変数の generalize が行われる。たとえば 'xxx'|'zzz' はliteral-string&lowercase-string&non-falsy-string
のように変化する
というわけでした。代入文 $found = true
で $found
の型が変化するか否かがポイントだったわけです。PHPStan がどのように foreach を処理しているのかをなんとなく把握すると、一見非自明な不具合も納得できるのではないでしょうか。
ちなみにこの原因を特定した方法は、PHPStan のソースコードに var_dump
を大量にぶち込んで、どこで何が行われているのかを少しずつ追跡していく、というものです。はい、print デバッグですね。一生これやってる気がする。まあ今回は流石にステップ実行が欲しくなりましたが……。
この issue については、作者が修正をしようとしていた形跡が見られたので、一旦保留ということになりました。次に見た issue がこれです:
PHP における配列は 2 種類あります。1 つは array<K, T>
で、いわゆる連想配列です。K 型のキーに対応する T 型の値のペアを格納できます。もう 1 つは list<T>
で、これは K が 0 以上の連続した整数である array です。他の言語における配列といったらこれでしょう。
前述の issue は、負の整数をキーとする array が list として認識されてしまうという issue です。ある array が list であるかどうかを確かめるときに、int 型の定数であることは確かめていたものの、それが正であることを確かめていないような実装になっていたので、修正する Pull Request を出しました。
何気に初の OSS コントリビュートかもしれません。
Rector
Rector は PHP 用の自動リファクタリングツールです。特定のルールに従って、コードを自動で書き換えることができます。
社内ライブラリを PHP 標準関数で置き換える
pixiv はサービス稼働期間が長く、社内ライブラリに実装した関数が、後に PHP によって提供されるようになった、というケースが存在していました。そのようなライブラリの利用箇所を、Rector による自動リファクタリングで置き換える、というタスクです。
Rector のルールは AbstractRector
を継承したクラスとして実装します。getNodeTypes()
で探索対象を指定し、 refactor()
で置き換え等を行う、という流れです。今回は関数呼び出しの置き換えをしたいので、 getNodeTypes()
で [FuncCall::class]
を返すことで、 refactor()
に関数呼び出し部分が渡されるようにします。 refactor()
では、呼び出される関数の名前に応じて、置き換え先を指定します。
ところで、今回の置き換え対象として、条件を満たす配列の要素を返す find()
みたいな名前の関数があります。これは PHP の array_find()
で置き換え可能なのですが、こちらは要素が見つからなかったときに null を返すのに対し、もとの関数は「第 3 引数が渡されていればそれを返す」という処理になっていて、挙動や引数の数に差異があるため、愚直には置換できませんでした。
これは JavaScript でもおなじみ null 合体 (coalescing) 演算子 5 こと ??
を使って解決できます。find($array, $callback, $default)
を array_find($array, $callback) ?? $default
にするわけです。
で、特に注意せず実装してしまったのですが、Rector はこのような置き換え時に自動で演算子の優先順位を考慮してくれるんですよね。例えば、 find([], $callback, 100) + 1
が array_find([], $callback) ?? 100 + 1
に置き換えられると困るのですが、この置き換え時には Rector が自動で (array_find([], $callback) ?? 100) + 1
にしてくれます。すげ~。
assert を型宣言に置き換える
割とタイトルのまんまなのですが、つまりこういうことです。
// こういうコードは
function f($a) {
assert(is_array($a));
// 処理
}
// こういう形で置き換えられる
function f(array $a) {
// 処理
}
前者は実行時に AssertionError が発生しますが、後者は TypeError が発生します。後者の方は IDE とかが忠告してくれるということもあって、なるべく型宣言で置き換えたいです。
この置き換えを Rector で自動的に行うためにルール作成を行いました。関数およびクラスメソッドの定義に対して、それらのステートメントを見ていって、assert があったらそれを削除し、また assert 対象が関数の引数であれば、関数の引数に型宣言を追加する、という感じで実装しました。
実装よりもルールの適用が大変でした。 throw される例外をチェックする単体テストが落ちるようになるなどの弊害が発生してしまったからです。
成果報告
最終日は 10 分程度でインターン期間中の取り組みを発表しました。めちゃくちゃ噛みました。見てくれた方々が Slack で実況してくれており、いろいろなコメントを貰えて嬉しかったです。
他の人の発表も聞きました。パーサー作成に取り組んでいる人とか、Web アプリによくあるインタラクションを実装した人とかがいて、すごかったです。大体の人が発表内でインターンの感想を述べていて、僕はマジで取り組んだ課題のことしか書いてなかったのでちょっと焦りました。このブログで許してほしいです。
その他
インターン生で集まって昼食をとる会が用意されていたり、社内で行われる交流イベントみたいなものに参加したりしました。期間中は違うコースのインターン生と関わる機会が(少なくとも僕は)全くと言っていいほどなかった 6 のですが、昼食会で他のコースの雰囲気について聞けてよかったです。まあインターンが始まった次の日だったので、皆ほとんど環境構築しかしておらず、故に具体的なタスクの話は出来なかったのですが……。
交流イベントの方は本当に色々な人と話せてよかったです。あとボードゲームとかポーカーとかもやってました。
感想
取り組んだタスクに関して
PHP とその周辺ツールって思ったより良いなあと思いました。僕はほんの少しだけバックエンド用途で PHP を触ったことがあるのですが、PHPStan 等によって、当時のそれとは大きく異なる開発体験を得られると分かりました。このサイトは既に Next.js にリプレイスされていて、かなり(僕にとっては)満足できる速度になったと思っているのですが、実は SSR (and/or SSG) フレームワークを自作しようと考えていた時期もあり、もし今後そのような機会があれば、PHP も選択肢に含めたいなという気持ちがあります。
それと、パーサーから渡された型情報や AST をいじって、静的解析や自動リファクタリングを行う体験ができたのも当然に良かったです。とりわけ PHPStan については、そもそも使おうとしなかっただろうというのはもちろんですが、たとえ興味があったとしても、ドキュメントを含む情報の少なさを考えると、僕 1 人だったら理解できないまま終わっていたような気がします。PHPStan に対する豊富な知識を持つメンターの下で、コードの意味をきちんと理解しながら PHPStan の拡張機能を作成できたのはかけがえのない経験だと思っています。
インターンそれ自体について
インターンそのものについては、Rector でリファクタリングしたコードを実際にマージして、本番環境にデプロイするところまで経験できたりと、かなり実務に近い経験ができて良かったです。ピクシブならではっぽいデプロイ戦略やその周辺技術を知ることが出来ました。
さらに、期間中に開催された社内イベント等を通して、様々な人と交流が出来たのも良かったです。昼食を別部門の方と食べたり、(これらは前述しましたが)社内イベントでポーカーをやったり、インターン生で集まって昼食を食べるイベントがセットアップされていたりと、何もしなくても交流が発生する(?)ので嬉しかったです。それらの交流を通して、僕のプロダクトに興味を持ってもらえたり、他のサービスにおける取り組みや課題を聞いたり、入社までの体験談を聞いたり、そしていろいろな分野の技術的な話をしたりして、視野が広がったような気がしています。
会社に対する印象
会社全体としては、Slack が活発だったのが印象的でした。業務に関係のない話題をするチャンネルが沢山あります。あとリアクションも豊富で、使いたいと思ったものがだいたいあって感動しました。ネットミームも豊富で素晴らしかったです。
ちなみに、最初に述べた通り、僕は pixiv のユーザーでもあります。しかし、投稿者としてではなく、閲覧者としてしか使ったことがありませんでした。故に見落としていたのですが、オフィス見学などを通して、ピクシブ株式会社はクリエイターのリスペクトが随所に見受けられるなあと気付くことができました。僕も(もう 1 年くらい作品を出せていませんが)曲を作ることがあるということもあり、クリエイターへのリスペクトがある、クリエイターと共に歩む toC サービスおよびそれを作る会社ってなんか良いなあ、と感じました。
まとめ
というわけで、全体を通して非常に良い体験をすることが出来ました。とても密度の高い日々を過ごすことが出来て、あっという間の 8 日間でした。
ピクシブのインターン、おすすめです。今回ぼくが参加した pixiv ウェブエンジニアリングコース以外にも、より具体的なサービスに関連していたり、別方面で低レイヤだったりするコースがあるので、募集が始まったらぜひ確認してみてください。
インターンの話はこれでおしまいです。あとは余談だけ
余談
インターン開始時点では既に引越しをしており、いちおう通勤も不可能ではなかったのですが、おそらく引越し前の住所を起点としていたようで、宿泊施設を用意していただきました。神保町のアパホテルです。実家のような安心感。というか引越してからすぐインターンが始まったので、現時点では家にいた時間よりホテルで過ごした時間のほうが長いです。 書き上げるまでに時間をかけすぎて、この文章が嘘になってしまいました。そもそも冷静に考えると、基本的にはずっとオフィスで過ごしていたので、実際にホテルで過ごす時間はそこまで長くありませんでした。
インターン期間中の夕食はすべて外食でした。ホテル周辺、つまり御茶ノ水駅あたりで食べていました。「一度も同じ店に入らない」というルールを定めてあちこち巡りました……というか、飲食店がかなり多くて、そもそも「同じ店に入る」という考えが一切浮かびませんでした。という話を他のインターン生にしたらびっくりされました。むしろ毎日サイゼリヤとかのほうが個人的にはびっくりなんですけど!
というわけでオススメ食事情報です。まず、御茶ノ水駅の聖橋口から出てすぐのところにあるうどん屋「おにやんま」のとり天うどんはかなり美味しかったです。
まあうどんそれ自体は悪くないかな程度なのですが、鶏天が信じられないほど美味しいです。鶏もも肉は柔らかくジューシーで、塩コショウのバランスが完璧で、最初の方は衣がサクサクしていてすごかったです。そこそこ大きめのそれが 3 つ入っていて 560 円です。この量の天ぷらが載っていて、しかも東京でこの値段?ありえないだろ……。
それと、駅からほんの少し歩いたあたりにある「鴻オオドリー」のスープカレーも良かったです。
具は野菜に、スープは赤(鶏ベース)にしました。具の方はチキンでも良かったかも。
ちなみに、個人的に一番美味しかったのは、インターン初日の夜に食べた SUBWAY の「カリとろチーズバジルチキン」なのですが、調べたらもうないらしいですね。残念。これはマジで本当に美味しかったです。そもそもバジルチキンにチーズで外れるわけがない。チキンはおそらくむね肉だったっぽいのですが、めちゃくちゃ柔らかくて美味しかったです。パンに載ってる焼きチーズみたいなやつの風味もかなり良いアクセントでした。
さらに余談ですが、インターン期間中も土日祝日はお休みです(今回の期間に祝日は含まれませんでしたが)。土日は当日になるまで一切の予定を立てていなかったのですが、秋葉原でパソコンパーツを買ったり、未踏会議に参加したり、SecHack365 の成果発表会に参加したりしていました。
そして、今年の目標でもあった美術館に行きました。インターン期間中の土曜日には東京国立近代美術館の「MOMAT コレクション」を見て、1 週間後(インターン終了後)には国立西洋美術館の「西洋絵画、どこから見るか?」に行きました。どちらもめちゃくちゃ良かったです。
当初の予定では、近かった東京国立近代美術館の方だけ見るつもりだったのですが、どこかの駅でたまたま国立西洋美術館の広告を見て、めちゃくちゃ興味を惹かれたので行くことにしました。時間の兼ね合いでセキュキャンフォーラムの直後になってしまいましたが……。15 世紀の宗教画から 19 世紀の印象派まで色々見れました。やっぱり印象派の絵はいいなあと再確認させられました。あと、同じチケットで常設展も見れて嬉しかったです。常設展も思ってた数倍のボリュームがあったし、なんかモネの睡蓮があってびっくりしました。これ日本にあったんだ……。
多分まだどっちもやってると思うので、是非。
Footnotes
-
pixiv 株式会社ではなくピクシブ株式会社で、ピクシブウェブ(略)ではなく pixiv ウェブ(略)です。サービスが pixiv で、会社がピクシブらしい(知らなかった) ↩
-
Todoist に追加したインターン応募のタスクを見て、「そういえばこれの締め切り今日までだったなあ」と思ったところまでは覚えているのですが、そこからの記憶がない ↩
-
Twitter のアーカイブデータから取ってきました ↩
-
この単語の使い方が正しいのかわからない(間違ってたらすみません) ↩
-
初めて聞きました。JavaScript でも同じ名前らしい ↩
-
オフィスから帰るところでたまたま出会うみたいなことは何度かあった ↩
Comments
Powered by Giscus