blog.8-p.info

ここ数年、仕事で JavaScript を書く機会がないのだけど、Error Handling in Node.js はコードレビューでよくリンクしている。

JavaScript 固有のところではなく、オペレーショナルエラーとプログラマーエラーを分けて考えましょうよ、というところが良い。日本語だとどう訳すんだろう?

People use the term “errors” to talk about both operational and programmer errors, but they’re really quite different. Operational errors are error conditions that all correct programs must deal with, and as long as they’re dealt with, they don’t necessarily indicate a bug or even a serious problem. “File not found” is an operational error, but it doesn’t necessarily mean anything’s wrong. It might just mean the program has to create the file it’s looking for first.

By contrast, programmer errors are bugs. They’re cases where you made a mistake, maybe by forgetting to validate user input, mistyping a variable name, or something like that. By definition there’s no way to handle those. If there were, you would have just used the error handling code in place of the code that caused the error!

脚注に

The concepts of an operational error and a programmer error long predate Node.js. In Java, this loosely tracks the use of checked and unchecked exceptions, though operational errors that are known to be unhandleable, like OutOfMemoryError, are grouped with unchecked exceptions.

とあるように、この区分は JavaScript 固有のものでもないし、あえて “in Node.js” と書かれた文章を JavaScript 書かない人に読ませなくても良いはずなのだけど、他言語でのちょうどいい文章を私は知らなくて、結果としてこれをすすめることが多い。

BPF というと、memcached を速くするとか、Ruby からコンパイルするとか、まず高度な話題を見かける2021年だけど、BCC についてくる色々なスクリプトを使うだけでも、色々とトラブルを解決できることがある。

opensnoop

opensnoop は文字通りシステムで発行されている open(2) をリアルタイムに覗き見できるツールだ。

去年に containerd/cgroup でメトリクスが来ないバグを直したときには、そもそも最初はこのパッケージまで絞り込めてなくて、

  • なんかメトリクスが来ないんだけど…
  • このメトリクスって /sys/gs/cgroup/blkio 以下のファイルから来てるんだよね?
  • これって containerd 本体が読んでるんだっけ? 多分 shim のほうだとは思うんだけど…
  • runc が読んでるってことはないよねえ?
  • ファイルは読まれているけど、中継経路のどこかで落としてしまっているとか?

みたいな状況で strace をかけるにもどのプロセスを見たものか今ひとつわからない。そこで opensnoop をかけると、そもそも誰もファイルを読んでいないじゃないか! というのが即座にわかって便利だった。

それ bpftrace で出来るの?

ちなみに、open(2) を文字通り全部ログするようなものなら、bpftrace で書くこともできる。

ただ、例えば opensnoop だと、open(2) の他に openat(2) と openat2(2) をハンドルするコードが入っている。

open(2) のことを考えたときに、その亜種のシステムコールもパッと浮かぶくらい Linux に詳しい人だったら全て bpftrace で事足りるのかもしれない。私はそこまで Linux に詳しくないので、BCC についてくるスクリプトから試すことが多い。

ちょっと前に、他のチームの書いたものをセキュリティ的な観点でレビューする、という仕事があった。「SQL インジェクションはありませんか?」みたいなチェックリストを片目に AWS SDK で DynamoDB にアクセスするようなコードをレビューするのは、なかなか隔世の感があった。

私は2000年代の後半から2010年台の頭にかけて、いわゆる Web プログラマ仕事をしていて、Perl から MySQL に SQL を投げて結果を HTML に入れて返すようなものをよく書いていた。当時は SQL インジェクションとか XSS が、よくある脆弱性として語られていた。

この感じ、パブリッククラウドの SQL じゃないデータベースを使っている人々や、最終的には SQL になるけど基本的には OR マッパーを使っている人々、React で HTML を作っているような人々には伝わらないんじゃないかと思う。

なぜ SQL インジェクションや XSS が起こるのか

SQL インジェクションも XSS も、諸悪の根源は、構造があって構文木のあるような言語 (SQL や HTML) を、その構造を理解してない低レベルな抽象 (文字列) で扱っていることにある。「構造体も最終的にはメモリ上のバイト列だから、普段から unsigned char[] にキャストして操作しましょう」なんてことは当時でも滅多になかったのに、HTML や SQL となると「じゃあ文字列に入れましょう」というのは、今になって考えるとなかなか野蛮だ。

最近はこういうことがなくて平和なのだけど、そういえば去年に Firecracker にエラーメッセージに " が含まれると JSON が壊れるというバグがあって、Rust における sprintf である format! しているところを serde を使うように直したのだった。JSON も構造があって構文木のあるような言語だから、ただの文字列として扱わないほうがいい。

コマンドインジェクション

