アカツキさんでインターンをしてきたので、成果報告として久しぶりにブログを書きました。
内部向けの成果報告も兼ねているので、外の人からするとよく分からない話や中の人からすると蛇足な内容も含んでいますが、何卒ご容赦ください。
スポンサーリンク
-->TL; DR
スライドは同じ内容ですが短くまとまっています。
自己紹介
自分のブログではありますが、一応配属されたチームと今までの経験などについてを軽く書いておきます。
今回インターンさせてもらったチームはGo言語で基盤サーバを開発しているチームでした。基盤サーバを書くのだから、そこそこGo言語の力を必要とされるはずですが、僕はもともと言語に関しては移り気なところがあって、Go言語も同様に際立って得意なわけではありませんでした。
一度このブログをGo言語に移行する計画を立てていましたが、そもそもGAEの費用が厳しかったのもあり、管理画面の作成や静的ファイルジェネレータのセットアップなどまでしか達成できませんでした。他の経験としては別のインターンで少しさわったという程度です。
そういった事情に加えて、面接した社員さん(のちのメンターさん)にもGo言語勉強してきます、と言っていたのもあり、インターン前にPythonで学習させたDCGANのモデルをTensorflow/Goで動かしてみるなどして練習していた、というのがインターン前の状況でした。
やったこと
具体的にインターンでやっていたことは以下のような感じです。
スタックトレースが出るようにするためリファクタリング
Go言語では、エラーをthrow
することなく関数の返り値として伝播するというのもあって、デフォルトではスタックトレースは表示されません。通常エラーが起きた場合は、エラー文字列としてエラー発生箇所の情報のみが出力されます。これでは情報が足りない場合も多いので、スタックトレースを表示できるようerror
インターフェースに情報を追加していくパッケージがあります。
最初に簡単にプロジェクト全体を見れるようなタスクを渡してほしいと頼んだところ、現状のコードで上記のスタックトレースを保持するためのパッケージの対応をしていない箇所を修正していくという絶妙なお仕事を渡してもらえました。
これのおかげで、いろいろ質問しながらもプロジェクト全体のコードを軽く読み流していくことができました。
エラーハンドリング周りの静的解析リンターの改善
つぎに対応したタスクもエラーに関するものでした。先述の通り、Go言語ではデフォルトではスタックトレースが表示されないのでパッケージを使った対応が必要なのですが、これを使うべきところを全て人間の目で見つけるのは厳しいものがあります。
そこで、メンターさんが既に開発していたLinterツールが使われていたのですが、先程のエラーハンドリングの修正を行っていく過程で、リントがほしい所にない場合や、必要ない所にリントがかかることがあり、それらのエッジケースに対応するツールの改善を行うことになりました。
もともと、インターン開始時には静的解析やコードジェネレータに興味があると伝えていたのですが、それらはほとんど完成していたというのもあって、おそらく取り組むことはないだろうと考えていました。そういう経緯もあったので、たまたまエラーハンドリングの修正をやっていた過程でタスクを発見できたのはとても幸運に感じました。
静的解析ツールに触れられたことで、SSAなどの概念を知ることができてとても良い経験になったと思います。
改修の過程で、Go言語の型や構文木に関する実装などもついでに読むことができ、ツールが整っていてかつそれらがGo言語自身で書かれているのが改めてGo言語の魅力だなーと感じました。
また、このツールのOSS版にも、一部の修正が適用可能だったので、オープンなGithub上でもプルリクを出すことができました。
重いサービスの別サービス化とそれに伴うCIやテストの更新
だいたいでいうとこちらの 「重いサービスの別サービス化とそれに伴うCIやテストの更新」 が最後の大きなタスクとなりました。
タスクのモチベーションとしては、ユーザーへのレスポンスまでに完了する必要のない仕事を一度おいておいて別で処理しているのに、それを処理しているサーバは同じサーバだったので、切り出すことでリクエストキューの分離を行いたい、という話と、ユーザからのリクエストに絞ったモニタリングを行いたい、という話の2つがありました。
画像は関係ない環境のグラフですが、負荷などをモニタリングする際に、確かにユーザ以外のリクエストが混じっていると難しいですし、アラートの閾値なども考えづらいです。
既に動いている既存のサービスを切り分けるため、ミスがあると1秒でも大量のリクエストがどんどんと滞留してしまうようで、(自分からできるだけ複雑なタスクをこなしたいと言ったとはいえ)使用しているクラウドの仕様の確認や曖昧な部分を検証するための実験などがかなり大変そうに感じました。
タスクの目的は与えてもらいましたが、そもそも何がモチベーションなのかといった話から理解する必要があったり、なんどか提案した叩き台も的はずれだったりで、自分の設計能力の甘さを改めて認識しました。
それでもいくつか設計案を考えるうちに、取るべき手法のメリット、デメリット、検証すべき事項の洗い出しや実際の実験などを通して、かなり得難い経験を積めたと今では感じています。
直面した問題
どんどん伸びる circleci.yml
今回のプロジェクトではCircleCI
を使用した継続的インテグレーションを行っていましたが、その設定ファイルがファイル分けできず、どんどんとコードが膨らんでしまう、という問題もありました。
最初は長いので読むのも大変かと思っていましたが、以下のことに気をつけていれば読むことはそこまで難しくないとも感じました。
circleci.yml
は CI だから基本走る(走らせたくない部分はfilter
する)- コメントを目印に、workflowごとに
require
をたどっていけばいい
とはいえ、これらも可視化ツールのおかげではありました。
最終的には上の図よりもだいぶ複雑になっていて面白かったです()。
Orbs
と呼ばれる機能を使えればだいぶスマートにはなるようですが、現在はPublicなものしか対応されておらず、プロジェクトには使用できないという事情もありました。
動かないSDK
1 2 3 4 5 6 7 8 9 10 11 |
def _HeadersFromTask(self, task, queue): headers = [] for header in task.header_list(): header_key_lower = header.key().lower() if header_key_lower == 'host' and queue.target is not None: headers.append((header.key(), '.'.join([queue.target, self._default_host]))) elif header_key_lower not in BUILT_IN_HEADERS: headers.append((header.key(), header.value())) # headers.append(('host', 'localhost:8081')) |
上記のコードはGAEのローカルサーバ用SDKですが、ターゲットの指定が適切に動いておらず、コメントアウトされているコードを追加すると動く、といった状況でした。このため追加するテストの方針を変える必要がありました。
「別サービス化」のリファクタの際の問題
最終的には、コードの一部を抽象化して、 encode~
といった副作用のない関数に切り出してテストする方針になりました。
既存のコードでは、任意のスライスをエンコードしてログに出すという目的のために reflect
パッケージを使っており、 SliceTo~
という名前の関数を使用していました。この関数は slice
以外を受け取ると panic
するのですが、名前が名前なのでこのままなら問題ないと考えられます。
しかし、今回のタスクのテストの追加の際には、この関数の内、副作用のない変換処理だけを取り出してテストを行うため、 encode~
といった命名の関数を新しく使用しています。その命名で slice
以外を受け取ると panic
するというのはあまりに理不尽なので、これもなんとかする必要が出てきました。
最終的にパッケージ外で使う exported
な関数の名前を AddSliceTo~
と改名することで乗り切ることになりましたが、最後までコードを直しており、なかなか満足することができなかったです。
Go言語はジェネリックがないからつらい、とよく聞きましたが、本質的にジェネリックは panic
を起こしやすい機能だと思うので、簡単にはそれを組み込もうとしない、というのも合理的には感じ、後でも書きますが難しい部分だと感じました。
やっておけば良かったこと
これからアカツキのインターンに限らず、Go言語でのインターンをする方に言うことがあるとすれば、以下の2点です。
#golang CodeReviewCommentsを読んでおく
日本語版のQiita記事もあるので、どのようなコードを書くべきか一度こちらを読んでおくことをおすすめします。Go言語はフォーマッタがしっかりしており、プッシュ前にgoimports
をかけておけば大体は問題ないですが、チームでコードを書く以上それだけでは足りないことも多いです。
インターン終了間際に振り返って納得する部分なども多く、簡単なので先に目を通しておくと良いと感じました。
無理に汎用的なコードを書かない
Go言語では型が強いぶん、汎用的なコードを書くのは他の言語と比べると(少なくともコンパイルを通すまでは)難しい傾向にあると思います。そのため、パッケージの力を借りて汎用的なコードを書くこともありますが、慣れないうちに無理をすると分かりにくいコードになったり、失敗するとpanic
でコードが終了したり、Go言語の良さが失われてしまうようにも感じました。
interface
やタイプスイッチを使う程度にとどめ、適切なエラーハンドリングを心がけて、無理に汎用的なコードを書かないで済ませれば、コンパイルが通った時点で安心することができると思います。
やっていて良かったこと
逆にアカツキでフルタイムのインターンをするにあたって、やっていてよかったと感じたこともまとめてみました(ここあたりからだんだんと文章に疲れを感じるかもしれません…)。
質問、日報のgit管理
- 共有できる。
- 後からコピペできる。
- 思いついたこと、忘れがちなメモなどを残せる
とりあえず理解できないコードも通しで読んでおく
- コメントとかで重要な情報が拾える
- 後からキーワードで思い出せる
- 大体のボリューム感がわかる。
- 会話のタネになる。
docの整理
- 環境構築はだいたい詰まる
- 新しく入った人が詰まった部分をdocに追記していくのがよい
感想
最後にざっくりと感想も書いておきます。
- 今回のプロジェクトは人がいなくて大変そうだった。
- フルで会社にいた方のうち
- CTOさんは他の仕事も山のようにあった。
- メンターさんは2人のインターンを抱えていて、大変そうだった。
- GCP勉強会
- 使っているクラウドに関して、最新の話題を知れてよかった。
- 興味のある話題を、他の人の知識なども聞きながら話せてよかった。
- 業務時間内に勉強会できるのはとてもよい。
- Github にもう少し慣れておきたかった。
- Tag をプッシュして恥をかく、など
- 別サービス化したサービスのインスタンスクラスについての議論
- IOの待ち時間が多くを占めるから非同期に処理できる数を増やしたほうがよく、そのためには
max_concurrent_request
の指定ができるF2が良いという意見 - 計測する前に推測している状態で設定を最適化すべきでない、という意見
- 議論のレベルがとても高く、自分の考えの浅さが理解できました。
健康的だった
- 美味しい弁当
- 朝会
なぜかケーキなどが食べられる日もありました(写真がなぜか微妙ですが美味しかったです)。
楽しく働けた
- 歓迎ランチ
- 進捗がある
- 朝会
- 日報
- 質問しやすい環境
- 結果的に生産性が高い
- ツールが良い
- Github, gamba!, TeamSpirit, CircleCIなど
- UI、デザインや操作性の悪い自社サービスを無理して使うと辛いので。
- メンターさんの詳しい領域についての話がかなり勉強になった。
-
- Go111では基本的に任意のコード実行ができるので頑張れば tensorflow/go も導入できるはず、など
- 有名なOSSを作っていた人や、自身で作ったプロダクトで賞をとった人が周りにいるため、刺激をもらえる。
オフィスがきれい
- ラウンジ
- 執務スペースと休憩スペース
- 会議室
ありがとうございました 🎉
お会いしたアカツキメンバーの方々、関わってくださった総務や人事の方々、同じインターンのメンバーやチームの方々、そして最後のデプロイ地獄(本当にすいませんでした)にお付き合いくださったメンターさん、大変お世話になりました!
画像のThe Go gopher(Gopherくん)は、Renée Frenchによってデザインされ、CC BY 3.0ライセンスが適用されています。
- IOの待ち時間が多くを占めるから非同期に処理できる数を増やしたほうがよく、そのためには
コメント
コメントはありません。