読者です 読者をやめる 読者になる 読者になる

GitHub の Issue リンクをリッチにする Chrome 拡張

どうもこんにちは、今日は便利な Chrome 拡張のご紹介です。

f:id:hatz48:20170323122302p:plain

これは GitHub 上の issue link (#666 とかっていうやつ)を情報量の多い、見やすいバッジに変換してくれる拡張です。 親イシューにいくつかイシューをまとめて管理したいときに、イシューの状態(open/close や assignee )が一目でわかってとても便利。

Chrome 拡張の permission の関係で GitHub 用と GitHub Enterprise 用の二つをご用意しております

使い方は簡単、上のリンクから使いたいほうの拡張をインストールして、設定を入力するのみ。

  • GitHub の public repository で利用する場合は、設定は不要です
  • GitHub の private repository で利用する場合は、GitHub 用の拡張をインストールしたのち、拡張機能のオプションで personal access token を入力して下さい
  • GItHub Enterprise で利用する場合は、GitHub Enterprise 用の拡張をインストールしたのち、拡張機能のオプションで origin と personal access token を入力して下さい

※ personal access token のスコープは repo のみで大丈夫です

これだけ。是非ご利用ください。

このバッジのデザインに見覚えのあるあなた、きっと id:motemen さんのファンですね。 原作はこちらになります。

motemen.hatenablog.com

TypeScript だけで Web アプリケーションを作る

typescript javascript node.js

はてなでアプリケーションエンジニアをしている id:hatz48 です。この記事は はてなデベロッパーアドベントカレンダー の 13 日目です。 昨日は id:dekokun による

dekotech.dekokun.info

でした。私は去年は

developer.hatenastaff.com こんな記事を書いていました。

今年は、はてなのサービス開発合宿で TypeScript のみを使ってアプリケーション開発をした話をします。

はてなのサービス開発合宿

はてなのサービス開発合宿については はてなスタッフアドベントカレンダー一日目で紹介されています。三日間、いつもとは違うチームで、通常業務から離れ、集中して開発するというものです。

開発自体は、実は三日間で完成はしなかったのでいまでも隙間の時間で開発を続けています。とある社内システムに、ちゃんとしたアクセス制御をいれて実装しなおそうとしています。再実装も一つの目標ですが、もう一つの目標は「TypeScript のみでアプリケーションを構築する」ことでした。

なぜ?

私は 2012 年にはてなに入社し、そこから三年間 Perl を使ってアプリケーション開発をしていました。そして今年の四月からは Scala をメインに、Perl も書いています。ちなみに Perl の前は Java を書いていました。

Scala を書いているうちに、やはり静的型による恩恵は大きいと再実感するようになりました。動的片付け言語にも良いところはたくさんあるのですが、例えば Perl で大きなコードのリファクタリングをするのは困難で、苦痛を伴います。 Scala の様に静的に型のついている言語であれば、たとえ影響範囲がアプリケーション全域にわたるようなリファクタリングでも IDE の支援を借りて比較的短時間で済ますことが出来ます。コンパイルが通り、テストが通ればだいたい OK です。

しかしすべての場面で厳格に静的型チェックを行う言語でコードを書くべきかというとそれは yes とは言いがたいです。 個人的には、まだ当たるかどうかわからない新規サービスをつくるのに Scala で書き始めるのはちょっとオーバーキルだなと感じます。

後述する async/await 機能が TypeScript で使えるようになったこともあり、現在 Perl を使っている場面において TypeScript を使うことはできないだろうかと考えるようになりました。

TypeScript

最初に言っておくと私は TypeScript が好きなのでこの記事では TypeScript に対して贔屓目に見ている部分があるかもしれません。また、サーバー上での実行環境は 現実的には node.js です。以下 「TypeScript は」という文脈で node.js の特徴をあげることがあるかもしれません。 TypeScript は型チェックをすることができ、さぼることもでき、ブラウザでも動く。Web アプリケーションを作るには実用的と感じています。

TypeScript ( node.js ) と Perl と比較してメリット/デメリットを想像してみます。

  • 言語
    • 型をつけることができる
    • 開発が活発である
  • 開発支援
    • Visual Studio Code などの IDE があり、型情報に基づいた支援が受けられる
    • IDE に組み込みのものや node-inspector などの debugger が利用できる
  • 処理系
  • その他
    • クライアントとコードを共有することができる
  • 学習

対して考えられるデメリットをあげます

  • 言語
    • 開発が速いため自分たちのコードが陳腐化するのがはやい
  • 開発・保守
  • 開発・運用
    • 例外時にまともな stack trace をとることができない
  • 運用
    • 例外をハンドルしそこなうとプロセスが終了してしまう

まだ運用知見のない状態ですので「いやいや○○が大変なんだよ」という意見があったら是非教えてください。

async/await によるコールバック地獄の解消

さて上に上げたデメリットですが、開発からみた一番大きな問題はノンブロッキング IO に起因するコードの複雑化ではないでしょうか。 いわゆるコールバック地獄が解決できないのであれば、上にあげた様々なメリットが得られるとしても採用するには抵抗があります。 node.js の採用事例を見ても WebSocket でクライアントとやりとりする部分や、シンプルだけど高速に動作する API サーバーのみといった用途が多いのではないでしょうか。

コールバック地獄を解消出来る仕組みとして、 Promise があります。シンプルなインターフェースでノンブロッキング IO を直列・並行に繋ぐことができます。これはとてもうまく動作するのですが、new Promise(function(resolve, reject) { ... } ) then(function(value) { ... }) といった boilerplate なコードがアプリケーション内に多く出てきてしまいます。またブロッキング IO なコードとは書き方も変わりますので学習コストも必要になります。

ありがたいことに、これについては async/await の機能によって解消することが出来ます。 async/await は TypeScript というより ECMAScript仕様策定中 の機能ですが、TypeScript や Babel などでは既に利用することができます。

今回 TypeScript を使おうと思ったきっかけでもありますので、コード例を紹介しようとおもいます。雰囲気をつかんでいただけたら幸いです。

Express っぽいもののコントローラーのコードだとおもってください。指定された id の情報を更新するために

  • 指定された id が存在するかを確かめる
  • 指定された id の情報を更新する
  • 更新後の情報を取得し直して返却する

これをコールバックスタイルで書くと

app.put('/something/:id', async (req, res) => {
    var id = req.params.id;
    var someData = req.body.someData;
    findSomething(id, (someThing) => {
        if (!someThing) { res.sendStatus(404); return; }
        updateSomething(id, someData, () => {
            findSomething(id, (updated) => {
                res.json({ somthing: updated });
            });
        });
    })
});

このように、IO の回数だけネストが深くなっていきます。someThing を更新するための権限管理のために DB アクセスが必要になったりするとさらに複雑になります。PerlRubyPython などで書くコードとだいぶ違いますね。

今度は async/await を使ってみましょう

app.put('/something/:id', async (req, res) => {
    var id = req.params.id;
    var someData = req.body.someData;
    var someThing = await findSomething(id);
    if (!someThing) { res.sendStatus(404); return; }

    await updateSomething(id, someData);

    var updated = await findSomething(id);

    res.json({ somthing: updated });
});

まるでブロッキング IO をしているかのよう!違うのは async function await というキーワードがあることくらいです。

今度は実際に Perl のコードを移植した例です。hatena/Plack-Middleware-HatenaOAuth · GitHub これははてなで公開している Perl モジュールです。Hatena OAuth で認証を行うエンドポイントをマウントしてくれるというものです。これを TypeScript で Express 用に書き直したのが hatz48/express-oauth-hatena · GitHub です。実際に見比べてみてほしいのは

ここです。 言語とインターフェースの違いはあるもののほぼ同様のコードなのがわかると思います。async/await はまだ正式な仕様が決定していませんが、今後の JavaScript での開発を大きく変える機能だと思います。

これで上のデメリットのうち、大きな一つが解消できそうです。(どうでしょう、 node.js で開発をするのがかなり現実的に思えてきませんか?)

やってみて

ここまでの内容を想定して実際に開発合宿に挑みました。 ここからは実際に TypeScript のみでサーバーアプリケーションを書いてみて、よかった点/キビシイ点/コネタ などを紹介していきます

(Good) 型が付けられる

やはり何と言ってもこれに尽きます。 これは私の場合ですが、動的言語を書くときは脳内で型を合わせながら書いています。自分にミスって型が合っていなければエラーになるわけですし、そのエラーがどこに起因するのかはコードを辿ってみなければわかりません。 静的な型が付いている場合「こういうロジック」というのをざっと書いていってあとはコンパイルエラーをとればだいたい動くので、動的片付け言語よりも結果的に速くコードを書くことが出来たと思います。

型をつけなくてもいい

これは Good とも Bad とも言えるのですが。 合宿は三日間で、実際に作業ができるのは二日間くらいのスケジュールでした。コンパイルを通すためにあまり時間をとられると、それだけで合宿が終わってしまいます。なので合宿期間中は「こまったら any」というスタンスでやっていました。上で「結果的に速く」できたのは any を許容したからだとも言えます。

ただ、とにかく動けばいい、そういう感じで動的言語で開発したアプリケーションはのちのち保守が難しくなっていきます。しかし TypeScript は後から型がつけられるのでその点は安心です。any と書いたところに合宿後にちょっとずつ型をつけていって「ここ無茶苦茶なんで直します」というようなフローが出来ます

(Tips) server/client でのコードの共有

モデル(というと人によって捉えるものが違も気しますが、いわゆるMVCアークテクチャにおけるModelのことです)クラスのコードを server/client で共有しています。モデルのフィールドの値から状態を計算する、と言ったメソッドが server/client で再実装する必要がなく、故に実装の乖離もないため便利です。しかし実装の共有以上に便利なのが、型を共有できることです。サーバー側で返した JSON をクライアントのコードで受け取るときは

// model
interface IUser { ... }
class User implements IUser { ... }

// server
app.get('...', async function(req, res) {
    ...
    const user: User = ...
    ...
    res.json({ user: user })
})

// client
axios.get('...').then(function(res) {
    const user: IUser = res.data.user
})

このようにすることでクライアント側でも実態と乖離なく型をつけることが出来ます。これは本当に便利です。

(Bad) await を書き忘れる

今回一緒に開発してくれたメンバーは JavaScript や TypeScript を書いてはいても、TypeScript でついこの間まで experimental であった async/await を使ったコードを書いたことがある人はいませんでした。そのため await を書き忘れて予期せぬ動作になったりということが何度かありました。ただ、型を確認すればすぐにわかるのであまり深くはまることはなかったと思います。あとそのうち慣れます。

逆に、ブロッキングIO のコードを書いたことがある人であれば async/await はスムーズに導入できる、と感じられました

(Bad) UUID を DB から取得・・・できない?

ID を生成するのに、MySQL の UUID_SHORT 関数を使おうとしていました。

SELECT UUID_SHORT();

よくやりますよね。これで取得した ID をレコードのプライマリキーとして使おうとしたら、なぜか Dupulication エラーが多発するという事態に見舞われました。不思議に思ってこの取得した ID をコード上で出力してみたところ、なんと同じ UUID が複数回表示されるではありませんか!なにが UUID だ!!! ・・・と思っていたのですが、チームのインフラ担当が調べてくれたところどうやら MySQL はちゃんと UUID を返している様子(そりゃそうですよね) わかってみれば初歩的なことだったのですが、 JavaScript の number 64bit double であり整数としては 53 bit までしか精度がでなく、取得した ID は number に変換された時点で丸め誤差が出ていた、ということでした。普段つかっている Perl では遭遇しない出来事だったため面食らいました。(これは TypeScript は関係ないですね)

これは mysql のモジュールに

supportBigNumbers: true,
bigNumberStrings: true

これらのオプションをつけ、数値に変換しないことで対応できそうでした。 ただこの時点で ID を number として扱うコードが書かれてしまっており、開発開始時に「ID は string じゃなくていいの」といわれ「number でいいんじゃん?」と答えてしまっていた私はこの件で最後まで詰られることに・・・

(Bad) エラーが貧弱

これは想定されるデメリットとして上にあげましたが、やはり厳しいという感想になりました。 基本的にスタックトレースがとれないので、エラーが起きていても直接どこが悪いのかを特定することが出来ません。

丁寧に例外をハンドルすることで擬似的に stack trace のようなものを出すことは出来そうです。

async function a () {
    try {
        await b()
    } catch(e) {
        throw new Error(e.stack);
    }
}
async function b () {
    try {
        await c()
    } catch(e) {
        throw new Error(e.stack);
    }
}
async function c () {
    throw new Error('I am C')
}
a().catch((e) => {
    console.log(e.stack)
})

このように自分で stack trace を継ぎ足してあげれば、見辛くはあるものの必要な情報を得ることができました。 もしうまい解決方法があるのなら教えていただきたいです。

まとめ

TypeScript / node.js の検証については、まだ「まとめ」ることは出来ていません。個人的にはかなり可能性を感じておりいつかは使いたいと思っていますが、stack trace の件は解決しなければならないし、開発は良くても運用面で問題があるかもしれません。ただ、(TypeScript に取り入れられる) ECMAScript / (実質的な実行環境である) node.js の界隈は非常に活発になってきていて、今ある問題も解決されるのでは、という希望が持てます。 また、今回の記事とは関係ありませんが JavaScript が動作する環境が多くなってきました。ブラウザ、サーバー、デスクトップ、スマートフォンアプリ、JS board・・・活用機会が多いという意味で JavaScript で書かれたコードは、いまやもっとも資産価値の高いコードと言えるかもしれません。

2009 年に node.js が出てきて世を賑わせていたとき「JavaScript はいいけどあんなコールバックだらけでやってはいけない」と感じた人、動的片付け言語のスピード感は好きだけど型がなくてつらくなってい人、今こそ TypeScript + node.js という環境を検討してみてはいかがでしょうか。

この記事のまとめ

  • 開発合宿で TypeScript の実用性を検証しました。一応断っておきますが「はてなでは TypeScript + node.js の本番導入を検討している」という話ではありません。
  • 今回はアプリケーション開発面をまとめました。オペレーション面についてはもし知見が得られれば書くかもしれません。むしろ知りたい。

おわりに

はてなでは慎重に技術検証を行いつつ、社内の次世代の標準を模索していけるエンジニアを募集しています

hatenacorp.jp

はてなデベロッパアドベントカレンダー、明日は id:nobuoka です。お楽しみに!

ES7 async/await でのエラーハンドリング

javascript typescript

async/await は ES7 の機能で、非同期処理を記述する上で非常に便利な機能である(仕様は安定していないと思う) まだ実装している処理系はないと思うが、babel などの transpiler をつかうと利用できる

async/await をつかうと非同期処理を以下のように書くことができる

function a() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() { resolve('hello, ') }, 0)
  })
}

