javascriptのprototype拡張はどこまで許されるか
孫引きなんだけどちょっと気になった点
404 Blog Not Found:javascript - es2pi はじめました http://blog.livedoor.jp/dankogai/archives/51859796.html
本来の JavaScript の利用方法(Prototype 拡張)に立ち返り、Array.prototype, String.prototype, Number.prototype 等を拡張しています
最近のJSは基本的に肥大化するし、DOMはグローバルな状態であり、またprototypeもどこからでもアクセスできるという点ではグローバルである。
手癖が悪い人はprototype経由で値の受け渡ししはじめる。JSではスコープチェーンなりで値の受け渡しをするように気をつけたい。
経験上、方針を決めずにprototypeを触りまくると遅かれ早かれ破綻する。が、元々プロトタイプ指向な言語だけに、うまく活用すると言語としての自由度がグンと上がる。そのバランスを取らないといけない。
以下にprototype拡張が許されると思しきケースを書く。
1. polyfill系ライブラリ
- prototype.js
- sugar.js
- es5-shim
- es2pi
JS5未満の標準ライブラリは貧弱であり、それを補完する系のもの。ECMA Harmony もしくは Rubyインスパイアのものが多い。
このライブラリを使っている限りは、ライブラリが提供するコードを使うべきで、よくわからないからと自分で定義してしまうと、ロジックが重複して見通しが悪くなる。
2. オブジェクト宣言, prototype継承
JavaScriptでオブジェクト宣言する際は勿論prototype経由で振る舞いを定義することになる。
coffee-scriptで継承を実現する際は、次のようなスニペットが挿入される。
// Generated by CoffeeScript 1.6.1 (function() { var List, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; List = (function(_super) { __extends(List, _super); function List() { return List.__super__.constructor.apply(this, arguments); } return List; })(Array); }).call(this);
このコードはArrayを継承したListクラスを作る。
まあ普通のprototype活用だとは思う。
3. 実装(not ライブラリ)
自分がフロントエンドを作る側で、かつ自分自身を継承する人がいない場合、独自の挙動を追加することは許されるとは思っている。
ただ、その場合1つのファイルを指定するなどの明示的なアプローチをとる必要がある。たとえば array_proto.js でArrayの拡張を書き、それ以外ではArray.prototypeは決して触れない、とか。
そしてpolyfill系ライブラリを使っている場合はある程度任せるべきで、かつ上書きしてはいけない。それでいてアプリケーションロジックの実行前に全部読み込んでおくべきだと思う。
プロトタイプはアプリケーション実行前後に同じものであってほしい。コード中で動的にprototypeオブジェクトを変更されるのは怖くて仕方ない。
まあ、便利だけど明示的に、ってのが自分の信条です。
DOMを高速に操作するための skin.js というライブラリを作った
(タイトル修正 DOMを高速に操作 => DOMの値を高速に更新 at Sat Mar 09 2013 15:30:09 GMT+0900 (JST))
(Skin#inject実装したのでタイトル元に戻した at Mar 09 2013 18:10:04 GMT+0900 (JST))
若手の会で、JavaScript Hell on Earth というテーマで話してきました。
js_hell_on_earth http://www.rvl.io/mizchi/js_hell_on_earth
というわけでDOM抽象ラッパーつくりました。
mizchi/skin.js · GitHub https://github.com/mizchi/skin.js
目的
クライアントサイドJSでは一回書いたら終わり、ではありません。ゲームなどのリッチなユーザー体験を提供する際、高頻度でDOMを書き換える際に、HTMLElementでキャッシュを持って高速に書き換えられるようにしたいという需要がありました。
そして、その際には簡単なDOMの対応をとるテンプレートエンジン(ただし他のテンプレートエンジンの邪魔をしない)ようなものが欲しくなるわけです。
使い方
初期化
new Skin(HTMLElement_or_CSSSelector [, html] )
挿入
Skin#set(key, val)
Skin#set({key:val})
特定のHTMLElement、もしくはそのセレクタをルートにして、newします。第二引数では、初期化時に渡すHTMLを指定出来ます(省略可)
この例では script type='text/template' でテンプレートとして初期化して渡しています。
値を書き換えたいDOMに対しては、data-value='hoge' のように、HTMLに対して属性を付与して、Skin#setで対応するものを書き換えます。
index.html
<div id='goblin'></div> <script type="text/template" id='status_template'> <div class='monster'> <span data-value='name'></span> <span data-value='hp'></span> <span data-value='mp'></span> </div> </script>
window.onload = function(){ var root = document.querySelector('#goblin'); var html = document.querySelector('#status_template').innerText; var goblin_skin = new Skin(root, html); goblin_skin.set({ name: 'goblin', hp: 100, mp: 20 }); };
その結果、こうなります。
<div id='goblin'> <div class='monster'> <span data-value='name'>goblin</span> <span data-value='hp'>100</span> <span data-value='mp'>20</span> </div> </div>
Skin#inject
(追加 at Mar 09 2013 17:27:26 GMT+0900 (JST))
HTMLElementを列挙しながら関数を適用します。
goblin_skin.inject('mp', function(el){el.style.visibility = 'hidden'})
もちろんjQuery使ってもいいです
goblin_skin.inject('mp', function(el){$(el).hide();})
skin.jsがやらないこと
特定のモデルとの対応
渡す側でやれ
エスケープなどのバリデーション
渡す側でやれ
イベントフックなどの複雑なデータ加工
渡す側でやれ
イテレーティブなデータ挿入
渡す側でやれ
同値挿入の無視
渡す側でやれ(いれてもいいかなと思ってるが、モデルの値を持ちたくない)
素早い初期化
セマンティクスの観点からdata-valueを使っています。
data属性でクエリを投げているので、初期化が若干遅いです。このライブラリは、ルートから全検索して値を挿入するものではありません。あくまで領域を絞って初期化して、特定のDOM以下で高速に値を書き換えることを目的にしています。
たとえば次のような状況で真価を発揮します。
var skin = new Skin('#timer'); setInterval(function(){ skin.set("unixtime", Date.now()); }, 1000/60);
毎秒60回 Unixtimeを更新します。(たぶんいけますがこれでもCPUは食います)
fork me!
この実装は、実はcoffeeで50行しかありません。どちらかというと、テンプレートを一括で書いてしまうサーバーサイド的発想から脱却するための提案みたいなものです。
世の中のJSは、setIntervalのループの中からモデルの特定の値を配信する際、非効率なテンプレーティングをしているものが多いです。そのようなものは、激しいCPU処理を発生させ、UIをブロックし「タッチにしにくい」といった状況を引き起こす原因になります。とくにスマホは計算資源が限られています。
特にHTML5のゲームでは、UIレイヤーをHTMLで書きつつも、FPSを出すためにレンダリングのコストは最小である必要があります。
テンプレートエンジンを持たないBackboneのヘルパとして作ったので、機能は最小限ですが、BackboneどころかjQueryにも依存してないので、どんなライブラリにも混ぜて使えます。*1
mizchi/skin.js · GitHub https://github.com/mizchi/skin.js
僕が言いたいことは以上です。
*1:テンプレートエンジン持ちのライブラリとは役割が被るので相性が悪いと思われる
動的型とか静的型の話の前に「作者の気持ち」を考えろ
自分の思考を整理する意味でも、件のアレについて考えたことを書いてみる。
- 変数に型がないということの利点について考える - サンプルコードによるPerl入門 http://d.hatena.ne.jp/perlcodesample/20130227/1361928810
この件に触れることはプログラマとしての中二病である。恥ずかしい。マジレス乙だ。
でも気づいたら5000文字も書いてしまったし、公開して酒のんで寝る。
型のフローは機械のためだけでなく、人間に対するものでもある
最近TypeScriptを書いている。こいつを使って、二次元座標上で二点間を求める関数、getDistanceを定義してみよう。
interface IPoint { x: Number; y: Number; } var getDistance = (a:IPoint, b:IPoint): Number => Math.sqrt(Math.pow((a.x-b.x), 2)+Math.pow((a.y-b.y), 2));
このコードには、「getDistanceというメソッドが二点間の距離を求める」という挙動が型によって説明されている。
getDistanceの中身が圧縮されて読みづらいのは、半分ぐらいわざとだ。知らない定理ならともかく、義務教育を受けたなら知っているはずの三平方の定理を、ごちゃごちゃと説明的に書きたくない。このコードの意図自体は型定義に圧縮されている。
そもそも、TypeScriptはコンパイルされるとただのJSになるので、実行速度の最適化には繋がない。それでも型情報をもってるのは、開発効率のために型があったほうがいい、という観点からだ。
良い静的型のコードは、良いユーザーモデルを発想させるような、良い型の流れのシナリオを持っている。
型はコメントよりよほど雄弁である -- Haskell
上記のコードをHaskellで書いてみよう。
自分がHaskellをあまり書けないが好きな理由の1つに、美しい型機構があり、そしてカリー化で手続きを意味的に分割できるという点がある。
ややこしいモナドも出てこないので、簡単なコードだ。
-- Point型を定義する data Point = Point {x :: Float, y :: Float} -- 関数の型を宣言する getDistance :: Point -> Point -> Float getDistance a b = sqrt $ ((x a) - (x b)) ** 2 + ((y b) - (y b)) ** 2 -- 第一引数を(Point 0 0)で部分適用。常に原点からの距離を求める関数として定義する getDistanceFromOrigin = getDistance (Point 0 0) main = do let a = Point 3 2 let b = Point 5 5 print $ getDistance a b print $ getDistanceFromOrigin a
(Haskell Wayというものを理解していないので、もしかしたら酷いコードかもしれない。が、サンプルのために許してほしい)
型定義と実装が別の箇所で行われている。意味を損なわない名前が正しく定義されている場合、関数名と型定義がドキュメントの役割を果たす。
このコードをよむとき、mainから読み始め、そして getDistance を目にした時に、 Point -> Point -> Float という型の流れだけを見ればいい。
この例を見る限り、型はコメントよりよほど雄弁だ。
Roy という言語
最近個人的に期待している RoyというHaskell系 Altjs(JSにコンパイルできる言語)は、次のように記述できる。
type Point = {x: Number, y: Number} let getDistance (a:Point) (b:Point) = Math.sqrt (Math.pow (a.x-b.x) 2)+(Math.pow (a.y-b.y) 2) let a : Point = {x: 3, y: 2} let b : Point = {x: 5, y: 5} console.log (getDistance a b)
Haskellよりはゆるふわだが、JSらしい記述を邪魔せず、型制約を取り入れることに成功している。
なお、この節はただのRoyの紹介である。
pufuwozu/roy · GitHub https://github.com/pufuwozu/roy
静的型が嫌われる例 -- Java
Javaは全般的に手続きがダサいというのはある程度共有された事実だろう。
もちろん、歴史的経緯があるのはわかるが、それにしてもダサい。
僕が初見でダサすぎてびっくりした、PrintWriterを使いたい場合こうなる。
File file = new File("/path/to/file"); FileWriter filewriter = new FileWriter(file); BufferedWriter bw = new BufferedWriter(filewriter); PrintWriter pw = new PrintWriter(bw); pw.println("content"); pw.close();
このコードで、僕らがやりたいことは1つ。「ファイル名を指定して文字列を対話的に書き込みたい」というpw.printlnだ。
そこに至るまでのデータを作る過程は、やりたいことに対して本質的ではない。勿論、組み込み型なので抽象レベルは低いほうがいいが、JavaはLLのパッケージ群のように外部ライブラリを気軽に呼び出してラッパーを作るという文化がないので、この手続きは覚えておかないといけない。
Pythonの組み込み関数を使って同等のことをやるとすると、こうなる。
file = open('/path/to/file') file.writelines(['content']) file.close()
openがグローバル名前空間に属しているという気持ち悪さはあるとしても、その意図は明確だ。
writeleinsを呼びたい。ユーザーがやりたい意図を、APIが可能な限り阻害しない。
この例から考えるに、ライブラリ実装者は可能な限りユーザーのメンタルモデルを阻害しないAPI体系をつくることを意図すべきだ。
このJavaのような、本質と関係ないデータフローばかり見せられた人が、静的型は糞だ!って結論に至るのは痛いほど理解できる。僕も最初に学んだ言語はJavaで、「プログラミングの楽しさ」に目覚めたのはPythonだった。
コードがその使用者に対する説明ではなく、あくまで計算機上のデータ本位でしか見てないというケースで、この問題が発生する。
僕らは機械に対してだけではなく、人間に対してコードを書かないといけない。それを忘れた瞬間、コードは邪悪な熱量を持ち始める。
マインドモデルをコードに書くか、文化に従うか、自己完結するか
静的型定義のメリットは、上に述べたようにデータフローを明示化することだった。
じゃあ、全部静的型にすべきかというと、そういうわけでもない。
昨日はjQueryのコードリーディングの勉強会に出ていたのだが、src/core.js に次のようなヘルパ関数があった。
// Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function access: function( elems, fn, key, value, chainable, emptyGet, raw ) {...
もうコメントと引数の数からして嫌な予感がするだろうが、このメソッドの振る舞いを簡単に説明すると、
$('#foo').attr('class', 'bar') を、$('#foo').attr({'class': 'bar'}) というようにハッシュで一括にアクセスできるようにしたり、$('#foo).text('str') は 値を更新するから文字列返すよ、この場合メソッドチェーンできないよ、というのを、この便利関数一個で対応している。
おそらくDOM操作系メソッドが多すぎるせいで、コード量の圧縮のために、このような抽象化をする必要があったのだろう。jQueryの特殊な事情に起因するものだ。(それにしても酷いとは思うが)。
これは jQueryの開発者同士は共有すべきだが、ユーザーに提供するメソッドではない。ユーザーはその文脈を共有する必要がない。あなたがもしjQueryの開発者なら、その文化に対する学習コストが発生するが、それによって開発効率の恩恵を受けることができる。
ひとりプロジェクトで好き勝手にコード書いて、その結果全部動的型でいいんじゃないの?となるのは自分の中にあらゆる実装ケースに対するマインドモデルが自分の中で完結しているからだ。チーム開発ではない場合、それも許される。僕も趣味でPythonしか書いていないときはそうだった。
でも、チームが徐々に大きくなっていくとどうだろう。僕は3つの要素があると思っている。
- 言語理解
- ライブラリ理解
- プロジェクト理解
そもそも実装方法がわからないとき、アルゴリズムやデザインパターンからマインドモデルを新規に学習する必要がある。それをオレオレ手法をとられると、その学習コストが跳ね上がって、属人性が高いものになる。優秀なマインドモデルから作られたライブラリは理解しやすいが、そうでないライブラリの思考に追従するのは悲しいものだ。
一般的に、マイナーな言語コミュニティほど平均的な技術力が高い
僕は Ruby は学習コストが高い言語だと思っている。言語仕様はそれほどでもない。が、その背景、哲学の量が半端ない。僕はRubyを「適当に」使うことはできるけど、「正しく」書くことはできない。僕が書いたRubyにはどんどん斧が飛んでくる。(Rubyistが多い会社なので要求水準が高い)
良い習慣を身につけることは、プログラマとしてあるべき正しい姿だ。だが、あらゆる面でRubyWayなコンテキストを共有していないと、舞台に上がることすらできないのは、はたして初学者に対して優しいと言えるだろうか。敷居が低い言語だと言えるだろうか。
僕は、喧伝されるRubyの生産性の高さみたいなものは、プログラミングの楽しさみたいなものを担保に、個々人に対して要求水準を上げて成り立ってる部分が大きいと思っている。別にRubyがきらいなわけではない。Java, C++, PHP以外のマイナー言語の生産性を喧伝する際に一般的に当てはまることだ。
Rubyの哲学を知らない人が業務としてRubyを使わないといけない時代がきた場合、その担保は崩れた時にどうするんだっていうのはいつも考えておく必要があるのだろう。HackersNewsで、Railsはスタートアップ界のJavaだ、っていう記事もあった。そういう時代は案外近いのかもしれない。
Rails, You Have Turned into Java. Congratulations! | Discursive http://discursive.com/2013/02/19/rails-you-have-turned-into-java-congratulations/
同じ文化を共有していればいるほど、命名には文化があって、それはデータの流れの情報すら内包している。その場合、動的型で型以外のロジックのコアについての生産性を上げることを目標とするのはおかしくない。ただし、全員が共有していれば、の話だ。
まとめ
コードの文量が増えれば増えるほど、なんにしろ破綻リスクは目に見えて大きくなる。言語自体の文化だけではなく、根幹ライブラリの理解、プロジェクト内の抑える文脈が増えていく。僕らはそれを抑える必要がある。
1万行のコードに対して、人間の短期記憶はあまりにも小さい。型情報は定義やIDEを通して僕らに語りかけてくる。データモデリングによって次の実装が創発される。正しい静的型プロジェクトはそうであるべきだ。
動的型で巨大なプロダクトを作る場合、小さいモジュールで適切に分離し、認知負荷を抑える。RubyやNodeが役割1つごとにライブラリに切り出すのは、再利用性ってのもあるし、インターフェースを適切に切ってモジュール間のコミュニケーションを最小にするテクニックだ。連綿と受け継がれてきたUnix文化だとも言える。
と、だいたいこんな感じのことを考えていた。
長々と書いたが、オチが他の記事と一緒でつまらん。それ一番言われるから。
なお、この文章はポール・グラハムみたいな文章を書きたくなり、僕の中のポール・グラハム的成分によって書かれました。
FirefoxOSはとにかく簡単にアプリが作れてスゴイかも
やっぱ鮮度が命だとおもってFirefoxOSのアプリをいじってた。その過程。
シミュレータのインストール
Firefoxで入れる。
Firefox OS Simulator :: Add-ons for Firefox https://addons.mozilla.org/ja/firefox/addon/firefox-os-simulator/
ダッシュボードからStoppedのところを押して起動(わかりにくい)
サンプルアプリの起動
nodeのインストールは前提。それからvoloを入れます。
$ npm install -g volo # nodeアプリなので本来ならここで npm install したほうがいい >|| mozilla系のビルドツールらしいということしか知らない。 >|| $ git clone git@github.com:darkwing/firefoxos-quick-start.git $ cd firefoxos-quick-start/app/ $ volo serve
firefox os simulatorでlocalhost:8008を開く
コードを読む
tools ├── almond.js ├── build.js ├── manifest.appcache ├── oneless.js ├── r.js └── volo
voloでのビルド用?
全体的にrequire.jsと、その拡張almondを使うことを要求される模様
実装はwww以下
www ├── css │   ├── app.css │   └── install-button.css ├── favicon.ico ├── img │   ├── glyphicons-halflings-white.png │   ├── glyphicons-halflings.png │   ├── icons │   │   ├── mortar-128.png │   │   ├── mortar-16.png │   │   └── mortar-48.png │   ├── offline.png │   └── online.png ├── index.html ├── js │   ├── app.js │   ├── init.js │   ├── install-button.js │   └── lib │   ├── install.js │   ├── receiptverifier.js │   ├── require.js │   └── zepto.js └── manifest.webapp
おそらくindex.htmlとmanifest.webapppだけで動きそう
manifest.webapp
{ "version": "0.1", "name": "Your App", "description": "Your new awesome Open Web App", "launch_path": "/index.html", "icons": { "16": "/img/icons/mortar-16.png", "48": "/img/icons/mortar-48.png", "128": "/img/icons/mortar-128.png" }, "developer": { "name": "Your Name", "url": "http://yourawesomeapp.com" }, "installs_allowed_from": ["*"], "locales": { "es": { "description": "Su nueva aplicación impresionante Open Web", "developer": { "url": "http://yourawesomeapp.com" } }, "it": { "description": "Il vostro nuovo fantastico Open Web App", "developer": { "url": "http://yourawesomeapp.com" } } }, "default_locale": "en", "permissions": { "systemXHR": {} } }
icons
おそらく解像度ごとのicon
installed_allowed_from
おそらくインストールIP制限
locales
多言語用設定。おそらくマーケット用
OS機能へのアクセスを調べた
FirefoxOSアプリではOSネイティブのコードをJSから触れると噂に聞いていたので、indicator-percentageにバッテリ残量が表示されているのがそれかと思い、調べてみた。
www/js/app.js
var battery = navigator.battery || navigator.mozBattery || navigator.webkitBattery
シミュレータ上ではホストOSのバッテリをみてるっぽい
ぐぐってみたらFirefoxOS API ではなく普通のAPIらしい
window.navigator.battery - DOM | MDN https://developer.mozilla.org/ja/docs/DOM/window.navigator.battery
ネイティブAPIについては、今回わからなかった。あとで調べる。
receiptverifier.js
どこでも使われてないけど気になったのがこれ
課金APIの認証周りだと思われる
インストール機能
www/js/install-button.js 参照
define(function(require) { var install = require('install'); function update() { var btn = document.getElementById('install-btn'); if(install.state == 'uninstalled') { btn.style.display = 'block'; } else if(install.state == 'installed' || install.state == 'unsupported') { btn.style.display = 'none'; } } function init() { var btn = document.getElementById('install-btn'); btn.addEventListener('click', function() { install(); }); install.on('change', update); install.on('error', function(e, err) { // Feel free to customize this alert('There was an error during installation.'); }); install.on('showiOSInstall', function() { // Feel free to customize this alert('To install, press the forward arrow in Safari ' + 'and touch "Add to Home Screen"'); }); } if(!document.getElementById('install-btn')) { document.addEventListener('DOMContentLoaded', init); } else { init(); } });
window.installというメソッドがあるらしい
インストール済みかどうかは、 install.state でわかる
install.on('change', function(){...})でステートが切り替わったことを受信できる。
というメソッドがある。
シミュレータだからなのかよくわからないが、どこにインストールされたかわからなかった。アイコンが出現したりはしない。
削ろうとしてみたけど…
やっぱこういうのを見るとスケルトンとして削りたくなる… んだけど
最悪manifestとindex.htmlがあればなんでもできるのでどうとでもなるので、どうでもよくなった
期待すること
複雑化しすぎたモバイルアプリ開発に対して、既存のウェブ技術だけでなんでもしようとするのは潔い。
ただインストールアプリをローカルで動かすことができるのかちょっとよくわからなかった。サーバー前提?
WebView的なものがおそらくOSとしてのファーストオブジェクトなので、おそらくAndroidやiOSのように、OSが低速で使いものにならないといったことはないだろう。それだけで期待がもてる。
ネイティブに迫るリッチアプリケーションが作れるかどうかというのは、これからのAPIの充実度次第。
最悪nodeのモジュールのように計算量が重い部分だけC++で書くのも、自分は別に構わない気がしてるけど、そうなるとAndroidの悲劇を繰り返しそうな気もする。トレードオフ。
とにかく開発者にやさしい構成になっていると感じた。その他の地獄と比べれば。
最近のJSの開発環境について知っておくべきライブラリ10個
ほんとに10個だと思った?(無意味に煽っていくスタイル)
最近JSだけのリポジトリで無益なゲームを大量に作っては破棄しているのだけど、割とストレスなく出来上がってきたので書く。
長々と書くが、要は次のリポジトリに概要が詰まってる。
mizchi/mizchi_client_boilerplate · GitHub https://github.com/mizchi/mizchi_client_boilerplate
前提として、最近はCSJSでもnodeのインストールを前提とする環境が多い。必須といってもいい。
grunt
gruntjs/grunt · GitHub https://github.com/gruntjs/grunt
ビルドタスクを簡単に作れる。make、rake、などに相当するが、node製らしくファイルシステムの監視でアクションを作れるのが特長。$ grunt serverで簡易サーバーも立てられる。
- coffeescript/typescriptの自動コンパイル、結合、minify
- sass/compassの自動生成
プラグインが大量にあって追加も簡単。ググればだいたい見つかる。
watchタスクにcoffeeのコンパイル手順を書いて、$ grunt watch server、みたいな使い方するとlocalhost:8000に簡易サーバーを立てつつ裏でファイルシステム監視しつつcoffeeの自動コンパイルしてくれて便利。
bower
twitter/bower · GitHub https://github.com/twitter/bower
Twitter製のクライアントサイドjs依存管理ライブラリ。gemとかnpmだと思ってもらえば問題ない。
component.json みたいなファイルを書いて、 $ bower install すると components/以下に保存される。require.jsに対応している。
{ "name": "MyProject", "version": "0.0.1", "dependencies": { "underscore": "latest", "jquery": "latest", "requirejs": "2.1.1" } }
使いたいライブラリがbowerに登録されていないことが多いが、バージョン名のところにgithubもしくはホスティングされているURLを直接参照すれば解決する。
yeoman
yeoman/yeoman · GitHub https://github.com/yeoman/yeoman
CSJS開発環境の総合フレームワーク。日本の紹介記事だとgruntと対になるような紹介をされていることが多いが、実際の中身はgruntのラッパーライブラリである。
angularやemberを試してみたいときに、おもむろに $ yeoman init angularすると雛形が出来上がって、気軽に試したいときにすごい便利。
紹介はするけど、ぶっちゃけ自分ではあまり使ってない。あまりにもyeoman流儀を強制されて、Railsの吐き出したディレクトリを最初に目にしたような目眩を感じる。僕のようにこの流儀が受け付けない人は、何も考えずに他人のboilerplate眺める用途に使おう。
require.js
RequireJS http://requirejs.org/
最近のJSの依存管理のデファクトなのだが、自分はあまり好きではない。
名前空間を決めて使おう!ってのはモダンなJSだと当たり前のことなんだけど、そんな当たり前の糞な規約を守れない糞JSコーダーを、require.jsという枷に入れてどうにかします、みたいな印象を受ける。
未だに自分のローカルの依存解決は gruntのconcat順で解決していてダサい(しデバッグしづらい)のでなんとかしたいのだけど、require.jsの規約はもっとダサい。ライブラリのインスタンスをコールバックで受け取るっていうのも、そのために無駄にネストを深くするのも、ちょっと嫌だ。
でもまあ、TwitterがFlightやBower等で使うことを強制してきてるし、外部ライブラリ間の依存解決もできるし、たぶんこれから標準になるんだろーなといった印象。TypeScriptもmoduleを使うと内部構造はrequire.js準拠になるオプションがある。
peerjs
PeerJS http://peerjs.com
つい先日リリースされたばかりで、WebRTCでクライアントサイドP2Pを実現する、そのAPIを使いやすくしたライブラリ。おそらくsocket.ioに似せたものなので、使ったことがある人にはわかりやすい。
クライアント間でハンドシェイクするためのstunサーバーもpeerjs.comで提供してくれている。(npm で自分で立てても良い)
今現在ChromeDevでしか動作しないが、自分でサーバー立てずにlocalStorageとp2pだけで「ウェブ」アプリを作ることもできる。そんな未来もある。HTML5でゲーム作ってる人はウェブサーバー管理しなくていいってのはすごい利点だと思ってる。
その他MVCフレームワークについて雑感
Backbone
無難に便利なんだけどCollection, Model部分はその他モジュールと綺麗に切り離されているので、そこだけ使いたいケースが多い。Viewはイベントハンドラとして使うと便利。
Backbone使いはよく言うんだけど、このライブラリはコード量が減る方向には貢献しない。
Angular
あんまりガッツリ使ってないんだけど、DOMツリーが名前空間を表象するのは自分にはちょっと受け入れがたい。抽象度が低く規約が多い。HTMLとデータ構造がそのままビューに反映するアプリには良いとは思う。裏で大量のロジックが走るようなアプリには向いてない。
Ember
Railsの規約をそのまま持ってきすぎで、ルーティング制約とそのモデル対応がきつい。シングルページアプリケーションしか作ってない身だと有り難みがなかった。
おそらくRailsとの親和性が高いのでRails組み込みとして使うと真価を発揮するんじゃないだろうか。まだそんな時代じゃない気もするが…。
疲れたのでこのへんで終了。
そうそう、Railsは4.0で遷移アクションがデフォルトpushStateになるらしいので、つまりはJSオブジェクトを破棄しない。グローバル汚染が厳しくなるので、より慎重なJSを書く必要があるし、遷移時のオブジェクトの再生成コストがなくなるのでJSの仕事が多くなると思われる。
アジャイルの「顧客に価値を届ける」の嘘と本当
酔った勢いでアジャイルについて思うところを書く。
顧客に価値を届けるのは誰か
顧客に届く価値 = 目に見える成果物、という評価は、フロントに近い人間しか評価されなくなる傾向を抱え込む。顧客に価値が届くまでには段階がある。複雑なものほどワークフローが長大になる。お互いの価値を見積もれるのは、小さいチームでお互いの職種について理解がある場合の理想であり、多くの場合理想は理想である。大きなチームほど、フロントに遠い人間は自分の価値を伝えるのが難しい。
難しいことを難しいということ
エンジニアが自分の仕事について、エンジニア以外への責任説明を果たそうとすると努力は必要だが、必ずしもそれが伝わるとは限らない。
難しいことを難しいと言えないと、「それってすぐできるんでしょ?」という展開になりがちで、「任せてくれ!」と言えるのはかっこいいが、誰しもがスーパーエンジニアではない。そして見積もりに失敗する。炎上する。
テスト リファクタリング
テストを書くことや、リファクタリングをすることによるエンジニアの脳内の「開発速度曲線」は、エンジニア以外に伝わらない。納期に間に合わせたいなら信頼して任せてくれ、というしかなく、そして大体の場合断られる。それに真に共感できるのは、それを必要とするエンジニアだけである。そしてマネージャからは自己満足的なリファクタリングと必要なリファクタリングを区別する方法がない。信頼されるか、否か。
テスト駆動はエンジニア以外(正直ほとんどのエンジニアにも)には理解されない。基本的に、未成熟なチームの場合、テストを書く時間がタスクの中に見積もられない。タスクが遅延した場合、テストを書く時間はまっさきに削られる。さらに急いで作れと言われた場合、テストは欠損する。
マーフィーの法則。せめてモデルだけはテストを書いてくれと頼んだ場合、モデルにあるべきコードがコントローラに書かれる。
言語とテストフレームワーク
RubyがTDDをやりやすいのはRSpecの改良の賜であり、同じ哲学を他の言語に持ち出すと破綻する。RubyでもWAFは破綻する傾向がある。
ビューと密接に関わるJSやテンプレートを内包したPHPは、最初からテストの困難性を内包している。「ロジカル」でなく「フィール」に関わるプロパティは、そもそもロジカルにテストすることがナンセンスである。
テストが無駄だということではなく、ロジックと感情を分離する手続きにこそ価値が置かれる。趣味でスパゲッティコードを作ってしまった経験や、モナドや関数型言語の経験はこういう場所に活かされる。
コード品質とチーム
言語理解とコード品質にしか興味が無い人は、エンジニア以外からは疎まれるが、持続的な開発速度を担保するためには必要な存在であると思う。
8人のエンジニアがいたら1人はそういう人間である必要がある。コードを監督する立場にある人がコード品質に興味が無い場合、コード量に比例して破綻するリスクを背負い込む。
Object.observeに未来を感じたので、es5でもちょっとだけ動くようにしてみた
互換っぽいのを作った話。
今日なんとなくAngular.jsを触ってて、「いや、俺が欲しかったのはこれじゃないんだ!」という感がムンムンだったので、前々から気になっていたObject.Observeを試してみた。
詳細は次のエントリが詳しい。
次世代JavaScriptでデータバインディング: Object.observe() を試す - ぼちぼち日記 | Postolog http://postolog.com/bbbkL2
哲学の話
Object.observeのやってることはBackbone.Modelと変わらないんだが、Backbone.ModelはBackboneのデータ構造であることをユーザーに強いてくる。どのモデルか意識せずに、オブジェクトの変化をViewから監視したい。
MVCのMの部分、JSのやるべき仕事はデータモデリングがメインになると思っていて、とくに明記せずともデータ構造を持つオブジェクトであると捉える必要がある。
ES5でも使いたくなった
つくった
mizchi/es5observe · GitHub https://github.com/mizchi/es5observe
イテラブルなオブジェクトを誤魔化すために、Object.definePropertyの嵐です。
詳細はREADME。しかし適当に作りすぎたせいで全然仕様を満たせておらず、名前空間を汚す副作用も残っている。誰かPullReqください。
リークしてたらすいませんって感じなんだけど、きたるべき未来の為に updated だけでも動く環境を作りたかった。
なにができるか
JSのオブジェクトをモデルとして、Viewの実装を次のように行える。
上のコードを読み込んだ状態か、Chrome Dev版でObject.observeが実装されてる版なら、このコードはちゃんと動く。
""" HTML <p id='status'> <span class='hp'></span> <span class='x'></span> <span class='y'></span> </p> """ window.onload = -> player_status = hp: 10, x: 3, y: 0 renderer = new StatusView $('#status'), player_status class View binding: {} hook: {} constructor: (@$el, @model) -> Object.observe @model, @_handler @_initialize() _initialize: -> for key, val of @model if typeof val in ['string', 'number'] @_update_value(key) _handler: (events) => for e in events when e.type in ['updated', 'new'] @_update_value e.name for method_name, values of @hook for v in values when v is e.name @[method_name](events) break _update_value: (value_name) -> selector = @binding[value_name] or '.'+value_name @$el.find(selector).text @model[value_name] class StatusView extends View binding: hp: '.hp' # "default binding is '.' prefix" hook: render: ['x', 'y'] render: (events) => console.log 'called by hook', events
とにかくJSの仕事をデータモデリングにだけにすると色々と幸せになるはずなんです!