コーディングをしていく上で良いお作法でできるようにしたり、チーム開発でスタイルを合わせたりするために Lint 系のツールを使います。今回 TypeScript で開発をしていて、TSLint のルールを厳しくしたところチェックに引っかかったところが出たのですが、ルールの意図を汲み取れず悩み中の備忘録。
環境
本記事の開発環境は以下となります。
- Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
- Visual Studio Code
- Node.js 8.10.0
- Yarn 1.13.0
- TypeScript 3.3.4000
- TSLint 5.14.0
TSLint 概要
TSLintはTypeScriptコードの読みやすさや保守性を維持するための静的分析ツールです。
不具合を起こしやすいコードを指摘したり、コーディングスタイルをそろえるためのチェックなどをしてくれます。
TSLintはTypeScriptに特化してチェックしてくれるのですが、最近ではTypeScript 開発チームのロードマップでESLint、JavaScript/ECMAScript 用の Lint をサポートしていくことが発表され、ESLintからもTypeScriptのESLint対応への歓迎とチームメンバーが開始したtypescript-eslint プロジェクトについての記事が公開されます。
今後はESLintを使っていくことになりますが、安定するまではTSLintをも使って行くことになります。
TSLintプロジェクトでも、移行に関するイッシューRoadmap: TSLint -> ESLint · Issue #4534 · palantir/tslintが上がっており、一時的にはオーバーラップする期間が発生して、一時的には両方のツールを使うことが推奨されています。
新しいプロジェクトのtypescript-eslintを使う方法については、@typescript-eslint ことはじめ - teppeis blogさんが詳しいです。
prefer-function-over-method ルールと発生した事象
今回悩みの種となったのが、TSLintのprefer-function-over-methodルールになります。
TypeScript でクラスメソッドを作った時にthis
を使わなかった場合にClass method does not use 'this'. Use a function instead.
という指摘をしてくれます。
以下のコード例ではok
メソッドはthis.template
でthis
を使っているので問題ありません。一方でng
メソッドはthis
を使っていないのでprefer-function-over-methodルール違反となります。
1 | export class Action { |
まぁ、確かにng
メソッドのほうは内部状態を触っていないので static なりにしたほうがよさそうです。
悩み事
ここで気になって悩んでいるのが “static にすべき” ではなく、”function にすべき” とメッセージが出ている点です。
つまり、下記のような修正ではなく (必要箇所のみ抜粋)
1 | export class Action { |
このようにする (必要箇所のみ抜粋)
1 | export class Action { |
ルールの名前も prefer function over method だから、メソッドより関数を優先して使いましょうということなんですよね。。。
クラスの中に関数を書いても文法上問題はないわけですが、どうして static ではなく関数なのかなと。せっかくなので理解しておきたいのですが、prefer-function-over-methodは Rationale、論拠の項目が書かれてないのです。
たとえばonly-arrow-functionsルール「JavaScript の伝統的な関数定義ではなく、アロー関数を使いましょう」ですが、以下のように書かれています。
Rationale
Traditional functions don’t bind lexical scope, which can lead to unexpected behavior when accessing ‘this’.
this
へアクセスしたときに予期しない動作を引き起こす可能性があるから、といった説明がされています。
この文章だけだとわかりにくいかもしれませんが、これをもとに調べることもできます。
しかしながら、prefer-function-over-methodは Rationale が書かれていない以上、どのように扱うのが望ましいのかゼロから調べるしかないわけです。
- ルールに従い関数に修正する
- ルールの修正方針とは異なるが static にする (これでも指摘はなくなる)
- ルールがプロジェクトにマッチしないので無効化する
ESLint では、どうなる?
この疑問をつぶやいたときに、お仕事チームの仲間が ESLint の情報で応えてくれました!(ありがとうございます)
typescript-eslint/ROADMAP.md at master · typescript-eslint/typescript-eslintに TSLint と ESLint の対応表があるとのことで、prefer-function-over-methodはclass-methods-use-thisにあたるとのこと。
では、ESLint のclass-methods-use-thisはどうなっているかというと「static メソッドにすること」。
メソッドと関数が混在するよりも、メソッドに統一して static と分けて実装するとのことで、わかりやすいです。
しかしながら、こちらも論拠は書かれてませんでした。
「関数を使う」という選択肢がなければ、そうだよねってことで static メソッドにするのですが、今回は関数が使えることを知ってしまったので「static と関数のどちらにすべきか」という疑問が生じてしまいました。困った。
TypeScript のコンパイル後を確認する
Lint ルールに明確な論拠がないので、出力された JavaScript/ECMAScript を見て考えます。
TypeScript のソース。
1 | export class Action { |
コンパイルされた結果。
※ ターゲットはクラス構文が導入されていない ES5 にしています。
1 | ; |
結果を見ると指摘のあったng
メソッドはAction.prototype.ng
に関数として追加されています。
TSLint のprefer-function-over-methodにしたがって関数に修正したngFunction
はコンストラクター内でthis.ngFunction
に追加されています。
ESLint のclass-methods-use-thisにしたがって static にしたngStatic
はAction.ngStatic
に追加されています。
こうしてみると以下の順が良さそうです。
- static にする
- メソッドのままにする (TSLintprefer-function-over-methodを無効化)
- 関数にする (TSLintprefer-function-over-methodに従う)
static にすればクラスレベルで追加されるので効率が良いでしょう。
メソッドと関数の違いについてですが、関数にすると各インスタンスに関数が追加されています。それに対してメソッドはprototype
に追加しています。とくに理由がなければprototype
に追加したほうが良い(はずだったと記憶)。
この辺の裏付けができていないのと、クラウス構文が入ってからも変わりがないのかはわからないので、決定打とはならないですが説明の方針としては1つ線が引けそうです。
現在の状況
この記事を書いている時点での対応は以下としました。
- static にできる場合は、static メソッドにする
- static にできない場合は、ルールを無効化してメソッドにする
今回、継承して実装するメソッドが一部指摘されていました。
簡易フレームワーク的な作りになっていて、親クラスから決められた abstract メソッドを実装するようになっており、その部分が引数だけでthis
を使わないで完結できるような処理になったりもします。
この部分は// tslint:disable-next-line: prefer-function-over-method
でルールを無効化してメソッドのままとしました。
参考情報
- TypeScript Roadmap: January - June 2019 · Issue #29288 · Microsoft/TypeScript
- The future of TypeScript on ESLint - ESLint - Pluggable JavaScript linter
- typescript-eslint/typescript-eslint: Monorepo for all the tooling which enables ESLint to support TypeScript
- @typescript-eslint ことはじめ - teppeis blog
- typescript-eslint/ROADMAP.md at master · typescript-eslint/typescript-eslint
Lint 系のツールはコードに対して指針を与えるので、やはり論拠が欲しいところですね。
とくにいくつかの選択肢が出てきてしまうと、いろいろと考えてしまいます。
今回は残念ながら TypeScript力というか、JavaScript/ECMAScript力がたりなくて明確な根拠を持って指針化できませんでしたが、考え方は整理できました。
裏付けをとるのは、ちょっと難しいかな。。