Quantcast
Channel: from scratch
Viewing all 105 articles
Browse latest View live

2020年の JSConf.jp は中止にします

$
0
0

表題の通り、 2020年の JSConf.jp は中止にします。2021年の開催を目指します。

TC39とのコラボレーションとかも検討していたのですが、楽しみにしていた方々、大変申し訳ございません。

いろいろな葛藤はありましたが、 JSConf.jp の前身になった Node 学園祭のときから「豪華なスピーカーに会えること」「Node.jsやTC39の中にいる人と直接交流できること」「知見が共有できる場所」というのを大事にしてきたのですが、オンライン開催ではその意味が半分以上失われてしまうと思っており、 JSConf.jp は開催しません。

ただし、少し形を変えて、オンラインで開催する小さなカンファレンスは開催するかもしれません(名前は TBDです、 JSHome とかにするかも?)。

よろしくおねがいいたします。


web.dev live 2020 を聴講した

$
0
0

今年はコロナの影響でいろんなイベントがオンラインになったり、中止になったりしてますが、 web.dev live 2020 が7月初頭にやっていたので、聴講してきました。

web.dev

その中でも面白かったものについていくつか紹介します。

Day 1

ほぼ Core Web Vitals についての話でした。

以下のトークが面白かったです。

  • What's new in speed tooling
  • Optimize for Core Web Vitals

Core Web Vitals についてはもう既にたくさん資料があると思いますが、一応解説しておきます。

Core Web Vitals

初期表示の新しい指標です。去年くらいからずっと Chrome Dev Summit とかでは言われていて、既にLighthouse をはじめとして、色々なツールでサポートされています。Largest Contentful Paint(初期表示範囲内で一番面積の大きいコンテンツが描画されるまでの時間), First Input Delay(最初に入力可能になるまでの時間), Cumulative Layout Shift (初期表示された後にレイアウトがずれた時のズレ幅)の3つを指標としています。

web.dev

Core Web Vitals 指標
Core Web Vitals 指標

一日目はこの指標の説明とどうやったら良くなるかが非常にたくさん描かれていました。

What's new in speed tooling

www.youtube.com

じゃあ Core Web Vitals はいいとして、『どうやってツールを使ってそれらを把握していくのが良いのか?』というのが語られてました。

ほとんどのツールでもう既にCWVは取れる

上記画像の通り、基本的にはどんなツールでも Core Web Vitals は取得できます。一方で、それぞれ特徴があるので、使い分け方についても解説します。

  • 本番環境では Search Console で警告が来るのでそれのレポートを見て、
  • Page Speed Insights で問題を深掘って発見し、
  • Lighthouse/DevTools でローカルの高速化をはかり、
  • web.devのガイダンスを読み
  • Lighthouse CI でレグレッションを防ぎ、
  • Chrome User Experience Report でフィールドデータも確認する

という流れを継続してやるのが CWV のやり方と流れです。

どうやって改善するか

便利だな、と思ったのは、 Dev Tools 上で Cumulative Layout Shift 違反が起きている箇所を特定できるところですね。これがあれば、CLS違反になっている箇所をどうやって修正するかを確認できるので、便利そうでした。

DevTools 上でCLS違反が起きてる箇所がわかる。

また、2020年までは上述した threshold にするものの、一年おきに水準や方法は見直していくという事でした。

Core Web Vitals 2021
Core Web Vitals 2021

Optimize for Core Web Vitals

www.youtube.com

具体的にどうやって Core Web Vitals を上げていくかというテクニックめいた話でした。 実際に Chloeというファッションブランドのサイトを Core Web Vitals に基づき改善していった話をしてました。

CLS の改善

先程の説明の通り、 Cumulative Layout Shift (CLS) は初期表示の後にレイアウトが変わってしまう問題です。馴染みやすい問題として、「ガタンッ」問題とも呼ばれているものです。ロード前のコンテンツがロードされることで高さが変わったり、幅が変わることでレイアウトが変わってしまう現象を指しています。

見た目にも悪いですし、何より押そうとしたらレイアウトがずれてリンクが押せなくなったりと実害が出る問題です。 このガタンッ問題をどうやって防ぐかという話を解説してくれていました。

最初に画像に関しては width, height を img タグに設定しておき、後から高さや幅が変わらないようにしておきます。 もしもできるなら、画像の背景色に合わせた類似色を表示エリアに事前に表示することも推奨されていました。

Chloeの例

背景色を画像に合わせて塗っておくことで、画像が表示される前であっても見た目やレイアウトが損なわれません。

また、後からコンテンツを表示された領域に挿入することは基本NGです。しかし、広告やプロモーション等でどうしても後から入るケースも考えられます。その時も高さに関しては事前に領域を確保しておきましょう。

広告の入る領域を事前に確保しておく

LCP の改善

Largest Contentful Paint は先程の説明の通り、『初期表示領域における、最大面積を持つコンテンツのロードにかかる時間』を指すのですが、対象は所謂ヒーローイメージであることが多いので、画像のチューニングが紹介されてました。

基本的に画像はCDNを利用し、webpをデフォルトで使ってほしい点、また本質的に必要なサイズの画像しか用意しない事で画像のサイズをカリカリに縮小した状態で表示させることで初期表示の改善に取り組んでいきました。

ヒーローイメージのチューニング

また、Server Push を使った改善も取り組んだことを紹介していました。 Naive な Server Push 自体は Cache Aware ではないため、 HTTP/2 の Server Push をそのまま活用したわけではなく、 Akamaiの Server Push を使ってインテリジェントな Push を実現したという解説がされていました。

blogs.akamai.com

結果として大幅な LCP の改善ができたことを紹介していました。

Server Push Impact

Day 2

ほぼ PWA の話でしたが、筆者が興味を持ったのは以下の2つでした。

  • What’s new in V8
  • Zoom on Web

What's new in V8

www.youtube.com

ほぼほぼ知ってる話でしたが、一応 recap しておきます。

ES 新機能

  • Nullish Coalescing
  • Optional Chaining
  • WeakRef

が追加されました。

Optional Chaining

Nullish Coalescing

WeakRef

それぞれ説明するまでもないかな、と思ったので解説は割愛します。WeakRefのユースケースをイベントリスナーの廃棄に使うっていうケースが紹介されてて、そこだけ面白かったです。

Memory Improvements

メモリの消費量を抑える新しい仕組みについて 2 点解説されていました。

  • Pointer Compression
  • JIT-less mode (V8 Lite)

Pointer Compression

イデア自体は凄くシンプルで、64bit アドレスでメモリアロケート先のポインターを持つのではなく、base address は64bitのアドレスにしつつ、そこからのoffsetを32bitアドレスで持つことで全体的にアドレスの管理をするメモリの消費量を減らすというものです。

Pointer Compression

内容自体は凄くシンプルですが、結果としては、平均で40%程度削減されている結果が見込めるという事でした。

Pointer Compression memory reduction

JIT less mode

V8 は多くのメモリ確保をしていますが、その内訳を見ると、JITに必要なメモリが含まれていることがわかります。

v8 memory の内訳

JITの機能をオフにしてしまえば、いくつかのメモリを持たなくて済むため、メモリ消費量を抑えることができます。

JIT less memory

まだこれは試験的な機能ですが、JIT less にすることでメモリ消費量は10-25%程度の削減が見込めるということでした。

jit-less result

Zoom on Web

www.youtube.com

Zoom の動画チャット機能を Web で提供するため、 Chromeと Zoom で組んで色々な機能のリリースやトライアルをやっているという話でした。

WebAssembly on SIMD, WebTransport, WebCodec の3つの技術を紹介し、それぞれを組み合わせていく話が紹介されていました。

WebTransport は UDPレイヤでのデータ通信を実現し、それを使って動画を転送しつつ、WebCodec で動画のデコードとエンコードを行い描画を行い、 WebAssembly SIMDを使って背景の透過などの動画エフェクトを掛けるという紹介がされていました。

wasm simd

Day 3

Privacy Sandboxセキュリティ 周りの話がほとんどでした。

www.chromium.org

そこまで目新しい話はありませんでしたが、 COOP, COEP の話は抑えておいて良いと思います。

また、 Signin Form Best Practice の話は割と面白かったです。

Cross Origin Opener Policy, Cross Origin Embedder Policy

www.youtube.com

Cross Origin 、つまりクロスドメインにおいて、オリジンの情報をいかに守るか、という話です。

Cross Origin Opener Policy

COOP は通常 window.opener から開き元になる origin の DOM が参照変更できることを避け、 window.opener がただの null になります。

COEP は Cross Origin Resource Policy が設定されていないリソースの読み込みをさせないようにリソースの読み込みポリシーを見て表示可否を制御できるポリシーです。

設定すると許可されていないリソースの読み込みをblockさせることができます。

COEP

Signin form best practice

www.youtube.com

おそらく Web エンジニアが幾度となく作ってきた サインインフォームについての話でした。

web.dev

  • 意味のあるHTMLを使って div とかだけで表現するな。
  • button は button として使うこと、 button を "送信"みたいなボタンにせず、 "Sign-in"などの意味のあるラベルを用意すること
  • 登録用パスワードを2個書かせたり、登録用メールアドレスを2つ書かせるような double up form にするな
  • autofocus を使ってどっから書くか明確にせよ
  • password の type は使うが、見たい時にはマスクが外れるようにせよ
  • input type は email や tel など入力フォームに合わせてちゃんと設定せよ
  • inputmode の利用も考慮すること
  • Sign in button が入力キーボードで見えなくなることを避けよ(押せなくなるから)

sign in ボタンがかぶってる例

  • Sign up のときやパスワード再入力時に name を new-password や current-password でわけて表現すること

などなど、ものすごくたくさん紹介されていた。

password のマスクを外すときは typeをtextにしつつ、aria labelもちゃんと変更する例

Recap

「Web Vitals で初期表示の改善の話がされ、 V8 や WASM の話でアプリケーションの能力を広げ、 Privacy をきちんと保護する」というなんとなく全体的に今推してる機能がわかる話だった。

【翻訳】Date and Times in JavaScript

$
0
0

この記事は littledanから依頼を受けて、翻訳しています。広く Date and Times の JS プロポーザルについて意見がほしいとのことです。 意見は以下の場所にポストできます。

docs.google.com

原文: blogs.igalia.com

tl;dr:

Temporal のプロポーザルについてフィードバックを求めています。 Polyfill を試したら、サーベイの回答を送ってください、ただしまだ本番環境では使わないでもらいたいです。

JavaScriptの Date クラスは壊れています、しかし Breaking the Webを起こさずに修正する方法はありません。風のうわさでは、 Date クラスは 10 日で作られた JS Engine のhackに含まれたもので、 java.util.Dateに基づいたものと言われています。しかも java.util.Dateは摩訶不思議なAPIだったため、 1997 年には deprecated になっていて、今ではより良い APIのものが使われています。 Javaが歩んできた歴史はそのまま JavaScriptも同じ歴史を辿ろうとしています。 built-in のDateクラスはとても使いにくいものとして残り続けることでしょう。

2, 3年前から、とある proposalが開発され始めました。その proposalは新しくグローバルに Temporalという JavaScriptのオブジェクトを生やすものでした。 Temporalは robust で modern な API設計で、日付、時刻、タイムスタンプを扱えるものになっています。さらに Dateでは不可能だったり困難だったこと下記のことが簡単にできるようになっています。

Dateでは不可能だったり困難だったこと:

  • Timezonesをまたいだ日付変更
  • 夏時間を考慮した上での時刻の加算、減算処理
  • 日付だけ、時刻だけでの処理
  • non-Gregorian カレンダーの時間を扱うこと

さらに Temporalは "just works (うまく動く) "なデフォルトの動作を持っていますが、コーナーケース(オーバーフロー、曖昧な時刻の解釈、などなど)に対応するために opt-in での制御ができるように設計されています。この proposal の歴史と「なぜ Date そのものを直せないか」をさらに知りたければ Maggie Pintのブログを読むことをオススメします。

maggiepint.com

Temporalの能力についていくつか例を挙げます。cookbookを見てみてください。 これらの例の多くは legacy Date クラスには困難なものが多いです。特に timezone の取り扱いに関しては Date では難しいものが多いです。 (ただし、このポストでは例を上げてますが、コードに関しては変更される可能性が大いにあります。)

この proposalは現時点では TC39 のプロセス内で Stage 2 になります。我々 Temporal Interest Group はすぐに Stage 3 に持っていこうとしてます。これまで Temporal の機能セットやAPIに関して長い間活動してきました。そしてついに、我々は 必要十分な機能が揃っており、 APIの妥当性を信じる所まで来ています。もちろんホワイトボード上の議論だけで良いAPIの設計はできません。 JavaScript開発者コミュニティに試してもらい、実際に要望を満たしているかを確認する必要があります。

まだまだ初期フェーズなので、 drastic な変更も必要になったらする可能性があります。ただしそれはフィードバックに基づいたものです。だから、どうか Temporal を試した上で我々にフィードバックを送ってください。

Temporal の試し方

カジュアルに試すのであれば、インタラクティブなpromptを使えば簡単です!ブラウザを開いて、 API ドキュメントページに行ってください。APIドキュメントもしくは cookbook のページのどこでも、ブラウザコンソールから Temporal をロードされた状態で試すことが可能です。 Example のコードを実際に実行して試すことが可能です。もしくは RunKit上でも試すことが可能です。

もしくは、あなたは Temporal を使った小さなテストプロジェクトを構築するような踏み込んだ評価に興味があるのかもしれません。あなた方の貴重なプロジェクトの時間を奪うようなことになってしまうかもしれませんが、その方法が最も価値のあるフィードバックになるでしょう。踏み込んだ評価をしてくれることに深く感謝します! npm 上に既に Temporal APIの polyfill をリリースしています。 npm install --save proposal-temporalをあなたのプロジェクトの中で実行すれば、 Temporal をあなたのプロジェクトの中に取り入れることができます (const { Temporal } = require('proposal-temporal');) 。

しかし、アプリケーションの本番環境では polyfill は使わないでください! proposal はまだ Stage 2 です。さらに polyfill は 0.x version です。それは APIが変更される可能性が高いことを示しています。我々はあなたがたからのフィードバックを受けたらそれに基づいてAPIを変更し続けていこうとしています。

フィードバックの送り方

再三申し上げましたが、Temporal を使った上で、あなたからの意見を聞きたいです!一度でもトライしたら、短いサーベイに記入してください。もしよければ、あなたへのコンタクト情報を教えて下さい。教えていただければ、質問にはフォローアップして回答を差し上げます。

我々の issue trackerも気軽に開いてください! 我々はあなたがサーベイに記入したかどうかに関わらず提案を喜んで受け入れます。 issue 内に書かれたフィードバックは閲覧できます、また賛成意見には thumbs-up を送ったり、反対意見には thumbs-down を送ることもできます。

参加していただけるのであれば、謝辞を述べます。私達が受け取った全てのフィードバックは proposal を Stage 3 に上げるため、またブラウザ内で Temporal を利用できるようにするための正しい決定をするのに役に立てていきます。

番外編 (筆者が試したところ)

というわけで、 Temporalを試してみた。まずすごいと思ったのは公式が用意している cookbook が気合が入ってる。

tc39.es

9種類の大きなユースケースを紹介した上で、それぞれのサンプルコードが書かれている。

