自己紹介とセッション概要

大原康平氏:みなさん、あらためましてこんばんは。LINE株式会社 OA Dev5チームの大原と申します。本日最初のセッションになりますが、「LINEポイントクラブにおけるPerlからKotlinへの移行を振り返る」というテーマで発表します。

まずは自己紹介します。私は、開発4センターのOfficial Account開発室に所属しています。サーバーサイド開発者で、これまでの仕事としては2018年4月から2022年3月まで、「LINEポイントクラブ」の開発をしていました。そして2022年4月から、LINE公式アカウントの開発をしています。Kotlin歴は2021年1月からです。その前はPerlメインでJavaをちょっとという感じでした。

(スライドを示して)本日お話しする内容はこちらです。まず、今回Kotlinに書き換えたPerlプロダクトであるLINEポイントクラブについて紹介します。その後、なぜPerlをKotlinに書き換えたのかを説明して、プロジェクトの全貌をご紹介します。そして、PerlからKotlinへ書き換えをするプロジェクトで起きた問題と、改善したことを紹介します。最後にプロジェクトを振り返っての感想などを話します。それではよろしくお願いします。

「LINEポイントクラブ」について

まず、今回PerlからKotlinに書き換えたプロダクトである、LINEポイントクラブについて紹介します。LINEポイントクラブは、LINEアプリのウォレットタブからアイコンをタップするとアクセスできます。LINEポイントを貯められるさまざまなミッションの提供や、連携サービスの利用の案内を行うポータルサービスです。LINEポイントクラブは、2013年にLINEフリーコインとしてサービスを開始しました。

LINEポイントクラブには貯めるタブと使うタブがあります。貯めるタブでは、動画を見たりアプリをインストールしたりすることで、LINEポイントを貯められます。

貯めるタブは、2013年からサービスを開始しました。サーバーサイドは2021年までPerlで書かれていて、2021年から2022年にかけてKotlinに書き換えました。これが今回の発表で話す内容です。

使うタブでは、LINEポイントをさまざまなものに交換できます。例えば、「LINEマンガ」のコインや「LINE MUSIC」のクーポン、その他さまざまなものに交換できます。使うタブは2015年にサービス開始しました。サーバーサイドはKotlinで書かれています。

開発の規模感とKotlinプロジェクトのサイズ感

開発の規模感について紹介します。Perlプロジェクトは.pmファイルの数でいうと、テストを除いて350を超える程度でした。Perlエンジニアでない方は.pmファイルが何かわからないと思うのでどういうものか説明すると、コントローラーやモデルなど、1機能のビジネスロジックの粒度のコードの単位です。コミット数は約31,000コミットでした。

Kotlinプロジェクトのサイズ感です。テストを除いて集計すると、1,100を超える.ktファイルがあります。Perlプロジェクトでは350超の.pmファイルだったので、すごく増えていると思われるかもしれないですが、その主な理由としては、データクラスが増えたためとロジックをレイヤーごとに分けたためです。

コミット数は約1万6,000コミットで、ざっくり計算すると2021年から平日で毎日約50コミット以上の差分が毎日取り込まれているようなイメージです。

PerlからKotlinに書き換えることに決めた2つの理由

なぜKotlinに書き換えたのかについて話します。そもそもなぜGoや他の言語ではなくKotlinなのかというところですが、その理由はシンプルで、Java/Kotlinが社内でもっとも使われる言語だからです。社内でもっとも使われる言語を選ぶことで別のチームと知見が共有しやすかったり、エンジニアが別のチームに異動しやすいことや、社内のエコシステムが利用できるメリットがあります。

なぜJavaを選ばなかったのかというと、チームで開発している他のプロダクトと技術セットを合わせたかったからという理由と、「Kotlinは書きやすい」と聞いていたというのも理由の1つです。

このタイミングでPerlからKotlinに書き換えることに決めた理由を話します。大きな理由としては2つあります。大きな理由の1つ目が課題解決のためです。どういった課題があったかというと、まずシステムの構成が複雑化していました。貯めるタブがPerlで、使うタブがKotlinと別の言語で実装されていたり、当時はPerlから直接HBaseが使えなかったため、Java/Kotlinで書かれたHBaseのGatewayアプリケーションを実装・管理したりしていました。

さまざまな機能が実装されたり削除されたりした結果、機能自体も複雑化していました。不要な機能も多かったです。機能自体の複雑化は言語の問題とは少し違いますが、書き換えプロジェクトによって解決できそうなポイントです。

