こんにちは!今夏エンジニア就業型インターンに参加させていただいた八木颯仁と申します。私は慶應義塾大学の修士1年で、主に整数論という数学の一分野を学んでいます。また、普段はモバイルアプリやWebアプリの開発をしたり、競技プログラミングに勤しんだりしています。
インターンではAndroidエンジニアとして6週間、iOS/Androidエクスペリエンス開発チームというところで、コミュニケーションアプリLINEのAndroidクライアントの開発に携わりました。
この記事の前半ではインターン期間中に取り組んだタスクについて詳細に説明します。また後半では私がインターンを通して感じたことなどを紹介します。
PDFプレビュー機能の開発
作ったもの
早速ですが、私がインターン中に実装したPDFプレビュー機能がどのようなものか見ていただきましょう!
既存のPDFメッセージ
|
PDFプレビュー実装後
|
---|---|
|
|
チャット内のPDFファイルに対してサムネイル画像をプレビューとして表示する機能を実装しました。
従来は
- ファイル名
- ダウンロードできる期限
- ファイルサイズ
の3項目のみが表示され、ファイルの中身を確認するにはビューアーアプリなどで開く必要がありました。
ファイルを開く前にサムネイ画像を確認できるようにすることで、複数のPDFファイルの中から目当てのものを探しやすくなるなど、ユーザー体験の向上が見込めると考えています。
デザインについては既存のWebページのプレビュー機能などを参考にしました。
設計
では、開発のプロセスに沿って私がやったことを紹介していきます。
今回実装した処理の流れは次のようになっています。右側の黄色い囲みの中が、新たに実装した部分になります。
ダウンロード済みのPDFファイルに対してそのサムネイル画像を生成し、プレビューとして表示します。
チャットを開き直すたびにサムネイル画像を生成し直さなくて良いように、生成されたサムネイル画像はローカルストレージに保存しています。
またPDFファイルがダウンロードされていない場合はプレビューは表示されません。このようにした理由は後述します。
クラス設計は以下のようになっています。
特に重要なのはPdfPreviewDataというクラスで、これがプレビューの表示に必要なデータを持っています。このクラスはsealed classとして実装されています。
プレビューの表示に関するデータは、大きく3パターンに分けられます。
- サムネイル画像を表示する場合
- PDFファイルがダウンロードされていない場合
- サムネイル画像の生成に失敗した場合
PdfPreviewDataLoaderは、これらを表す3種類のclassのうちいずれかを、PDFプレビュー表示用のデータとして返すようにしたいです。
Kotlinにおいて、異なる種類の型のうちいずれかとなるような型を表すのに使えるのがsealed classです。もちろんPdfPreviewDataLoaderの型をAnyにしてしまえば、複数の型を返すことができます。しかしAnyではなくsealed classを使って複数の型を返せるようにすることで、型の種類を限定して誤った使い方を防いだり、不要な条件分岐を無くしたりすることができます。TypeScriptのUnion型のようなことが実現できるんですね。enumの拡張のようなものと理解しても良いかもしれません。
sealed classは同じファイル内でしか継承することのできないclassということになっています。したがって返したい複数の型を、1つのsealed classを継承したclassとして実装することで、継承されたクラスのうちいずれかを表現することができるという訳です。
PdfPreviewDataの実装はこんな感じになっています。
sealed class PdfPreviewData {
class Available(val bitmap: Bitmap) : PdfPreviewData()
object PdfNotDownloaded : PdfPreviewData()
class Failure(val failureType: FailureType) : PdfPreviewData()
enum class FailureType {
PASSWORD_PROTECTED,
IO_FAILURE,
}
}
PDFプレビュー用のデータはChatMessageViewDataという既存のクラスに持たせることもできましたが、既存のクラスに不要な変更を加えることは影響が大きく、今回は必要なかったので新たなクラスを作ることで対応しました。
またPDFだけでなく様々な種類のファイルに対応できるファイルプレビュー機能として実装することも考えましたが、今回のプロジェクトのスコープはあくまでPDFファイルであり、YAGNIという原則(実際に必要になるまで余計な機能は実装しない)に従ってPDFファイルに限定した形で実装しました。
設計レビュー
実装に取り掛かる前には、どのように実装するかの詳細や懸念事項などについてDesign docと呼ばれる文書にまとめ、議論を重ねます。というのもLINEの巨大なコードベースに変更を加えるにあたり、その全体像を完璧に把握して実装することは難しいです。必要な変更や適切なレイヤー構造などについて、複数人の視点で実装前にレビューを受けることで、設計が洗練されたり見落としに気が付けたりします。Design docは実装が始まってからも必要に応じて見直し、より良い設計になるよう改善を繰り返します。
PDFのサムネイル画像をプレビューとして表示するにあたっては、特にセキュリティについて十分に考慮する必要があると指摘をいただきました。サムネイル画像を生成するためには、PDFファイルをダウンロードする必要があります。しかしファイルを自動的に(ユーザーの気が付かないうちに)ダウンロードすると、この機能を悪用される危険性があります。この点レビュー会などで議論を重ねて、ファイルはユーザーがメッセージをタップするまでダウンロードされず、一度ユーザーがファイルダウンロードの操作をするまではプレビューも表示されないという最終的な仕様を決定しました。
またパスワードで保護されたPDFファイルについてはどのように処理するのかということについても設計初期段階では見落としており、Design docのレビューの中で指摘をいただきました。パスワード保護されている場合サムネイル画像を生成することができませんが、単にプレビューを表示しないようにしてしまうとユーザーを混乱させてしまう可能性があるので、パスワードで保護されている旨を画面に表示することにしました。
実装
PDFのサムネイル画像生成にはPdfRendererというAndroid APIの機能を使いました。PDFの1つのページを開いてBitmapに描画するというシンプルな機能しかありませんが、今回の用途では十分でした。サムネイル画像としてはPDFの1ページ目をBitmapにレンダリングし、トリミングして表示しています。
PdfRendererを使う部分は、Kotlinの拡張関数useによるラムダ式のネストが深くなってしまったので、新たに拡張関数を定義しました。
private fun <T> File.usePdfFirstPage(action: (PdfRenderer.Page) -> T): T =
ParcelFileDescriptor.open(this, ParcelFileDescriptor.MODE_READ_ONLY)
.use { parcelFileDescriptor ->
PdfRenderer(parcelFileDescriptor).use { pdfRenderer ->
pdfRenderer.openPage(0).use(action)
}
}
実装が終わったらGitHub Enterprise上でPRを作ってレビューしてもらいます。
私たちのチームではReadability reviewというルールを取り入れており、メインのレビュワーとは別にもう一人、コードの可読性をチェックするレビュワーをランダムに追加します。コードの可読性に関してはlinterなどで設定されたルールを守るというだけでなく、関数名の付け方やコメントの書き方など、細かいところまで様々なコメントをいただきました。
非常に多くのエンジニアが同じプロダクトに携わって同時に開発しているので、自分が分かるとか正しく動くとかいう基準ではなく、誰が見ても分かりやすいか、勘違いされないかといったことが重要な指標になるとよく分かりました。
コードの可読性を高めるためのこのような取り組みは、Review committeeという組織が主導して精力的に取り組んでいるそうです。私はインターン期間の初期にCode readabilityの講義受けたのですが、これを担当してくださった方もReview committeeの一員です。この講義については後のセクションで詳しく説明します。
LINEはどんな会社だったか
ここから先はLINEで6週間働いて経験できたこと、分かったこと、感じたことを書き綴っていきます。
エンジニアの働き方
実は今回私がインターン中のメインのタスクとして取り組んだPDFプレビュー機能は、私が欲しいと思って提案した機能なんです。
インターン2週目の始めにこの機能の提案をしたところ、その場でOKが出て実装してみることが決まりました!凄まじい自由度とスピード感。インターン用に良さげな別のタスクをメンターさんが探してくださっていたみたいですが、「自分で提案してくれたこっちの方が楽しいと思うので」とのことで提案が採用されました。
正直なところ提案時点では「言ってみるだけ得だし、将来的に採用されたら俺が提案したんだぞって自慢できるな」くらいの気持ちで、自分で実装することになるとは全く思っていませんでした。(ちなみにアイデア自体は当然今までにも話に上がったことはあるとのことでした。そりゃそうだよなあ。)
インターン期間中に同じチームのエンジニアの方がSlackにアイデアを投げてFeasibility studyをしているのも観測しました。LINEはすでにかなり成熟したサービスだと思いますが、エンジニアが自分のアイデアを積極的に試したり提案したりできるような、柔軟性や風通しの良さがある環境なのだなと感じました。
開発の進め方
私の所属したチームはiOSとAndroidのエンジニア合わせて10人弱、Androidエンジニアは希少なようで大変歓迎されました。
開発の進め方ですが、1週間を1スプリントとする、いわゆるスクラム開発の形式を取っていました。月曜日にその週取り組むタスクを確認し、毎日のミーティングでその進捗などを報告します。ちなみに概してエンジニアは朝に弱いので、12時過ぎに始まるこのミーティングを朝会と呼称しています。
タスクの管理やレビューのしやすさ(PRを大きくしすぎてはいけない)といった観点から、タスクは適切な粒度で切り分ける必要がありますが、これが意外と難しかったです。どのように実装を進めるのかの見通しを事前にある程度立てておく必要があるし、各タスクにどれくらいの時間がかかるかや実装がどれくらいの行数になるかも大雑把に把握できていた方が良いです。その点では経験を積むことが大切と言えるかもしれませんが、逆に必要な子タスクに切り分けるという作業を通して、具体的にやるべきことが言語化されて見通しが立つという側面もありました。
スクラムはチーム単位で行っていましたが、LINEアプリに携わっているエンジニアは実際にはもっとたくさんいます。東京オフィスだけでなく福岡や韓国、台湾などに所属するエンジニアもいます。その中でうまくコミュニケーションを取りスピード感を持って開発を進める手法は、今後のために大変参考になりました。
Code readability
インターンの最初の2週間ほどで、コードの可読性に関する講義を受けました。内容はhttps://gist.github.com/munetoshi/65a1b563fb2c271f328c121a4ac63571ここから誰でも確認できますので、興味のある方はぜひ読んでみてください。書籍化もされているそうですよ。
1時間の講義が8回とかなりのボリュームで全てを吸収し切るのは大変でしたが、その後実装を進める中で学んだ内容が役立ったと感じる機会がたくさんありました。PRのコメントに講義スライドのリンクを貼って「この原則に従うならこうすべきではないか」などど議論する場面もありました。
やはり説明を聞いただけで完全に理解して実践することは難しいです。しかし少しでも意識しながらコーディングやレビューを行うのと、基本的な読みやすいコードの原則を知らないのとでは、アウトプットの質は大きく変わると感じました。一方現実ではその原則をそのまま適用するのが難しい例に遭遇することもあります。私の場合は上記の講義をしてくださった石川さんに直接PRのレビューをいただいたり、疑問点を個人的にZoomで教えていただいたりして、大変助かりました。上述のReview committeeやreadability reviewといった仕組みも、可読性の高いコードを書く意識を根付かせるという点で大きな意味を持っているのだと思いました。
また最終回のレビューに関する講義は、これまで私が読んだことのある可読性に関する書籍や記事には無かった新しい視点でした。これまでコミットの並び替えや統合(squash)などのforce pushが必要になるような操作は、面倒な上必要性を感じ無かったため忌避していました。しかしコミットの分割にしっかりと意味を持たせることで、レビューが格段にしやすくなるということがよく分かりました。
ここで学んだ原則や考え方は、プロダクトやチームの規模、使用する言語などに関わらず使えるものなので、今後にしっかり活かしていきたいと思っています。
コミュニケーション
LINE社内のミーティングでは日本語と英語どちらも使われています。当然最低限の英語力は必要とされましたが、LINEでは言語の壁によって発言しづらくなったり疎外感を感じたりすることがないような配慮が強く感じられました。
例えば週例のミーティングなどには通訳担当の方も参加されていて、Zoomの通訳機能によって同時通訳が聞けるようになっているようです(私はこの機能を試さないままインターン期間も終盤に差し掛かってしまい未だにどんなものかよく分かっていませんが)。Slackにもメッセージを自動で翻訳するbotが導入されています。
上記のような仕組みの部分だけではなく、社員のみなさんの意識も、ネイティブでない人にも伝わりやすいよう簡単な表現を心掛けようとか互いにフォローし合おうというようなものがしっかり行き渡っているようです。
オフィス
インターン期間中はほぼ自宅からリモートで働いていましたが、四ツ谷にあるオフィスにも行きました。眺めが良いのと、ホワイトボードがたくさん置いてあるのが印象的でした。ホワイトボードには謎の生命体のイラストがたくさん書かれていました。
ランチはオフィス近くのとんかつ屋さんに連れて行っていただきました。柔らかくてジューシーでとっても美味しかったです。
最後に
LINEでのインターン期間中は、自分のやりたいことに挑戦でき、またレベルの高いエンジニアに囲まれてアドバイスなどいただいてとても成長できたと感じています。
メンターさんを始めとするチームメンバーやHRの方々などたくさんの方にサポートいただいたおかげで6週間無事完走できました。この経験を今後に活かしていきたいと思います。本当にありがとうございました。