const future = Temporal.Date.from("2020-08-28")
const today = Temporal.now.date()
const days = future.difference(today).days; // 31

これだけで、日付からの差分が出せるところは嬉しい。あとは細かく、 Calendar形式で1日追加するとかの細かい操作や timezone の変更などの操作が直感的にできる。 <input type=date>な field にも直接入れることができたりと細かいニーズに対応できている。さすが次世代 Date といったところか。

const datePicker = document.getElementById('calendar-input'); // カレンダーのDate Picker
const today = Temporal.now.date(); 
datePicker.max = today; // 現在日付をmaxにして、過去の日付しか選べないようにする。
datePicker.value = today;

若干使いにくそうだな、と思った所も上げる。

使いにくいと思った所その1, epochtime を取る所

// epochsecond を取る時は Date.now() / 1000 だったが、今回からはこうなる。const now = Temporal.now.absolute();
now.getEpochSeconds();

まず、長い。長いけどAPIはわかりやすい。一方で手軽さがないので、 Date.now() / 1000を手癖で覚えてるとそっち使ってしまいそう。ただDate.now()/1000と比較すると、小数点のケアをしなくていいので、楽。これからは Temporal の方法を覚えていきたい。

一方で absolute instance を一度作ってしまうと次から getEpochSeconds()を呼び出しても同じ値になってしまう。一回はこれ絡みの不具合が起きそうな気がする。