貯めるタブを開発するためのPerlエンジニアの採用が難しいのも理由の1つです。Perlエンジニアが少なくて、いずれ開発続行が不可能になりそうでした。Perlに関しては、最近のミドルウェアに対するクライアントライブラリが豊富とは言えなくなってきました。これはPerlを悪く言うつもりはなくて、当時の私たちが望む結果が得られなかっただけで、別のプロダクトで使えばうまく動くかもしれないです。

大きな理由の2つ目は、書き換えプロジェクトに取り組める時期だったからです。プロジェクト開始前の段階で、直近半年ぐらいは大きなシステム改修がなさそうだという見通しが立っていたため、今やろうと踏み切ったわけです。

技術スタックについて紹介します。言語はKotlinで、フレームワークはSpring BootとArmeriaを利用しています。ミドルウェアはMySQL・HBase・Redis・Kafkaで、APIはRESTとgRPCで実装しています。

PerlからKotlinへ移行するプロジェクトの全貌

ここから、PerlからKotlinへ移行するプロジェクトの全貌について話をしていきます。まずKotlinのシステムアーキテクチャを見せます。(スライドを示して)青い四角がSpring Boot+Kotlinのアプリケーションになっています。

media appはユーザーがLINEポイントクラブへアクセスした際に、各種コンテンツを返すアプリケーションです。adminは管理画面用のアプリケーションです。

cv-workerは、ユーザーが動画を見るなどミッションをクリアした際に、ポイント付与処理を行うアプリケーションです。core appは貯めるタブの主な機能をgRPCで提供するアプリケーションです。その名のとおり、コアとなるアプリケーションです。

とりあえず今のところは「コアと他のアプリケーションがあるんだな」程度に覚えてもらえれば大丈夫です。他の部分についてはあとで説明します。

2021年1月頃のプロジェクトの状況

ここからPerl/Kotlin移行プロジェクトのスケジュールについてお見せします。2021年1月に全体設計、coreアプリケーションの設計、開発環境の整備をしていました。ここでいうcoreアプリケーションが、先ほどシステムアーキテクチャをお見せしたcoreアプリケーションのことです。まずはこの設計・開発から始めました。

2021年3月にはcoreアプリケーションの開発が完了し、他のアプリケーションの設計・開発に入りました。8月には全体の開発が完了して、9月にインテグレーションとQAを行いました。そして9月27日にメイン機能をリリースしました。そのあとはPerlに残していた機能を開発していました。まず、2021年1月の頃の話を振り返りたいと思います。

開発者の構成ですが、合計6名の構成でした。3名のJava/Kotlinエンジニアと、3名のPerlエンジニアで構成されていました。Java/Kotlinエンジニアについては、Perlプロジェクトの仕様についてはぜんぜんわからない状態からのスタートで、もともとのアサインとしては使うタブとLINEポイントクラブ以外のその他のプロジェクトの開発に携わっていました。

PerlエンジニアのJava/Kotlinについては、私はPerlの傍らSpring Bootのコードも書いていたので、少しだけわかる。他の2名はぜんぜんわからないという状態からのスタートでした。(この3名は)もともと貯めるタブの開発に携わっていたメンバーでした。

前のスライドでも話したとおり、チーム内の業務知識や開発スキルが不均衡だったため、タスクの割り振りや見積もりに課題がありました。解決策として、移行プロジェクト初期はゆるい分業体制をとりました。Perlメンバーが仕様を書き、Java/Kotlinメンバーが開発を簡単に進められるような事前準備をするといった具合です。

結果として、仕様策定と開発の準備を並行して進められました。そこはよかったです。しかし、Perlメンバーが技術スタックがわからない間は、仕様が曖昧な部分が残ってしまい、開発しながら仕様を整えていくことになっていました。

(また、)Java/Kotlinメンバーには、開発・コードレビューの負荷が寄ってしまいました。コードレビューのコストも大きめになっていたと思います。そして、仕様の全貌が見えていないのと、技術の全貌が見えていないので作業量の見積もりが難しかったです。

今振り返ってよりスムーズなスタートを切るためにはどうしたらよかったかというと、仕様策定の時間をもっと長く取るとか、仕様レビューや技術習得の前準備をより丁寧にやるということが素直に思いつく方法でした。

理想を言えば、上記2つでプラス2ヶ月ぐらい取れるとよかったかなと感じます。工数を見積もる際に、Java/Kotlinメンバーのコードレビュー負荷をより大きめに考慮するのも検討するとよかったと思います。

