freeeの開発情報ポータルサイト

freee 会計に残る 400ファイルの CoffeeScript を decaffeinate を使って書き換えた話

こんにちは、freee会計でエンジニアをしている jaxx です。

freee 会計におけるメイン業務とあわせて、会計フロントエンド委員会というフロントエンドに思い入れのある有志で集まった委員会にも所属しており、フロントエンドの技術的負債と向き合ったり、新しい技術導入に向けて意見を交換し合ったりしています。

今回はその改善の一環として freee 会計に残る 400ファイルの CoffeeScript を decaffeinate を使って書き換えた話をします。

freee 会計と CoffeeScript

参考:「10分でわかるfreee エンジニア向け会社説明資料

freee 会計の開発が始まった 2012 年ごろ Ruby on Rails3 では CoffeeScript が標準サポートされており、freee もそれにならってフロントエンドでは CoffeeScript がメインで使われていました。この段階で CoffeeScript と Backbone.js の組み合わせによるフレームワークとして多くの画面で利用されました。

CoffeeScript ファイルの数は 2015 年の 10 月の 918 ファイルがピークでしたが、同年に ES2015 が登場したこと、React の導入によってフレームワークが取って代わったことで、新規画面については CoffeeScript を使うことが無くなり、機能改修がある画面については段階的に CoffeeScript から JavaScript へ書き換えが行われて減少傾向となっていきました。

一方で、CoffeeScript から JavaScript へ書き換えが進まない画面も一部ありました。要因としてはいくつかあって明確なものを挙げるとすると「機能要件の変更がないため、改修が発生しなかった画面」や「共通ロジックをライブラリに切り出してリポジトリ分離したコード」などで CoffeeScript のまま特に問題がなく動作しており、書き換えするタイミングがあまりない部分で CoffeeScript が約 400 ファイルほど残っていました。

freee 会計における .coffee ファイル数の推移

書き換えの必要性について

CoffeeScript の JavaScript への書き換えについては、所属している会計フロントエンド委員会でもウォッチしている課題の1つで、これまでも CoffeeScript の廃止にむけた一括での書き換えについては、何度か議題には挙がっていた形跡があります。が他の技術負債と比較したときに、そこまでクリティカルな問題ではなく、優先度は高くないとの判断がされていました。

これまでの間 CoffeeScript の JavaScript 書き換えは、手動で段階的に書き換えが行われていましたが。委員会のような横断的な組織内で、レガシーコードを仕様漏れなく書き換えができているかを担保するのが難しく、月に平均 5, 6 ファイルというような書き換えのペースでした。

しかし今回、本腰を入れて CoffeeScript の廃止にむけた一括での書き換えに踏み切ったのは、以下の3点が挙げられます。

  • 他の技術負債解消(Babel, webpack のアップデート)で CoffeeScript に対するワークアラウンドが必要となるケースが出てきた
  • 新しい技術の導入にむけて新旧の技術スタックが混在する状態を少しでも解消したい
  • CoffeeScript を書いた経験のあるエンジニアが減少して、廃止するためにキャッチアップが必要という課題が出てきた

特に 3 については、freee 会計で開発するエンジニア全体だけでなく、専門性を持った会計フロントエンド委員会の中でも経験者が減ってきているなと危機感を感じる部分でした。

decaffeinate を使った移行について

CoffeeScript を JavaScript に書き換えを行う場合に CoffeeScript や Babel などを組み合わせてトランスパイルする方法があるのですが、今回は decaffeinate/decaffeinate というライブラリを使って書き換えを実施しました。decaffeinate の特徴として、厳密な書き換えを優先するか、曖昧さを許容することで可読性のあるコードへの書き換えを優先するかを、いくつかのオプションとして提供している点が挙げられます。書き換え以前から改修頻度は高くない画面が多いものの、不具合等で改修入ることを想定して decaffeinate へ渡すオプションを選定しました。