const now = Temporal.now.absolute();
setInterval(() => {
  console.log(now.getEpochSeconds()); // 毎回同じ数}, 1000);

最新の値を更新したければ毎回 absolute から取るしか無さそう。

setInterval(() => {const now = Temporal.now.absolute();
  console.log(now.getEpochSeconds()); // 毎回違う数}, 1000);

epoch seconds を取るときはロギングする時とかが多い気がするが、それを毎回 Temporal instance を作っては捨てるようなことしてもパフォーマンス的に問題ないのかとかは一瞬気になったが、実装次第だろう。個人的にはinstanceを作らなくても epochtime くらいならサクッと取れるようにしてほしい。 Temporal.getEpochSeconds()のように。

Temporal.xxx.from でパースできない文字列を食わせると例外がthrowされる。

from関数は本当に便利。日付だけで扱いたければ Temporal.Date.from(2020-01-01)で日付のみの Temporal instance を作ってくれる。また、時刻だけで扱いたければ Temporal.Time.from("12:10")で 12時10分 のTimeにしてくれる。また、 日付のオブジェクトを渡せばそれに合わせて instance を作ってくれる。

ただし、 ISODate 形式(YYYY-MM-DDTHH:mm)に反する文字列だと例外が throw される。また、ありえない日付(13月32日のような)ものを食わせると、object形式とstring形式で動きが異なる。

Temporal.DateTime.from("2019-12-10T12:10") // from 関数は文字列を parse して Temporal instance にしてくれる。便利。
Temporal.DateTime.from({ year: 2019, month: 12, day: 1 }) // こっちもわかりやすい、object としてpropertyつけて渡せば良い。
Temporal.DateTime.from("2019-12-10a12:10") // もしも invalid な文字列だと RangeError: invalid ISO 8601 string: 2019-12-10a12:10
Temporal.DateTime.from("2019-13-32T12:10") // 文字列にありえない月や日が入ってても RangeError がthrowされる。
Temporal.DateTime.from({ year: 2019, month: 13, day: 32 }) // object で渡したときは throw されない、 2019-12-31 などの一番近い日付に補正される。
Temporal.DateTime.from({ year: 2019, month: 13, day: 32 }, {disambiguation: "reject"}) // disambiguation を reject にすれば RangeError が throw される。

ここは、若干動きがわかりにくいかも。。。文字列で渡したときは disambiguationが必ず "reject"のようになるが、object で渡したときは disambiguationが "constrain"になる。 polyfill の問題かな。

ちなみに、これまでの Date だと invalid Dateという number で言う NaNのような扱いになっており、例外は throw されなかった。

newDate("2012-12-11T12:20") // 2012-12-11T03:20:00.000ZnewDate("2012-12-11a12:20") // Invalid Date

どっちが良いかは諸説あると思うが、いままで手軽に扱っていた Date のマインドセットのままでは使えない。ちなみに momentとかも同様にパースできなくても例外を投げたりしない、 NaN 扱いになる。パースできない時に NaN 扱いだった Date ときちんと例外を処理しないといけない Temporal のどちらが良いかは筆者には判断付かないが、 JavaScriptはこれまでの歴史上、例外の取り扱いが難しいので、この辺の議論は是非参加してみたい。

まだまだありそうだけど、翻訳を先に出したかったので、ここまで。後は読者の方々も試してみてほしいです。試してみたら、サーベイに回答しましょう。

assert.CallTracker と must-call

$
0
0

この記事はリクルートエンジニアアドベントカレンダーの2日目の記事です。過ぎてるかもしれませんが、メンバーから脅迫されて書いてます。

f:id:yosuke_furukawa:20201204210950p:plain

assert.CallTracker

Node.js で experimental な APIとして assert.CallTrackerがv14 で追加されました。 この機能は呼び出された回数を検証するという機能を持った新しい assert 関数です。

コールバック関数をテストする際に使える便利関数です。機能としては地味ですが、使いこなせると便利なので紹介します。

const assert = require('assert');

const tracker = new assert.CallTracker();

function func() {}// 一度だけ呼ばれることを期待const callsfunc = tracker.calls(func, 1);

// ここで呼び出す。
callsfunc();

// tracker.verify を呼ぶことで何度呼ばれたかを検証する。// これはプログラムが終わるときに1度だけ呼ばれてることを検証する。
process.on('exit', () => {
  tracker.verify();
});

これだけ見ていると何が便利かわからないかも知れませんが、テストを書いた時にコード内の不具合で assert が呼ばれずに終わってしまう状況を防ぐことができます。

const assert = require('assert');
const tracker = new assert.CallTracker();

const http = require('http');

const server = http.createServer();
// 一回だけリクエストが来ることを期待
server.on("request", tracker.calls((req, res) => {
  assert.strictEqual("/", req.url);
  res.end("Hello World!");
}, 1));

server.listen(3000);

process.on("SIGINT", () => {// もしも一度もリクエストが来なかったらここで検証失敗が出る
  tracker.verify();
  process.exit();
});

とまぁ、ここまでが CodeGrid で書いた内容です。

www.codegrid.net

ここから蛇足と言うか多少細かすぎて伝わらない話をしようかと。

mustCall

この機能もともとどこから来たかと言うと、 Node.js のコアのテストでよく書かれてた common.mustCall っていう test utility 関数から来ています。

https://github.com/nodejs/node/tree/master/test/common#mustcallfn-exact

これを一般向けに公開したのがこの機能です。 Node.js の中では Event を使ったテストが多く、『イベントが呼ばれなかった時に検証が行われずそのままパスしてしまう』というのを防ぐモチベーションで作られていました。

今では実は jest とかでもやっちゃうときはやっちゃいますよね。

余談ですが、この機能はめっちゃ便利だなーと思っていたので昔 must-call っていうライブラリとして公開していました。

www.npmjs.com

f:id:yosuke_furukawa:20201204215906p:plain

今となっては assert.CallTracker で置き換えるか、 npm モジュールにしなくても数行で書けると思うので、役目を終えたライブラリになりました。

他にも test common な utility にある便利な関数はあると思うので、意外とこの辺りは公開すると便利かもしれませんね。

https://github.com/nodejs/node/tree/master/test/common

急遽書いたので内容薄いですが、もう少し濃いやつはまた次回。

この半年やったこと、継続していること

$
0
0

syohex.hatenablog.com

studio3104.hatenablog.com

あまりにも同じことをしていたので「せっかくなので」と思って筆を執ることにする。

@syohex さん@studio3104 さんも僕もだいたい同年代の人たちが同年代の sugyanや色々な方の影響を受けて同じことをしているというのはシンパシーを感じますね。

僕は今の仕事はフロントエンドエンジニアであることが多いのですが、「知識に垣根は作らない」をモットーにしているので、色々半年間挑戦してみました。

Leetcode

この半年で545問解きました。

github.com

sugyan が leetcode に取り組んでいたのも見てたのですが、僕の場合は自分の会社の面接でコード面接をやることがあり、コード面接の時に自分が知らないような事を問題として出すのは恥ずかしいな、という思いから勉強し始めたら楽しくてハマってしまったという経緯です。(もちろん同世代の sugyan がやれていることにも憧れはありました)

あとコロナ禍だったりでリモートになり、仕事と家の間の行ったり来たりが減ったことで、割と融通がきくようになったのでここは一つと思って競技プログラミングの勉強がてら、習慣として leetcode を毎日実施しています。

f:id:yosuke_furukawa:20201217021152p:plain

https://leetcode.com/yosuke-furukawa/

毎日 Leetcode を書いているおかげか、Easy, Medium の問題は全てではないですがなんとか解けます。Hard もそこまで難しい問題ではないときがあるので解けるときは解いてます。

ちなみにずっと JavaScriptで解いてるのですが、こういう競技プログラミングには全く向きませんね。 Javaとかであれば LinkedList が使えたり、 Heap の実装が既にあったりするのですが、 JS の場合はそれを自作するところからだったりします。

標準ライブラリを仕様に入れるという構想があるようなので、コレクションクラスの充実を望みます。

書き始めてから半年しか経過してないのでなんとも言えませんが、レビュー等での指摘で多少賢いやり方を教えられることが増えたようにも思うのと、 OSSでも基礎的なアルゴリズムを使ったようなものは多少すばやく書けるようになった気がします。

後最近は Advent of Code も毎日出るんで解いてます。

f:id:yosuke_furukawa:20201219013703p:plain
Advent Of Code 2020

個人的に好きな leetcode の問題

LRU Cache ですね。

leetcode.com

一度ライブラリで自作した時に JavaScriptで実装するとこんなに面倒なのか、、、と思ったのですが、 leetcode でも出てきて「おおっ」となりました。ちなみに OSSで自分の自作した LRU Cache を使っているのですが、一時的な APIの Cache として保持する時に使っています。有用だし、高速化されるし、作ってみると面白いと思います。

他には数独 Solver も好きです。

leetcode.com

Backtrackで解ける問題昔は苦手だったのですが Leetcode を経て楽しく解けるようになりました。

英語

DMM 英会話くらいしかやってませんが、半年で 4900 分間レッスンを受けました。

f:id:yosuke_furukawa:20201219011736p:plain

こっちは物理的にも時間的にも制約があるのと相性の良い先生とのレッスンを取ろうとすると相手の都合もあったりで、毎日コンスタントにやるのは難しいのですが、大体週5-6日は英語で50分間はしゃべっています。

これもコロナ禍の空き時間を使った形ですね。英語を再勉強するというきっかけになりました。あまり発音も良くないので、発音を矯正しようと思って本で勉強したりしています。

www.amazon.co.jp

あと今年は海外での登壇予定があったので、そのためにも気合を入れてやってました。

youtu.be

これまでもそれなりに話せたといえば話せたのですが、発音を気をつけてやってみたら agektmr さんからも褒められて嬉しかったです。

後最近は Youtuber の Daijiro さんの動画を笑いながら見てます。

www.youtube.com

数学

あまりちゃんとやれてないですが、数学も解いてます。 Youtubeで数学の問題を必ず一日一問解説してる 鈴木貫太郎さんの動画があるので、それを見てます。

www.youtube.com

大学のノートみたいなのを買って朝の30分だけ解いたりしてます。朝起きた時に問題を見て、解いてみて、あってるか動画で見て、動画に自分がどうやって解答したかを書いてます。完全に知識が鈍ってるのでエレガントな方法をスッと思いつくのは苦手ですが。

数学ができると leetcode の問題でも数学の知識を問う問題があって解けるようになったりと色々付加的な効果がありました。

鈴木貫太郎先生が教えてくれる整数問題はパズルのようで楽しいです。ただやってると高校の時にもっと勉強しておけばよかったな、、、と思うことが多いですね。

競技プログラミングや英語と違い、数学は特に明確な目的があって始めたわけじゃなく、趣味みたいな感じです。

これから

こういう活動を会社の仕事や家の事があるので柔軟な時間にちょこちょことやってます。多分これからも同じことを続けていきたいと思いつつ、ある程度切りの良いところを見つけたら別な挑戦もしてみるかもしれません。新しいプログラミング言語を身につけるとか低レイヤをやってみるとか、ずっと漠然とした憧れがありながらもできていないので、コロナ禍のまだリモートワークを中心とした生活の中で会社の仕事とのバランスを取りながらやってみようかと思ってます。

yuroyoro.hatenablog.com

(ちなみに、この記事すごくいいですよね。やってみたくなります。)

Node.js で最近変わりそうな Permission Policy について

$
0
0

さてさて、 25日目の Node.js アドベントカレンダーです。もう年の瀬ですね。振り返りシーズンなんで色々書きたかったんですが、ネタを見つけているうちにこの日になってしまいました。

Permission Policy とは

Node.js に新しく Permission を提供しようという試みです。元々 Node.js では同じプロセス内で動いてしまえば どんなモジュールであろうと同じ権限で色々できますね。外部ネットワークにアクセスしたり、ファイルを読み書きしたり。

プロセスに元から許可されている権限は全てできてしまいます。これが今まででは普通でしたが、今後はもしかしたら変わるかも?という話です。

権限に関して制限をかけて、拒否させることが可能です。

以下のような要領で拒絶させることができるようになります。

$ node --policy-deny=net

上のオプションでプロセス内のネットワークアクセスを拒否させることが可能です。lint や formatter のときはネットワークアクセスとか不要なのでこのようなオプションで動かすほうが望ましいかも知れませんね。

元々 policy が去年入ってたのですが、それをエンハンスするような形ですね。

ちなみにまだ議論真っ最中です。

github.com

Options

下記のオプションを設定することが可能です (現時点での Permission の実装では)。

  • fs ファイル読み書き
  • fs.in ファイル読み込み
  • fs.out ファイル書き込み
  • net ネットワークアクセス
  • net.in ネットワーク入力
  • net.out ネットワーク出力
  • process 子プロセス起動
  • signal プロセスシグナル送信(プロセスの強制終了などを許可するか否か)
  • env 環境変数読み込み
  • worker ワーカースレッド起動
  • wasi WASI の可否
  • timing 高精度タイマーを拒否(おそらく process.hrtime が使えなくなる、サイドチャネル攻撃対策)
  • addon ネイティブアドオンの実行拒否 (native addon から何でもやられてしまうのを防ぐ目的)

などなど、どう使うのか使途がよくわからないものもありますが、 上記の処理に制限をかけることが可能です。

この他にも、 Permission Policy の機能にはそもそも policy.jsonで細かく設定できるように予定されているものもあり、例えば port 番号で制限するとかネットワーク接続先で制限するとかの今後の展望は用意されています。 ブラウザの Content-Security-Policy のような allow/deny list で管理するとかも検討が進めばできるようになるかもしれません。

内部実装

ここは内部実装向けなので、興味のある方だけで良いです。

Node.js が v8 を起動する際に permission が設定されます。Permissionチェックは runInPrivilegedScope 関数が用意され、その関数の中で実行した場合のみ、権限チェックが行われます。その関数のコールバックの外側では権限チェックがされないので、内部の fs, net などのコアパッケージでは権限チェックをするところと権限チェックが行われない特権実行の両方が行われます。これは Node.js 内部関数で全てを制限されてしまうと何もできなくなるため、特権を持った関数実行と特権を持たないユーザーが設定した Permission とを区別するために行われています。

function foo(a, b, c) {return a + b + c;
}const privilegedFoo = runInPrivilegedScope.bind(this, foo); // この中でのみ権限チェックされる

console.log(privilegedFoo(1, 2, 3));

// この外では権限チェックされない。

これって deno の permission と一緒?

そう思った方は deno を追いかけてる方ですね!

FAQ に書いてありますが、その回答としては Yes でもあり No でもあるとのことです。

元々の発想は deno から来ていますが、実装は物凄くシンプルに作られていて、 Node.js の internal API部分で制御しています。 deno はセキュリティモデルからして node とは違うので同じものとは考えにくいです。 deno は特権が必要なときに (OS がカーネルに伺いを立てるかのごとく) 特権を要求するように設計されています。そのタイミングで制限をかけるようになっています。またデフォルトでは制限されていて、 opt-in で権限を付ける所が deno のがセキュリティとしては強いものであると言えるでしょう。

node はデフォルトでは全ての実行が許可されています。制限を後からかけるように設計されています。後から追加した機能なので、ある程度徐々に制限を厳しくできるように設計されたものと言えるでしょう。

package 毎の制御はできるのか?

これを思った方は deno を追いかけてる(以下略

今はできないと思います。 deno もできないのですが、本来はより粒度を細かい単位で実現できるできないの制御をしたいですよね。一律プロセス全体で ネットワーク接続できる、できない、ではなく、このパッケージではできるけど、このパッケージでは許可しない、といったような制御がしたいですよね。

deno も検討中ですが、まだできてないですね。

ただし、面白そうな議論は既に上がっています。

github.com

permission フィールドを package.jsonに追加させるようにして、その単位で制限をかけられないか?という npm の RFCですね。 npm のスクリプト実行時に net へのアクセスが必要ならそのタイミングで --grant=netのような許可を要求できるようにしたい、というものです。

まだ全然議論が始まったばかりですが、このあたりは非常に注目していくほうが良いでしょう。

Deno 化する Node.js / Node.js 化する Deno

Node.js に ESM が入り、 top-level await などのモダンな機能が追加されていき、パーミッションの実行までできるようになってくると徐々に deno との差別化ポイントは薄くなっていきます。

Deno は Deno で Node.js との完璧な互換性を求めていないものの、 std:node のような標準モジュールを用意し、 compatible にできるところは合わせていく流れもあったりします。

github.com

結局エコシステムがどういう機能を求めるか次第で JavaScriptの世界の APIや環境は変わっていくので、どちらもそこまでの差がなくなっていくようにも思えました。

筆者はよく「これからは Deno を使ったほうが良いのでしょうか?」という旨の質問を受けることがあります。本質問に対しての筆者の意見を書いておきます。

Deno も Node.js もどちらも Web コミュニティの中にあるものであり、 Web コミュニティの進化に合わせて機能が作られていくという意味では外側の API面ではあまり変わらないものになっていくと思われます。Hello Worldサーバは Deno でも Node.js でもほとんど同様の書き方で提供されています。

Deno

import{ serve }from"https://deno.land/std@0.82.0/http/server.ts";const s = serve({ port: 8000});forawait(const req of s){
  req.respond({ body: "Hello World\n"});}

Node.js

import{ createServer } from 'http';
import{ on } from 'events';
const reqs = on(
  createServer().listen(3000), 
  'request'
);
for await (const[_, res] of reqs) {  res.end('Hello World\n');
}

外側に大きな差異がないのであれば、非機能要件での進化、パフォーマンス、セキュリティ、運用継続性などの観点で選べばよいと思っています。そのうちどちらも変わらない書き方で提供されるようになれば移行もそこまで難しくはなくなると思います。

開発者のタイミングで Deno を使うか Node.js を使うかを選べば良いのではないかと思っています。

Advent of Code 2020 完答した。

$
0
0

memo.sugyan.com

すぎゃーんの宣伝によって参加したけど、「楽しかった!が八割、辛かった!が二割」って感じでした。

Advent of Code とは

adventofcode.com

所謂アルゴリズム系の問題が25日間のアドベントカレンダー形式ででてくるので、ひたすら解く感じのものです。 ランキングとかを狙おうとしなければそこまで厳しいものではなく、既に解答した人が Redditだったり youtubeとかに解法を上げてたりするので、あまりにも厳しい時はそれを見て回答するのもありです。

最初のほうが簡単なので、『あれ?これなら普通に解けるんじゃね?』と思わせておいてからの Day 17 あたりから急に牙を剥いてくる感じがありましたね。。。

前半の問題

入力値を正規表現だったり、 parse したりして、 扱いやすい形に変換したら問題の半分は解けたような問題が多かったです。

Day12 の船を目的地まで操縦させるようなゲームとか。

Day 12 - Advent of Code 2020

Day11 の飛行機の空いてる座席を探させるやつとかも面白かったですね。

Day 11 - Advent of Code 2020

最初の関門は中国の剰余定理を使わないといけない Day13 でしたね。なんとか数学の知識が役に立ってよかったです。

Day 13 - Advent of Code 2020

後半の問題

前半に比べて後半は格段に難しくなりましたね。特に問題文を噛み砕いて理解するまでが時間かかりました。 難しかったのは Day17 でした。問題が読み解けなかったのと、何言ってんだろっていうのを理解しきれず、タイムアップでしたね。

Day 17 - Advent of Code 2020

答えを見てみるとなんのことはなく、最初から例題の読解よりも書かれている事をある程度愚直に実装したら解けそうでした。

Day20 の part2 も歯が立ちませんでしたね。これはジグソーパズルを解かなきゃいけないのですが、解き方はわかってもパズルのピースをぐるぐる回したり、デバッグしてたりしてたら時間が全く足らなくて、諦めました。

Day 20 - Advent of Code 2020

part1 がジグソーパズルそのものを解かなくてもよかったので安心してたら part2 で格段に難しくなったのがビックリしましたね。

個人的には Day19 の part1 が好きでしたね。正規表現に変換すればいいやと思いついた後は楽でした。part2は解法があってたのか自信がないですが、答えを導くことはできました。

Day 19 - Advent of Code 2020

まとめ

AoCは楽しいです。多分来年もやるだろうなーとは思います。次は Rust とかで挑戦してみようかなと。周りで流行ってるし、最近 すぎゃーんが作ってくれた slack workspace があるので、そこで聞きながらやってみようかなーと思います。

2020年振り返り

$
0
0

はじめに

yosuke-furukawa.hatenablog.com

今年もちゃんと書きました。

マネジメントとシニアソフトウェアエンジニア

二足のわらじで4年目になりましたね。去年も書いたんですが、メンバーが優秀であるがゆえに二足のわらじができていると思っていて、それを4年目も継続できました。新しく何名か入ったおかげで非常に強力なフロントエンド体制ができてるなと思っています。

adventar.org

recruit-tech.co.jp

recruit-tech.co.jp

recruit-tech.co.jp

上記のブログは技術ブログの方に書いてもらったやつですが、他にも CodeZine@IT等に記事にしてもらっています。

codezine.jp

www.atmarkit.co.jp

関連会社の技術顧問

また去年から新しくリクルートの関連会社のニジボックスの技術顧問として活動していますが、そこでもエンジニアコミュニティを作って活性化させようとしています。

www.wantedly.com

www.wantedly.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

頼りになるメンバーが自組織の成長だけではなく、関連会社全体にも広げることができたので、これをより成長させていかないとなと思っています。

社内イベント

R-ISUCONを開催、スピードハッカソンも同時期に開催しました。ギリギリ COVID 19 が本格化する前だったのですが、どちらもオンラインではなくやりました。開催できてよかったですが、これから COVID-19 が本格化した影響でイベント系は予定がすべて一旦延期になりました。なので、開催できたのは2つだけです。

R-ISUCON 2020 を開催しました。

社内ISUCONも開催3度目で、今回はWebメールがお題でした。これがきっかけ(?)で、本家の ISUCON でも運営になる機会をもらえました。ISUCONについては後述します。

recruit-tech.co.jp

スピードハッカソンも開催しました。

recruit-tech.co.jp

イベント

コロナの影響で早々に JSConf は開催を断念しました。

yosuke-furukawa.hatenablog.com

しかし、 ISUCON は運営側としてオンラインで開催できました。オンライン開催する話も増えてるので来年は JSConf.jp やりたいです。

ISUCON 10 の予選に運営として参加した。

ISUCON 10 の予選を作成しました。予選の問題はある程度コンパクトでありかつチャレンジングな課題を設定できたと思っています。非常に好評だったのでよかったのですが、運営がはじめてということもあり、色んな人に迷惑をかけてしまったな、という反省は反省でありました。

isucon.net

ただ参加して本当に良かったと思っています。 941 さんとも色々話した結果はこちらに掲載されています。

zine.qiita.com

登壇系

AMPFest 2020 に英語で登壇した。

www.youtube.com

AMPFest 2020 に英語で登壇する機会をもらえました。この話をきっかけに英会話を学ぶようになり、英語を再学習するきっかけになったのと、 AMPFest 2020 という大きな舞台でオンラインとは言え話ができて非常に良かったです。

Chrome Advisory Board のメンバーとして LT を実施

docs.google.com

こちらも英語で登壇しました。年に2回も英語で発表するきっかけをもらえてよかったです。

継続して来年もどこかでやりたいですね。2019年のふりかえりで書いたことがこんなに早く叶うとは思ってなかったのですが、きっかけになってめっちゃ良かったです。

DevSumi 2020

デブサミ2020でクッキーの話ししました。ブラウザの中ではプライバシーが非常にホットなトピックでしたね。

speakerdeck.com

FEStudy 2

パフォーマンスチューニングの話をしました。 Web Vitals 周りの話だけだともうたくさんされているので、それをキープするために考えることや対処することをまとめました。

speakerdeck.com

PWA Study

Next.js と AMP のはなししました。 Next.js はほんとに今年かなり大きなプラットフォームになりましたね。

speakerdeck.com

競技プログラミング

全体的にイベントがそこまで多くなかったので、それを逆手に取ってインプットに回ることができました。インプットを全力にやった結果を以下のブログにまとめました。競技プログラミングを割と本格的に入門できました。今年は JavaScriptで解いてしまったのですが、来年は Rust とか新しい言語でも挑戦してみます。

yosuke-furukawa.hatenablog.com

yosuke-furukawa.hatenablog.com

Node.js

あまり大きな話題は少なかったのですが、いくつかアウトプットしました。

Node.js v14/v15 のまとめ

www.codegrid.net

www.codegrid.net

新機能系

yosuke-furukawa.hatenablog.com

yosuke-furukawa.hatenablog.com

英語

ほぼ毎日英会話50分間してます。ただあまり語彙が増えてない気がしているので、ちょっと他のチャレンジも考えてみます。

f:id:yosuke_furukawa:20201231214857p:plain

数学

毎日 Youtubeの問題といてました。今でも解いてるので続けます。

もう少しやらなきゃなぁと思ってたけどできなかったこと

今回ベーシックな競技プログラミングや英語や数学やってたらできなかったですね。もう少し来年は上記のところもバランス良くできるように頑張っていきます。

まとめ

今年もマネジメントにプログラミングにイベント開催に登壇にと色々やれた1年でした。非常に充実していたのですが、反省もありました。また来年も自分の反省を乗り越えて色々できるように精進していきます。今年お世話になった皆様ありがとうございました。


Node.js や deno に Web Standard な API をなんでも取り入れるのが良いことなのかについて

$
0
0

この記事は Node.js Advent Calendarの 11 日目の記事です。

qiita.com

Web APIと Node.js

ES2015 以前の Node.js は Web Standard な APIの中で足りないものを自分で補う形で進化を続けてきた。 Callback や Event 主体での非同期処理や Common JS な形でロードできる独自のモジュールの仕組みがその筆頭だと思う。ただ逆に Web Standard な APIが流行ると今度はそれに追従していかないといけなくなってきた。 ES2015 以後に流行ったものといえば、 Promise 主体での非同期処理であり、 async-await での処理だと思う。また、 ES Modules の台頭もあり、今日では Node.js でも普通に呼び出すことが可能になった。

今ではどちらも Node.js で普通に使える。エコシステムを壊さないようにした結果、 Node.js の ES Modules が普通に使えるようになるには時間がかかったが、いずれにせよ今は使えている。

TC39 だけが Web Standard なグループではない。 WHATWGや WICG 、 W3Cなどのグループもそれぞれ存在し、それぞれが Web Standard な APIを作っている。これらを後追いで Node.js は使えるようにしてきた。 Event Target API, Text Encode / Decode, WHATWG URL, Web Stream, Web Crypto, AbortController などなど、足りないパーツを補う形で作られている。

deno は最初から Web Standard な APIをベースに設計されており、割と Node.js よりも既存ブラウザに存在する機能を積極的に持ってきている方だと言える。後発なだけあって、エコシステムに配慮する必要がない分迅速に対応ができている。

Node.js / deno が Web Standard APIに追従する状況は現在でも続いている。ただし、最近は若干やりすぎなのではないかというか、本当に必要なのか?と思うようなものまで入っているし、検討されている気がする。

自分の立場を明確にしておくと、「新しい Web APIに追従することは良いことだと思うが、不要な APIにまで追従する必要はないし、無理矢理ブラウザと同じ APIにする必要もない」という立場だ。

新しい Web APIが必要か不要かにはいくつかの観点があると思う。筆者は以下のように観点を感じている。

  • 全てのブラウザでコンセンサスが取れていること
  • Node.js / deno の利用者が呼び出した時にどうなるのかが既存の APIと比較してイメージしやすいこと
  • 新しい機能が入ることでセキュリティホールが生まれにくいこと

この観点でいくつか考えてみようと思う。

atob / btoa が Node.js / deno に入った。

atob と btoa は 文字列を encode して base64にしたり、 decode して元に戻す時に使う、binary から ascii (base64) に変換 (btoa) し、 ascii (base64) から binary に戻せる (atob) という APIだ。ブラウザでは昔から実装されている。

developer.mozilla.org

ただし、この文字列は名前の通り ascii (base64) にだけしか適用できない。 btoa が binary to ascii (base64)の略語だと知っていれば binary (latin1) な文字列にしか使えないことはわかるが、一方で日本人のように latin1 以外の表現を文字列としてナチュラルに使っているところもあると思う。特に任意の文字列を base64に変換する APIだと誤解して使っていると不用意なバグを埋め込む可能性もある。 encodeURIComponent などで無理矢理日本語を binary (latin1) に変換してから使えば一応使えるが、イディオム的で直感的ではない。

> atob(btoa("こんにちは"))

Uncaught:
DOMException [InvalidCharacterError]: Invalid character

> decodeURIComponent(atob(btoa(encodeURIComponent("こんにちは"))))

'こんにちは'

すでに Node.js にも deno にも実装されている。しかし Node.js は実装した瞬間にこれは使うべきではないと実装者から言われている。

Node.js には Buffer APIがあるので、これを使わなくても、base64に変換するような処理は表現することは可能。

const str = "こんにちは";
const base64 = Buffer.from(str, "utf-8").toString("base64");
console.log(Buffer.from(base64, "base64").toString("utf-8"));

こっちのほうが長いので、一見難しそうに見えるかもしれないが、やっていることは simple で utf-8から base64に変換している処理であることは掴みやすい。 btoaのような4文字で表現されているAPIは easy な APIではあるものの、ぱっと見てこれが binary to ascii の略で base64に変換してくれる APIだと調べないで分かる人は少ないのではないかと思う。

上述した観点でいうと、「既存のAPIと比較してイメージしにくい」という点と「知らないで使ってしまった時に不用意なバグを埋め込むのではないか」という点でそもそも Node.js には入れなくても良かった APIだと思っている

じゃあそもそもなんで使ってほしくない APIを Node.js が実装したのか、というと、Web Standard APIに合わせるというコアチーム全体の合意ともう一つが atob / btoa を polyfill して作られているライブラリの存在、最後に deno が既に実装しているという競合からの後押しの3つの理由で実装しているのではないかと推測している。

自分の立場で言えば入れなくても良かった APIだと思っているものの、多少複雑な思いもある。 Buffer が Node.js に詳しい人は分かっていたとしても、ブラウザ側のフロントエンドエンジニアにとっては atobbtoaの方が身近な存在である可能性はある。 Node.js のユーザーがそちらに傾きつつある現状においてはその方が良いという意見もわからなくはない。一方でブラウザの歴史的なレガシー APIをそのまま持ってくることが本当に良いことなのかは慎重に検討したほうが良い気がする。 特に簡単(Easy) な APIというのは難しい。誰かにとっては簡単でも、誰かにとっては不便なものだからだ。今回の例で言えば、「atob/btoaを知っているブラウザのフロントエンドエンジニアにとっては簡単」だけど、知らないエンジニアにとっては一見わかりにくい Bad Parts 的なものであり、これ以上増えていくことは避けるべきではないかと思っている。

だからこそ、コアチームの中にも矛盾した思いがあり、「新しく実装したけどなるべく使わないでくれ」というメッセージを出している。

File system accessAPI

Web を構成する要素として一番難しいブロックの一つにファイルの取り扱いがある。この APIファイルシステムにアクセスできる APIをブラウザに実装しようというものだ。

wicg.github.io

Node.js はまだ検討中で、実装するようなフェーズに入っていない。とはいえ、アイデアとしては検討はしているようだ。

github.com

deno は検討中で、 draft PR は出されている。

github.com

github.com

この APIはブラウザ間のコンセンサスがまだ取れていない。

mozilla.github.io

ブラウザ間のコンセンサスが取れてない状況で実装したとしても変わる可能性は大いに有り得るし、最終的に実装されなかった場合には誰も得しない APIになってしまう。 まだマージするフェーズにどちらも入っていないものの、Web Standard APIを採用するとしても、ブラウザのコンセンサスはさすがに取られたものにしてほしい。プラットフォーム側がいち早く Web APIを実装しなくとも、コミュニティ側が「使いたい」という意見が出てから実装したとしても遅くないように思う。

そもそもファイルを取り扱うという一番サーバサイドでよくありそうな基本的な処理をクライアントとして使われるブラウザの APIに任せるのは難しい気がしている。

fetch

Node.js では未だに議論を重ねている fetch のサポートだが、 deno には既に入っている。ただ fetch もよくよく仕様を読むと deno / Node.js には不要なものも多い。特に CORS 周りの同じドメイン以外にリクエストを送る時の仕様は Cross Origin という概念が存在しないサーバーサイドでは形だけ APIとして設定できるように使われていて、実行しても何も意味がなかったりする。つまり、 mode: "same-origin"など設定できるものの特に無意味でリクエストは送れてしまう。こういう形だけの APIはどこまで正確に模倣するべきなのかは議論が分かれるところだと思う。逆に Cross Origin 相当の設計を今から deno / Node.js に取り入れるのも労力の割にリターンが見合わないし、どういうものになるのか想像がつかない。

Cache をどうやって取り扱うのかも fetch 内にオプションとして設定できる。ブラウザであればブラウザの cache storage を使う際のオプションとして使われるが、 サーバーサイドで fetch した時にはもちろん無視される。というより、サーバサイドで統一された cache storage なんてものはないし、あったとしても実装がメモリ内に保存するのか永続化するのか、するんだとしてどうやって expired データを取り扱うのかといった概念をセキュリティに配慮しながら実装するのも不毛な気がしている。

Cookieとかはさらに頭が痛い問題である。ブラウザで幅広く使われているが、サーバサイドに持ってくるべきかどうかに関しては今もってお互い議論中だ。

なので、 fetchはあくまでも表向きのよく使われそうな仕様だけ実装してあり、ブラウザとの 100% compatible なものを目指すことは deno にせよ Node.js にせよ考えていない。

とはいえ、それでも HTTP をリクエストするという APIにおいては、ブラウザにせよ Node/deno にせよ必要な APIであり、どちらも表向きでいいから同じ APIが欲しいというのは理解できる。ただし表向き同じ APIというのがどこまでを指していっているのかが、Node.js / deno コミュニティ内で深く考えきれていない気がする。単純に似たようなものであれば、 Next.js でも提供されているし、 unfetch などの 3rd party 製のものもある。 Node.js のコアチームは undici と呼ばれる HTTP クライアントを次の HTTP クライアントとして提供している。

※ ちなみにマニアックな話になるが、 deno の fetch は ALPN を使った HTTP/2, HTTP/1.1 のネゴシエーションをしてくれるが、上述した Node.js のライブラリはどれも ALPN でのネゴシエーションはしてくれない。

fetch の中身を見ずにただ「fetchという表向き同じAPI」を指して、 fetch をコアの中に入れようとするとその「表向き同じ実装をどこまで頑張るのか」のコンセンサスを取るのに難しいし、既存のエコシステムを壊さないように入れるのは非常に時間がかかる。

筆者は fetch が一番複雑な思いを抱いている。現状の Node.js の http クライアントはブラウザのクライアントとはノリが違いすぎるので、気軽に call できる新しいクライアントはほしい。一方でブラウザがブラウザのために作った APIと同じ APIが実装されることは表向き良いとは思うものの、実際使ってみたら無意味な設定や設定しているつもりでも動かない機能が多くなり、結果として Bad Parts になってしまわないかという懸念はある。特に fetch は前述の simple か easy かという議論で言うと、 easy 寄りの APIとして提案されているように思える。 URL を fetch 関数に渡せば Promise で response が返ってくるという仕様は非常にわかりやすいが、実際には fetch クライアントが中でやっている処理は非常に複雑になっている。ブラウザが実装するものとしてはセキュリティに配慮した形でこのような APIになることも理解できる。一方で、サーバサイドで呼び出す時にこの APIがマッチしているのかに関しては、まだそこまで検討が進んでいないと思う。

自分が isomorphic だとか universal だとか言ってきた頃より時代が進み、同様なものが実装されるようになってきた。これ自体は良い兆候であると思う。一方でどこまで行っても「表向き同じもの」であって、細部がどこまで表現されているかはドキュメントには書かれていないことが多い。どこまで実装されているのかはもう少しドキュメントに書かれてほしい。

その他

これ以外にも clipboard APIとかを実装したり、 navigator にあるようなクライアントの情報が取れる APIを実装したりしようとしている issue も見かけたが、サーバサイドで実行された時にセキュリティホールになりそうだと最初に思ってしまった。基本的にブラウザのセキュリティモデルとサーバサイドで動くことが基本のプラットフォームとは同じ感覚で考えすぎるのは良くないと思っている。 deno にはパーミッションで防げる仕組みがあるとはいえ、サーバサイド内で実行されて困るような APIを実装するべきではないと思っている。

まとめ

これまでは同じ APIが増えることが Web というエコシステムを後押しするように思えていた。一方で、なんでもやりすぎるのはどうなのかと一旦立ち止まって考えるようになってしまった。特に atob/btoa を実装した辺りが個人的に立ち止まって考える切っ掛けになった部分だ。 Web Platform Test のカバレッジが増えることが良いことのようにされ、 mdn 上にある星取表が Yes になることが良いことだと思われているが、一方で、本当になんでも入れるのが良いことなのかについては考えていくようにして、フィードバックしたい。

例外を初めて実装した言語

$
0
0

リクルートアドベントカレンダー20日目の記事です。

adventar.org

例外を初めて実装した言語ってなんですかね?

最初にこの疑問を思ったのは、今も忘れない R-ISUCON 2021 というリクルートの社内ISUCONの運営で炎上していた時の話です。 ちなみに R-ISUCON 2021 は劇的な結果で終わっているので、興味のある方は見てみてください。

blog.recruit.co.jp

R-ISUCON 2021 では、 Node.js (TypeScript), Go, Javaの3パターンの実装が出てくることが通例になっていまして、今回は Javaの実装から Node.js, Go に適用していた時に一緒に実装していたメンバーからの疑問が『例外には色々な議論があるけれど、「例外を初めて実装した言語」ってどういう気持ちで実装したんだろう』という話が挙げられたので、そのネタを持ってきました。

ちなみにここで指している例外というのは、値を return した時の valueが既定値以外かどうかでチェックする方法ではなく、専用の機構として例外という考え方を実装したのはどういう言語からで、どのくらいのタイミングからだろうかというのが気になったので調べてみた感じです。 return と if で既定値以外かどうかをチェックする仕組みも例外ハンドリングの一種だと思いますが、そうではなく、専用の仕組みとして例外を実装した言語は何なのかが知りたくなって聞いてみた感じです。

竹迫さんからのコメント:

takesako さんコメント
takesako さんコメント

koichikさんからのコメント:

koichikさんからのコメント
koichikさんからのコメント

実際には諸説あったので、いくつか紹介します。ただおそらく一番古いのは LISPであるという事になりそうです。

StackOverFlow からは PL/I

StackOverFlow からのコメントでは、 PL/Iと CLU が挙げられていました。PL/Iが 1964 年頃、 CLU が 1973 年頃に実装されていたものと言うことで、だいぶ昔の話ですね。

stackoverflow.com

PL/Iは専用の機構として、 try catch構文で表すものではなく、 SIGNAL という信号とそれを受け取る ON という専用の命令を持っていて、それを使った形で実装されているみたいです。なんか JavaScriptのイベント駆動なエラー処理の方法と全く同じですね。

SIGNAL ERROR;

ON ERROR BEGIN;
.
.
END;

実際にはこの辺りに記述があります。

web.archive.org

ON-condition
An occurrence within a PL/I program of a condition that could cause a program interrupt, such as division by zero.

ON条件は PL/I のプログラム内で問題が発生した時にプログラムに割り込む事が可能になる。問題というのは例えば 0 で割った場合などを指す。

という記述がありますね。

例外的な状況に陥った時に割り込み命令のような形でエラーを出し、それによって緊急ハッチのように大域脱出な動きをするのが初期の例外という感じですね。こうなっちゃったらもう潔く死んでしまうという判断をするんでしょうけど、死ぬ前にログを書くなどの処理をしていたのでしょう。

CLU も例外ハンドリングができるようになっていますが、こちらの場合は Javaで言うところの検査例外のように例外を補足する時に任意の例外を選んで補足するという when句を使った書き方が可能になっています。

stack$pop(foo(mystack))
    except
    when empty:
        % handler code
        stream$putl(stderr, "popped empty stack")
    when foo_ex(i: int)
        stream$putl(stderr, "foo exception: " || int$unparse(i))
    when bar, baz (*):
        % ignore exception results, if any, of these
        % bar and baz may have different number and types of exception results
    others:
         % all other exceptions handled here but results are lost
    end
 % flow continues here

なんとなく、こちらの方が後発っぽいですが、最近の流れに親しきものを感じますね。

英語版 wikipediaからは LISP

en.wikipedia.org

Software exception handling developed in Lisp in the 1960s and 1970s. This originated in LISP 1.5 (1962), where exceptions were caught by the ERRSET keyword, which returned NIL in case of an error

1960年代から70年代にかけて、例外処理が開発されてきた。これはLISP 1.5 (1962年から) が起源であり、エラーの時に NIL を return する代わりに ERRSET キーワードによって例外をキャッチするものとして登場した。

こちらのほうが正確そうですね。 LISPが 1962 年から ERRSET なる機構を用意し、それが return ではない形で処理するための専用の機構ということです。これにはちゃんと続きがあります。

Error raising was introduced in MacLisp in the late 1960s via the ERR keyword. This was rapidly used not only for error raising, but for non-local control flow, and thus was augmented by two new keywords, CATCH and THROW (MacLisp June 1972), reserving ERRSET and ERR for error handling.

エラーを上げることは MacLisp 内に ERR キーワードを使って 1960 年代後半に導入されました。これは急速に利用用途が広がり、いわゆるエラーを上げるという事だけではなく、ローカルの制御フローにも使われました。これにより2つの新しいキーワードが導入されます。 CATCH と THROW (1972年) をローカルの制御フロー用のものとして使い、 ERRSET と ERR はそのままエラーを上げる用途として残りました。

おお、、、となると、最初は例外(エラー)を上げることと、ローカル制御フロー用の例外的な状況、所謂 Javaで言う検査例外的なものはそもそも制御構文からして違ったわけですね。

ちなみに PL/Iの SIGNAL / ON の構文は所謂エラーにも検査例外にも使われていたようです。ただ英語版 wikipediaには このような使い方をするのは現代では一般的ではないと言われてますね(JavaScript ...) 。

例外が発明された後

LISPが発明したものが MacLisp に専用構文として使われ、その後 C++がこの機構を try catch throwを使った形で導入します。 C++は例外処理の中で利用したリソースを解放するという目的でも利用されます。メモリの解放や open したファイルのクローズ等が必要になるため、大域的に脱出して終わりにするのではなく、 catch に入ってから後始末をやることがあります。ちなみに C++にはデストラクタと呼ばれるオブジェクトの終了時に呼び出すメソッドがあり、 RAII (Resource Acquisition Is Initialization)という考え方も早くから使われていたのでfinally構文がなくてもリソース解放はできていました。また、 Javaには finally 句があったり、 try-with-resource 構文があります。

Javaは検査例外という堅牢(だけど、割と面倒な)仕組みを作り、例外処理をきちんとハンドリングさせようとしますが、 C#などの Javaの影響を受けた言語にはそれが引き継がれませんでした。 Javaの検査例外に対する批判は「結局例外処理を強制させようとしても、殆どのプログラマーが無視したり、そのまま投げたりしてるだけ」という話に繋がります。

この後は Go が多値の return で表現しつつ、エラーは panic で表現するなど、原点回帰していく流れをみせており、今日の流れにつながっています。

最後に

ひょんな雑談からこんな話に繋がりました。みなさんもぜひ一度は英語版の例外処理の項目を見ることをおすすめします。ちなみに Vue.js の話や React の話なんかも少しだけ書いてあります。

en.wikipedia.org

Node.js コアモジュールの import/require には `node` schemeがつけられる

$
0
0

Node.js アドベントカレンダーの 3 日目の記事です。空きを埋める形で始めました。

qiita.com

www.codegrid.net

CodeGrid でも書かせていただきましたが、 Node.js で ES Module / CommonJS を使ってコアライブラリのロードをする際、nodeから始まる schemeを付けることが可能になっています。

nodejs.org

// ESMimport fs from "node:fs/promises";
// CJSconst http = require("node:http");

これにはいくつかのメリットがあります。基本的につけておくことが望ましいです。今回はメリットをいくつか紹介します。まだこれがデファクト・スタンダードになっている訳ではありませんが、これから付けてもらうように推奨していきたいと思います。

メリット1: Node.js コアモジュールであることが明示される

Node.js のコードを始めてみたときに、このモジュールが Node.js コアから提供されているのかそれとも npm から提供されているのかイマイチよく分からなかった経験ありませんか。 querystringというモジュールがコアからも npm から 3rd party library としても提供されているので、昔誤って npm install querystringしてしまうこともありました。これをインストールしたとしてもコアモジュールのほうがロードされるので、余計なものが入ってしまうだけではあるのですが、なるべく避けたい事態ですね。

今回からは node:querystringで初めておくことで、コアモジュールからのロードであることが明示されます。意図しないダウンロードも防げるでしょう。

メリット2: Node.js の将来のコアライブラリが既存のライブラリと被ることを避けられる

http2 モジュールを作るときに最初 require("https").http2のような形で提供するかどうかを議論になったのですが、これはもともと http2モジュールが取られていたからで、メジャーバージョンアップで提供する際に require("http2")にする形に落ち着きました。

こういうことは将来的にも起き得る可能性があります。つまり、 Node.js コアモジュールとそれ以外との名前が将来的にかぶってしまうと面倒なことになります。 http2 のときはまだそこまで流行っていないライブラリでしたが、今後流行っているライブラリで起きた場合はエコシステムに影響が生まれます。

そういう事も考慮して nodeschemeを付けておくことで、 3rd party ライブラリとの差別化を最初から図れるメリットがあります。

細かいところ

これ以外にも細かい所としては、 ES Modules は本来 import 文にかけるのは URL を書く仕様になっています。この node:から始まることで URL valid な文字列をちゃんと記述することができるようになります。今の書き方は Node.js 独自の拡張です。(ただこの独自拡張も import maps などの新しい仕様により、仕様側が Node.js 独自拡張もカバーする形になるかもしれませんが)

さらに細かいところ

require 構文の中で nodeschemeを付けた場合、 require.cacheで中身を差し替えるハック は使えなくなります。これを使って色々 Node.js のコアモジュールを差し替えているモジュールがあった場合、うまく動かなくなる可能性があります。 ES Modules の場合は require.cache を使っておらず、そもそも改変はできないようになっています。

// このように書いた所で、 node:http モジュールは変更できない。
require.cache[require.resolve('node:http')] = function() { console.log("http modified") };

まとめ

nodeschemeはつけていこう。

Node.js の assert の小話

$
0
0

Node.js Advent Calendar の4日目の記事です。

 Node.js の assert は結構歴史が深いです。あまり直接使ってる人は少ないかもしれません。使うとしたら test で使ったりするケースでしょうか。 それも最近は jest に生えてる便利ライブラリを使うほうが多いのかもしれないですね。 unassertなんかで開発中に埋め込んでいるケースもあるかもしれません。このようにたまに使うこともあると思うので、覚えておくと良いでしょう。

assert には 4 年ほど前から strict assertion modeというのが追加されています。

nodejs.org

今日はそんな小話を。

require("assert") は直接使ってはいけなかった。

もう昔の話ですが、 require("assert")が deprecated になっていた時期がありました。知らない人も多いんじゃないかと思います。 なんで deprecated だったかというと、 一部の関数が意図しない動きをしていたからです。

const assert = require("assert");
assert.deepEqual(/a/gi, newDate()); // 例外が上がらない (v9.0 時点での話。v10.0 以降だと例外が上がる)
assert.deepEqual('+00000000', false); // 例外が上がらない (最新でも例外が上がらない)

deepEqual は中身のプロパティが Loosely に同じかどうかを見ているだけなので、 ==とかと同じく、 厳密な型チェックの同一性を確認しない働きをします。

この他にも assert.failがよくわからない動きをします。

assert.fail("should not be reached") // Throw AssertionError should not be reached

assert.fail は fail するという関数です。本来的な使い方で言うと、 test でここに来ちゃったら fail ですよっていうときにメッセージを出す目的で使います。

ただ2つの引数を渡すと第1引数を actual 、第2引数をexpected として Error にメッセージを詰めて throw してくれます(そんな機能いらない)。

assert.fail("a", "b") // Throw AssertionError a != b
assert.fail("a", "a") // Throw AssertionError a != a

ちなみに assert.failに引数を2つ以上渡すことは 今でも deprecated になっています。

https://nodejs.org/dist/latest-v17.x/docs/api/assert.html#assertfailactual-expected-message-operator-stackstartfn

そんなこんなで、いくつかの関数が意図しない動きをするので昔は deprecated でした。assert モジュールを使うことは 今は deprecated ではありません。

strict method が出てくる。

この状況を改善するべく、 strictEqual と deepStrictEqual というメソッドが出てきます。これは前述した型が一致しているかまで見る assert method になっています。普通にこっちを使うほうが良いです。

assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]); // OK 3 と '3'は == では一致するため

assert.deepStrictEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]); // Throw Assert Error

ただ細かい注意点としては、 deepStrictEqual は厳密等価演算子である ===と同じではありません。 どちらかというと、 同値かどうかを表しています。 ECMAScript的に言うと Same Valueかどうかなので、 Object.is()の deep 比較と似ています。 そのため、 NaN 同士の比較も true になります。

assert.deepStrictEqual(NaN, NaN); // OK NaN と NaN は === で違うが、 Object.is() では true のため。
assert.deepStrictEqual(+0, -0); // Throw Assert Error +0 と -0 は Object.is() では false のため。

(それじゃあ deepSameValueのが良いのではないか?と思って、一応 issue で提案したんですが、まぁ微妙な議論になりそうだったので、引き下がりました。)

strict assertion mode が出てくる。

こんな感じで deepStrictEqual が出てきてメデタシメデタシではあったのですが、 deepStrictEqual というメソッドが微妙に長くて使いにくいです。 あと、 strictDeepEqual だっけ? deepStrictEqual だっけ?ってどっちがどっちかわからなくなったりします。そもそも deepEqual 微妙だよねっていう話もあり、最近では "assert/strict"の方を使って、 deepEqual でも deepStrictEqual と同じ動きになるように変更されています。

import assert from "node:assert/strict";

assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]);

