今年買ってよかったもの

あんまり買い物していない。高いけど欲しかったものを買った。わりと満足感は高かった。

カメラ

これは自分のじゃなくて妻が買ったものだけど、素人でもなんとなくうまい感じに撮れるし、大きすぎず重すぎずでよいカメラだと思う。

冷蔵庫

一人暮らしの時にかった無印良品の冷蔵庫を使ってたんだけどさすがに小さくて不便だった。この無印の冷蔵庫は一個古い型で、深沢直人っていう人がデザインしていてかっこいい。無印良品が冷蔵庫を作ってるわけではなくて東芝に作ってもらってたようだけど、東芝が小さめの冷蔵庫つくるのやめたらしく、なくなってしまった。今のデザインは気に入らなかったので無印はやめた。

容量が4倍になったのでもう比較とかできないけど、冷蔵庫は大きいほうが省エネらしく前よりも電気代が安いらしい。大きくて引っ越しするときに気を使うけど氷も勝手に作ってくれるしとても便利。

タブレット

photo by Ben Atkin

iPad mini retina を買った。ずっとタブレット欲しいと思っていたけんだけど特に何に使うとか目的がなくて、ずっとタブレット欲しいとだけ思ってて買えなかった。ちょっと重いとかそういう買わない理由をみつけて買わないようにしてたんだけど、自分的に買わない理由が一つもなくなってしまったので買ってしまった。

iPhone あるしけっきょく使わなくなるかもと思っていたけど、iPhone で出来ることは iPad でも出来て iPad のほうが見やすい/使いやすい(アプリによっては)なのではてブとか RSS 見るみたいなのはだいたい iPad でやるようになって、思ったより活用しててよかった。あと Kindle で漫画読むのには本当に最高。


年々物欲がへってきている。お金で買えない欲しいものが増えた。

Chrome 拡張の 2ch ブラウザを作った

クライアントで少しリッチなアプリが作れるようになりたいと思って、 chrome 拡張 で 2ch ブラウザを作ってみた。

f:id:hatz48:20131222133006p:plain

開発周り

Chrome 拡張は js/css/html での開発になるので、それぞれ TypeScript/less/underscore-template を Grunt でビルドするようにした。 サーバーサイドはないので、デプロイツール等はなし。 Grunt では本当はテストも走らせる予定だったのだが、まだテストが書いてない。。

クライアントサイドMVC

Backbone.js を使っていたのだけれど、いつくつかの理由から何も使わずに MVC(P?) っぽくかいてみることにした。基本的はに Backbone.js のやり方をまねて、Model の役割を一部サービスクラスに分離した感じ。

Service層導入の経緯

Backbone はいろんな書き方ができるので、Backboneが悪いとかじゃなくて自分の書き方がよくなかったのだろうけど、コントローラー同士のやり取りがうまく書けなかった。(Backbone.View をコントローラーとしてます)

あるコントローラーが管理しているDOMのイベントに応じて、別のコントローラーが管理しているDOMを変更する、というようなこと(例えばスレッド一覧をクリックされたときにスレッドの内容を新しいタブとして開く)をしたい場合どうするか。

  1. 上位のコントローラーに管理させる
  2. コントローラー同士に参照関係をもたせる
  3. コントローラー同士に Pub/Sub 関係をもたせる
  4. DOM の(カスタム)イベントを介してやり取りする
  5. Model のイベントを介してやり取りする
  6. URLを変更する

Backbone を使う場合こんな感じだと思う。1 は管理する範囲が大きくなるのがイヤで、2 コントローラー同士を疎結合にしたいのでイヤな感じ。3 はイベントの把握が煩雑になる。

4 のDOMを介すっていうのは悪くないと思うんだけど、コントローラー同士が同じDOMを購読している必要があるのでやっぱり1つのコントローラーの責務が大きくなりがち。

いろいろ挙げたけど実際にBackboneでやることが多いパターンは 5 か 6 だと思う。ただ URL を変更するやり方は Chrome 拡張ではうまくいかなかったのと、コントローラー同士のやりとりをするときに必ずURLを変更をするわけにもいかないだろうと思う。最後、model のイベント経由にしなかったのはなんでか。