async function b() {
  var value = await a()
  return value + 'world'
}

async function c() {
  var value = await b()
  console.log(value)
  return 'this is async world'
}

c().then(function(val) { console.log(val) })

上のコードを babel-node などで実行すると hallo, world に続いて this is async world が表示される。

async function は Promise を返し、 await キーワードにより Promise が解決されるのを待つ(かのように見せる)ことができるので、async/await をつかうと boilerplate なコードを減らして非同期処理を扱うことができる。

TypeScript 実装での async/await の動作

現在の TypeScript の実装から await の動作をも少し詳しく見てみる(仕様は確認していない。。) 上のコードを TypeScript 1.6 で --target ES6 --experimentalAsyncFunctions オプションをつけてコンパイルすると、以下のようなコードが出力される

// runtime
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promise, generator) {
    return new Promise(function (resolve, reject) {
        generator = generator.call(thisArg, _arguments);
        function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function (resolve) { resolve(value); }); }
        function onfulfill(value) { try { step("next", value); } catch (e) { reject(e); } }
        function onreject(value) { try { step("throw", value); } catch (e) { reject(e); } }
        function step(verb, value) {
            var result = generator[verb](value);
            result.done
              ? resolve(result.value)
              : cast(result.value).then(onfulfill, onreject);
        }
        step("next", void 0);
    });
};
// 上の async function b はこのように展開される
function b() {
    return __awaiter(this, void 0, Promise, function* () { // --- (2)
        var val = yield a();
        return val + 'world';
    });
}
  • async function は必ず Promise オブジェクトを返す。その Promise オブジェクトは
    • async function が return した場合、その値で resolve する
    • async function の中で throw されると、その値で reject する(例外は catch される)
      • 特別な場合として、一度も await されてない状態で例外が投げられると、その例外は補足されることなくそのまま throw される --- (※)
  • await は何らかの値をとり、その値が Promise でなければ、そのままその値を返す
    • 正確には、Promise でなければ Promise.resolve でラップされる
  • await に渡した値が Promise であれば
    • Promise が resolve した場合 > T (resolve した値) を返す
    • Promise が reject した場合 > reject した値をもって (generator を) throw する