+ actual - expected ... Lines skipped

  [[[
        1,
        2,
+       3
-       '3']
...
    4,
    5
  ]
    at file:///private/tmp/test/strict.mjs:3:8
    at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12) {
  generatedMessage: true,
  code: 'ERR_ASSERTION',
  actual: [[[ 1, 2, 3 ]], 4, 5 ],
  expected: [[[ 1, 2, '3']], 4, 5 ],
  operator: 'deepStrictEqual'}

まぁ正直どっち使っても良いんですが、 assert/strict にして deepEqual つかうか、 assert にして deepStrictEqual 使うかという選択肢が増えています。

まとめ

assert に strict assertion mode ができたよっていう話でした。

Advent Of Code 2021 完答した

$
0
0

Advent Of Code 2021 に参加して、今年も毎日コード書ききりました。。。大変だった。。。

Advent Of Code 2021
Advent Of Code 2021

Advent Of Code とは

adventofcode.com

Advent Calendar 形式で一日ずつ問題が出てくるので、それを毎日ひたすら25日まで解かせるというやつです。去年もやったんですけど、ただただ大変で、後半は問題出てきたとしても気持ちが折れかけたりします。ただ解けると楽しいんで、やってしまうという中毒性のある遊びですね。

ちなみに去年のやつです。