Model のイベントを経由する -> しない

model はいろんな場所で参照されるので、 model がイベントの発行者だとどこでイベントが発行されるのか把握しづらくないだろうか。

// コントローラーAでmodelを購読
this.listenTo(model, 'change:hoge', this.onChangeHoge);
...
// どこかでmodelのイベントが発行される
model.set('hoge':'new value');

こんな感じでカジュアルにイベントが発行されるのだけど、この「どこか」がどこになり得るのか、極端な話 template の中ということもありえる。まぁそれはさすがにないだろうけど、規模が大きくなるほどイベントの流れが把握しにくくなる。

もう一つ、modelはインスタンス化される数が多い。つまりイベントの発行者が多いわけで、「どこで」「どいつ」がイベントを発行しているのかちゃんと把握するのがむずかしい。なんか二回実行されてる、みたいなバグがでたりする。

いやいや設計ちゃんとしろよ、みたいな話なのかもしれないけどそれより model をイベント発行者じゃなくすことを選んだ。

サービスクラスが何をするか

  • イベントの発行
  • IO 処理

IO 処理もサービス層の責務にした。IO完了時にイベント発行する必要があるからというのと、 どこからイベント発行されるのか把握しにくいのがイヤなのと同様に、IO 処理もどこから行われるのかわからないのはイヤだし、model にやらせるべきじゃないと思っている。

処理の流れ

  1. ユーザーの入力
  2. DOMがイベントを発行
  3. コントローラーが購読して、サービスにモデルを渡して処理を委譲
  4. サービスが処理をおこない、イベントを発行
  5. コントローラーがサービスのイベントを購読して、ビューを変更

コントローラーがビューを変更しているけど、Backbone.View みたいな感じ。 別にビューをつくってサービスを購読するようにすると良いのかもしれない。 モデルは基本的にデータを持つ/持っているデータを操作するだけになる。

まとめ

コントローラー同士の処理の流れがわかりやすくなって、規模が大きくなったときに全体が把握しやすい。 細かいところでダメなところはけっこうあって、例えばコントローラーがサービスに依存しているので、コンポーネント化がしにくそうとか、ポリモーフィズムがうまくない(model.fetch だとクラスごとの処理が行えるが、service.fetch(model) とする場合には fetch の中で分岐する必要がある)とか。コンポーネント化したい部分はサービスを使わずにDOMのカスタムイベントで完結するとよさそう。実際に jquery ui は自然に組み込めた。

Backbone.Event を拡張してここで言うサービスを実装すればいいので、別にBackboneを辞める必要はなかったかも。

あとこのやり方は実は URL を変更するやり方と同じで、Backbone.Router が URL を扱うサービスになってる。URLを扱う以外の部分もサービスにしたというだけの話だった。 Angular.js には Service というのがあるようなので勉強してみたい。 ネイティブのGUIアプリ作ったことないのでそっちも勉強してみたい。

ソースは github においてあります


2ch ビューワーとして

  • レスのポップアップ周りは適当なのでまだちゃんと作らないと見づらいし、タブ表示の切り替えがモッサリしていたりして実用するにはもう少しがんばる必要がありそう。
  • 最初は拡張じゃなくて Chrome packaged apps (Chrome apps になる前のやつ)で作っていたのだけれど、なぜか manifest が通らなくて拡張に変更した。packaged apps は古いからもう新規に登録できないとかなのだろうか??誰か知っている人いたら教えてほしい。

【はてなスタッフ非公式ブログバトン】TypeScript のすゝめ

id:hitode909 さんからバトンが回ってきたので、TypeScript いいよね、っていう話をする。

TypeScript

TypeScriptC# を作ってる人が作ってるという altJS 。言語の特徴とかは wikipedia 、勉強するときは TypeScript クイックガイド を見るのが良いと思う。JavaScript 書ける人にとっては学習コストは比較的低めと思う。