となっている。(余談だが、__awaiter のコードは tj/co · GitHub とほぼ同様である)

async/await のエラー処理

async/await をつかったときのエラーの流れを見てみる。以下のコードは

function a() { return new Promise(resolve) { setTimeout(function() { resolve('hoge') }, 0) } }
async function b() {
  console.log(await a())
  throw 'hello, '
}
async function c() {
  try {
    await b() // --- (1)
  } catch(e) {  // --- (2)
    throw e + 'error'
  }
}
async function d() {
  var val = await c()
  console.log("control don't reach here")
}
d().catch(function(val) { console.log(val) }) // --- (3)
  1. a で発生したエラーは async function 自体が捕まえ、自身が返す Promise を reject する
  2. (1) の await は Promise が reject したのを受けて、例外を投げるが、すぐに (2) で補足される
  3. エラー内容を更新して再度例外を投げるが、b が async function であるためやはり補足され、b が返す Promise を reject する
  4. c でまた同様に、 catch -> reject -> throw -> catch が行われ、(3) で出力される

となる。 内部的には Promise のエラー処理が走っているのだが、async function より先のコードでは .then .catch はいっさい出てこない。 エラー処理の書き方が同期プログラミングに置ける try ~ catch と同様になるので、Promise のエラー処理に手こずった人でも問題なくなっている

