紳士淑女、エンジニア、あるいはそれを志す皆様おはようございます。かさいさん @streamwest1629 です。
今この記事を書き始めたのは午後3時。普段、私が個人的な趣味で書く記事だと、冒頭は 「依存関係逆転則含む諸原則に苦しめられた方々,いかがお過ごしでしょうか」 であるとか、「Gopherの皆様、いかがお過ごしでしょうか」 であるとか、ある程度対象読者層を絞った文言で始めています。しかし、この記事の読者層をどの程度絞って書いたものかと少し悩みながら上記冒頭文を選びました。
さて、タイトルにある通り私はこの夏に1ヶ月半という短い期間ではあるのですが、ミラティブのインフラストリーミングチームにてインターンをさせていただきました。
自分の技術力にはある程度自信があったので、心理的に余裕を持った状態でミラティブのインターンに参加しました。
しかし、実際にミラティブのインターンとして参加しインフラストリーミングチームで業務する中で、様々なカルチャーショックを受けました。私が普段選択していたものとは真逆の手段で課題解決に向かっていく環境に新鮮さを感じ、本当に学びの多い1ヶ月半でした。この記事ではそんな、インターンを通して見てきたことや思ったこと、私の受けたカルチャーショックの話などを書き残していきます。
余談ですが、私の先輩に相当する方の記事「【北の国から】休学を決めてからミラティブで1年間インターンするまでを振り返る」 が一年間ミラティブにインターンをして長期的な目線で振り返っています。いわゆる "就活を進めていく中で行うインターン" とはまた違った、なかなか経験することのできない話が書かれているのでよろしければ(※別に宣伝しろとか言われてないです)。
目次
参加経緯
6月の中旬頃、インターンはそれとなく探していました。私は割と前からバックエンドやクラウドインフラ、SREなどに興味がありました。その中で、就活サービスの企業と学生の1on1で話すイベントではじめてミラティブのインターンについて知りました。
実はこの時、「インターンは長期で行くもの」、という固定概念が私の中に存在していました。というのも、これまでスタートアップとベンチャー、それぞれ1年強と数ヶ月といった長さの就業型インターンに参加していたからです。そのため、1週間や2週間のインターンと言うのは、「直接実務に触れるわけではないハンズオンや講座」くらいの認識でした。その中で長期間のミラティブのインターンがとても魅力的だったのです。インフラエンジニアという部門で見ると同等の長さのインターンはあったのですが、それ以外の部門でも長期間インターンできることが特に印象的でした。
そのため私はミラティブのインターンに応募しました。
インターン面接の話
面接対策は個人的に好きじゃないのですが、一方で面接での出来事は書いておいた方がいい気がしたので、どんなインターン面接だったのかを簡単に書き残しておきます。
面接は基本的に技術面接で、インフラストリーミングチームのチームマネージャーである ハタ さんと1時間程度、「果たしてこれは面接なのだろうか」と思うくらい、とてもラフかつ軽快に技術の話をしました。今まで私が行った個人開発や、そこで苦労した話、技術選定の話などをした記憶です。
完全に余談かつ私の個人的な意見ですが、技術選定はしっかり行なった方がいいです。そのプロダクトの開発の進めやすさや完成度、コストパフォーマンスの総積は、プロダクトに求められる速度や管理された外部のライブラリがあるかどうかなど、技術選定の段階でかなり決まってくるからです。
本当に雑談をした記憶しかないので面接らしい質問の受け答えをした記憶がないですが、普段の行動にちゃんと理由付けができていれば、少なくとも自分に対する質問はどんなことを聞かれても答えられるし、答えられなければ自分に対する理解不足である、という心持で面接を行いました。
インターンが始まりました
AWS Developer Associateを取ってみたり、大学の前期授業を受けたりしながら時間は過ぎていき、8月1日のインターン開始日となりました。
社内イベント
ところで、ミラティブでは「わかりあう願いをつなごう」と言うミッションと、「わかり合おうとし続ける、課題に向き合い続ける、期待を超え続ける、そして楽しみ続ける」と言う行動指針を掲げています。また、私がインターンとして参加しはじめた8月第1週の終わりに、代表の赤川さんからの発表で「好きでつながり、自分の物語(ナラティブ)が生まれる居場所」というビジョンが新しくできました(インターンとして参加した第1週目だったのは本当に偶然です)。
それらは社内イベントの随所に現れていました。
ミラティブでは全社員(インターン生なども含む)が週に2度オンラインで集まり、各部署の状況を共有することで、1ヶ月半しかいなかった私でも、後半では、今ミラティブが目指していることや、それに対する課題などがわかるようになりました。
また、それとは別にユーザーフィードバック会があることも印象的でした。これも前述したものと同様、全社員が週に1度オンラインで集まります。その名前にある通り、アプリストアなどのレビューや直通ポストなどから届いたレビューを読む会です。ポジティブなものもネガティブなものも、しっかりと向き合ってプロダクトに反映されていく様子が特に印象的でしたし、ユーザーのエモい話を聴きながら、「好きでつながり、自分の物語(ナラティブ)が生まれる居場所」が確かに構築されている様子を見る事も新鮮な体験でした。
インフラストリーミングチーム
一言でいえば、インフラストリーミングチームはプロフェッショナルな集団でした。
それぞれに抱えているタスクをこなし、時折、各所からくる障害や監視のアラートを確認し、週に1度定例ミーティングの際に顔を合わせるチームです。
そんなチームに私が入って最初に驚いたカルチャーショックはIaCツールを使わない(以前は使っており、その上で使用しなくなった)ことでした。IaCツールによって開発速度が落ちることを防いだり、IaCツールの冪等性による弊害について教えていただきました。また、ある程度パターン化した内容でもちょうどいいツールがない場合は自社内でツールを開発していました。
私は「はぇ〜、そういう考え方もあるんだ。」という小学生と同じくらいの語彙力でしか表現できない驚きがありました。私は普段、個人開発でAWS+Terraform(+Cloud Formation)で遊んでいるので気付かなかったのですが、IaCツールはある程度安定的かつわかりやすい表現でインフラリソースを定義できる代わりに、それを導入するための学習コスト自体は決して安くありません。また、開発速度が遅くなったり、安定稼働を考えながらリソースを更新することが難しいという課題感は私も持っていました。しかしながら、課題感があったにせよ、私はIaCツールを使わないという選択には至りませんでした。
技術選定をしっかり行なった上で、IaCツールを使わないという選択をしていたことが、私がこのインターンの中で感じた一番のカルチャーショックでした。
行った業務の話
インフラストリーミングチームで私が実際に行った業務についての話です。前章で紹介した通り、IaCツールに頼らず必要なツールは必要に応じて社内で開発する」という方針があるので、私はロードバランサーとその周辺リソースを自動生成するツールの作成をGolangで行いました。
ミラティブ内の各用途で使用されている Compute Engine のインスタンスと、その用途ごとに割り当てられているドメイン名、その他ヘルスチェックの設定などをファイルから取得し、その設定を基に、下の図にある、左側にあるDNSレコードから、右側にあるバックエンドサービス内にあるインスタンスグループまでを含めた経路内にあるリソースの新規作成、インスタンスグループにインスタンスを追加・削除するなどの更新を自動で行うツールです。
メンターである清水さんにリソースの仕様について確認したり、私が出したプルリクエストにレビューをしてもらったり、「この機能があるといい」といったアドバイスをもらいながら、ツールを完成させました。
また、今回の業務は、前任者であるハタさんが途中まで進めていたものを私が引き継いで、さらにそこからバックエンドサービスとインスタンスグループの新規作成、更新、削除を盛り込んで完成させたのですが、このコードのお陰でどのようなツールを使うべきか、設定ファイルを読んで実行するまでの大枠としての要望、求められるコーディングスタイルなどをうまく汲み取れたと思っています。
冗長的なコードの話
求められるものを汲み取りながら開発を進めていくにあたって、今回、私は普段の開発では意識していなかった(寧ろ逆のことを意識していたと言っていい)、「冗長的なコード」を意識して作りました。
ソフトウェア開発を本業としていなくても整備のしやすいプログラムであるとか、不用意にコードが複雑化することによって行わなければいけないテストの種類が増えるといった弊害を避けるなどの理由があります。
私としては最初かなり渋っていた記憶があるのですが、あえて複雑な構造体や関数を組むことなく単純で冗長的なコードとすることで、結果として可読性や整備性の高いコードを実現できたと思っています。
ケース1: メンバ関数
今までの私だと、Golangの構造体をモジュールと見做して扱うので、そのモジュールの中で収まっているだろう関数は以下のようにメンバ関数にしてしまいます。理由としては、呼び出す際にその構造体とそれに紐づいた関数を呼び出すだけで済むので普段はこの書き方を採用していました。
type ShopKind string type Shop { Name string Kind ShopKind Address string ShortComment string BeginAt time.Time CloseAt time.Time } func (s *Shop) Display() string { return strings.Join([]string{string(s.Kind), s.Name}, “ | “) }
しかし、この関数を呼び出すためには Shop
構造体を定義しなければいけません。
もちろん、 Kind
と Name
さえ入れておけばこの関数は正常に動きます。しかし、呼び出す側から見れば内部がどのような実装になっているかを把握しなければいけませんし、メンバ関数側から見れば、 Shop
内のメンバーは全て有効な値であると解釈する以上、今後どういう仕様変更が起きるかわからないので呼び出す側は結局全てのメンバー変数を入れざるを得ないのです。
もっとも、呼び出す側がここまでのことを考えていればいいのですが、おそらくそんなに都合のいいことはありません。
しかし、構造体を作らずに以下のように構造体を介さずに呼び出せる関数とすることで、「この関数が扱う変数は何か」がより明瞭になります。
type ShopKind string func DisplayShop(Kind ShopKind, Name string) string { return strings.Join([]string{string(Kind), Name}, “ | “) }
ケース2: コードブロック
別の例です。今までの私だと、forやif、selectといったコードブロックが入れ子になっていくことで横に伸びて見にくいコードになるのは避けたい、後から使い回すことがありそうだと考えてしまい、次のように別の関数(今回は普通の関数にしていますが、これもまたメンバ関数にすることが多くありました)を作って外に機能を追いやってしまいます。
func Run() error { args := os.Args() data, err := LoadSaveData() if err != nil { log.Println(“failed load save data: %s”, err.Error()) return } // データの整合性をチェックする for _, customer := range data.Customers { if err := Validate(customer, data, args); err != nil { return err } } } func Validate(customerData *Customer, data *SaveData, args []string) { // 無差別に構造体をポインタにしがち for _, row := range customerData.UpdateHistroty { // … if customerData.Canceled { // … } else { switch { case /* … */: // … // … } } } }
こういうコードは、レビュー者視点で見ると、関数の呼び出し元と呼び出し側でそれぞれ適切な引数が与えられているかを検証する必要が出てきて反復横跳びなレビューを行う必要が出てきます。もちろん、単純な仕組みであったり検証するだけの価値があれば良いのですが、私が書く、こういった階層構造を改善するための関数の中には1箇所でしか呼び出していない関数も決して少なくはなく、結果としてコードリーディングの難易度を上げてしまっていたというのが正直なところです。
また、関数内の処理で使用するために、わざわざ Validate()
関数の引数として Run()
関数内で定義した変数を渡していますが、こういう実装は他の場所で呼び出そうと思った時に痛い目を見ます。というのも、呼び出す条件が変化した際に求めている引数の形が変化したり、他の箇所で呼び出した時に与えられる引数の関係性が今回と同じであるとは限らず、引数同士の関係性の変化に敏感になってしまうのが実情です。それをレビューすることも、そのためのエラー処理を書くのもかなりの手間となります。
下のコードのように多少コードブロックが深くなったとしても処理を同じ関数内に収めることで見通しが良くなり、他の場所で予期していない引数を与えられることもなくなります。結果として、下手に関数で包むよりも読みやすいコードになるのです。
func Run() error { args := os.Args() data, err := LoadSaveData() if err != nil { log.Println(“failed load save data: %s”, err.Error()) return } // データの整合性をチェックする for _, customer := range data.Customers { for _, row := range customerData.UpdateHistroty { // … if customerData.Canceled { // … } else { switch { case /* … */: // … // … } } } } }
参考にしたドキュメント
感想・謝辞
大学での研究に集中するため1ヶ月半という短い期間でしたが、本当に学ぶことの多いインターンでした。
冒頭で書いた通り、私の中で固定観念となっていたものが「なるほど、そういう選択の仕方もあるんだ」と思う場面がいくつもありました。
私は、自分で書いたソフトウェア設計の記事が高く評価されたり、なまじインフラリソースを触っていたために、何をするにしてもある程度決まった技術選定をしたり、最初から複雑なコードを書きがちでした。それがインターンを通して、実際の業務で求められる可読性や整備性のよさ、求められているものや課題にしっかり向き合うこと、ユーザーに向き合うことがどういう意味なのかを実感する機会になったように思います。
最後になりましたが、忙しい中、私の作ったクソデカプルリクエストのレビューをしていただいたメンターの清水さんや面接の時から毎週の1on1まで私の質問に楽しげに答えてくださったハタさんをはじめとする、インフラストリーミングチームの方々、ミラティブの皆様、ありがとうございました。
We are hiring!
ミラティブでは通年でエンジニアインターンを募集しています!
興味を持った方は、気軽にエントリーすることをオススメします。