今回利用したオプション

  • --loose-for-expressions
  • --loose-for-of
  • --loose-includes
  • --disable-babel-constructor-workaround
  • --disallow-invalid-constructors

オプションについては上記のような組み合わせで利用しました。 冗長なコードによって可読性を下げたくなかったため基本的には曖昧さを許容するように設定をしました。対象のファイルが CoffeeScript と Backbone.js のフレームワーク上の実装だったということもあり、コードの書きっぷりは揃っていたため、この指定による不具合は特にありませんでした。

decaffeinate suggestions について

decaffeinate による書き換えを実施した際に JavaScript ファイルの先頭部分に decaffeinate suggestions というコメントが追加されます。decaffeinate が厳密な書き換えを優先して、必要以上に冗長なコードとなった場合のリファクタリングのガイドラインです。

/*
* decaffeinate suggestions:
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
*/

上記のようなが decaffeinate suggestions が追加されます。一覧は以下で確認ができます。 https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md

ガイドライン一覧は書き換えの優先度順に並んでいるのですが、この中で DS0XX に該当する DS001, DS002 の2つの decaffeinate suggestions は、書き換え対応をしないとコードが動かないので注意が必要です。対応方法がガイドラインにも記載されていますが –disallow-invalid-constructors というオプション指定をすると「constructor の中で super の前に this を使用したり、 subclass で super 呼び出しを省略した場合に、エラーを発生させる」ことができるので、オプションを有効にして書き換えないようにしました。

これによって decaffeinate による書き換えができなかった CoffeeScript が 20ファイルほどありましたが、これらについては手動での書き換えを並行して行いました。

その他の decaffeinate suggestions について、手動でリファクタリングはこれまでやってきた通り時間的にコストがかかるのと、元々の課題感として CoffeeScript を書き換えてコード自体のメンテナンス性を上げたいというよりは、他の技術負債と向き合う上で CoffeeScript という技術スタックを廃止することが目的だったため、後述しますが jscodeshift を用いた書き換えのみに限定して、プロジェクト内では手動での書き換えは一旦行わずに Danger を使って CoffeeScript から書き換えたコードに変更を加えたタイミングでリファクタリングを促すメッセージを GitHub 上に出すよう設定をしました。

Danger で経緯とリファクタリング方法を案内しました

bulk-decaffeinate について

今回、書き換え対象の CoffeeScript のファイル数が多かったので decaffeinate/bulk-decaffeinate というライブラリを使用しています。

decaffeinate との主な違いとして。 check コマンドで dry run での実行が可能で、トライアンドエラーが容易なのと、convert コマンドで CofeeScript から JavaScript へ書き換えたときに decaffeinate 後の処理が追加できるのと、各ステップでコミットを生成してくれます。convert コマンドはイメージしやすいようステップを少しだけ詳細に書きます。

  1. 対象の CofeeScript に decaffeinate のドライランを実行
  2. 拡張子を .coffee から .original.coffee に変更してバックアップファイルを作成
  3. 拡張子を .coffee から .js に変更して1度目のコミットを作成します(git が追跡できるようにファイル内容は変更なし)
  4. 対象の CofeeScript に decaffeinate の実行、2度目のコミットを作成
  5. jscodeshiftScripts の設定をしている場合)jscodeshift を実行
  6. fixImportsConfig の設定をしている場合)decaffeinate によって動かなくなった import を静的解析によって修正
  7. 対象の CofeeScript に eslint --fix を実行、リポジトリの lint rule に基づいてコーディングスタイルを修正 fail したものは eslint-disable~ を追加
  8. codePrefix の設定をしている場合)ファイルの先頭にその文字列を追加
  9. ここまでの変更で3度目のコミットを作成

一括での書き換え処理について自作という選択肢もありましたが、上記のように decaffeinate 後のステップが手厚く用意されているのと、decaffeinate 以前の git history を引き継ぐことができる点から利用することにしました。