簡単にいうと ECMAScript 6 を一部先取りして型アノテーションをプラスしたもの。自分が altJS 選ぶ時に基準にしたのが以下の二つで

  • 静的な型チェックがある
  • 導入・離脱が容易

この二つの要求にもっとも合致するのが TypeScript だった。特に後者は「プログラミング言語」として語られると軽視されがちだけど実用上とても重要だと思う。

静的な型チェックのうれしさ

特に説明しなくてもいいかもしれない。「機械が型をチェックしてくれる」のがうれしくない人はいないはず。

TypeScript の場合はけっきょく JavaScript になってしまうので、静的型付と言ってもパフォーマンスが上がったりはしないけど、特にリファクタリングのときに型チェックがあるのとないのとでは開発効率が全然違う。

適切に(この"適切"が議論の種なのだけども)型がついていれば、例えばリネームとかメソッドを別のクラスに移動するとかは安心してできる。IDE使ってれば機械でできるかもしれない。

JavaScript は柔軟な言語で、ブラウザですぐ実行できるし簡単にUIがいじれる(目に見える)ので、とりあえず動かすっていうところまですぐに辿り着けてやる気がでる。あふれるやる気に身を任せてコード書く

_人人人人人人人人人人_
> 突然のクソコード <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

とすぐに汚くなるのでリファクタリングが重要だし、それがやりやすいのはとてもうれしい。

オプショナルな型アノテーション

TypeScript の型アノテーションの良いところはそれがオプショナルで、柔軟に型付けすることができることだと思う。 型アノテーションをつけないこともできるし、型チェックが通らなくてもコンパイルはできるので、静的型付言語はガシガシコード書けなくて開発効率下がるという人も安心して開発できる。

もちろんこれはデメリットでもあって、全然型安全じゃないし安心できないっていう人もいると思う。この辺は結局ガシガシ書きたい気持ちと安心したい気持ちのバランスをどうとるのかっていう問題になる。

TypeScript はその辺り開発者が幅をもって選択することが出来て、例えば

  • ライブラリはきっちり型をつける
  • アプリケーションのModel層ではしっかり型を付け、Controller層ではすこし緩くする

っていうこともできる。

個人的にはメソッド・関数の引数・返り値とクラスのフィールドにちゃんと型を付ければ(ある程度型推論もあるので)アプリケーションのコードではけっこう事足りるし、書きたい気持ちも阻害されない。 クライアント側は比較的バグっても致命的になりにくいし、厳格に型を付けるのはコストが高いかなと思う。 総じてバランスがよいと思う。

導入・離脱の容易さ

新しくプロジェクト作るぜっていうときは良いけど、現実には既に書かれた大量の JavaScript コードがある。ほらあなたの目の前にも。ロジックは同じとはいえこれを別の言語に全部かきかえるっていうのは結構骨の折れる作業で、やりたくない。ファイル単位でaltJS導入するっていうのも出来るだろうけど、それもあんまりやりたくない。

TypeScript は JavaScript のスーパーセットなので、極端な話、拡張子を js から ts に変更してコンパイラに渡してやればコンパイルできる。(エラーいっぱい出るけど。)

$ mv hoge.js hoge.ts
$ tsc hoge.ts

これで一応導入完了。あとはコツコツ型を付けたりオレオレクラスを TypeScript のクラスに書き換えたりアロー関数式つかったりしていけばいい。そもそもロジックの部分はほとんどそのままなので、書き換える部分が少ない。 前に Backbone で書いたコードを TypeScript に書き直したら、手で愚直にやっても一日 1000 行くらい書き直せた。いいツールがあればもっとさくっと行くと思う。

既存の JavaScript のライブラリもそのままつかえる。そのライブラリが TypeScript で書かれてなくても、型を別に定義することができる。GitHub に 型定義ファイル集めるリポジトリ があって、有名なライブラリはけっこうそろっている。型定義ファイルをローカルにインストールする tsd というコマンドもあって tsd install jquery のように使える。 ここに型定義がなかったときは自分で型定義するか、そのライブラリの型チェックを諦めてもいい。型定義のしかたは上のクイックガイドを見てもらうとして、無視する場合は