プログラミング言語のサブプロセス呼び出し、とりわけ system(3) を真似たものには、同様の問題があり、可能であればシステムのシェルを呼び出さないのが主流になりつつあると思う。シェルを呼び出すと、意図しないところでコマンドインジェクションが起こる可能性がある。

Python だと subprocess の Security Considerations

Unlike some other popen functions, this implementation will never implicitly call a system shell. This means that all characters, including shell metacharacters, can safely be passed to child processes. If the shell is invoked explicitly, via shell=True, it is the application’s responsibility to ensure that all whitespace and metacharacters are quoted appropriately to avoid shell injection vulnerabilities.

デフォルトが安全側に倒れていることが主張されているし、Go の os/exec では

Unlike the “system” library call from C and other languages, the os/exec package intentionally does not invoke the system shell and does not expand any glob patterns or handle other expansions, pipelines, or redirections typically done by shells. The package behaves more like C’s “exec” family of functions. To expand glob patterns, either call the shell directly, taking care to escape any dangerous input, or use the path/filepath package’s Glob function. To expand environment variables, use package os’s ExpandEnv.

そもそもシステムのシェルを経由したコマンド呼び出しができない。

Python や Go に比べると、Julia のサブプロセス呼び出しは、文字列を介したサブプロセス呼び出しの書き味を保ちつつ、でもそれを安全に取り扱う、というのを目指していて良い。

Running External Programs

In general, the Julia backtick syntax is carefully designed so that you can just cut-and-paste shell commands as is into backticks and they will work: the escaping, quoting, and interpolation behaviors are the same as the shell’s. The only difference is that the interpolation is integrated and aware of Julia’s notion of what is a single string value, and what is a container for multiple values.

Julia の作者の一人である Stefan Karpinski は、このあたりの設計の意図についてブログで語っている。

適材適所論について

先週に書いた、シェルスクリプトを書かない話はよく読まれた。前回の話も、今回の話も「それって適材適所ですよね」と言われがちな話題だと思う。

私は、自分の適材適所判断能力にあんまり信頼を置いていない。「このくらいの HTML なら sprintf() したらいいじゃない?」「まあ確かに…」みたいなものがだんだん育ってしまうのをよく見かけた結果、ちょっと面倒だけど後々困らなそうなものを選んでしまう傾向がある。CSV を扱うコードには、いずれ ," を投げ込んでくるやつが現れる。終末に備えよ!

この先にあるのは、FizzBuzzEnterpriseEdition であり、スタートアップなのにマイクロサービスやろうとする大企業出身プログラマなので、程々にしないといけないけれど。

今年から、できるだけシェルスクリプトを書くのをやめようとしている。私が毎日 zsh に打ち込んでいるのも広義のシェルスクリプトだし、自分用の雑なスクリプトを書くことはあるけれど、チームの他の人も将来に使ったり改変したりするようなものは、なるだけ他の言語を使っている。

シェルスクリプトを書くのは難しいし、その難しさは、学ぶに値しないといったら言い過ぎかもしれないけれど、2021年に初心者が取り組むべき問題とは言い難いと思う。

シェルは悪いプログラミング言語である

Bash Strict Mode とかを使ってみても、シェルスクリプトには落とし穴が多すぎる。自分で書いたものを自分で使っている分には大丈夫なのだけど、スクリプトがチーム内で使われるようになると、考慮していなかったところ、例えばファイル名に空白文字が含まれるとか、そういうレベルの微妙なところで、ちゃんと書かれていないスクリプトは壊れたりする。

公平をきすためにいっておくと、シェルは、対話的環境でありながら、プログラミング言語でもある。ほとんどのプログラミング言語はこの二つを同時に満たそうとしていないところで、シェルはどちらにもなろうとしている。記号を多用した、文字数の少なさとしての簡潔さは、対話的環境としては正しい。

とはいったものの、その試みはあまり成功していない。普及したという意味では「成功」はしているけれど、例えば過去の遺産の全くない状態で、シェルスクリプトの仕様だけを見て「これ良いですよね?」といわれると、私は言葉を濁してしまう。最近 AWS の Marc Brooker も、

If sh-style shell programming was invented today, everybody would think it’s a sick joke.

と、Twitter に書いていた

プログラミング言語が必要なら、プログラミング言語だけになろうとしている言語を使った方が良い。

シェルはあまりパワフルではないので、結果として複数の言語を混ぜることになる

シェルスクリプトが一定以上の複雑さになると、だんだん awk やら sed やら、最近だと jq なんかを呼び出すようになる。私と似たような世代/業界の人々は、キャリアのどこかでこれらの言語を学んでいる。こういう小さいソフトウェアを合わせて大きいことをするのは、Unix らしさがあって良い。

