インラインスクリプトは本当に悪か? 「控えめなJavaScript」再考 | パークのソフトウエア開発者ブログ|ICT技術(Java・Android・iPhone・C・Ruby)なら株式会社パークにお任せください

パークのソフトウエア開発者ブログ|ICT技術(Java・Android・iPhone・C・Ruby)なら株式会社パークにお任せください

開発の解決方法や新しい手法の情報を、パークのエンジニアが提供します。パークのエンジニアが必要な場合は、ぜひお気軽にお問い合わせ下さい。 株式会社パーク:http://www.pa-rk.co.jp/

はじめに

ちかです
毎度のことですが個人の見解です。
会社の見解ではありません。
むしろ他の社員からは反対意見が出そうな話です。

Web ページの構成について「控えめな JavaScript (Unobtrusive JavaScript)」という慣行があります。
太古の昔、 JavaScript は馬に乗って行列を作って HTML 上を走り回っていたそうです。
「控えめな JavaScript」は革命を起こし、横柄な JavaScript とは対極の立場を取りました。

『インラインスクリプトは絶対に書きません!』

でもこれはあまりに極端では?? というのが今回の記事です。

「控えめな JavaScript」信奉者の主張

『Rails で JavaScript を使用する』の例を見てみます。

最もシンプルなJavaScriptを例にとって考えてみましょう。以下のような書き方は'インラインJavaScript'と呼ばれています。

<a href="#" onclick="this.style.backgroundColor='#990000'">Paint it red</a>

このリンクをクリックすると、背景が赤くなります。しかし早くもここで問題が生じ始めます。クリックした時にJavaScriptでもっといろんなことをさせるとどうなるでしょうか。

<a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';">Paint it green</a>

だいぶ乱雑になってきました。ではここで関数定義をclickハンドラの外に追い出し、CoffeeScriptで書き換えてみましょう。

paintIt = (element, backgroundColor, textColor) ->
  element.style.backgroundColor = backgroundColor
  if textColor?
    element.style.color = textColor

ページの内容は以下のとおりです。

<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a>

これでコードがだいぶ良くなりました。しかし、同じ効果を複数のリンクに与えるとどうなるでしょうか。

<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a>
<a href="#" onclick="paintIt(this, '#009900', '#FFFFFF')">Paint it green</a>
<a href="#" onclick="paintIt(this, '#000099', '#FFFFFF')">Paint it blue</a>

これではDRYとは言えません。今度はイベントを活用して改良してみましょう。最初にdata-*属性をリンクに追加しておきます。続いて、この属性を持つすべてのリンクで発生するクリックイベントにハンドラをバインドします。

paintIt = (element, backgroundColor, textColor) ->
  element.style.backgroundColor = backgroundColor
  if textColor?
    element.style.color = textColor
 
$ ->
  $("a[data-background-color]").click (e) ->
    e.preventDefault()
 
    backgroundColor = $(this).data("background-color")
    textColor = $(this).data("text-color")
    paintIt(this, backgroundColor, textColor)
<a href="#" data-background-color="#990000">Paint it red</a>
<a href="#" data-background-color="#009900" data-text-color="#FFFFFF">Paint it green</a>
<a href="#" data-background-color="#000099" data-text-color="#FFFFFF">Paint it blue</a>

私たちはこの手法を「控えめなJavaScript」と呼んでいます。この名称は、HTMLの中にJavaScriptを混入させないという意図に由来しています。JavaScriptを正しく分離することができたので、今後の変更が容易になりました。今後は、このdata-*属性をリンクタグに追加するだけでこの動作を簡単に追加できます。

直接 onclick とは書いていないだけ

> JavaScriptを正しく分離することができたので、今後の変更が容易になりました。今後は、このdata-*属性をリンクタグに追加するだけでこの動作を簡単に追加できます。

とおっしゃっておられますが
分離前でも onclick="paintIt(this, '#990000')" のような記述を追加するだけでこの動作を簡単に追加できていました。
なにをもって分離後の方が「変更が容易」と言えるのでしょう?
「JavaScriptを正しく分離することができたので」では全く理由になっていません。
(「これではDRYとは言えません。」という部分も何が DRY でないのか自明でなく説明もありません。。)

コンテンツ (HTML) と振る舞い (JavaScript) と見た目 (CSS) とを別ファイルにする目的は
関心を分離して互いを抽象化することにあると思うのですが
マークアップには振る舞いを表現するためのクラスや data-* 属性が必要で
抽象化は実現できておらず
何が変わったかと言えば単に「直接 onclick とは書いていない」だけです。

