@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

OSSライブラリからの学びかた npmからJavaScriptのライブラリを探してソースコードを見つける編

はじまり

blog.magnolia.tech

CPANに上がってるモジュール、一つ一つの粒度が小さいから読みやすいし、ドキュメントもテストもしっかり揃ってて挙動を把握しやすくて、自分にとっては最高の教科書だった

OSSで公開されているソースコードは、最高の教科書ですよね。 Perlにはなじみがないので、卑近な例としてJavaScriptの場合を考えてみます。

npmからJavaScriptのライブラリを探してソースコードを読む

例えばHTMLエスケープの実装が知りたいとき

HTML文字列を生成する際に、ユーザー入力をそのまま出力するとタグが生成されてHTMLが壊れることがあります。 というかXSS脆弱性です。そこでHTMLエスケープをしたいなと思うのですが・・・どう実装したら漏れなく対応できるかわからない状況を仮定しましょう。

npmリポジトリを検索

JavaScriptではnpm | build amazing thingsというパッケージリポジトリが主流です。 まずはここで検索します。

HTMLエスケープしたいので、雑にhtmlescapeで検索してみましょう。 https://www.npmjs.com/search?q=html%20escape

f:id:ledsun:20210108134104p:plain
npmjs.orgでhtmlとescapeで検索した結果

224パッケージも出てきて、どれを見たらいいか迷います。 まずは一番うえのhtml-escaperから見てみましょう。

https://www.npmjs.com/package/html-escaper

f:id:ledsun:20210108134346p:plain
html-escaperのダウンロード数

まずダウンロード数を確認します。 700万とか異次元の数値が出ています。 広く使われているようです。

参考までに2番目のxssパッケージも見てみましょう。

f:id:ledsun:20210108134559p:plain
xssのダウンロード数

90万件と十分に使われています。 が、html-escaperは一桁多いので、HTMLエスケープ用のパッケージとしては、html-escaperがもっとも広く使われていると推測できます。

つぎにhtml-escaperの実装を見てみましょう。 右側のHomepageの欄にGitHubへのリンクがあります。

f:id:ledsun:20210108134921p:plain
html-escaperのGitHubへのリンク

GitHubで実装を探す

https://github.com/WebReflection/html-escaper

f:id:ledsun:20210108135901p:plain
html-escaperのGitHubリポジトリ

JavaScriptのライブラリはindex.jsがエントリポイントになっていることが多いので、inedx.jsを見てみましょう。 一番下までスクロールすると、次のようにexportsescapeunescapeを代入している箇所が見つかります。

f:id:ledsun:20210108140143p:plain
html-escaperのexport

JavaScriptのCommonJSというパッケージでは、exportsオブジェクトを使って、定義した関数を公開します。 このescapeの実装が見つかれば、HTMLエスケープの実装がわかりそうです。

少し上にスクロールすると、次のように41行目でescapeが定義されている場所が見つかりました。

f:id:ledsun:20210108141012p:plain
escapeは41行目で定義されている

escape関数の実装を読む

次の定義からescapeはreplace関数を呼んでいることがわかります。

const escape = es => replace.call(es, ca, pe)

このreplaceは何でしょうか?

24行目でconst {replace} = '';と定義されています。 これはつまりString.prototype.replaceです。 分割代入を使って、組み込み関数への参照を取得する方法を初めて見ました。驚きです。勉強になりますね。

replacecallで呼び出しているので、es.replace.call(ca, pe)と同じです。 es => replace.call(es, ca, pe)とアロー関数で定義されているので、次の関数定義と同じです。

function espace(es) {
  return es.replace(ca, pe)
}

第一引数caは何でしょうか?28行目で定義されていいます。

const ca = /[&<>'"]/g;

HTMLエスケープで、置き換えたい文字を表す正規表現です。

第二引数peは何でしょうか?30〜37行目を見てみましょう。

const esca = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  "'": '&#39;',
  '"': '&quot;'
};
const pe = m => esca[m];

置き換えたい文字列をキーにして、置き換え後の文字列を返す関数です。 String.prototype.replaceは第二引数に関数を指定できます。 MDNでは次のように説明されています。

新しい部分文字列を生成するために実行される関数で、regexp や substr でマッチしたものを置き換えるのに使われます。この関数に渡される引数は下記の「引数としての関数の指定」で述べられています。

つまり、escape関数は引数で与えられた文字列で、escaマップに定義された文字の組み合わせで置き換えることがわかりました。 &, <,>, ", 'を置換できれば、十分有効にHTMLエスケープとして動きそうだと推測できます。