yosuke-furukawa.hatenablog.com

今年こそは全問何も見ずにちゃんと自分で考えて解くぞと思ってたんですけど、 day23 で気持ちが折れてしまって、解答を見てしまいました。自分との勝負ではあるものの、年末くらいはゆっくりしたいっていう気持ちに勝てませんでした。。。

今回は Rust で参戦

去年は JS だったんですけど、 Rust 勉強するかーって言って一年間 Rust で LeetCode やったり、 exercism で勉強したんで、集大成として Rust で参戦しました。

github.com

まだまだ Rust っぽい書き方ができるようになったとは言えないんですが、普通にコードは書けるようになりました。もうちょい実用的な処理を書いてみたいですね。来年はなんかライブラリとかアプリとか作ってみようかな。

思い出に残っている問題

思い出に残った問題を上げるなら以下の通り。ネタバレがあるので、これから自分で解きたい人は見ないでください。

Day 4: Giant Squid

ビンゴゲームをイカと一緒に解くっていう問題。数字が記載されているので交互にビンゴを完成させていって、勝ったらスコアが加算されるので、総スコアを答えよ、という問題。意味不明なお題で面白かったです。イカゲームと掛けたのかなこれは。。。

Day 13: Transparent Origami

折り紙を一定の座標軸で折り曲げていって、最後に出てきた画像に書いてあるアルファベットを答えさせる問題。これまで見たこと無い感じの面白い問題でしたね。

Day 14: Extended Polymerization

拡張された重合反応?っていう日本語訳になるかもだけど、要は化学元素記号みたいなものが反応されることで別な記号に生まれ変わる、その記号が反応し合うことで最終的にどういう文字列になるかという問題。

バカ正直に最初解いてて、途中から短く解けるアイデアを使ったら一発で解けてよかった。

Day 15: Chiton

よくあるスタートからエンドまでの迷路を一番効率よく辿れるパスを当てるっていう問題。 A* みたいなアルゴリズムで解こうか迷ったけど、結局 BinaryHeap を使った優先度キューみたいなもので解いてしまった。

この辺りまでは楽しいんですよね。前半の15日目くらいまでは。。。(ずっと前半やってたい)。

Day 16: Packet Decoder

なんでかよくわからないけど、エルフが出てきてバイナリーで話し始めたという所から独自のバイナリをデコードさせるっていう問題。とにかく仕様が複雑で、パケットの中にもサブパケットと呼ばれる入れ子構造になったパケットをデコードさせるので、再帰呼び出しで解く必要があり、また複雑なことにいくつかパケットのタイプが分かれてて、特別な処理が必要なパケットもあったりします。最後に leading zero で zero 値埋めがされているところとかも現実っぽい感じになっていて、とにかく「めんどくさい」問題でした。後半戦にはいったな、っていうことを思わせる問題でしたね。

Day 19: Beacon Scanner

三次元座標上にあるビーコンがあり、センサーがそれを検知するんですが、センサーの向きは教えてもらえず、ビーコンが見つかった三次元座標は渡されるものの、どの向きで発見した座標なのかはわからない、という問題です。書いただけじゃ何言ってるかわからないですね。三次元座標の原点の向きがバラバラになっているところで、すべてのセンサーが発見したビーコンの数を当てよという問題。頭の中でずっと座標をぐるぐる回転させてて、それでも全くどういうパターンが有るのかは分からず、色々座標を書いてパターンを見つけ、、、というほんと大変な問題だった。。。

Day 22: Reactor Reboot

原子炉を再起動させるために特定のパターンでボタンを押さないといけないが、こちらも三次元平面上にあるボタンをオン・オフして最後にオンになっているボタンの数を数えるという問題。書いただけじゃよくわからない問題パート2ですね。問題自体はめちゃくちゃ大変というわけではないものの、アルゴリズム力が求められるような問題でした。

Day 23: Amphipod

ここで一回力が尽きます。 Day 23 も問題自体はシンプルです。いくつかの微生物がいて、そいつらがパズルのように動くので、動いたコストの最小値を求めよ、という問題です。パズルを解ければ OK なんですが、もう day 22 で一回力尽きてたので、やる気がどうしても起きず、一回答えを見た上で解きました。気が向いたら自分でも解いてみます。

Day 24: Arithmetic Logic Unit

僕一番好きなやつですね。 Arithmetic Logic Unit というアセンブリ言語のような言語があるので、仕様どおりに計算を進めた上で、 4 つの processing unit がどのような値になるかをシミュレートするやつです。入力から出力を出すのではなく、出力から入力を推測しろという問題なので、全パターンを試そうとすると 9 ^ 14 の入力パターンを試す必要があり、そのままやると終わらないです。

自分はキャッシュを使って簡単なやり方で解いてしまったのですが、終わった後に色々見たら、 アセンブリ言語JITを作った猛者がいたりと、めちゃくちゃクリエイティビティなやり方で解いてる人が居て面白かったです。

https://github.com/cemeyer/advent-of-code-2021/blob/master/day24.rs#L164-L304

まとめ

去年 すぎゃーんに言われて始めた Advent Of Code だったけど、今年も継続してやることができました。しかも去年やるって言ってた Rust を学んだ上でそれで挑戦し、解くことができたのは、嬉しかったです。

来年は全部ちゃんと解けるようになりたいなーと思いつつも、健康や家族を優先にし、その中でやれる範囲でやれるようにしたいと思います。

この一年やったこと、継続していること (Rust とか 英語とか)

$
0
0

前回エントリを踏襲し、さらに一年間どんな事をやったか、という話を書こうかなと。

yosuke-furukawa.hatenablog.com

一年間やったことを振り返ると、英語と競技プログラミング、その過程で Rust をやっていました。

競技プログラミング

LeetCode

この一年間でトータルで解いた問題が806問になりました。これまで JavaScriptで解いてたやつを Rust で解き直したりしてたので、解答数はそこまで増えてないのですが、 Rust の勉強と割り切っていたので、目標としては良かったかなと思います。

github.com

LeetCode の戦績
LeetCode の戦績

一年間ずっとやったことで、金バッジをもらいました。

https://assets.leetcode.com/static_assets/others/2021-annual-badge.gif
LeetCode Annual Badge

ただ競技プログラマーとしてなにか成長したかと言うと、前回よりは解けるものの、「解けない問題が解けるようになった」というより「安定して解ける問題が解けるようになった」っていう感じですね。Rust を覚えたりしたものの、解けない問題は相変わらず言語を変えようと解けないですね。 ただ Rust は JS と比較すると既に BinaryHeap の実装があったり、 binary search を実装したものがあったりするので、この関数使っちゃえばおしまい、みたいな利点がありますね。

Rust を覚えるためにやったこと

exercism.io っていうところで問題を50問くらい解きました。

このツイートに書いたとおりなんですが、 exercism は問題を解くだけじゃなくて、解いた後メンターが見てくれます。その上で、性能が遅いとか、もっと match 式を使えとか、こういう関数あるからこれでやれとか教えてくれます。 approve がもらえると嬉しいです。無くても解ければ次の問題にいけます。 main track と sub track があって、 main track は最後に小さい Stack ベースの言語書かされます。めっちゃ楽しい。

後は地道に LeetCode 解いたり、プログラミング Rust を読んだりしていました。第2版がそろそろ出るということなので、買い直そうと思います。

Advent Of Code 2021

今年も完走しました。前回エントリの通りなので詳細はそちらで。

yosuke-furukawa.hatenablog.com

英語

今年は DMM 英会話と途中から ELSA Speak に手を出してやり始めました。

DMM 英会話

20000分以上受けたので、マスターレベルになりました。

DMM 英会話マスターレベル
DMM 英会話マスターレベル