工夫したこと

約 400 ファイルの CoffeeScript 書き換えは decaffeinate を使ったことで比較的簡単に書き換えができましたが、30 ほどの機能に改修影響があり、のちの開発工程(コード修正、QA、リリース)での手作業の部分が、重たくなる懸念がありました。

(その1)jscodeshift と eslint –fix の活用

ただ今回、コード修正については bulk-decaffeinate にある jscodeshifteslint –fix の実行ステップを活用して、リポジトリのコーディングスタイルを揃えるように設定しました。

特に facebook/jscodeshift については ASTベースでコード変換できるので、 transform file を自作する場合も扱いやすいです。また cpojer/js-codemod のような transform file をまとめている OSS も存在していたり、bulk-decaffeinate 側にも いくつか transform file を用意してくれています。

eslint –fix では解消できない Spread Operator への書き換えや、Arrow Function への書き換えについてはこの jscodeshift を活用しました。

(その2)GitHub のコードオーナー機能による .coffee レビュー必須化

約 400 ファイルの CoffeeScript 書き換えだったこともあり feature branch での開発で、着手からリリースまで 3か月ほど時間を要したため、それまで CoffeeScript 側に不具合等で、どうしても改修が必要になるケースがあった場合、 decaffeinate の feature branch も追従してリリース時の先祖還りを防止する必要がありました。あと freee 会計の開発に関わるエンジニアの人数が多いため、この周知や連携をどのように徹底するかが運用上の課題でした。

これについては freee 会計で GitHub のコードオーナーによるレビューが必須化されているため、全 CoffeeScript のコードオーナーとして CODEOWNERS ファイルに自分たちを設定しました。これによって PR を自動検知、早い段階でコミュニケーションとれたのが良かったです。

とはいえ自動化できない部分は頑張った

残りの自動化できないタスクはボリュームがあって大変でした。これについては1人でやったという訳ではなく、多くの人のアドバイスや協力によって乗り越えた部分が大きいと思います。

会計フロントエンドの uki くんにはプロジェクト初期段階から参加してもらい、 decaffeinate で書き換えできなかった(–disallow-invalid-constructors でエラーとなった)ファイルの手動での書き換えを、通常業務の合間や開発合宿の時間で一緒に進めてくれたり、書き換え後のメンテナンスガイドラインを社内ドキュメント上に用意して周知・啓蒙してくれたりとプロジェクト全体を通じて一緒にプロジェクトを進めてもらいました。

また QA チームには、プロジェクト初期時点から QA 実施方法や実施フェーズの分割について相談させてもらい。約 400 ファイルの CoffeeScript から画面や機能を列挙するグループワークを一緒に実施、テストケースの設計〜実施まで多くの部分で QA リソースを割いてもらいました。結果として 2022 年 2 月にドメインを限定して26ファイルを書き換え第一弾をリリース、2022 年 3 月末に 393 ファイルを書き換え第二弾をリリース、最速で最善のリリースを迎えることができました。

freee 会計における .coffee ファイル数が 0 件に 🎉

最後に

自動化できない部分については、この場を借りて謝辞みたいになってしまいましたが、ボトムアップでスタートしたカイゼンタスクだったにも関わらず、担当のドメインやチームを越えて協力してもらったと思っており freee のムーブメント型チームを実感したプロジェクトでした。freee の開発組織における良い文化だと思っているので、もしそういった環境に身をおいて自分自身も貢献したいと思った方は freee 会計のアプリケーションエンジニア採用募集していますので、エントリー をお待ちしております。

技術に関しては、2022 年に CoffeeScript が残っているプロダクトは少なくなっていると思います。ですが同じような背景で CoffeeScript のまま特に問題がなく動作してきて、古い技術スタックが断片的に残っているようなケースはまだあるかも、と思ってこのエントリーを書きました。 もし書き換えるときには、少しでも参考になる部分があればいいなと思います💡