declare var Hoge:any;

とかすると、Hoge に何してもコンパイルで怒られることはなくなる(かわりに型の恩恵もうけられない)

ただし、TypeScript の機能とライブラリが競合する場合(独自にクラスを実装をしているライブラリとか)には工夫が必要。Backbone.View の場合は以下の感じで動いたけど、それぞれどんな実装かによって対応を変える必要がある。この辺は面倒。

class Hoge extends Backbone.View {
    model: Model.Hoge;
    template: (...args: any[]) => string;
    constructor (options?: Backbone.ViewOptions) {
        this.tagName = 'li';
        this.className = 'class';
        this.events = {
            'click .item' : (event) => this.onClickItem(event),
        };
        super(options);
        this.template = JST['thread-tab-item'];
        this.listenTo(this.model, 'change', this.onChangeModel);
    }
    ...
}

離脱

TypeScript をおすすめする理由として、TypeScript は辞めやすいっていうのを挙げるのもどうかと思うけど、大事なことだと思う。

TypeScript が吐き出す JavaScript はかなり綺麗で、自分はデバッグするときも特に SourceMap とか使ってない。 Playground で試すことが出来るので試してみてほしい。 あとコンパイラ-c オプションを渡すとコメントも全て残すことができる。なので TypeScript 廃れたらコンパイルされた JS を保守していけば良い。

そもそも TypeScript の仕様のほとんどが ECMA6 のものなので、そのうち JavaScript に実装される(ハズ...)そうなったら TypeScript は型アノテーションを処理するだけになるので、吐き出される JS との差分もより少なくなる

その他メリット

class, module, アロー関数式とかあって便利なのは他の altJS とだいたい一緒だと思う。 その他、静的に型がついてるのでコード補完が効くとかあると思うんだけどちょっと手を出せていない。

デメリット

JavaScript そのまま使うのにくらべると、コンパイルの手間と学習コスト、廃れるリスクっていうデメリットがある。

他の altJS(あまり知らないけど) と比べると、Coffee ほどタイプ数減らない、Haxe ほど型が強力じゃない、JavaScript の悪いところが悪いところのままになってたりする、主な IDE が VisualStudio で Mac に入れられない(WebStorm が使える?)とかが挙げられる。

あと吐き出されるJSの改行コードがCR+LFになる。オプションとか見つからなかった。

まとめ

JavaScript は altJS というより betterJS という感じで、コンパイルの手間が許容できるなら、JavaScript より TypeScript 使ったほうがいい。 ブラウザのUIを書く用途では TypeScript くらいの型付けがバランスがよい。 かなり贔屓目になっているけど、業務に使うなら CoffeeScript よりは TypeScript だと思う。理由は

  • CoffeeScript で得られるメリットはだいたい TypeScript でも得られる(Lint, クラスベース, アロー関数式, SourceMap)
  • JavaScript 互換なので比較的 学習・導入コストが低く、廃れたときのリスクも低い
  • 型チェックの恩恵を受けられる

(とはいえ結局はケースバイケースで、Coffee がいいプロジェクトも当然あると思う。とか防衛的なコメントつけておく)

次回

明日は id:tarao さんが dart か Haxe か何かについておもしろい話をしてくれます

こっち -> http://d.hatena.ne.jp/tarao/20131212/1386859873

ImageMagick(PerlMagick) でモノクロビットマップを作る

あんまりやる機会はなさそうだけど、メモ

convert image.png -colors 2 +dither -type bilevel image.bmp

こうやるらしい。 最初 -type Bilevel だけでやっていたら、色は確かに減るんだけどサイズは減らない(24bppになっていた)。逆に -colors 2 だけでもだめで、このときは 32bpp にむしろなってそうだった。両方つけると1bppのbmpが出来上がる。

これを PerlMagick でやろうとすると

$image->Read($input_name);
$image->Set(type => 'Bilevel');
$image->Quantize(colors => 2);
$image->Write($output_name);

こうなる。