この一個上にレジェンドレベルというのがあるのですが、 30000 分以上受けないといけないです。一旦そこまで目指してやってみようかなーと思っています。レジェンド以上は今のところ無いみたいですね。 英会話は勉強って言うより楽しく話せる機会としてやっていて、特にニュースを読んで議論するのが楽しいですね。先日はサービス残業を当たり前のようにする人を「Presenteeism」と呼ぶらしいのですが、 Presenteeism の人は日本に多いのか、クリエイターの人はなぜ Presenteeism になりやすいのかについて議論しました。また他の日にはアフガニスタン情勢がアメリカ軍が引き上げたことで変わったことについて議論しました。だいぶセンシティブな話題を議論しています。

ELSA Speak

ELSA Speak は発音矯正アプリなのですが、これまた楽しくてやってます。自分で実際に発音して、それがネイティブにどれだけ近いかどうかで点数を測ってくれます。アメリカ英語の発音とイギリス英語の発音もその他の英語の発音にも対応していて、それらのどれに近いかを認識して、近い方で点数を出してくれます。

ネイティブに近ければ近いほど点数が高いのですが、今の所発音は 95% まで来ました。

ただ実は自分がネイティブの発音に近いからこの点数というわけではないんです。何度も何度も繰り返して良い点数が出るまでやってるので、この点数なのです。とにかく反復して何度も発音して、コツを掴んでもう一回やって、、、というのの繰り返しですね。発音した後88%以上だと合格みたいな独自ルールでやっていると割と高くなりました。 やり続けているおかげか、まだまだ発音も完璧ではないですが、少しずつネイティブっぽい発音に近づけています。

読書

今年は途中で割と読書も心がけてました。というのも DMM 英会話で雑談するにしても知識がある程度共有されていないと有効な話し合いにならないのと、家事の合間に軽くサクッとインプットできる趣味を考えていたら読書が最強だったという事、後は最近やることが増えてきて、自分の知識が単純に足りないなと思ったことがきっかけでした(特に経営面)。

大体毎月1, 2冊は読んでたと思います。

こんな感じで幅広く読んでました。ただもう少し思ったのはちゃんと読んだ後、 Twitterで放流するだけじゃなくて、ブログとかでまとめておかないと忘れてしまうなと思ったので、ちょっとどっかでまとめようかなと思います。

数学

数学のインプットはむしろ減ってしまったかもしれません。というのも、朝に英語やる事で数学を解く時間が減ってしまったことが原因ですね。まぁこの辺はなにか別なことにリソースを割いたら、別なことが犠牲になっているだけなので、どっかで濃度を調整すればまたやれるようになるかなと思います。一応毎日購読してる数学の Youtuber のチャンネルは見れるときに見ています。

www.youtube.com

www.youtube.com

さて次どうしよう

去年やるという目標を立てていた Rust の勉強はできたのですが、 Rust で何か面白いアプリを作ったり、ライブラリを作っているわけではないので、もうちょい実用的なものを作ってみようかなと思います。フロントエンドエンジニアにも Rust が必要になるケースが増えてきており、なんかその辺りでできることないかなーと思っています。英語に関しては一旦スラスラとは話せないものの、ある程度まで話せるようになってきているので、後は繰り返しなのかなーと思ってますが、実は文法とかあやふやだったり、語彙ももう少し身に着けないとなと思っています。話すこと、聞くことはできても、読むこと書くことが苦手になりつつあるので、もう少しちゃんとできるようになりたいですね。

読書や数学と言った新しいインプットも継続的に行いつつ、来年はアウトプット方面をもう少し頑張りたいです。

実力なのか運なのか

$
0
0

最近 SNSを見ていて、「人に努力をしろ」と勧めにくくなった、「もっと努力した自分を称えるべきでは」といった内容の投稿を見かけました。 この他にも「運も実力の内」、と言ったり、「運は実力によってカバーできる」といった言説があります。

いくつか本を読んで自分の中で思ったことをまとめて書こうかなと思います。

イメージ

何かしら成功する際に、自分の実力と運が両方必要になるケースがあります。例えばプロのソフトウェアエンジニアになるとして、そもそも実力が足りなかったらなれませんし、一定以上の規模の会社で働こうと思った場合は面接があったりするので、面接官との相性だったり、たまたま出てきたコーディング試験が得意な問題だったといったような事があるでしょう。他にもプロ野球選手になるというケースでも実力はもちろん、たまたまスカウトが見に来た時に運良く活躍するケースもあるでしょう。こういう人のことを「持ってる人」と言ったりしますよね。これは「特別な運を持ってる人」という意味で使われるケースが多い気がしています。

つまり何かしら成功する時に「運」と「実力」の両方が必要だという感覚はほとんどの人が持っていると思います。

運と実力
運と実力

ここで実力というのが足りない時に実力を伸ばし、目標に向かっていくのが「努力」と呼ばれる行動です。 逆に「運」というのはこちらが調整できないモノを指している事が多いです。

努力のイメージ
努力のイメージ

そういう「運」と「実力」を対比した上で、上のイメージで言えば、運の要素に頼らないくらいの実力を身につければ関係ない、という話が「運は実力に寄ってカバーできる」という言説です。

運と実力のイメージは正しいかどうか

さて、実力と運という対比でものを見ていると上のイメージは理解しやすいのですが、実力と努力と運というのはそう簡単なイメージで片付けられるものでしょうか。 最初から実力を強く身につけている人は少なく、何かしら努力して手に入れてきていると思います。ただ努力するという機会に恵まれているという事は、そもそも「こちらが調整できないモノ」である事が多いと思います。家庭環境が裕福だった事によって私立受験する人は多いと思いますが、家庭環境が裕福という事が既に大きいアドバンテージになっています。これは最初から調整できるものではありません。同じようにソフトウェアエンジニアに多い、「家に最初からパソコンがあった人」というのもそういう機会に恵まれた人だと思っています。

つまり、努力と実力と運といった時に結局努力できる機会も「運」によってもたらされているものなのです。

実力部分も運から来ている
実力部分も運から来ている

そういう事を教えてくれた本が、「実力も運のうち、能力主義は正義か」という本です。

www.amazon.co.jp

この本の中では努力を誇ることを「エリートの傲慢」と痛烈に批判します。著者は現代社会の能力偏重な状況を良いと思っていないわけです。本には「そういった能力偏重主義は不平等さを加速させる」という話が出てきます。ただ間違っちゃいけないのは「努力を誇ること」を批判しているのであって「努力そのもの」を批判しているわけではないです。

なんで努力を誇ってはいけないのか

「努力を誇る」というのは成功者の特権のように扱われています。努力したこと自身が大変だったということは理解しますが、実際には努力している影で支えている人たちが居ます。また努力できる機会にすら恵まれなかった人たちも居ます。上述した本の中には「そういったエリートの傲慢さが分断を産み出している」という非難をしています、これが昨今のコロナのワクチンクーデターや brexit、トランプ政権誕生といった話につなげています。

この本の中で著者は「社会的に評価される仕事の能力を身に着けて発揮すること」は否定していませんし、むしろ推奨しています。一方で、「努力を称えて努力した人としなかった人に優劣をつけること」は否定されています。

人はスタート地点からして不平等であり、その中で実力をつけながら努力することはどうあれ必要です。ただ努力を称えることは能力主義に繋がります。能力主義は「不平等の存在はもはや是正できないため、不平等を正当化する」事で成長を刺激します。ただこれが行き過ぎてしまうと不平等による格差が生まれ、分断が生まれてしまう事を著者は危惧しており、それを社会的に変えていかなければいけないという話に展開しています。

この本自体は割とそういった社会に対してのテーマを語っているため、個人個人でやれることは少ないのですが、一つあるとすると「努力をしたことを誇る」という事をやめて、「周囲の人達へのサポートに感謝し、運が良かっただけである」という自覚をする事ができることなんじゃないかと思いました。

運の利益率

もう一つ本を紹介します。ビジョナリー・カンパニーZEROという本の中に、企業や経営者がどうやって成長していくかという話があります。

www.amazon.co.jp

この本の中で「運の利益率」という話が登場します。「偉大な企業の中で多かれ少なかれ運に恵まれた企業はあったものの、 "運だけ"で成長したという企業は存在しなかった」という話が出てきます。言い換えると「運という要素をどう活かしたか」が経営ひいては企業の成長に欠かせない要素であることを挙げています。

この事を運の利益率という言葉で語っていました。これは個人においても同じことが言えるのではないでしょうか。要は恵まれた運をどうやって活かすか、運の利益率を挙げていくには結局努力は必要なのです。これらの本は努力を否定しているのではなく、むしろ推奨していて、ただし分断を産まないようにしないといけないのです。

まとめ

運か実力かといった議論が若干的を外した感じでお互い議論しているのを見てきました。言いたいことは結局シンプルで、努力はしないといけないけど、成功したとしても運に恵まれたことを感謝するべきという話ですね。 昨今の親ガチャなどにも通じる話かもしれません。親が良かった・悪かったというのが運の要素によって左右されていることをガチャというソーシャルゲームの要素で表した皮肉な意見ですが、分断が産まれている現状においてはこういった話も大いに有り得そうに思えます。

自分は「努力は努力できる機会に恵まれた人の権利であり、最大限に利用できる範囲で利用する。そのおかげで成功したとしても自分の努力のおかげとはみなさず、運の要素によってもたらされたものである」という考え方を持っていこうと思っています。


2021年振り返り

$
0
0

はじめに

今年も書きました。毎年書くことで去年との差分を知れるので、良いですね。来年もちゃんと書きます。

yosuke-furukawa.hatenablog.com

会社

去年は "マネジメントとシニアソフトウェアエンジニア"で4年目になったんですが、今年はそれに経営的なロールまでついて 5 年目突入しました。 大変でしたが、やりがいがある仕事につけて楽しいです。

qiita.com

この手の経営とマネジメントとシニアソフトウェアエンジニア的な話をするタイミングももらえてよかったです。

社内アドベントカレンダーもどちらも活況でした。

adventar.org

qiita.com

優秀なメンバーに恵まれてるなとつくづく感じます。

また、ニジボックス会社内で研修として JavaScript/TypeScript/Next.js 研修をメンバーと一緒にやることができました。

www.wantedly.com

www.wantedly.com

こういう研修が巡り巡って、リクルートの社内研修にも活かされていたりします。

recruit-tech.co.jp

そういえば研修ではブラウザの話も同様にしました。

speakerdeck.com

社内イベント

R-ISUCON 2021 を開催しました。

blog.recruit.co.jp

こういったイベントは完全にオンラインに変わりましたね。オフラインでやってた時のワイワイ感をいかにオンラインでも出すかという所にフォーカスされてきている気がします。 一応このイベントではオンライン飲み会用のケータリングサービスである nonpi を使って食事を配ったりしました。

イベント

JSConf.jp 2021

JSConf.jp 2021 を開催しました。

jsconf.jp

一年越しの開催で、だいぶ難しい形になってしまいましたが、非常に活況だったかなと思っています。 すべてのトークYoutubeにスタッフの方が分割してあげてくれています。内容を後から確認できるところも含めて非常に面白いですね。

www.youtube.com

また、発表は StreamYard 、聴講は Youtube Live と Spatial Chat という二段構えで実施していました。もう少し Spatial Chat 側を盛り上げたかったなと思いましたが、また来年やる時にはちゃんと調整します。 一方で非常にオンラインは後片付け等が少なく、スタッフの負担も軽いので、これをどうするかは今後要検討ですね。まだ探り探りですが、来年もオンラインのがいいかなーと個人的には思っています。

東京 Node 学園

3回ほどオンラインで開校しました。

Node学園 35時限目 オンライントライアル - connpass

Node学園 36時限目 オンライン - connpass

Node学園 37時限目 オンライン - connpass

来年もちゃんとやります。オンラインになることでこちらの負担がだいぶ軽くなりましたね。一方でずっと続けてしまうと逆にリアルタイムで見なくていいやと思う人も増えそうなのでそれはそれで避けたい気もしていて、複雑です。

登壇系

Engineer Career Study

スペシャリストになる覚悟を話しました。だいぶ偉そうなことを書いていますが、何度もあの後色んな所で話す機会をいただき、参考になったというフィードバックをもらえてよかったです。

speakerdeck.com

Developer eXperience Day

フロントエンドエンジニアの開発体験について話しました。この時のトークも面白かったですね。他にビッグネームな人たちがたくさんいたことを覚えています。

speakerdeck.com

Node 学園 37 時限目

WHATWG Stream の話をしました。何気に Node.js の発表久々だったかも。

speakerdeck.com

Fastly Yamagoya

性能に関する考え方の話をしました。尊敬するエンジニアが Fastly には多いので、ちょっと張り切りました。

speakerdeck.com

この時に頂いたパーカーがマジですごかった(裏起毛で温かい、この時期外にパーカーだけで出てもなんとかなる)。重宝しています。

Chrome Advisory Board

Chrome諮問委員会というボードのメンバーなのですが、そこで英語で JSConf.jp の話やどうやってコミュニティを運営しているかを発表させていただきました。

speakerdeck.com

英語で登壇したのですが、割と良いフィードバックをもらえたので、ものすごくありがたい機会でした。割と時間がなかったのですが、英語毎日勉強してたおかげで資料を作る時間はそこまでかからなかったです。

技術顧問勉強会系

この他にも技術顧問で勉強会を開いているのですが、以下の奴が良いフィードバックをもらえました。

レビューの仕方

speakerdeck.com

フロントエンドテストプラクティス

speakerdeck.com

書籍を書く

Web+DB Press vol.123 Next.js 特集

gihyo.jp

Next.js 特集を記述することができました。 @takepepe さんと共著しました

Node.js

CodeGrid

今年も CodeGrid で記事を書きました。

www.codegrid.net

www.codegrid.net

ブログ

急遽アドベントカレンダーを追加して3つくらいエントリーを書きました。問題提起系を1エントリ、あまり知られていない新機能系が2エントリ。

yosuke-furukawa.hatenablog.com

yosuke-furukawa.hatenablog.com

yosuke-furukawa.hatenablog.com

Rust

新しい言語を覚えようということで Rust を覚えました。楽しいのでやり続けます。なんか別なことにも使ってみたいですね。

yosuke-furukawa.hatenablog.com

競技プログラミング

Advent Of Code を完走したのと LeetCode を 800 問以上解きました。

LeetCode の戦績
LeetCode の戦績

Advent Of Code 2021
Advent Of Code 2021

yosuke-furukawa.hatenablog.com

英語

DMM英会話マスターレベルになりました。Happy almost New Year!!みたいなことを今日は言い合って終わりました。喋れるようになると色々とネタが出てくるので楽しいです。もっと使いたいですね。

DMM 英会話マスターレベル
DMM 英会話マスターレベル

ELSA Speak もやってました。

プログラミング

なんか競技プログラミングで毎日コードを書いてましたね。

github all green
github all green

とはいえ、もう少しなんか実のある事もやりたいんですよねー。できてないけど、来年はもう少しこの辺りをちゃんとアウトプットします。

まとめ

  • 会社でエンジニアコミュニティづくりをやってます
  • 登壇も頑張った、だいたい毎月一個くらいはなんかやってた気がする
  • 書籍も書いた。 Web DB Press は非常に良かった
  • Node.js 系の記事 5 件位書いた。
  • 新しく Rust を学んだ。
  • 競技プログラミングで LeetCode 800問解いたのと AoC完走した
  • 英語で DMM 英会話マスターレベルになった。