設計で大切にしたこと・前提となった機能要件

設計で大切にしたこと・前提となった機能要件について紹介します。もっとも大切にしたことは、LINEポイントクラブ運営に携わる人が、施策を円滑に遂行できるようにすることです。メディアとしての柔軟性や拡張性を重視する必要がありました。そのため、貯めるタブと使うタブのアプリケーションを統合することを目指しました。

貯めるタブと使うタブが別のアプリケーションだったため、機能を追加・修正する場合には両方のアプリケーションに手を入れる必要があり、工数が肥大化していました。これを統合することで、メンテナンスコストの削減と属人化の解消を狙いました。設計の見直しも行いました。例えば、自分たちでメンテナンスしていた独自のジョブキューを廃止して、Kafkaを導入したことなどがあります。

設計で選ばなかった案についても紹介します。今回は、データベースに大きく手を入れることを避けました。スキーマ構成が複雑化していたので本当は手を入れたかったのですが、工数が膨大になって最初のリリースまで時間がかかりすぎることが判明したため、避けたということです。代わりに、アプリケーションのコア部分でデータベースの複雑性を隠ぺいする方針にしました。

実際に設計したKotlinでのシステムアーキテクチャを再度お見せして、ポイントについて話します。まず、mediaアプリケーションはRedisにのみ接続して、スケーラビリティが高い状態にしています。独自のジョブキューをKafkaに移行しました。貯めるタブと使うタブの統合を目指しました。ここまででプロジェクトの初期を振り返る話は終わります。

2021年の3月から9月までは粛々と開発していました。いろいろあったのですが、時間の関係で省略させてください。この部分の話はLINE DEVELOPER DAY 2021でも話しています。興味があれば動画記事があるので、そちらを見ていただけるとうれしいです。

メイン機能リリースの後日談

最後に、2021年9月27日に行ったメイン機能リリースの後日談です。

初期のリリースですべてを移行したわけではなかったので、(後にリリースさせるものとして)どういった機能を残したのかという話です。当たり前ではありますが、Perlで独立して動作可能な機能を初期のリリースに含めませんでした。バッチ処理・管理画面・外部向けAPIの一部を残しました。

最初の設計でデータベースへの変更を最小限に留めたことで、Perlを残しながら移行できました。そのおかげで新システムでの新機能の開発と並行して、残りの機能の移行を進められました。

現状どうなっているかというと、ほとんどは移行できました。現在も若干の移行が残っていますが、全メンバーがフルで移行作業をしているわけではありません。残っているのは、細かいバッチ処理程度です。coroutineやインラインクラスなどの機能も利用し始めています。

Kotlinへ移行するプロジェクトで起きた例外処理の問題

ここから、PerlからKotlinへ移行するプロジェクトで起きた問題と、改善について話していきます。今日は2つだけ話したいと思います。1つが例外処理に問題があった話と、もう1つは静的解析ツールのDetektを導入したらよかったという話です。その他にも問題と改善があったのですが、この話はLINE DEVELOPER DAY 2021に譲ります。

例外処理のやり方がよくなかった話に入っていきます。(スライドを示して)Java/Kotlin開発者の方に質問ですが、「こんなコードを書いていませんか?」という例です。

try catchになっていて、doSomething()を実行した結果、IOExceptionがthrowされた場合はログを出して、HogeExceptionとしてthrowするコードです。これの何がよくないのかわかりますか?

これは一見よさそうに見えますが、ログの部分が自前でロギングしてから再送出するアンチパターンになっています。

自前でロギングしてから再送出するアンチパターンについて説明します。この書き方をするとまずどんな問題が起きるのか、何が悪いのかというと、同じ問題に起因するログが複数出力されることで、原因調査時に混乱を引き起こします。

なぜ混乱が起こるのかというと、先ほどcatchの内側で出力した個別のログに加えて、大域例外ハンドラでもログが出力されるためです。同じ記述を特に複数箇所に書いてしまっていた場合は、よりカオスなことになります。

大域例外ハンドラという用語の説明をすると、フレームワーク側で用意している大域的な例外処理機構のことです。複数コントローラーに共通した例外ハンドラが書けます。Spring MVCでは@ControllerAdviceを付与したクラスで記述します。このアンチパターンについては、2018年のJava Advent CalendarのSHIRAISHIさんという方のQiitaの記事にも書いてあって、他のアンチパターンも書いてありますが、とても参考になりました。Java/Kotlinを書く場合はぜひ一度読んで、例外処理のアンチパターンについて学んでみてください。