一方で、今までこれらの言語を学んでいない人からすると、awk や sed や jq は新たな障壁になる。なんでも知らないよりは知っているほうが良い。ただ、コンピュータに何かをさせるとか、私が書いたスクリプトを理解するときに、簡単な理解でいいので4つ小さなプログラミング言語を学んでください、というのはどうなのかと思う。このスクリプトはそこまで大袈裟なことをしていたっけ?

シェルが存在しない環境のことを考えると、書いたものも、そこまでポータブルではない

これは他よりはちょっと弱いけれど。

distroless のような環境では、シェルも単なる一つの依存の一つでしかない。シェルならどこにでもあるから、依存を減らすためにシェルスクリプトを書こう、というのは、必ずしも真ではない。

じゃあ代わりに何を使うの?

私は Python や Ruby を使うことが多い。個人的には Ruby が好きなのだけど、Amazon Linux 2 とか Python がデフォルトで入っているところだと、とりあえず Python を使っている。

Go や Rust も良いけれど、ビルドの過程を理解できなくても、実行しているものを直接読めるというスクリプト言語の利点は捨てがたい。zxOil は気になるけれど、まだ試していない。

ShellCheck 使えばいいのでは?

(2021-09-16: ここ以降は追記です)

ShellCheck は良い。ただ、落とし穴を教えてくれるソフトウェアを使ってまで、落とし穴の多い言語を使う必要もないのでは、とも思う。

Python とか Ruby でシェルスクリプトみたいなことするの面倒くさくないですか?

面倒くさい。でもシェルスクリプトをちゃんと書くのも少し、しばらくみない間に育ってきてしまったシェルスクリプトをがんばって Python や Ruby に書き直すのはかなり面倒なので、だるいなーと思いながら import subprocess している。

外部のプロセスをどう呼び出すかという問題、Go の os/exec が

Unlike the “system” library call from C and other languages, the os/exec package intentionally does not invoke the system shell and does not expand any glob patterns or handle other expansions, pipelines, or redirections typically done by shells.

system(3) の代わりではないですよ、と切り捨てているなか、Julia がシェルスクリプトっぽく書けるけど、空白の扱いなどをよしなにがんばる独自の backtick 文法を作っていて面白い。

Running External Programs

The command is never run with a shell. Instead, Julia parses the command syntax directly, appropriately interpolating variables and splitting on words as the shell would, respecting shell quoting syntax.

良いけど、なぜ Julia がそこを頑張るのかすこし謎。

Python とか Ruby で書いたときに、依存パッケージとかどうしてますか?

依存パッケージが無いように、言語本体についてくるライブラリだけでがんばって書いている。

これも面倒くさいけど、GNU date 前提に date --date '3 days ago' なんてしているのを macOS で動かしたら BSD date ではサポートしてないぞこれ! とかやるのに比べると、もう日付の計算が普通にできるだけでありがたい…。

Manning から出ている Own Your Tech Career を読んでいる。著者はオンラインにプロフェッショナルなプロフィールを作ること、自分のブランドを意識すること、そして仕事と関係ないことはそこから分けることを勧めている。

この種の助言は英語圏だとよく見かける。例えば Making Peace with Personal Branding (2017) でも、

I recommend keeping your tweets mostly focused on your field. Some people deal with this by having separate personal and professional twitter accounts (for instance, Data Science Renee does this).

と Twitter の使いかたを指南されているし、The Developer’s Guide to Content Creation にも同じような話があった。

私自身は、Web 日記時代からインターネットをしているせいか、生活とプログラミングが混ざったようなブログが好きなのだけど、一方で、こういう、読者を意識した、キャリアのための「ちゃんとした」ブログというものを、やる前から否定していたようにも思う。

そういったわけで、今年の残りは「ちゃんとした」技術的な話を一定のペースで書く、ブログというものに挑戦してみることにした。

ブログというメディア自体がどうか、という話を以前に karino2 さんがしていて

インターネットの発信では、昔はブログとか絵とか静的なコンテンツが主体だったので、その時の国語は読解と作文が主体だった気がする。 でも最近はブログは衰退傾向で発信はyoutubeとかpodcastに偏っている気もする。 すると喋るという事が重要になってきているよな。 国語の授業というのも時代に応じて重点を入れる項目が変わっていってもいい気もするがどうだろう?

説明用の図やアニメーションを作る能力は欲しい気もするけれど、一方で、文章は書くのも簡単で読むのも速いので、本当のマスにリーチするのを諦めれば (テレビの代わりになるのを諦めれば)、いまだに良いメディアだと思う。

アメリカに住んでいてキャリアを心配しているなら、英語で書くべきではという話はあって、英語でも書くつもりだけど、日本語でも同じような方針で実験してみます。