振り返り

去年目標にしてできたこと: - 新しい言語の習得 - ブログその他のアウトプット

できなかったこと: - 新しい言語でなにか作る

来年の目標

来年の目標: - なにかライブラリやアプリを作る - 人に教える系の何かをちゃんとやりたい - もう少しブログのアウトプットを増やす

最後に

今年も一年間インプットもアウトプットも走り抜けた気がしています。今は少しゆっくりしたいかなーと思っています。 もう少ししたら新年です。個人的には新年になる前の数時間がものすごく好きで、まったり過ごそうと思っています。家族や友人とゆっくり過ごして新年リフレッシュしたいですね。 来年もまたよろしくお願いいたします。

Unhandled Rejection の考え方

$
0
0

はじめに

twitter上で議論になっていたネタを本人の許可を得て記載しています。

実はこの話は会社の中でも一回議論になったネタなんですよね。僕も microtask と呼ばれる Promise キューイングの仕組みとイベントループでタスクをハンドリングする仕組みの両方が組み合わさった時に Unhandled Rejection が起きる理由がわかりにくくなるなーと思っています。誤解していた様子でしたが、それもうなずけます。僕も誤解してました。今回はその理由を補足しながら解説してみます。

Unhandled Rejection

Promise には Pending, Fulfilled, Rejection の3状態があります。Pending は処理中の状態を示し、 Fulfilled と Rejection は Promise が成功したか失敗したかという2つの状態を指しています。 Rejection だった時に catchして処理が行われたのであれば、 Rejection はハンドリングされたものとなります。誰も catch しなかったら Rejection はハンドリングされなかった状態になります。この状態を Unhandled Rejection と呼びます。簡略化のために下記のコード例を記載します。

main()

async function main() {
    await fail(); // Unhandled Rejection
    console.log('Successfully handled')
}

async function fail() {thrownew Error('failed')
}

4行目で throw されてる例外を誰も catchしていないので、 Unhandled Rejection 扱いになります。try-catch.catchなどの方法で catch すれば Unhandled Rejection 扱いではなくなります。

じゃあこのときはどうなるのか

// https://gist.github.com/shqld/be21d1e82cec4dccfc933477f9b60356

main()

async function main() {const tasks = []

    tasks.push(fail())

    // Commenting out this line makes it work
    await new Promise((resolve) => setTimeout(resolve, 0))

    tasks.push(fail())

    await Promise.allSettled(tasks)

    console.log('Successfully handled')
}

async function fail() {thrownew Error('failed')
}

該当のスクリプトですね。この時も Unhandled Rejection になります。 main 関数の3行目の await new Promise((resolve) => setTimeout(resolve, 0))で起きます。一見うまくいきそうに見えるから不思議ですね。しかもこのコードをそのまま Chrome等で実行してみると特にエラーも何も出ずに成功したかのように見えます。

しかし、 Node.js/deno のときは Unhandled Rejection で process が落ちます。