結局、先ほどのコードはどうすればいいのかというと、ロギングは大域例外ハンドラに任せるのがいいかなと思います。Kotlinではスタックトレースがログに出力できるので、下の階層で細かくロギングする必要はないです。(スライドを示して)この図の例みたいに、ログの部分がいらないということです。

なぜ今回このアンチパターンにハマっていたかという説明をします。Perlプロジェクトでは大域例外ハンドラやスタックトレースをログに出力する仕組みになっていなかったため、逐一ロギングしていました。そういったコードを同じ書き方でKotlinに移行する際に、アンチパターンに陥っていたということでした。

静的解析ツールであるDetektを導入したらよかった

PerlからKotlinへ移行するプロジェクトで起きた問題と改善の2つ目の話です。静的解析ツールであるDetektを導入したらよかったです。Detektを導入前は静的解析ツールを一切使っていなかったわけではなく、ktlintというツールを使っていました。ktlintとDetektの違いは、ktlintは主にコーディングスタイルを確認するツールで、Detektはコードの複雑度やコードの匂い、問題の兆候を検知するツールです。

Detektのいい点ですが、LongMethodやComplexMethodあたりの警告が特にありがたかったです。人間が「このメソッドは長い」とか「複雑なので修正してください」とレビューをするのは難しいです。主観的な側面があって指摘しづらかったり、人に寄ったりする部分が大きいためです。この部分について機械的に警告が出ると、品質の維持がしやすいです。

もう1つのメリットとして、大きいプロジェクトでも途中から導入しやすいです。既存のコードに出ている警告を無視するためのbaseline.xmlを出力できる機能が便利です。例えば、導入する時はいったん既存のコードの警告は無視するようにしてからCIでDetektを動かすことで、新規のコードに対する警告が出るようにしたあとで、徐々に既存のコードの警告を直していく運用ができます。この点で、わりと大きいプロジェクトでも途中から導入しやすいです。

PerlからKotlinへ移行して感じたことのポジティブ編

最後に、プロジェクトを振り返っての感想について話します。まずPerlからKotlinへ移行して感じたことのポジティブ編です。この感じたこととはチームメンバーにアンケートを取って集めた結果で、PerlとKotlinの比較に限らず、Kotlinのポジティブに感じたことを書いています。

まず、型があるという違いが大きくて、型があるとコードのどの変数がどんなデータなのかすぐにわかるし、メソッドに渡す引数などを間違えにくいメリットがあります。そしてNull安全性があるというメリットもあって、「表現力も高くていい」という意見がありました。表現力についてはPerlもけっこう高いので「両方好きだ」という意見もありました。また、「豊富なJava資産を使える、実行速度が速い」という意見もありました。

「Null安全性と表現力が高い」とはどういうことか

ここから「Null安全性と表現力が高い」という項目について、具体例を添えつつ説明したいと思います。まずNull安全性があるという話です。Kotlinでは、nullableとnon-nullを区別して宣言できます。nullableとnon-nullについて知らない人がいると思うので、軽く説明します。non-nullはnullが入らない変数です。

(スライドを示して)図の変数aがnon-nullなのですが、nullを入れようとするとコンパイルエラーになります。IDE(Integrated Development Environment)でも警告されて、図のように赤い波線がでます。

どうして変数aがnon-nullとわかるかというと、データ型がStringで、末尾に"?"が付いていないからです。nullableの場合はデータ型の末尾に"?"が付きます。

次に、変数bを見てください。String?のようにデータ型の末尾に"?"が付いています。この場合はnullableを宣言したことになります。変数bはnullableなので、nullをセットしてもエラーが出ないです。

次に表現力が高い話で、拡張関数について紹介します。どういったものかというと、クラスを継承せずに、クラスに新しい機能を追加するものです。(スライドを示して)右図の例だと、StringをHumanに変換する拡張関数を書いています。一番上にdata class Humanというものがあって、下のfunから始まるところが拡張関数の書き方です。

実際にこれを使うと、例えばこのコードの例だと"ohara"という文字列の後ろにつなげて.toHuman()をつなげて書くことで、Humanクラスのインスタンスが作れます。