実際には関心は分離できていないのです。

grep しないと理解できないコード

それどころか
無視できない大きなデメリットがあります。
関係のある記述が散らばることです。

端的にビフォーアフターで比較しましょう。
レンダリング結果が予測しやすいのはどちらでしょうか?

<a href="#" onclick="paintIt(this, '#000099', '#FFFFFF')">Paint it blue</a>
<a href="#" data-background-color="#000099" data-text-color="#FFFFFF">Paint it blue</a>

コードを書いているときはまだいいのですが
後になって HTML を読んでみると
data-background-color 属性が何を表しているのか
見た目にどう影響するのか
振る舞いにどう影響するのか
grep しなければ分かりません。

やがて HTML は得体の知れないクラス名や属性で埋め尽くされます。
そして JavaScript はイベントリスナーの登録処理で埋め尽くされます。
ビジネスロジックに集中できない!

『間違ったコードは間違って見えるようにする』 の言葉を少し借りると、

関係するものを隣り合わせにしておくことでコードを改善できる例はたくさんある。コーディング規則の多くは以下のような規則を含んでいる:

  • 関数を短くする。
  • 変数は使う場所のできるだけ近くで宣言する。
  • 自分のプログラミング言語を作ろうとしてマクロを使わない。
  • gotoを使わない。
  • 閉じる括弧は、対応する開く括弧から1画面以上離さない。

これらのルールに共通しているのは、1行のコードが実際にすることに関連した情報を可能な限り物理的に近づけるということだ。そうすることによって、あなたの目玉が何が起きているのか理解できる可能性が高くなる。

(だからといってハンガリアン記法はオススメしませんが。。)

もっと地味な問題: コードの恣意性

もっと地味ぃ~な問題もあります。
コードの恣意性が強くなることです。

例えば:
div 要素 A ではクリック時に処理 1 の結果をもとに処理 2 を行いたいとします。
div 要素 B ではクリック時に処理 1 の結果をもとに処理 3 を行いたいとします。

インラインスクリプトで実現するならこうです。

<div onclick="procedure2(procedure1('arg1'));"> ... </div>
<div onclick="procedure3(procedure1('arg2'));"> ... </div>

インラインスクリプトを使わないコードを想像してみてください。
しっくり来るでしょうか?
その「しっくり来たコード」は誰が考えても似たようになるでしょうか?
しっくり来るまでにかかる時間はインラインスクリプトで実現するのに比べて長くならないでしょうか?
(この例ではインラインスクリプトが見えているのでアレですが)

これらは「やり方がいろいろある」ことの弊害です。
スクリプトを分離しようと考えると、コードの恣意性が高まります。
(恣意性が高くなる原因についてはパッと説明できませんが。。)

やり方がいろいろあると
思いつく限りの方法の良し悪しを比較する必要があり
「しっくり来る」のに時間がかかるのです。
間違えようのないやり方がひとつだけあるのがいいと考えるのは私だけではないはずです。

React

コンテンツとその振る舞いは切っても切れません。
コンテンツとその振る舞いは同じ場所に記述した方がメンテナンスしやすくなります。
などと私ごときが言っても伝わらないかもしれませんが...
React の JSX コードがどうして次のようになるのか (次のように書けると何が嬉しいのか)
考えてみてはいかがでしょうか。

  • DOM Event Listeners in a Component | React
    var Box = React.createClass({
      getInitialState: function() {
        return {windowWidth: window.innerWidth};
      },
    
      handleResize: function(e) {
        this.setState({windowWidth: window.innerWidth});
      },
    
      componentDidMount: function() {
        window.addEventListener('resize', this.handleResize);
      },
    
      componentWillUnmount: function() {
        window.removeEventListener('resize', this.handleResize);
      },
    
      render: function() {
        return <div>Current window width: {this.state.windowWidth}</div>;
      }
    });
    
    ReactDOM.render(<Box />, mountNode);
    

だいたい以上です

インラインスクリプト禁止の弊害について書いてきました。
やたらめったらインラインスクリプトを使えと言うつもりはありませんし
プロジェクトのポリシーとしてインラインスクリプト禁止と言われれば私もインラインスクリプトは使いません。
ただ
思考を停止してインラインスクリプトを悪とする風潮に対しては冷静になりたいと思った次第です。

p.s. インラインスタイルについて

インラインスタイルが悪かどうかについては検討できていません。
いろいろ思うところはありますが
ノーコメントにしておきます。