Error: failed
    at fail (/private/tmp/t3.js:10:11)
    at main (/private/tmp/t3.js:5:11)
    at Object.<anonymous> (/private/tmp/t3.js:2:1)
    at Module._compile (node:internal/modules/cjs/loader:1097:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

ただし、Chromeであったとしても、 ログのレベルを verbose に上げた状態で上のコードを実行すると Unhandled Rejection が発生していることが確認できます。

ログレベルで verbose まで上げておく
ログレベルで verbose まで上げておく

Unhandled Rejection エラーになる (Chrome)
Unhandled Rejection エラーになる (Chrome)

一旦スクリプトを細かく分割して整理しつつ、中でどんな事が起きてるのかを解説します。

もう少し細かく

Unhandled Rejection が起きるといいましたが、一応条件があります。それは Promise が内部的に利用している microtask キューと呼ばれるキューが空になったタイミングで Rejection 状態で handle されてない Promise があった時です。 また、 microtask キューは "queue"と名付けられているだけあって、 FIFO (First In First Out) な動きをします。つまり、queue にタスクが積まれるのですが、それは先頭から消費されていきます。

function main() {
  Promise.resolve().then(
    () => console.log("promise first") // キューに積まれる
  ).then(
    () => console.log("promise second")  // キューに積まれる
  );
  console.log("show first"); // 同期的に実行される}

main();
// show first// promise first// promise second

microtask queue の動き
microtask queue の動き

microtask キューが空になったタイミングで Rejection 状態になると Unhandled Rejection になります。

function main() {
  Promise.resolve().then(
    () => {thrownew Error("failed") }// rejection 状態になる
 ).then(
    () => console.log("promise second")  // rejection 状態なので then は通らず、ここは queue に積まれない
  ); // taskqueue が空になった状態で Rejection のままになる。このままだと Unhandled Rejection になる
  console.log("show first"); // 同期的に実行される}

main();

Unhandled Rejection 状態
Unhandled Rejection 状態

元のスクリプトを見てみる

この状況で元のスクリプトを確認してみましょう。まずは setTimeout の Promise が無い状態だとどうなるかを考えてみます。

main()

async function main() {const tasks = []

    tasks.push(fail())

    // Commenting out this line makes it work// await new Promise((resolve) => setTimeout(resolve, 0))

    tasks.push(fail())

    await Promise.allSettled(tasks)

    console.log('Successfully handled')
}

async function fail() {thrownew Error('failed')
}

この状態だと、 Promise.allSettled関数が throw したエラーを内部的に catchしてくれます。 Promise.allSettledはすべての Promise のタスクが Fulfilled にせよ Rejection にせよどの状態になったとしても終了するまでハンドルする関数です。この動きをさせるために内部的には Promise の例外をキャッチしてくれています。なので、この状況だと Promise は Unhandled Rejection にはなりません。普通に終了します。

Promise.allSettled により、Unhandled Rejection にならない
Promise.allSettled により、Unhandled Rejection にならない

ただ逆に最初の tasks.push(fail())で止めたらどうなるでしょうか。

main()

async function main() {const tasks = []

    tasks.push(fail())
}

async function fail() {thrownew Error('failed')
}

この場合は Unhandled Rejection になります。 microtasks のキューが空になった状態で fail() 内で Error をスローしてそのままだからです。

fail() が Throw してるので Unhandled Rejection
fail() が Throw してるので Unhandled Rejection

さて、 もう一回もとに戻してみましょう。今度は該当の setTimeout を入れてみます。

main()

async function main() {const tasks = []

    tasks.push(fail())
    
    await new Promise((resolve) => setTimeout(resolve, 0))

    tasks.push(fail())

    await Promise.allSettled(tasks)

    console.log('Successfully handled')
}

async function fail() {thrownew Error('failed')
}

さらにもう少し説明を追加します。

Node.jsのイベントループモデルと一緒に解説します。下記のように EventLoop でのタスク管理と Promise でのタスク管理 (図で microtask queue と書いたところ)に分かれています。

EventLoop とタスク管理
EventLoop とタスク管理

緑色の箱で示したフェーズと青い箱で示した nexttick queue / microtask queue が存在しています。各フェーズにもコールバックを貯めておくキューが存在します。それぞれ赤い線で nexttick queue や microtask queue を適宜呼び出し、フェーズが終了した段階で microtask queue に積んだり、 nexttick queue に積んだりします。

上のコード例で言うと、 await new Promise((resolve) => setTimeout(resolve, 0))の時に setTimeout をしているので、このタイミングで、 EventLoop の Timer のコールバックにタスクが積まれます。この時点で一旦 EventLoop 側に Task が移ります。

setTimeoutしたタイミングで Timer に処理が移るも、そのタイミングで microtask queue が空であると判定され、rejection したままになる
setTimeoutしたタイミングで Timer に処理が移るも、そのタイミングで microtask queue が空であると判定され、rejection したままになる

この際に microtask queue は一旦空になります。空になったタイミングで、 Handle されていない1つ目の fail()関数が Error を Throw しているため、 Unhandled Rejection 扱いになります。

さぁ、わかりにくいですよね。非常にトリッキーです。Promise の処理が行われた後に 「EventLoop 側の処理機構に移る」という言い方をしていますが、この際に別なフェーズに処理が移ってしまいます。中の青い箱で示したキューから緑のフェーズに処理が移るタイミングで中の Promise のキューがあれば即座に実行されます。その結果、 fail()内部でエラーが throwされているので Unhandled Rejection 扱いになってしまいます。ちなみに nexttick queue があればそちらを優先します。

setTimeout の内部が呼ばれた時点での microtask の状況
setTimeout の内部が呼ばれた時点での microtask の状況

同じ task の中で同期的に実行していれば (Timer の処理がなければ) Promise の Unhandled Rejection な状況にはなりません。同じタスク内の最後で allSettledなどで catchしてくれていれば、意図通りに動きます。しかし、その途中で setTimeout, setImmediate などで Timer の処理を動かしたり、 fs でファイルを操作したり、 http リクエストしたりするとその段階で EventLoop の各フェーズに処理が移ります。この時点で一回 microtask queue を実行します。この時に Rejection 状態になってしまっていると、 Unhandled Rejection扱いになります。

わかりにくくしているポイント

実は Node.js / deno も Chromeもどちらも Unhandled Rejection に一回なっています。ただ Chromeの場合は一回 warn として処理された上で、次の tick に行った場合にそこでちゃんとハンドルされているのであれば、 verbose ログに出すだけで、エラーになりません。

一方で Node.js/deno は一回でも Unhandled Rejection になった場合はエラー扱いになり、落ちます。この動きの差はわかりにくいものの、仕様通りの挙動になっています。

Promise の ECMAScript上での仕様は Unhandled Rejection 状況になったことを Platform 側に伝えるところまでが明記されています。ただ伝わった後どう Unhandled Rejection を処理するかは Platform 側で自由に決めて良いことになっています。 Chromeのようにクライアントサイドのブラウザであれば、Promise が例外だったとしても warn 扱いで留めた上で、次のイベントループで処理されていればログレベルを下げて表示する、という対策もありでしょう。逆に Node.js/deno はサーバサイドで未処理の例外が発生した状況のままなにも通知せずに動いてしまうとリソースの開放漏れがあったり、例外が発生してレスポンスを返せていないのに正常に動作してるかのように見えるといった、プロセスが落ちるよりもさらにひどい状況になり得ます。そのためプラットフォームごとに動きが異なる、という仕様になっています。非常にややこしいですね。。。

ちなみに Node.js の場合、デフォルトでその動きになっているものの、変更することも可能です。 --unhandled-rejectionsという起動オプションに対していくつかの設定が用意されています。

  • none UnhandledRejection が起きたことを警告はするが、特に詳細は表示しない。 Chromeとほぼ同じ扱い
  • warn Unhandled Rejection が起きたことを詳細に警告ログに残す
  • throwデフォルトの挙動、エラー扱いとして unhandledRejectionイベントを process に通知、何もハンドラがなかったら uncaught exceptionと同じ扱いとして、エラーにする。
  • strictハンドラの有無に関わらず、uncaught exceptionと同じ扱いとして、エラーにする。
  • warn-with-error-code Unhandled Rejection が起きたことを詳細に警告ログに残しつつ、終了時に exit code を 1 にする(異常終了扱いする)。

これらの扱いをうまく使えば Chromeと同じ挙動にすることも可能です。ただし、Node.js/deno といったプラットフォームが提供しているデフォルトの挙動は安全サイドに倒したものになっており、これを意図を考慮しないで変更することは個人的にはオススメしません。プラットフォームのデフォルトの動きがなぜそうなっているのかを把握した上で変更するのであれば、良いと思います。

まとめ

Unhandled Rejection の状況について解説しました。 Timer や fs といった EventLoop の機構と Promise の機構が混在しわかりにくくしている上に、Chromeなどのブラウザは Unhandled Rejection なエラーを verbose でしか出していない所が、余計わかりにくくしています。ただ、それにはちゃんとした理由があるという所まで分かっていただけると幸いです。

個人的には Unhandled Rejection 状態になった時の Promise の仕様がプラットフォーム依存になっているところがもう少し周囲が把握できているといいなとは思います。 Chromeと Node/deno だけではなく、 Firefoxとかも動きが違うので、本当に難しくなっています。 Node.js に関しては複数の動作パターンを用意していますが、 Unhandled Rejection 時のデフォルトの挙動をどうするべきか議論が白熱したため、このようにオプションできりかえられるようになっています。

今一度 Unhandled Rejection をプラットフォームがどう実装しているのかは確認してみると良いと思います。

参考資料

2月に発表したいくつかのスライド

$
0
0

2月にいくつか発表したので記録として残しておく。

開発組織の持続可能性について

speakerdeck.com

A Philosophy of Software Design前半

speakerdeck.com

どちらも最近読んだ本をベースにしている。

読書をベースにした事で情報としての新鮮さは失われているものの、普遍的な話はできている気がしている。

毎月2-3冊を読めていることは非常に効果的だと感じるし、英語の読書を習慣づけていこうと思う。

こういうのも記録として残しておこうと思ってブログにもポストしておく。

Node.js / Deno の徹底討論を Node 学園で行いました。

$
0
0

3/17 に徹底討論という形で denoland の 日野沢さんをお呼びして Node学園で徹底討論という形で討論しました。

いくつか面白いトピックがあり、参考になると幸いです。

少しだけ紹介します。

ESM vs CJS

ESM と CJS の対応が Node.js がグダグダだと思っていると言われた点がありました。

討論内でも Twitterを見ていても、そういう意見があって、意外だなーと思いました。

もちろん現実的に ESM / CJS の移行は今はまだ過渡期です。既存のエコシステムを壊さないために CJS との相互運用性を持ち込むしかなかったという状況においては現時点の拡張子やpackage.jsonでの指定で識別可能にし、既存のエコシステムを壊さないように乗り切ったというところはむしろ評価していました。この移行措置が無く、もしも Node.js v20 からは ESM でしかロードできないと言われた場合はどうなっていたでしょう。多分誰も v20 に上げられず、結局 v19 がLTSが終わるまで使い続け、LTSが終わったあともどこか (Red Hatとか) が 3rd party製の Node.js v19 のメンテナンスを名乗り出て、サポートし続けるといった悲劇が生まれていたように思えます。エンタープライズはそれで乗り切ろうとかそういう議論が行われていたでしょう。それがなかったとしても、 このモジュールはESM, このモジュールはCJSといったように自分で見分けるとかそういう事はやってられないと思います。結局 TypeScript でずっとトランスパイルして、 Node.js でも ESM ではなく、しばらく CJS で使われていたのではないでしょうか。

そう思うと非常に今の移行過渡期の状態というのはそこまでグダグダではないと思っています。

でもいま現実的にきついんだけど

それはそのとおりですが、それは Node.js だけのせい、というわけでもないように思えます。結局 ESM は CJS でロードされた JavaScriptとは互換性がないものとして設計されてしまっています。むしろ Node.js は被害者で、そこで一定の互換性をプラットフォームとして保ちつつ、新しい仕様に追従していく、というのは非常に根気のいる作業です。個人的には新しく2つめのロード方法を作り直してしまって後はユーザーが選べば良いという位割り切ってしまってもよかったように思えますが、それをせず、どちらから呼ばれても動くようにするために、色々な仕様を TC39 とも調整し、 WHATWG側にも話を聞きに行ったりしつつ、最初のPRから数年掛けて今の相互運用できる状況にまで達成したことは非常にすごかったな、、、と思っています。

xxx feature is bad.

よく JavaScriptを見ていると、この機能が駄目だとか、この機能がイケてないという話をよく目にします。これだから JavaScriptは、、、となってしまう気持ちもわかりますが、ちょっと調べてみると奥になんでその仕様になっているかの背景が結構語られています。その内容には歴史的な背景が紐付いていることが多いです。「ウッ」となって文句を書いたり言う前に何故そうなのかを調べてみると面白いと思います。

こういう話をたくさんしました。ぜひぜひ上の動画を見てみて下さい。

Node.js の原罪

$
0
0

Intro

ちょうどタコピーの原罪が流行ってるのでこのタイトルにしたけど結構気に入ってる。

d.potato4d.me

この話を読んでの感想とここまで大きくなった Node.js の振り返りをしようと思う。

どんなプログラミング言語であってもみんなから使ってもらって開発者をハッピーにしたいと思ってる。ただ最初は良かったと思ってた機能がなんか古臭くなったり、他にクールな機能を持ったものが登場したことによって徐々に飽きられていき、最終的に他の言語に乗り換えられる。

まぁどんな言語も同じだと思う。C言語だって生まれた当初はすごくクールでみんなをハッピーにしてた。今丁度「戦うプログラマー」を読んでるが、C++が出てきて、周りのエンジニアが C++を使おうとするシーンが出てくる。そこで、「あんなの使って何が良いんだ、Cで十分だろ」とWindows NT 開発リーダーのデーブカトラーが言ってたりする。ちょうどその頃(正確には NT リリースの少し後)に JavaScriptも生まれている。

タコピーが特定の小学生を幸せにしたいと願ったように、プログラミング言語も開発者を幸せにしたいのだ。

それで言うと、 Node.js がここまで成功したことは喜ばしいことなんだと思う。個人的には 2013-2014年 くらいに Node.js やってますというと、「あんなの流行らないっすよねぇ」とか面と向かって言われたり、「いやーあんなネストが深くなる言語よく使ってるねー」とか言われたりしてたのが嘘のようだ。今はたしかに様々なクラウド環境、色んな場所で Node.js をサポートしてくれるようになっている。一方で何かが流行るという事は別な何かが廃れるという事でもある。 potato4d さんの書いた

いつから僕らは Node.js しか使わなくなったのか。
あれだけ話していた Rails などの多くの Web 技術にときめかなくなったのか。

これは Node.js が流行ったことで元々あった技術を振り返り「これでいいのだろうか」と自問をしているんだろうと思う。基本的にどんな技術であっても何かが流行る時は何かが廃れる時なのだ。

逆に言えば Node.js だっていつか廃れる側に入る。

これはまさに原罪なのではないか。永遠の命を生きることができたはずのアダムとイヴが知恵の実を食べた事で人はいつかは必ず死ぬようになったのと同じく、プロダクトやテクノロジーもいつかは必ず廃れるのだ。

つまり、「呪い」や「原罪」と書いたが、実はなんのことはない、数々のプロダクトやテクノロジーでは日常で起きている流行り・廃りという普通のことなのだ。

何がこの憂いの原因か

一方このポエムの中に流れる憂いのようなものもわかる。

真綿で締め付けられるように少しずつ、でも確実にロックインされていて、いつかそれの終焉が来た時に、自分の手元にあるものが「その技術」しかなかったらどうしようという焦燥感が感じとれる。 ものすごくよく分かる。ちょうど potato4d さんとは一度 1on1 を公開でしていたが、その時にも漠然とした焦燥感を感じた。

www.youtube.com

僕も同じような焦りはある。特にNode.js / フロントエンドの領域はどんどん作成するのが簡単になっていて、ちょっと前までは専門知識が必要になっていたことが、半年後には常識になっていたりする。コモディティ化してきている状況において、人材としての希少価値だったり、専門性みたいなものを追い求めようとすると常に最先端に居なきゃいけない気もするし、一方でそれだとロックインがどんどん進み、いつかこの技術が誰にでも使えるようなものになってしまった時、専門家としての自分の価値がなくなってしまう気もする。

ちなみに Deno に変えるっていう意見も見たが、あまり同意していない。徹底討論したときも思ったが、 Node.js と Deno は非常によく似た機能をどちらも有しており、多少の差はあれど、どっちも同じような所に落ち着きそうに思っている。 Deno は Node.js にとって破壊的なイノベーションではなく、持続的な改善だと認識しており、両者でキャッチアップして変わらない世界観の提供になると思っている*1。なのでまぁ、廃れる時は現代のフロントエンド技術そのものと共に両方廃れると思っている。

yosuke-furukawa.hatenablog.com

じゃあどうするのか

個人開発者としては開き直って使い続けててもいい。ここまで成熟して広まってしまうと延命措置もすごく長くなるし、どこかしらで使い続けられるだろうと思う。すぐに無価値になることはない。 一方で別に新しい技術を追うのも良い。どうあれ一個の技術だけで何でもできるような時代ではない。全然別な新しい技術が新しい革新を産んでいるんだな、と興味を持ったらやってみればいい。

正直どっちでもいいんじゃないかと思っている。

いずれにせよ、 Node.js などの技術 (Deno 含む) が現在の開発者を幸せにしているのであれば、ずっと追ってきた自分としては嬉しい限りだ。ここまで広まったのであれば、今後廃れたり、飽きられて終わってしまうならそれもまた良しと思っている。自分はまだ使えるうちは使っていくし、引き続き新しい機能が出たら紹介するといった動きを継続してやっていきたい。一方で、新しい技術を学ぶこともやめるつもりはない。 Go が流行ってる時に Go をやったり、 Rust が流行ってる時に Rust をやったりしてきた。なにか新しいことを吸収しては吐き出すという事を常にやろうと思っている。

こういう流行りにも乗っかりながら、今の自分の強みとなる技術を磨き続ける事が僕の中での最適解だ。

Node.js 年表

それはそれとして、メンバーとこの potato4d さんのブログ記事について色々話していたら、 Node.js でこういうポエムが書かれるのって昔と比較すると信じられないねっていう話になり、俺たちずっと追ってきたけど、良かったよねっていう話をして懐かしさに浸ってしまった。 今 Node.js が流行っている理由の一つは React や TypeScript による強固な仕組みが整ったことが要因だと思っているが、それが流行るまでの歴史的な年表作りたいなと思ったので、書いてみることにする。

出来事
2009年春 Node.js プロジェクト始動、 Ryan Dahl が v8 にネットワーク I/O を組み込んだアプリケーションを作る。最初の名前は netv8 だった、(微妙に .Net感もあったからなのか知らないけど) rename されて Node.js になる。
2009年秋 Node.js を jsconf.euで初公開、一気に注目を浴びる。
2010年 Node.js が Joyent に買われる、 Joyent は Node.js を中心とした PaaS を作っていきたいという思いがあった、 Node.js は安定した収益のもとでコラボレーションしたかった。両者の利害が一致して買収された。
2011年 破壊と創造の時代、 API作っては壊し、破壊的変更が起きまくっていた。色々なライブラリが雨後の筍のように現れ、使われていった。 express / socket.io などがその筆頭。この頃に Node 学園祭も実施される、 Ryan Dahl が日本に来てた。 Guille (Vercel CEO)も来てた。
2012年 いきなりここまで来たけど、破壊と創造の時代を終わらせて、Ryan がリーダー辞めますって言い出した。2代目リーダー (isaacs) に権限を移譲
2012年 Node 学園祭で isaacs が新しい Stream とかの構想を発表、 substack が small module の話と unix philosophy の話をしていた。この頃から小さいモジュールをたくさん作って組み合わせようぜ的なノリになる。
2013年 2代目リーダーを中心に Stream 周りやエラーハンドリング (domains など) 周りで色々と新しいものが追加される。
2013年 この頃くらいから grunt / gulp / yeoman などを筆頭に Node.js がフロントエンドのツールとして使われていく。
2013年 ちなみにこの頃に Rendr というアプリケーションフレームワークが少しだけ脚光を浴びる、今の next.js などがやっている、ブラウザでもNodeでも動く、 Universal な フレームワークがここで登場。この頃から Node 学園祭で取り上げてた。
2013年 古川が 2 代目 日本 Node.js ユーザーグループ代表になる
2014年 年初 isaacs 氏がここに来て npm をちゃんとした会社にする、と言って、リーダーを抜けて 3代目に引き継いだ (TJ Fontaine)
2014年 この頃くらいから browserify などを筆頭に Node.js がフロントエンドの主にモジュール解決周りで使われだす。
2014年 issue / PR がめちゃくちゃ停滞する。APIの安定を求められたことといきなり引き継いだリーダーとして導くのが3代目では難しかったように思う。ここに来て、 Joyent という会社の中だけでリーダーを選出していたことが仇になった感じがある。いわゆる GitHubで "Is Node.js dead ?"って聞かれてしまうようなレベルで特に何も改善がないままズルズルと放置されてた
2014年 AWS Lambda が Node.js サポートで初期リリースされる。
2014年 年末 Node.js が fork されて io.js が誕生する。 yosuke-furukawa.hatenablog.com
2015年 年初 BDFL を中心にした Node.js と コミュニティを中心にした io.js で分断が起きる、あっという間に v8 が最新になり、次々と新しい機能が出現した
2015年 io.js と Node.js の再マージ案が出る、このまま分断されるのかどうなるのかを見守っていた自分としてはマージされる運びとなり、一安心、 YAPCで顛末を発表したどうしてこうなった? Node.jsとio.jsの分裂と統合の行方。これからどう進化していくのか? - Speaker Deck
2015年 ES2015 が出て、 babel が支配的になり、トランスパイルに抵抗がなくなる。 React などのフロントエンドライブラリが大きく躍進する。この頃色んなカンファレンスどこも JavaScriptの話ししてた気がする。
2015年 electron とかが流行ってた。自分もなんかアプリ作って出したりしていた。
2015年 isomorphic tokyo meetup 開催、 Next.js とかが話されるよりも前に、クライアントとサーバで同じ処理が動くようにすれば Node.js ってもっと面白くなるんじゃね?みたいな話ができてた。
2016年 npm が勝手にライブラリを消したことで leftpad 問題と呼ばれる大きな問題が起こる、この頃から「俺たちなんかたくさんライブラリに依存しすぎじゃね?」みたいな雰囲気になる。
2016年 ES Modules と CommonJS との相互運用についての議論が起きる。 yosuke-furukawa.hatenablog.com入れる必要ある?いらなくね?いや、いるだろ、ブラウザと同じコード動かすかもしれないんだし、差は少ないほうがいい、みたいな議論がたくさん起きて、一回 Issue がクローズされる。その後、 TC39 や WHATWGを巻き込んだ議論になっていく。
2016年 この頃にはもう browserify じゃなくて webpack とかでやるのが普通になっており、 React / Redux とかアプリケーションを作る方法も変化しながらも確立されていく。僕はこの頃、 React で SSRとか BFF とか言ってた気がする。今でも言ってるか。この年の ISUCON 本戦の問題が確か SSRだった気がする。
2016年 Next.js がリリースされる。微妙にクエリーパラメータでしかルーティングできなかったので、この頃はそこまで流行ってなかったように思う。ただ React の SSRを基本にしたボイラープレートは雨後の筍状態でたくさんあった。
2017年 Node.js に HTTP/2 を入れたり、 async-await が入ってたくさん色んな改善が進む
2017年 React で SSRやるとどうしても Node.js のプロセスが圧迫されるので、パフォーマンス・チューニング系の話が多かった気がする。 async_hooks などの非同期で状況をトレースできるようにする仕組みが入ったり、 Node.js にもパフォーマンス計測系の活動が行われてた気がする。
2017年 ESModules の解決が Node.js でできるようになった。一方でブラウザ側はトランスパイルする事で形だけ ESM を使っているような状況であり、 HTTP/2 が入ったり色々な改善がブラウザに起きていたものの、 ESM が普通に使えるような状況ではなかった。逆に Node.js で先に実装が進んでしまった。
2017年 2016年くらいから TypeScript が VS Codeとともに流行る。3rd party モジュールの型を解決する方法に決着が付いたことも大きかったように思う。
2017年 Node.js内でWeb Community との親和性を上げていこうという動きがもっと活発になる。 async-awaitなどのパーツが揃ってきたことで、どんどんフロントエンド開発者にも使われるようになり、なるべく APIを揃えようという流れに。
2018年 Deno が jsconf.euで発表される。 Node.js の10の後悔という内容でめちゃくちゃバズる。 yosuke-furukawa.hatenablog.com TypeScript で開発されており、ものすごく注目を浴びる。
2018年 Node.js の性能やセキュリティ面、デバッグ可能性をあげていこうというものすごく下回りの整備がされていく。 http parser が llhttp になって、なんじゃこりゃーすげーってなったのを覚えてる。
2018年 npm が資金難みたいな感じでなんか色々と開発者が辞めたり、レイオフにあったみたいな話が出てくる。ゴタゴタする。
2019年 npm vs yarn 抗争激化、どっちも zero install とか言い出す。結果は・・・
2019年 npm のレジストリを管理していた人たちが entropic という分散管理レジストリの構想を出す。結果は・・・
2019年 React に hooks が入る。 Redux とかの状態管理周りのやり方が中央集権的なものからコンポーネントの状態へ、分散独立的な流れになった。
2019年 Next.js が動的にパラメータを受け付ける書き方ができるように改善される、他にも Googleのメンバーから色々とコントリビューションを受け付けるようになり、一気に大規模化する。自分もこの段階で Next.js を再評価した気がする。
2019年 jsconf.jp 第一回目開催
2019年 Node.js 10周年、ここらあたりで、「あーなんか Node.js って前よりも普通に受け入れられるようになってるんだなぁ」と実感する。
2020年 COVID-19 pandemic になる、ほとんどのカンファレンスが中止、一気に色々とリモートワークに変わる
2020年 npm が githubにジョイン、一旦資金難とか開発者不足は解消される
2020年 Deno v1.0 リリースされる。
2020年 Node.js next 10 という次の10周年のためのマイルストーンが出てくる。 fetch / single executable app / ESM 強化 / TypeScript との親和性強化 などの進化が掲げられる
2021年 Node.js に Promise 化や AbortController など Web Standard との親和性に必要なパーツが徐々に揃ってくる。一方で、なんとなく Deno との競争意識からか、本当にいるのか不明なものも入ったりする。 btoa/atob とか。。。
2021年 また少しずつフロントエンド周りの競争が激化する、特に webpack vs esbuild や remix vs next.js 、 deno vs Node.js などの競合と競争関係になる。新陳代謝の時期かもしれない。
2022年現代へ。この記事書くの大変でした。

こうやって歴史的な年表を書いてみた(だいぶ自分の話もあるが)。けど、いつ流行りが終わるのか、それともまだまだこれからも続くのかという疑問に回答は出なかった。なんとなくだけど、身近な競合がまだまだ雨後の筍のように出てきている現状では廃れるという事はしばらく無さそうではあった。ただ数年後にはわからない。

いずれにせよ、振り返るのも面白かった。

*1:自分はそれが非常に残念である、 Deno が Node.js 互換を提供するとは言ってほしくなかった、ぜんぜん違う世界観を追っていてほしかった。

Viewing all 105 articles
Browse latest View live