やりました。 ここまでで、HTMLエスケープの仕様を調べずに、HTMLエスケープの仕様をなんとなく把握しました。

オチ

いい感じにOSSのライブラリから実装を見つけられないときは、 大体「HTMLエスケープの実装が知りたい」みたいないい感じのゴールを設定できていない時なんですよね・・・。

2021年の目標設定

2021年の目標

テーマはプログラミング速度が速い分野を増やすです。

2020年は、特定のプロジェクトでのプログラミング速度の向上に成功しました。

とはいえ、今年のオープンソース活動振り返り @ 2020 | Web Scratch と見比べると大分遅く感じます。 また、2020の成功例は、プロジェクトや技術ドメインへの習熟度、プロジェクトへの関わり方に依存しているように思います。 そこで、他のプロジェクトでのプログラミング速度向上を目標とします。

サブ目標

  • publicなサイドプロジェクトのプログラムを完成させる
  • Ruby並行並列大全」のractor/fiber scheduler対応版を書く
  • TOEICの最高得点を更新する(英語の学習習慣を組み立てる)

2020年の取り組み

うまくいったこと

2020年のテーマは「使う道具を増やす以外の方法で技術力を上げる」でした。

2019年のふりかえりと2020の目標 - @ledsun blogによると、当初は次のような作戦を立てていました。

理解しているレイヤーを増やすようなアプローチが必要かな?と思っています。 「作って理解するOS」を読み始めました。まだ途中です。 2020年には、実装するところまでやりきりたいです。 2017年に「RubyでつくるRuby 」を読んだときは、考え方を応用してできることがぐっと増えたので、同じような効果を期待しています。

半年ほどたって、「論理的思考の放棄」をパクる - @ledsun blog

1日1000行のリファクタリングなら、できる気がする。

と感じて、作戦変更しました。 プログラミングそのものの速度を追求するアプローチです。

ふたたび Gitのdiffを振り返る - @ledsun blogによれば、どうやら成功したようです。 概ね次のような策を実施しました

  1. 論理的思考を放棄する
  2. コミット粒度を小さくする
  3. テスト実施頻度を増やす(テストを実施したらタグを打つ)
  4. テスト手順書をソースリポジトリにマージする

それぞれ次のような効果がありました。

  1. 机上で考える時間が減りコーディング作業の時間が増える。実施前に予想していたほどは、手戻りは起きませんでした。
  2. 手戻りが起きた際にコミット単位で戻せる。修正で予想外の影響が出たとき原因をコミット単位で特定できます。追加の修正が必要であればamendすればいいです。不要であればrevertすればいいです。
  3. 不具合が早くたくさんみつかるようになった。手動テストでは、テスト時に実施者の行動のブレから新しいバグが見つかることがあります。この現象はテスト実施時間を減らして、テスト実施頻度を上げた方が、よく起こるようです。また、一回のテスト実施で少ないテスト項目に集中できるので、テスト手順の修正が丁寧になります。テスト実施時間が短くなるとデバッグで、テスト実施時間を食い潰したと時に減る時間が短くなります。
  4. ソースコードとテスト手順を別リポジトリで管理していると、ワークスペースの切り替えコストが馬鹿になりません。これはテスト実施頻度が増えると影響が大きくなります。

アプローチを変更してから、明らかな効果が出るまで半年掛かりました。 半年程度頑張るとプログラミング速度は目に見えて上げられるようです。

あんまりうまく行かなかったこと

必要があって、2週間ほどSVGの勉強をしました。 2週間の間は、結構な時間をとって集中しましたが、成果は芳しくありませんでした。

SVGの概念の把握や周辺ツールの理解は進みました。 しかし、SVGで絵が描けません。 またSVGを操作したり、生成したりするプログラミングを書いたとして、それが仕事になるイメージができません。 要するに、新しい技術ドメインに取り組む場合、サイドプロジェクトで取り組んでも全然時間が足りないことがわかりました。

冷静に考えれば、2週間で仕事になるレベルで技術が身につかないのは、当たり前です。 ですが、知らない技術ドメインは解像度が低すぎて、不当に難易度を高くまたは低く見積もってしまいます。 メインプロジェクトとして取り組む時間が確保できれば、もう少し解像度が高くなるのでしょう。 ただ、その時点ではメインプロジェクトになる予定が延期されました。

メインプロジェクトで、技術力を上げられるアプローチを採用しました。

その他