はじめに
JavaScriptで文字列の有無を調べるにはindexOfではなくtestを使う | iwb.jp
という、謎タイトルの記事をみました。
なんだか、indexOfより、testの方が、可読性が高いらしい...
いやいやそれは無いわー
うーん、なんかJS界隈では有名なブログっぽい気がする。何回か見たことある。
しかし、これは、ちょっと同意できないな。
indexOfは、たいていどんな言語でもindexOfとして作られているのだから、可読性は低くない。というか、自分にとっては、読みやすい。
むしろ正規表現パターンを記載するほうが、めんどくさいし、読みにくいわ....
極力、正規表現は自分のプログラムに入れ込みたくない。Perlしかない時代じゃあるまいし....正規表現を使いたいときは明示的に正規表現を使います。って場面だけで使う。これ鉄則ですわ。
if (/apple|banana/.test(str)) {
これって、可読性低くないかなあ。
正規表現リテラルをしらなければ文字列が指定されているのかどうなのかもわからないし、縦棒がorを表すのも知っておかないといけないし、
???何このドットテストって???って気分になる。
appleやbananaを文字列変数に置き換えようとすると、途端にコード書き換えなきゃなんないわけで....
というのは世代の違いかも
かもねかーもね、そーぅかーもね。
正規表現ネイティブ世代にはすぐにわかるのかもねー。
そうかもしれないので、可読性が高いとか低いってのは、主観的なものなわけで、万人が同意するレベルの可読性の高い低いというものはあるだろうけど、万人が同意するレベルではない、可読性の高い低いというのも、あるわけだろうね。
ということで、ラッピングします。
ということで、世代の違いを超えて最も可読性を高くするために、自作関数 contains を作ります。
あ、名前はご自由に変更きくので自分が最も、ピンとくる名前にするとよいです。
JavaScriptだと、array には includes というのがあるので、それと同じでもいいかもしれないですね。
※JavaScriptの新しい仕様ES6にはstringにはincludesが使えるそうですから、わざわざ自分で作らなくてもいいらしいですが。ここは一つES6非対応環境に向けて作ってみましょう。
ちなみに containsというのは、Go言語の命名にならってみました。
contains1:通常のindexOfで作った関数
contains2:正規表現で作った関数
<!DOCTYPE html>
<html lang="ja"><head>
<meta charset="utf-8">
<title></title>
<script>
var contains1 = function(str, search) {
return (0 <= str.indexOf(search));
};
var contains2 = function(str, search) {
return (new RegExp(search, 'g').test(str));
};
var str1 = 'This is a apple pen';
var str2 = 'This is a apple pen*';
if (contains1(str1, 'apple')) {
console.log('test01-1 OK');
}
if (contains1(str1, 'apple') || contains1(str1, 'banana')) {
console.log('test02-1 OK');
}
if (!contains1(str2, '.') ) {
console.log('test04-1 OK');
}
if (contains1(str2, '*') ) {
console.log('test05-1 OK');
}
if (contains2(str1, 'apple')) {
console.log('test01-2 OK');
}
if (contains2(str1, 'apple') || contains2(str1, 'banana')) {
console.log('test02-2 OK');
}
if (contains2(str1, 'apple|banana') ) {
console.log('test03-2 OK');
}
if (!contains2(str2, '.') ) {
console.log('test04-2 OK');
}
//このtest04-2テストは通過しない
if (contains2(str2, '*') ) {
console.log('test05-2 OK');
}
//このtest05-02テストは正規表現として正しくないということで
//Syntaxエラーになる
console.log('test finish ');
</script>
</head><body>
</body></html>
テスト結果が一致しませんよ。っと。
contains2は、test04-2 を通過できません。
str2に、ピリオドは含まれていないのに、含まれているという誤判定します。
また、test05-2 では str2にアスタリスクは含まれているのに、あろうことか、シンタックスエラーです。
ということで、正直いいますけど、contains2は、使えません。
もちろんわかっている人にはわかるわけで、
検索文字列に正規表現自体が入り込んでいるから誤動作するわけです。
test03 のように正規表現のORの表現が可能な自由度はありますが、
正規表現を熟知でもしないかぎりは、こんな不安定な関数は役にたちません。
ということで、このcontainsを正規表現で実装する場合は、このようにするべきです。
正規表現をエスケープする
contains3:正規表現は使っているが、検索文字から正規表現をエスケープする。
//追加分
var contains3 = function(str, search) {
return (new RegExp(
search.replace(/[\\\*\+\.\?\{\}\(\)\[\]\^\$\-\|\/]/g, "\\$&")
, 'g').test(str));
};
if (contains3(str1, 'apple')) {
console.log('test01-3 OK');
}
if (contains3(str1, 'apple') || contains1(str1, 'banana')) {
console.log('test02-3 OK');
}
if (!contains3(str2, '.') ) {
console.log('test04-3 OK');
}
if (contains3(str2, '*') ) {
console.log('test05-3 OK');
}
これは、contains1 と同じテストをすべて通過します。
ということで、JavaScriptの文字列内一致を判定する場合は、contains1でも、contains3でもいいですが、contains関数を作ってしまいましょう。の巻でした。
参考
JavaScript で全置換(正規表現も使った)の速度比較 - Qiita
の、@ygknさんのコメント
とっても、勉強させて頂いております。
終わりに向けて。
え?やっぱり複数文字のOR検索をやっておきたいって?
そういうときは、自分で、containsAnyとか作るんだって。
俺が作った、indexOfAnyFirst使えば、簡単だよ。ってこと。
Go言語の命名法則をまねて、indexOfAny の First や Last(後方検索) や indexOfFunc の First や Last をテストコードも用意しているのは、こちらのライブラリですよっと。
stsLib.js/stslib_core.js at master · standard-software/stsLib.js
終わりに
null判定だとか、undefined判定だとか、Number判定、Int判定、どれもこれもなんでもいいんだけど、
JavaScriptを学んでたら、何かのif判定が、trueだのfalseだの、何だのを覚えなければいけない風なのが山ほどあるんですが、そういうのは全部自作関数でラッピングするといいですわ。
小手先の記述のテクニックじゃなくて、可読性が高くなるような自作関数をどんどん作っていったほうがいいですよ。
というわけで、長期間に渡って生産性を上げていく秘訣として、ちょっと老練な開発者からのメッセージですわ。
追記:リンク
標準的な関数を置き換えたり、存在しないブラウザ用に対応することを Polyfill と呼び、同等の関数を自前で作ってしまうことを、Ponyfillと呼ぶようです。
どのようにソースコードを構築していくかということで、参考になるURLがあったのでリンクしておきます。
JavaScriptの文化とleftpadの話とpadStartについて - from scratch
Ponyfill は車輪の再発明になるからいけない、Polifill がいい。という意見もネットでよくみかけますが、自分は依存関係を減らしたいので、自作で関数を作る Ponyfill の方を好みます。