まとめ

ES7 async/await でのエラー処理(throw -> catch -> reject -> throw -> ...)の流れを追った。このフローは try ~ catch が多発してあまりパフォーマンスが欲はなさそうだけど、実際に ES7 として実装されるときはもっと効率的なものになるかもしれない(ので今 TypsScript がエミュレートしているコードを見ても仕方ないかもしれない、そもそも async/await の仕様自体まだ固まってない) ただ、今の時点でこれまでよりずっと直感的な記述が可能になっているし、この後そこまで大きな変更はない?かもしれない。tranpiler で使うのもいいけどはやく普通に使いたいですね。

DOM をストーカーする

dom mutationobserver tumblr

新年あけましておめでとうございます。

唐突だけど、 tumlbr の検索画面でスクロールしていくと、画面から見えなくなった部分の画像は DOM から remove されていく。 コンソールからdocument.querySelectorAll('img').length とかするとそれを確認すること出来る。

たぶん描画の負荷を減らすためなのかなーと思うけど(DOM書き換えるより display:none した方がいいような気もする?)、とにかく DOM 上からいなくなってしまうので下までスクロールして表示された画像の url を全部集めるとかが出来ない。 そこで DOM をストーキングしてみることにした。

MutationObserver

昔は DOMNodeInserted というイベントをハンドルするという方法があったようだけど、今は非推奨のようだ。MutationObserver はより新しい機能で、 DOM の挿入だけでなく属性の変更や子孫要素の挿入なども監視することが出来る。Chrome 拡張のような環境ではこちらを使っておけば良さそう。使い方はリンク先を参照。

