はてなブックマークコメント閲覧専用の新しいFirefoxブラウザ拡張機能を公開しました

タイトルはオマージュです。 はてなブックマークのコメント(簡易)閲覧専用のFirefoxブラウザ拡張を公開しました。

gyazo.com

先日 公式な新しいはてなブックマーク拡張 が公開されました。その告知を読んでいて知ったのですが、Firefoxではブクマ拡張が新規インストールできなくなっているようです。 さっとブコメ見たいときってやっぱりあると思うので作ってみました。簡易版なので公式から閲覧用の拡張が公開されるまでのつなぎとしてご利用ください。

addons.mozilla.org

機能

情報の取得には はてなブックマークエントリー情報取得API を利用しています。

  • ブックマークの表示順は新着順のみ
  • ログイン機能なし
    • なのでマイブックマークからの検索などはないです
  • スターなし

のシンプルなものです。 あとタグの表示の実装を忘れていて今は表示できていないです。使う人がいれば追加するかも。スターは表示したかったのですが、HatenaStar.js を動的に読み込んだ内容に対して発火させるのが難しそうだったのとかなり動作が重たそうだったので、やるならAPIを使って実装する必要がありそうです。あ、あとアイコンとか作れないので設定していないです。

ちなみに全く同じコードが Google Chrome でも動作しますが Google Chrome 版の旧拡張はいまも利用可能なので特にストア登録はしていません。コードは Githubで公開している ので奇特な方はどうぞ。

リモートワークおひるごはん

コロナの影響で基本自宅勤務となり、お昼も家でつくって食べるようになった。買ってきてたべることもあるけど。 お昼休みという限られた時間の中で「作って」「食べて」「片付ける」までをやり、できるだけ満足感をたかめたい。欲をいえば20分くらいは休憩もしたい。

よかったメニュー、いまいちだったメニューの紹介

ステーキ

f:id:hatz48:20200401114325j:plain
ステーキ

スーパーで買ってきた100g 158円くらいのアメリカ牛。お昼休みになる1時間くらいまえに冷蔵庫から出しておくと吉。 昼休みに入ったらドリップを拭いて塩胡椒をし、フライパンで焼く。ステーキソースはモランボンの粗挽き黒胡椒が大変おすすめ

ステーキソース あらびき黒胡椒味 225g|商品情報|モランボン

会社の近くにいきなりステーキがあったが、300gで1500円くらい、こちらは300gで500円くらいである。味は大体同じなのでは・・・

モランボンのソースが優秀なのでとても美味しいし、時間もかからないので食後にゆっくりすることもできる。ニンニクの匂いがついてしまっても、zoomは匂いを伝えないので問題ない。ただし午後、部屋に牛肉の匂いが残る。

いわしの香草パン粉焼き

f:id:hatz48:20200406115524j:plain
いわしの香草パン粉焼き

小洒落たメニューがでてきた。この機会に初めてつくったけど、小洒落た外観のわりにとても簡単。パン粉にオリーブオイルやら塩やらバジルやらを混ぜて、かけてオーブントースターで焼く。いわしは捌いてあるやつを買ってこよう。 焼いている間に一息つくのもよいだろう。洗い物も面倒なものは特にない。

おすすめではあるけど、パン粉焼きをそんなに頻繁に食べたくならないかも。

ブリカマの塩焼き

f:id:hatz48:20200515160949p:plain
ブリカマ

ブリ美味しいよね・・・。これはカマの部分が150円で売っていたので焼いてみました。 塩をふってグリルにインするだけ。冷凍ご飯をチンしてインスタンスの汁物を用意すればブリカマの塩焼き定食。 楽にできて美味しい、まではいいんだけどグリルの片付けがとても面倒。面倒・・・。

グリルを使う時点でリモートワークおひるごはんには向いていなかった。カマではなくて切り身をかってきて、フライパンで焼けばたぶん満足度たかい。

パスタ

f:id:hatz48:20200515161656j:plain
カルボナーラ

この日は家族もいたので量が多いです。パスタは特にいうことなし、お昼にさっとつくって食べるのにむいています。

五目あんかけ焼きそば

f:id:hatz48:20200508121738j:plain

レシピは味の素のやつ

具だくさん!とろーりあんかけ焼きそばのレシピ・作り方 | 【味の素パーク】の料理・レシピサイト‐レシピ大百科 : 中華蒸しめんや白菜を使った料理

王将的な中華が食べなくなったのでチャレンジ。 麺をパリパリにするのに思ったより時間がかかったので、昼休みにはいったらまず麺を焼き始めるとよいでしょう。その横で野菜を切ってあんかけを作る。 これが素人料理のわりに美味しくできて、夜ご飯に作ってもいいなと思った。

ステーキ焼くだけ、とか適当なパスタを作るよりは時間がかかる。野菜の種類を減らすとちょっと楽になるかも知れない。あんを作るのと麺をやくのを同時にやるので洗うフライパンが複数になる。 野菜を切ったやつを事前に準備しておくといいのかもしれない。

まとめ

出社して外でお昼を食べるとするとだいたい500~1000円くらいはかかってしまう。リモートワークおひるごはんだとステーキでも700円しない(ライスとか含めても)し、魚の塩焼き定食なんか外で食べる金額の半額以下でたべられる。 自分は作るのは楽しめるので作るのに多少手間がかかってもいいけど、片付けにあんまり時間がかかるものは向かない。片付けするより休憩したくなってしまうので、うっかり片付け忘れると退勤後に汚れたキッチンを目にすることになる。とにかく魚焼きグリルは使うべからず。

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 アプリケーションを作る

はてなでアプリケーションエンジニアをしている 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 のコードを移植した例です。GitHub - hatena/Plack-Middleware-HatenaOAuth: Plack: :HatenaOAuth - provide a login endpoint for Hatena OAuth これははてなで公開している Perl モジュールです。Hatena OAuth で認証を行うエンドポイントをマウントしてくれるというものです。これを TypeScript で Express 用に書き直したのが GitHub - hatz48/express-oauth-hatena: express middleware for oauth with Hatena です。実際に見比べてみてほしいのは

ここです。 言語とインターフェースの違いはあるもののほぼ同様のコードなのがわかると思います。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 でのエラーハンドリング

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 のコードは GitHub - tj/co: The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc) とほぼ同様である)

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 で使うのもいいけどはやく普通に使いたいですね。