次に、if・try・whenなどが値を返せるという話です。図のようにval code = if……みたいに書くことで、if elseの結果を変数に入れられます。ここではapiResult.isSuccessなら"0000"、そうでないならapiResultのエラーコードが入ります。これを使うことで、コードをすっきりと書けます。

次に、nullの場合の処理の書き分けが簡単にできるという話です。"?:"や"?."などが便利です。"?:"というのはエルビス演算子と呼びます。"?."はセーフコール演算子と呼びます。(スライドを示して)まずエルビス演算子についてですが、例えば図のようにhogeResitory.findを実行した結果、nullだったら後ろの処理をする。ここではNotFoundObjectExceptionという例外を投げるような書き方ができます。

次に"?."のセーフコール演算子についてですが、図のように、text?.lengthみたいに書くと、テキストがnullならlengthは実行されずにnullを返します。そしてnullでない時だけlengthを実行します。このメリットとしては、nullに関するif文の条件分岐が不要なので、コードが短く書けます。このoutputLengthというメソッドを実行すると、右下の図のような結果になります。

最初はテキストがnullなので出力結果もnullになりますが、"hoge"が代入されたあと、再度outputLengthを渡して実行すると4が出力されます。

ポジティブ編の2ページ目の話を紹介します。「開発環境に関するエコシステムは整っている」という意見がありました。Kotlinの開発元がJetBrains社であって、IDEや周辺ツールも開発しているため、強力なサポートがあります。

そして、軽量な並列化ができます。Coroutineなどがあります。Perlではプロセスをフォークしてマルチプロセスな並列化などをやっていましたが、より軽量な並列化ができるという話です。

あとは組織的なポジティブな話として、社内の他のチームと情報交換が盛んなため、サーバーサイド開発の相談が行いやすいというメリットもあります。母数の関係上、やはりPerl開発よりも相談が進みやすいのかなという意見がありました。

PerlからKotlinへ移行して感じたことのネガティブ編

PerlからKotlinへ移行して感じたことのネガティブ編です。ビルドに時間がかかるため、手元での修正イテレーションはPerl時代よりも遅くなったと感じました。ただし、型があることでバグは減っているので、トレードオフなのかなと考えています。Spring Bootやgradleなどアプリケーションを作成する上で必要な知識や技術が多いので、初期の学習コストは高いと感じました。

Perlではシンボルテーブルをいじることで、monkey patchが非常に容易で特殊なテストが簡単に書けたのですが、Kotlinではそうはいきませんでした。ここも速度や型の存在とトレードオフがありそうです。

あとは、スレッドセーフに関して注意する必要がありました。Perlではマルチプロセス・シングルスレッドのアプリケーションを書いていたので、スレッドセーフに関する意識が薄かったです。これに関して気を付ける必要が出てきました。

PerlからKotlinへ移行して感じたことのその他編

PerlからKotlinに移行して感じたことのその他編なのですが、「gRPCのbi-directional streamingを使ってみたい」という意見がありました。これについてはPerl時代のAPIを再現することを優先していたので、まだ手が回っていないです。

プロジェクト担当を終えた気持ち

プロジェクトの担当を終えた今の私の気持ちをここで紹介します。当たり前ではありますが、別の言語に完全に移行するのはけっこう大変でした。テストを除いて.pmファイルが350を超えるぐらいの規模だと、エンジニア6人で1年以上かかるような規模でした。チーム一丸で短期間にプロジェクトの全体を別の言語に移行するのは、チーム全員にとってすごいよい経験になったなと感じています。Perlも好きでしたが、Kotlinもすごく好きになりました。

Kotlin未経験者・静的型付け言語未経験者でも、Kotlinはすごく書きやすい

まとめです。LINEポイントクラブのPerlからKotlinへ移行するプロジェクトの紹介をしました。なぜ今Kotlinにしたのかについては、Kotlinは書きやすく社内ではよく使われているためという理由でした。なぜ今かというと、長年運用している中で課題が積み重なっていたためと、取り組める時期だったためという理由でした。

設計で大切にしたことや現場の紹介をしました。プロジェクトで起きた問題と改善の話では、例外処理の仕方が違うという話と、Detektを導入したらよかったという話を紹介しました。最後に、振り返っての感想を紹介しました。

最後に、Kotlin未経験者・静的型付け言語未経験者でも、Kotlinはすごく書きやすいです。そのため、何を書こうか迷っている人は、ぜひサーバーサイドでKotlinを書いてみてください。これで私の発表は終わりにします。ご清聴ありがとうございました。