Let's stalk Tumblr.

上にも書いたように tumblr 検索での img タグは見えなくなった時点で DOM から除去されていく。ひとしきり autopager で表示した後にページ内の画像 URL を集めるためには、消え行く img タグの url を消えない要素の data-attribute に保存してあげれば良さそうだ.

function helper(nodeList) {
    for (var i = 0; i < nodeList.length; i++) {
        var article = nodeList[i];
        var divs = article.querySelectorAll('[data-lightbox]');
        var urls = $(divs).map(function (idx, div) {
            return JSON.parse(div.getAttribute('data-lightbox'));
        }).toArray();
        article.setAttribute('data-image-urls', JSON.stringify(urls));
    }
}
var mutationObserver = new MutationObserver(function (records) {
    records.forEach(function (record) {
        if (!$(record.target).is('#search_posts'))
            return;
        helper(record.addedNodes);
    });
});
var container = document.querySelector('#search_posts');
mutationObserver.observe(container, { childList: true, subtree: true });
var articles = container.querySelectorAll('article');
helper(articles);

こんな感じで実現できる. #search_posts に検索結果が挿入されるので、ページ読み込み時と autopager による要素の追加時に必要な URL の保存処理を行う. やってる処理自体は、子孫要素からそれっぽい url を拾ってちょうど良さそうな先祖要素の data-attribute に追加するだけ。 tumblr の仕様がかわればすぐに使えなくなるようなものだけど、とりあえずストーキングは完了.

