eslintのno-unused-vars
というルールを使うと、使われていない関数や変数を簡単に検出できます。よっぽどの理由がない限りは、コードの見通しを良い状態に保つためにも、意図しない不具合の発生を防ぐためにも、自分はno-unused-vars
の警告が出た部分は片っ端から消すようにしています。
しかしながら、それでも見落としが発生する場合があります。それは、ES Modulesでのexport
された関数です。
ES Modulesのモジュール定義の様式にはいくつかのやり方がありますが、自分が好んで使っているのは以下のような、モジュール関数をたくさん定義してそれぞれexport
するスタイルです。
export function getXXX() {
...
}
export function setXXX(aValue) {
...
}
// exportされた関数全てをMyModuleという名前のオブジェクトのメソッドとして呼べる形でimport
import * as MyModule from './my-module.js';
// モジュール関数の呼び出し
MyModule.setXXX(true);
このスタイルだと、「他の箇所から参照されていないのにexportされているモジュール関数」という物ができてしまいがちです。何故でしょうか。
ES Modulesでは、export
されていないオブジェクトには他のモジュールから触れません。言い換えると、export
されているオブジェクトは他から使われる可能性があります。そのため、「実はどこからも参照されてないんだけど、他から触られる可能性があるからno-unused-vars
の警告は出ない」関数というゴミが残ってしまうという訳です。他のプロダクトに組み込んで使うライブラリでないなら、こういう物もガンガン消したいですよね。
ということで、Gitで管理されているリポジトリを対象に、こういったゴミ関数を検出してexport
しなくするワンライナーを書いてみました。UbuntuのBashで実行する想定です(自分はWSLで実行しました)。macOSとかBSDとかだとオプションを変えないといけないかも。
git grep -E '^export.+function ([^(]+)' | while read line; do file="$(echo "$line" | cut -d : -f 1)"; name="$(echo "$line" | cut -d : -f 2 | sed -r -e 's/^export.+function ([^(]+)\(.+$/\1/')"; git grep -E "\b$name\b" | grep -v "$file" > /dev/null || (echo "$file / $name"; sed -r -i -e "s/^export (.+ $name)\(/\1(/" "$file"); done
長いので適宜改行し、つつそれぞれの箇所の意味を解説してみます。
# exportされている関数を探す
git grep -E '^export.+function ([^(]+)' |
# その結果を1行ずつ取り出す
while read line;
do
# git grepの結果から、ファイルパスの部分を取り出す
file="$(echo "$line" |
# 「:」で区切った左側を取り出す
cut -d : -f 1)";
# git grepの結果から、関数名の部分を取り出す
name="$(echo "$line" |
# 「:」で区切った右側を取り出す
cut -d : -f 2 |
# 不要部分を除去して関数名だけにする
sed -r -e 's/^export.+function ([^(]+)\(.+$/\1/')";
# 他のメソッドや関数の名前の一部ではない、単独の名前としてその名前が出てくる箇所を探す(「\b」は単語の区切りを意味する)
git grep -E "\b$name\b" |
# その関数が定義されているファイルは除外
grep -v "$file" \
# grepの出力は使わないので捨てる
> /dev/null || # grepの結果が0件だったら次を実行
# 「(~)」で囲って、これ全体を「grepの結果が0件だったら次を実行」の対象にする
(echo "$file / $name";
# 関数を定義している箇所から「export」を取り除く
sed -r -i -e "s/^export (.+ $name)\(/\1(/" "$file");
done
実際にこのワンライナーでゴソッと関数をprivateにしたコミットの例も併せて見てみて下さい。
後は、eslintでno-unused-vars
の警告が出た関数を適宜削除すれば、どこからも使われていない関数をプロダクト内から一掃できます。
IDEを使っていればこういう事もIDE側の操作で一発でできちゃうんだろうなあ……と思いながら、IDEの使い方を覚えるのが億劫で、ワンライナーでやってしまっています。
逆に言うと、IDEでこういう機能がサポートされていない場合、効率よく&見落としなくやろうと思うと、何らかのプログラムを作ったり、こういうワンライナーで処理したりといった方法を取るのがお薦めです。こんな事いちいち人力でやっていると、時間も気力も消耗してしまいます。
自分もかつてはシェルには苦手意識があって、時間をかけて人力でやったり、あるいは比較的得意な言語であったJavaScriptで無理矢理やってみたり、という具合でした。今では、機械的な作業をワンライナーに任せることで、時間と気力の余裕が生まれ、自分自身はより難しい問題の解決に集中して取り組めているのではないかと思っています。
「いまどきシェルなんて……」「サーバーなんて触らないし……」「Windowsだし関係ないし……」と思う人もいると思いますが、覚えておくと(日々の開発でも)できる事の幅が広がりますし、Windows 10なら今はWSLで手軽にBashを使える状況になってきています。少なくとも、今も人力作業で消耗しているという人は、シェルのコマンド操作を覚えて普段の作業を楽にしてみてはいかがでしょうか? 拙著シス管系女子では、自分で自信を持ってシェルのコマンド列を組み立てられるようになる事を目指して、各コマンドのはたらきを図解しながら説明しています。「シス管」と題してはいますが、コマンド操作をする機会があるすべての人におすすめできる内容なので、挫折した経験のある方、苦手意識が抜けきらないという方は、一度見てみて頂ければ幸いです。
ところで、シェルでワンライナーというとxargs
を使うやり方を真っ先に紹介するケースが多いと思うのですが、シス管系女子ではxargs
ではなくwhile
とread
の組み合わせの方を紹介しています。これは以下のような理由からです。
-
xargs
だと、パスに空白文字が含まれる場合に特別な対応が必要である。while
ループでは、特別な対応を覚えなくてもよい。 -
xargs
だと、この例のように複数の事をループの中でやるのは大変(それらを一旦関数にする必要がある)。while
ループでは、ループ処理の中に複数ステップが必要な処理を書く場面にも対応しやすい。
このようにxargs
には色々落とし穴があるので、「xargs
でないと解決できない問題」に突き当たるまではxargs
のことは教えないのが初学者フレンドリーなのではないか、というのが自分の考えです。いかがでしょうか。