Chrome extension

以前つくった Chrome拡張 に上の機能をつけてみた. 個別の Tumblr サイトからのダウンロードはうまくいかないが Tumblr 検索から zip でガッとダウンロード出来る. 年末のイベントの画像収集などにお使いください.

Golang の websocket サンプルを書き直してみた

golang websocket

Golang の勉強がてらちょっとしたツールを作ろうと思って、まず websocket サーバーを書いてみた。 書いてみたと言っても go の websocket chat は golang-samples/websocket · GitHub ここにサンプルがあって、これをそのまま使っても良かったのだけれどいくつか気になったのでよりシンプルに書き直してみた。

気になったのは二つあって、一個は client id が競合しそうに見えた点。 https://github.com/golang-samples/websocket/blob/master/websocket-chat/src/chat/client.go#L35 ここで maxId をインクリメントしている. この NewClient 関数は https://github.com/golang-samples/websocket/blob/master/websocket-chat/src/chat/client.go#L35 ここで呼ばれていて、この onConnected はハンドラとして渡されているので GOMAXPROCS が 1 じゃなければ同時に実行されることがありそうとおもった。インクリメント処理はアトミックじゃない(と思ってる)ので、ここでインクリメントすると maxId を破壊することがありそう?? こういう時例えば Java の Thread とかだと maxId を synchronized で排他制御するのが考えられるけど、go では channel を使って制御するのがオシャレなようだ。

func (server *Server) WebsocketHandler() websocket.Handler {
    return websocket.Handler(func (ws *websocket.Conn) {
        client := NewClient(ws, server.removeClientCh, server.messageCh)
        server.addClientCh <- client
        client.Start()
    })
}

競合を避けるために、server に client 追加用の channel(addClientCh) を持たせ、websocket で接続されたらサーバーオブジェクトに client を渡してその先で client id を入れてもらうようにした。この addClientCh を liesten してるのは単一の goroutine なので競合することはない(が、詰まる可能性はある??)

気になったもう一点は、クライアントをハンドルする goroutine が複数あること。client (クライアント、ブラウザとかをハンドリングするサーバー側のオブジェクト)はクライアントからのメッセージを listen しなくちゃいけないのだけれど、チャットなので他のクライアントからのメッセージを受け取って自身が担当するクライアントに投げないといけない。そのため client はクライアント(=ブラウザ)だけでなく server も listen している。たぶん下の図ような感じになっている

f:id:hatz48:20141221192131p:plain

黒線は生成線で、グレーの線が listen 関係を表している。白い箱が goroutine で色ついてるのがオブジェクトとか。request handler が点線になっているのは揮発性(すぐ終了する)goroutine を表現してみた。 これはパフォーマンス的にはいいのかもしれない(?)けどちょっと複雑になって読みづらかった。クライアント切断時に goroutine を殺し忘れないように終了処理もちゃんとしないといけない。以下のように、client を担当する goroutine が一つになっている方がわかりやすかった。

f:id:hatz48:20141221192136p:plain

client から server を listen する goroutine がいなくなったので、message を送る際に server が client のメソッドを直接呼び出している。そのまま呼び出すと server の goroutine がブロックするのでメソッド呼び出すだけの goroutine を生成してる。これだと client の終了処理も goroutine 一つ潰すだけで済む。

まとめ

Golang の勉強がてら websocket server 書き直してみた。 websocket は状態を持つので常駐する goroutine が複数存在することになるのだけど、なるべく簡素化したい。 とくに双方向に listen し合うと複雑になるなーと感じたのでそうならないように書いてみた。

とここまで書いてはみたものの Golang 初心者なので理解が間違ってるのかもしれない。できれば有益なマサカリをゲットしたい。