@ledsun blog

無味の味は佳境に入らざればすなわち知れず

透過pngを作る

ChatGPTで画像が作れるようになりました。 楽しく画像をつくりました。

ChatGPTで作った画像

作った画像でトレーナーが作りたくなりました。 背景色の白が印刷されてしまいます。 絵だけを残して背景を透過したいです。

Windowsの3Dペイントには良い感じに塗りつぶしてくれる機能があります。 グリーンバックの容量で #00FF00 で塗りつぶします。

背景を #00FF00 で塗りぶした画像

#00FF00 の単色のはずなので、素朴に透過色で塗りつぶします。 これをRubyでやりました。

require 'chunky_png'

image = ChunkyPNG::Image.from_file('path/to/your/image.png')

# 透明にしたい色を指定(例えば白の場合)
target_color = ChunkyPNG::Color.from_hex('#FFFFFF')

# 透明色を定義(透明度が0の場合)
transparent_color = ChunkyPNG::Color.rgba(0, 0, 0, 0)

image.height.times do |y|
  image.width.times do |x|
    # ターゲットの色が見つかった場合は透明に置き換える
    if image[x, y] == target_color
      image[x, y] = transparent_color
    end
  end
end

# 修正した画像を保存
image.save('path/to/your/output_image.png')

ChatGPTの提示したスクリプトです。 ファイルパスを修正して使用しました。

#00FF00 を透過した画像

よく見ると縁に緑が残っています。 3Dペイントはアンチエイリアシングが効くみたいです。 今回の画風では不要です。が、OFFにできません。

完全一致では緑が残るので、ある程度遊びを持つようにRubyスクリプトを変更します。

require 'chunky_png'

transparent_color = ChunkyPNG::Color.rgba(0, 0, 0, 0)

# 比較対象の色
target_color = ChunkyPNG::Color.from_hex('#00ff00')

# 色の類似度を判定する閾値
threshold = 100

image = ChunkyPNG::Image.from_file '/mnt/c/Users/led_l/OneDrive/デスクトップ/背景の緑化.png'
image.height.times do |y|
  image.row(y).each_with_index do |pixel, x|
    # 現在のピクセルの色
    r, g, b = ChunkyPNG::Color.r(pixel), ChunkyPNG::Color.g(pixel), ChunkyPNG::Color.b(pixel)

    # 目標の色との差分を計算
    r_diff = (r - ChunkyPNG::Color.r(target_color)).abs
    g_diff = (g - ChunkyPNG::Color.g(target_color)).abs
    b_diff = (b - ChunkyPNG::Color.b(target_color)).abs

    # 差分が閾値内ならば似ている色として配列に追加
    if r_diff <= threshold && g_diff <= 70 && b_diff <= threshold
      image[x, y] = transparent_color
    end
  end
end

image.save '/mnt/c/Users/led_l/OneDrive/デスクトップ/背景のtrasparent化2.png'

緑っぽい色を透過した画像

結構綺麗になりました。 ただ、拡大すると緑色が残っています。

少し戻って、塗りつぶしの手順を改善するのが良さそうです。

KPTでトライ狙いすぎ問題

KPTは「チームの力で問題を見つけるふるまい」の養成ギブスです。 ふるまいに慣ていない間は違和感があります。

たとえば次のような問題が起きます。

トライ狙いすぎ問題

KPTの「改善活動」の面に強く期待しすぎて生じる問題です。 無意識に、KPTの成功指標を「TRYの数」にします。

TRYを出すことに意識をとらわれると、慣れている「個人で問題を見つけて解決する」方法を取ることがあります。一つのKPTの場に集まって、参加者がそれぞれ別々に問題を発見して解決します*1

すると、途中のプロセスが無駄に見えると思います。特にKeepに意味を感じないのではないでしょうか?アイスブレイクの一緒だと思ってはいませんか?たとえばKPTの参加者にKeepを出していない人が居ても問題ないと思っていませんか?あるいは、時間短縮のため事前にKeepやProblemを用意していませんか?

KPTをK→P→Tの順に進めることには意味があります。この意味を知るためにKPTを逆順に見て行きましょう。

良いTRYには良いProblemが必要

良いProblemが出せれば、TRYは自然に出ます。問題解決の第一歩は問題発見です。 TRYが出ない、あるいは何度かKPTをやると出なくなってくるのは、良くないProblemを並べているからです。 良くないProblemとはどんなProblemでしょうか?

解決方法がわかっているProblemは良くない

解決方法がわかっているProblemは良くないProblemです。 たとえば「自動テストが導入されていない」は良くないProblemです。

良くないというのは「正しく認識できていない」という意味です。 本当に「自動テストが導入されていない」がProblemならば、導入するというTRYになるはずです。というか、KPTをするまでもなく、業務上のタスクとして導入しているはずです。 導入していないなら、何か理由があるはずです。 それがProblemです。

て、いうと「チームメンバーが・・・」とか「うちの会社では・・・」とか言い出すんですけど、そうじゃないです。 もうこの時点で、良いProblemじゃないんです*2。 これをどんなに掘っても問題は見つかりません。TRYは生まれません。

「解決方法がわかっているProblem」から問題が発見されることはありません。 Keepからやり直しましょう。

KPTでは「チームの力で問題を発見したい」です。 解決方法がわからないProblemの方が良いProblemです。

全員が知っているProblemは良くない

全員が知っているProblemは良くないProblemです。

全員が知っているのに解決していない問題は、重要でない問題です。 例えば「社内システムがSSOに対応していないので、ログインに手間が掛かる」みたいなのは大抵チームで取り組む価値がない重要でない問題です*3

KPTでは「チームの力で問題を発見したい」です。 全員が知らない、一部のメンバーが気がついてるProblemの方が良いProblemです。 もし、1人しか気がついていないとしたら、とても良いProblemです。

良いProblemを出すのは難しい

良いProblemを出すのは難しいです。

解決方法がわからないProblemの場合

「解決方法がわからないProblem」を挙げると、リーダーの機嫌が悪くなることがあります。 リーダーが「チームの問題はリーダーが解決するもの」と、思い込んでいるためです。 責任感の強いリーダーほど、この傾向が強いです。 「問題を抱えているチームのリーダーは無能である」とか「自分のチーム運営に、チームメンバーが不満を感じている」などと思っているはずです。

リーダーの機嫌が悪くなると、メンバーは空気を読みます。「解決方法がわかっているProblem」を挙げるようになります。 あるいは、一見解決できなさそうなProblemを挙げておいて、「実はこういう手段があるんですよ・・・」と、リーダーの機嫌を使って自分の提案が通るように、チームを誘導します。

リーダーがポーカーフェイスをすればいいのか?というと、そうでもありません。 ポーカーフェイスは完璧ではありません。ポーカーフェイスから漏れる感情を読み取る人間が偉くなります。 KPT外の場でリーダーの気にしてる問題を知ろうとしてくるかもしれません。忖度ですね。

簡単な解決方法は、KPTに参加している全員がKeepを挙げることです。詳しくは後述します。

一部のメンバーだけが気がついているProblemの場合

自分しか気がついていないProblemを挙げるのは意外と難しいです。 Problemに気がついたとして、それがチームのProblemなのかわかりません。

もしも自分しか気がついていないProblemだった場合、説明が大変です。 がんばって自分なり説明した結果、上手く伝わらなかったら、 明後日のことを言う変な人になってしまうかもしれません。 恥ずかしくないですか?

こういうとき、人間の脳はいくらでも言い訳が思いつきます。

  • みんなが知っていて当然で、自分だけが知らない情報があるだけ、後になればわかる
  • 気のせい、見間違い
  • 極レアケースで滅多に問題にならない

どんだけ言い訳を並べても、本当にProblemである可能性はなくなりません。 チームのProblemか確かめるには、KPTの場でProblemとして挙げてみるしかありません。

簡単な解決方法は、KPTに参加している全員がKeepを挙げることです。

良いProblem出すには全員がKeepを挙げる必要がある

KPTを良くするための簡単な方法があります。 「全員がKeepを挙げる」です。

仕事に取り組んでいるときの緊張した心理状態では、大抵のProblemが深刻な問題に見えます。 そのため、解決できそうにないProblemを出すこと出されることに、大きな抵抗を感じます。

悪いことを言う前に、良いことを言うと、この抵抗感が消滅します。 なぜか全然わからないんですが、経験則的にマジです*4。 同時に「明後日のことを言ってしまうかも」問題も深刻でないものにしてくれます。 「解決方法がわからないProblem」「一部のメンバーだけが気がついているProblem」どちらを挙げる場合にも効果的な魔法です。

私は、Keepは挙げることが重要で、Keepの内容はあまり重要でないと思っています。 どんな内容でも良いことを口にするだけで、なぜか問題を深刻に感じなくなる効果があります。

例えば、つぎのものでも大丈夫です。

  • 天気が良い
  • 給与が振り込まれた
  • おいしいご飯屋さんをみつけた
  • 楽しみしているゲームが発売された

自分がやった結果でなくても良いです。 チームで取り組んでいる内容でなくても良いです。 単にKPTの場で、脳が「現状は最悪ではない」と認識すれば十分です。

何よりも大事なのは、KPTに参加している全員がKeepを挙げることです。 誰か一人でも最悪の気分の人がいると「解決方法がわからないProblem」を挙げられなくなります。 その人は「自分だけが気がついているProblem」を挙げてくれないでしょう。

Keepが挙げにくい状況

Keepが挙げにくい状況があります。 例えば、次のような場合です。

  • KPTに、チーム外の偉い人が参加する
  • 事前にKeepを考えてくる

こういうとき人間は、かっこいいKeepを挙げたくなります。 「チームで取り組んでいる素晴らしい施策」を挙げたくなります。 こういうKeepの候補は少ないです。 また、前の人が出したKeepと同じでは格好がつきません。 そして、先に格好いいKeepを思いついた人だけがKeepを挙げ、残りの人は「Keepはありません」になります。

また、こういう格好いいKeepを挙げるKPTでは、KPTに急に参加した偉い人は格好いいKeepを挙げられないので「チームの普段の細かい取り組みはよくわからないから」とKeepを挙げるのをサボります。

これはとても良くないです。 偉い人が「現状は最悪ではない」モードでない場で、問題は発見されません。 時間の無駄です*5

Keepが効かない状況

  • 事前にProblemを考えてくる

Problemを考えているとき、参加者全員が「現状は最悪ではない」モードに入っているかわかりません。 事前にProblemを考えてしまうと、良いProblemは出てきません。

ただし、KPT以外の時間でProblemを考えることには意味はあります。 考えたProblemはKPTを待たずにチームに共有しましょう。 KPTの開催日を待つ意味がありません。 共有が遅くなると、チームは損をします。

もし、KPT以外にProblemをチーム内で相談する場がないとしたら、それはそれで別の問題です。 KPTに大きすぎる役割を与えているように思えます。

まとめ

KPTでは「チームの力で問題を発見したい」です。 「良いProblemを挙げられる場を作れるか」の勝負です。 「こんなこと言っても良いのかな?」というProblemが挙げられる会になったら成功です。 良いProblemさえ挙げられればTRYは自然に出てきます。

そのために

  1. 全員がKeepを挙げる
  2. Keepの内容はくだらないことほど良い。特に偉い人ほど

*1:KPTがチームとしてのふるまいを学習する場ではなくなっています。業務と別にサブプロジェクトがあって、KPTがその進捗会議になっているイメージです。

*2:もっというと、これはたぶんKPTを使って「自動テストの導入」に誘導しようとしています。個人の問題を他のメンバーに押しつけているだけで、チームで解決するムーブになっていません。「自動テストは我々の開発の役に立つだろうか?」という問いをすっ飛ばして、個人の意見をいきなり結論として押しつけています。

*3:社内システムを改善するチームだとしたら、重要な問題です。が、その場合はKPTで扱うような問題ではなく。業務上のタスクになっているはずです。

*4:アンカリングの一種なんですかね?理由を知っている人がいたら教えてください。

*5:「偉い人がチームを褒めたたえる」場にしてもいいですが、別にKPTでやる必要ないですよね?

wasmバイナリを読む

お題

WebAssembly text formatからwasmバイナリをつくる環境を整える - @ledsun blogで、watからwasmが作れるようになりました。 すこし複雑な例をつかってwasmバイナリを生成して読んでみましょう。 WebAssembly テキスト形式の理解 - WebAssembly | MDNにwatのサンプルがあります。 今回は最初のサンプルを使います。

(module (memory 1) (func))

手法

これをwat2wasmでwasmバイナリに変換します。

~/wabt/bin/wat2wasm example_one.wat -o example_one.wasm  

バイナリを読む

出力されたバイナリを見てみます。

►hexdump -C example_one.wasm
00000000  00 61 73 6d 01 00 00 00  01 04 01 60 00 00 03 02  |.asm.......`....|
00000010  01 00 05 03 01 00 01 0a  04 01 02 00 0b           |.............|
0000001d

00 61 73 6d 01 00 00 00がプリアンブルです。 01 04 01以降がセクションです。

ふたつの違和感

ここで違和感があります。 セクションは1バイトのidに続いて4バイトのサイズがあると思っています。 4バイトのサイズというのは04 01 60 00のことです。 これはいくら何でも大きすぎます。

次に(memory 1)コンパイルしたので、memoryセクションのid 05があるはずです*105 03 01 00 01 0a 04 01 02 00 0bのあたりがmemoryセクションのようです。 これも値1を表すには長すぎます。

セクションの並び順

そういえばwat上ではmemoryセクションが前にあったのに、wasmでは後ろにずれています。 これはwasmのセクションはid順に並ぶためのようです。 そう思ってみると、01と05の間に03セクションがあるように思えます。

01(typeセクション) は 01 04 01 60 00 00ではないでしょうか? そう考えるとスッキリします。

  • 01 : id
  • 04 : size
  • 01 60 00 00 : cont

と読むと良さそうです。

サイズは1バイト

sizeは4バイトだと思ってましたが、1バイトでした。 *2

そうやって見るとこのバイナリには次のセクションが含まれていることがわかります。

  • 01 04 01 60 00 00 : type
  • 03 02 01 00 : function
  • 05 03 01 00 01 : memory
  • 0a 04 01 02 00 0b : code

なにやら、それっぽいです。 wasmバイナリが読めるようになった気がしてきました。

*1:セクションのIDの意味はModules — WebAssembly 2.0 (Draft 2023-09-06)のテーブルを見るとわかります。

*2:wasmバイナリのカスタムセクションを書いてみる - @ledsun blog でsizeを4ではダメで、7にするとよい理由がわかりました。

WebAssembly text formatからwasmバイナリをつくる環境を整える

wasmバイナリのカスタムセクションを書いてみる - @ledsun blog でプリアンブルとセクションを持つwasmバイナリを書き出せるようになりました。 最低限のwasmバイナリファイルの構造を理解したといえます。

お題

Writingの次はReadingです。 シンプルなwasmバイナリを作って読んでみましょう。

WebAssemblyにはテキストフォーマットがあります。 たとえば、最小では次の文字列です。

(module)

このテキストフォーマットからどのようなwasmバイナリが生成されるのでしょうか?

  1. プリアンブルだけのバイナリ 0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00になる
  2. 何かしらのセクションがつく

どちらになるでしょうか?

手法

WebAssembly text formatからwasmバイナリを生成してみましょう。 Converting WebAssembly text format to Wasm - WebAssembly | MDNによると、wabtというwasmバイナリ用のツールがあるようです。 これを使えばテキストフォーマットからwasmバイナリが作れそうです。

wabtのインストール

わたしの環境はWSL上のubuntuです。

リポジトリのClone

git clone --recursive https://github.com/WebAssembly/wabt
cd wabt
git submodule update --init

ビルドツールのインストール

CMakeninja-buildが必要です。 それぞれインストールします。

CMake

sudo apt install cmake
確認
►cmake --version
cmake version 3.22.1

CMake suite maintained and supported by Kitware (kitware.com/cmake).

ninja-build

sudo apt install ninja-build
確認
►ninja --version
1.10.1

ビルド

make

確認

►bin/wat2wasm --version
1.0.33 (git~1.0.33-31-gbceb2434)

検証

wabtにふくまれるwat2wasmを使って、WebAssembly text formatからwasmバイナリを生成します。

バイナリファイルを生成

echo '(module)' > minimam.wat
~/wabt/bin/wat2wasm minimam.wat -o minimam.wasm

バイナリファイルを確認

►hexdump -C minimam.wasm
00000000  00 61 73 6d 01 00 00 00                           |.asm....|
00000008

正解は 1. プリアンブルだけのバイナリ 0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00になる でした。

参考

wasmバイナリのカスタムセクションを書いてみる

最小限のWebAssemblyのバイナリファイルを書く - @ledsun blog でwasmバイナリのプリアンブルの書き出しに成功しました。 続いてセクションを書き出してみます。

Modules — WebAssembly 2.0 (Draft 2023-07-24) によると

セクションの定義
1バイトのID、4バイトのサイズ、サイズで指定した中身のようです。

ID 0はカスタムセクションというデバッグ情報を埋め込むセクションです。 カスタムセクションはnameと呼ばれるUTF-8文字列を埋め込みます。 ということはnameがhogeなカスタムセクションは0x00 0x04 0x00 0x00 0x00 0x68 0x6f 0x67 0x65で良さそうです。

次のRubyスクリプトを使って書き出します。

File.open("custom_section.wasm", "wb") do
  # preamble
  _1.write [0].pack('C')          # 0x00
  _1.write 'asm'.bytes.pack('C3') # 0x61 0x73 0x6d
  _1.write [1].pack('I<')         # 0x01 0x00 0x00 0x00

  # custom section
  _1.write [0].pack('C')           # 0x00
  _1.write [4].pack('I<')          # 0x04 0x00 0x00 0x00
  _1.write 'hoge'.bytes.pack('C4') # 0x68 0x6f 0x67 0x65
end

これをGoogle Chromeコンパイルします。 実行するためのHTMLです。

<html>
  <title>Load wasm binary</title>
  <script>
    fetch('./custom_section.wasm')
      .then(response => response.arrayBuffer())
      .then(buffer => WebAssembly.compile(buffer))
      .then(module => console.log(module))
      .catch(e => console.error(e));
  </script>
</html>

すると次のエラーが起きます。

Google Chromeの出力したコンパイルエラー

(インデックス):8 CompileError: WebAssembly.compile(): section (code 111, "") extends past end of the module (length 103, remaining bytes 1) @+14

理由はよくわかっていません。 結論だけいうと、sizeを7にするとWebAssemblyモジュールとして認識されます。

# custom section
_1.write [0].pack('C')           
_1.write [7].pack('I<')  # 4ではなく7
_1.write 'hoge'.bytes.pack('C4')

この3バイトはどこからでてきたのでしょうか? sizeをセクションの頭から数えるなら、7ではなく8や9になりそうなものです。

最小限のWebAssemblyのバイナリファイルを書く

仕様

WebAssemblyのバイナリファイルはModuleと呼ばれます。 Modules — WebAssembly 2.0 (Draft 2023-07-24) に仕様があります。

  1. プリアンブル
  2. セクション

に分かれています。セクションは空でも良いです。 つまり、最小限のWebAssemblyモジュールはプリアンブルだけあればよいです。

WebAssemblyモジュールのプリアンブル

プリアンブルは0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00の8バイトです。 これは前半の0x00 0x61 0x73 0x6DがASCIIのasmです。 後半の0x01 0x00 0x00 0x00はWebAssemblyモジュールのバージョンを表す1です。 1が1バイト目にあるのでリトルエンディアンの32ビット整数です。

バイナリファイルを書く

次のRubyスクリプトで書き出せます。

File.open("preamble.wasm", "wb") do
  _1.write [0].pack('C')          # 0x00
  _1.write 'asm'.bytes.pack('C3') # 0x61 0x73 0x6d
  _1.write [1].pack('I<')         # 0x01 0x00 0x00 0x00
end

hexdumpコマンドで確認してみます。

hexdump -C preamble.wasm
00000000  00 61 73 6d 01 00 00 00                           |.asm....|
00000008

良い感じに出来てそうです。

動作確認

WebAssemblyモジュールとして読み込めるか試してみます。 次のHTMLファイルを用意します。

<html>
  <title>Load wasm preamble</title>
  <script>
    fetch('./preamble.wasm')
      .then(response => response.arrayBuffer())
      .then(buffer => WebAssembly.compile(buffer))
      .then(module => console.log(module));
  </script>
</html>

作ったバイナリファイルを読み込んでWebAssembly.compileします。

ruby -run -e httpd .でHTTPサーバーを起動します。 ブラウザで http://localhost:8080/ を開きます。 開発コンソールを開きます。

開発コンソールのModuleと出力される

無事WebAssemblyモジュールとして認識されました。

不正な場合は?

もしかすると何もチェックされていないかもしれません。 間違ったWebAssemblyモジュールも試してみます。

asmじゃなくてackだったら

マジックワードが不正なWebAssemblyモジュールを読み込ませると

バージョンが2

バージョンにWebAssemblyモジュールを読み込ませると

WSL向けのWindowsポートフォワード設定スクリプト new

WSL向けのWindowsポートフォワード設定スクリプトが動かなくなった - @ledsun blog のでバッチファイルをやめてPowerShellで書き直しました。

$port = 8000

if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators"))
{
  # Start portforward
  $ip = wsl ip addr show eth0 | Select-String -Pattern 'inet\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' | ForEach-Object { $_.Matches[0].Groups[1].Value }
  netsh interface portproxy add v4tov4 listenport=$port connectaddress=$ip connectport=$port
  netsh interface portproxy show v4tov4

  # Display the IP addresses of the computer
  $WiFiIPInfo = Get-NetIPAddress -InterfaceAlias "Wi-Fi" -AddressFamily IPv4
  $WiFiIPInfo | ForEach-Object { $_.IPAddress }

  Read-Host "Press Enter to continue..."

  # Stop portforward
  netsh interface portproxy delete v4tov4 $port
}
else {
  Start-Process powershell.exe "-File `"$PSCommandPath`"" -Verb RunAs
}

ロジックは既存のままです。 次の項目をPowerShellで実現する方法をChatGPTに聞きました。

また、PowerShellスクリプトは右クリックから「管理者権限で実行」が出来ませんでした。 こちらは昔ながらのググって解決しました。

参考

WSL向けのWindowsポートフォワード設定スクリプトが動かなくなった

WSL向けのWindowsポートフォワード設定をスクリプト化する - @ledsun blog が動きませんでした。

バッチファイルを起動したところ

パッとみ気が付きませんでしたが、netsh interface portproxy add v4tov4 listenport=8000 connectaddress=は途中で途切れています。 正しくは次のように、connectaddresのあとにWSLのIPアドレスが続き、さらにconnectportパラメータが続く必要があります。

netsh.exe interface portproxy add v4tov4 listenport=8000  connectaddress=172.25.143.70 connectport=8000

バッチファイルの全文は以下です。

FOR /F "usebackq" %%i in (`wsl exec hostname -I`) DO @SET IP=%%i
netsh interface portproxy add v4tov4 listenport=8000 connectaddress=%IP%
netsh.exe interface portproxy show v4tov4

netsh interface ip show address | findstr "IP Address"
pause

netsh.exe interface portproxy delete v4tov4 8000
netsh.exe interface portproxy show v4tov4

このうち上手く行っていないのは、最初の行のwsl exec hostname -Iです。 これだけを実行すると次のように沢山のIPアドレスが表示されます。

C:\Windows\System32>wsl exec hostname -I
172.25.143.70 172.19.0.1 172.23.0.1 172.20.0.1 172.24.0.1 172.18.0.1 172.21.0.1 172.22.0.1 172.17.0.1

どういうことでしょうか? WSL側でIPアドレスを確認してみましょう。

ledsun@MSI:/m/c/U/led_l►ip addr                                                                                                                  20:40
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:5d:00:29 brd ff:ff:ff:ff:ff:ff
    inet 172.25.143.70/20 brd 172.25.143.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::215:5dff:fe5d:29/64 scope link
       valid_lft forever preferred_lft forever
3: br-ca29cad65f14: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:29:38:75:78 brd ff:ff:ff:ff:ff:ff
    inet 172.24.0.1/16 brd 172.24.255.255 scope global br-ca29cad65f14
       valid_lft forever preferred_lft forever
4: br-e8b0de7f1b72: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:8f:c5:a4:21 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-e8b0de7f1b72
       valid_lft forever preferred_lft forever
5: br-f8b610f612b1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:5c:30:4b:23 brd ff:ff:ff:ff:ff:ff
    inet 172.21.0.1/16 brd 172.21.255.255 scope global br-f8b610f612b1
       valid_lft forever preferred_lft forever
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:f8:ed:2b:a4 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
7: br-543258e3ee83: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:b8:24:bc:b8 brd ff:ff:ff:ff:ff:ff
    inet 172.22.0.1/16 brd 172.22.255.255 scope global br-543258e3ee83
       valid_lft forever preferred_lft forever
8: br-7dfb510f7ade: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:42:89:28:64 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 brd 172.19.255.255 scope global br-7dfb510f7ade
       valid_lft forever preferred_lft forever
9: br-93389832bec3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:3c:0e:c8:07 brd ff:ff:ff:ff:ff:ff
    inet 172.23.0.1/16 brd 172.23.255.255 scope global br-93389832bec3
       valid_lft forever preferred_lft forever
10: br-998f0cb53ef4: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:58:94:61:57 brd ff:ff:ff:ff:ff:ff
    inet 172.20.0.1/16 brd 172.20.255.255 scope global br-998f0cb53ef4
       valid_lft forever preferred_lft forever

実際、沢山出てきます。 WSL上で動かしているWebアプリケーションを外部から参照する - @ledsun blog で試したときは1つのIPアドレスが得られていました。 何が変わったのでしょうか? ChatGPTにネットワークインターフェースについて質問してみました。

WSL Docker Network Interface

br-998f0cb53ef4 はDockerに関連したネットワークインターフェースの名前です。具体的には、Dockerが作成するブリッジネットワークのインターフェースの名前の一例として見られます。

なるほど、そういえばWSLにDockerをインストールした記憶があります。

推測するな、計測せよ

Rob Pike先生は「推測」は否定していないのでは?

「推測するな、計測せよ」はミスリードと言う話 - aki33524’s blog

Rob Pikeが批判しているのは「合理的な根拠なしに決めつけてハックをすること」であって、「推測」すること自体ではない。

「推測」の否定っておかしいのでは?という疑問です。興味深いので検索してみます。

Rob Pike先生がsecond guessと書いたのを「推測」と訳したのが間違い?

Coding and Marketing on Tumblr

後述しますが、前節の勘ぐるなは guess ではなく second guess です。guess は推測する、second guess は(合理的な根拠なしに)予測する、勘ぐる、という意味です。

guessとsecond guessは違うみたいです。

Notes on Programming in Cで1987年の原文が読めます*1

Rule 1. You can't tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don't try to second guess and put in a speed hack until you've proven that's where the bottleneck is.

確かにsecond guessです。

second guessの意味・使い方|英辞郎 on the WEBでは

〔合理的な根拠なしに~を〕予測する、勘ぐる

と、second guessに「勘ぐる」という意味があるとしています。 ということは、Rob Pike先生がguessではなくsecond guessと書いたものを、「推測」と訳したため誤解が生まれてそうです。

Rob Pike先生は「推測」すること自体は否定していなさそうです。 さらに、次の情報も見つかりました。

「推測するな、計測せよ」の元ネタはRob Pike先生でない?

「計測なくして改善なし」は誤引用 - ぜぜ日記

そもそもプログラムの速度など計測可能なものについては、"Measure Don't Guess." 「推測するな、計測せよ」という情報工学界隈にて誰が言い出したかわからない*2格言で殴ったほうが誤解は少なそうである。

*2:ベル研でUNIXの開発に携わっていたRobert C. Pikeという説もある。Wikipediaによれば彼の著書のNotes on Programming in Cに近い文章は書いてあるようだがそのものは見つからない

「推測するな、計測せよ」はRob Pike先生とは独立という説です。 また、原文の"Measure Don't Guess." の時点で、second guessのsecondはなくなっています。

"Measure Don't Guess."で検索するといくつか使用例が出てきます。

また、「燃えよドラゴン*2」風の"Don't guess, measure!”という言い回しもあるようです。

"Measure Don't Guess."や"Don't guess, measure!”はそれで十分意味が通ります。 説明の補足として、Rob Pike先生の文章を添付することもありそうです。

しかし、Rob Pike先生の文章の要約としては不十分にも思えます。 後半でより多くの字数をつかって説明しているアルゴリズムとデータ構造の話を捨てて、前半のRule1とRule2だけを要約するのは不自然です。

自分で訳してみる

以上の理解を踏まえて、自分で訳してみます。

UNIX哲学 - Wikipediaの日本語訳*3では

ルール1: プログラムがどこで時間を消費することになるか知ることはできない。ボトルネックは驚くべき箇所で起こるものである。したがって、どこがボトルネックなのかをはっきりさせるまでは、推測を行ったり、スピードハックをしてはならない。

「推測」が使われています。確かに、なんとなくニュアンスが違うような気がしてきました。 自分で訳してみます。

Rule 1. You can't tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don't try to second guess and put in a speed hack until you've proven that's where the bottleneck is.

プログラムのどこに一番時間が掛かっているか上手く説明できますか? ボトルネックはあなたが予想した場所にはありません。 当てずっぽうでチューニングしてはいけません。 まず、計測してボトルネックを特定しましょう。

Bottlenecks occur in surprising places

を肯定文で書くのすごいです。 僕が、日本語で表現しようとすると、どうやっても否定文になります。

second guess and put in a speed hack

は、 and でつながっています。Wikipedia訳の 「推測を行ったり、スピードハックをしてはならない。」はsecond guessとput in a speed hackを別々に実行できそう見えるので、良くないように思います。「推測でスピードハックをしてはならない。」くらいが良さそうです。

なんかこの辺が原因っぽい気はします。

もしかしたら

推測を行ったり、スピードハックをしてはならない。

は「推測してはならない」そして「スピードハックしてはならない」と読めてしまいそうです。この前半部分だけ取り出すとRob Pike先生が「推測してはならない」と主張していると読み取れます。

これにRule 2の「計測すべし。」をくっつければ「推測するな、計測せよ」のできあがりです。

こんな感じで「『推測するな、計測せよ』の元ネタがRob Pike先生である」説が生まれたのかもしれません。

*1:あらためて読むと「チューニングのために流行のアルゴリズムを入れるのを、やめろ。計測してボトルネックを特定して、データ構造を作り直せ」みたいな気持ちが読み取れて面白いです。

*2:燃えよドラゴン」は1973年の映画です。出演したブルース・リーの台詞に「Don't think! Feel.」があります。

*3:2008年5月の訳のようです。https://ja.wikipedia.org/w/index.php?title=UNIX%E5%93%B2%E5%AD%A6&diff=prev&oldid=19897006

寿司屋の玉子焼き、最初に食べるか最後に食べるか

お寿司屋さんで握りランチを頼むと、大抵玉子焼きが手前に置かれます。「手前に置く」ということは「最初に食べろ」ということなので、なるべく最初に食べます。ただ、ご飯の最初に甘いものを食べるのはなんとなく据わりが悪いです。

この議論は昭和5年にはすでにあったようです。

「すし通」という昭和5年の本の「二十五、「鮨は玉子焼から」の論」によると

古くから「鮨は玉子焼から食べるものだ」とか、鮨通はそうするものだとか聞いている。で食通にいわせると、「玉子焼から食べると、そこの飯のよしあしや酢加減が一番よくわかる」と。

玉子焼き先派がいるようです。

そういえば一蘭でもラーメンの前に半熟塩ゆで卵をすすめています。

ゆでたまごは、口の中に残っている今までの味を全く無にし、次に口にする物の味を鮮烈にする効果があるともいわれています。最初に食べることで味覚がさらに研ぎ澄まされ、ラーメンの味をより鮮烈にまた奥深く味わうことができます

なるほど。続く握りを楽しむために、最初に玉子焼きを食べるのもよいのかもしれません。 「すし通」を読み進めていくと

鮪や穴子のような濃厚な味のものを食べてから最後に口直しに食べるのが至当である。

とあります。ですよね。僕にはこっちの方がしっくりきます。 そして、結論は

だからあえて「鮨は酢の物から」と言っておく。ただしこれは味覚上からの問題で、礼儀からいったら皿の一番前のものから食べ始めるべきものである。

あ、はい。ですよね。 玉子焼きが手前に置いてあったら手前から食べますよね。

まあ、なんとも据わりの悪い結論でした。

参考

ラーメンとゆで卵 - rekihachiのブログ

WebAssembly勉強メモ

ruby.wasmの発表ラッシュが終わりました。ruby.wasmのハックをしていると、WebAssemblyの理解があやふやなことが我ながら気になります。ここらで一度、足元を固めたいと思います。

じっくり、本を読んでみようと思います。

Emscriptenを使っているので、WASIを使っているruby.wasmとはツールチェーンがちょっと違います。WebAssembly自体の理解には良さそうです。

読んだ範囲では、asm.jsについて簡単な説明があったのが良かったです。JavaScriptと互換性を保ったまま型アノテーションをつけるそうです。TypeScriptっぽいアプローチなのが面白いです。型がわからないとJITができないのも、YJITのLazy Basic Block Versioningで型を決め打ちするのと似てて面白いです。

とりあえずはWebAssemblyバイナリーに含まれる情報を理解することを目指そうと思います。WebAssembly Specification — WebAssembly 2.0 (Draft 2023-07-24)に仕様があるので、これがなんとくなく読めるようになるのをゴールにすると良さそうです。

WebAssemblyはアセンブラ相当の命令を書き出せば動きます。機械語やリンカは考えなくて良いのですこしハードルが下がります。katsyoshiさんの真似してコンパイラをつくってみるのもいいかもしれません。RubyJavaScriptならASTも簡単に手に入ります。四則演算くらいから始めると良さそうです。

ゆくゆくは、WASM Spec難しすぎへんかのようにWebAssemblyバイナリーを動かすランタイムをつくってみるのもいいかもしれません。

RubyKaigi 2023 follow upでしゃべりました

発表

RubyKaigi 2023 follow up - connpass で発表しました。 発表資料です。

speakerdeck.com

TokyuRuby会議14にてLTをした - @ledsun blogとちぎRubyの勉強会 拡大版で発表しました - @ledsun blog につづき3連続です。 この3回ではスライドのテンプレートを統一しています。 背景の上半分が赤で下半分が青です。それぞれRubyとWebAssemblyを表しています。 ruby.wasmなのでまんまです。

SpeakerdeckにアップロードしているPDFはグラデーションですが、発表時のスライドは上下二色分割です。 これは背景色をPowerPointの上下グラデーションでグラデーションの傾度を0にして描いているからです。 PDF出力するときは傾度が反映されないようです。

発表の前日のruby.wasmのビルドができなくなるトラブルがありました。 当日はデモの代わりにビルドエラーを見せる飛び道具で乗り切りました。

懇親会

勉強会に続いて、懇親会もPixivさんで行われました。 とてもよい体験でした。 人数の割に会場が広いので小グループにわかれて雑談がしやすかったです。

19時から23時と長い懇親会だったので、色々な人と話せて良かったです。 食べ物も飲み物も潤沢で、会社からスポンサー費用が出せて良かったなと思います。 会場の片付けは大変だったと思います。Pixivさんやbashさんにはとても感謝です。

昔から知っているおじさん達とも話せましたし、Sousuke Suzukiさんのような若い人とも話せて良かったです。

PixivのDJ陣の人で、2000年代スカ(SNAIL RAMPKEMURI)を流している人がいて、知らない曲があったので聞いてみたらZebraheadでした。 あとで家に帰ってからZebrahead聞いてみたら、確かにスカっぽい曲がありました。いまさら知りました。 30代の方なので、2000年代スカは世代が違うのでは?と思ったら、おじさん向けに選曲してくれてたそうです。

あと、浅草の人たちとソース焼きそばの起源が浅草であるという話をしました。このとき「オープンソース焼きそば」というダジャレをインパクトの瞬時に拾えなかったのが悔やまれます。 日記を検索すると 焼きそばの歴史《上》: ソース焼きそば編 - @ledsun blogが引っかかりました。

 が僕が読んだ本です。

 も、同じ作者の本です。新しいので、今読むならこれがよさそうです。

ruby.wasmのビルド中にruby/zlibのconfigureに失敗する

ビルド終盤の linking ruby のあとでエラーが起きています。

ruby.wasmのビルドエラー

zlibがインストールされていない?

dpkg -l |grep zlibを実行すると

ledsun@MSI:~/ruby.wasm[1]►dpkg -l |grep zlib
ii  zlib1g:amd64                        1:1.2.11.dfsg-2ubuntu9.2                     amd64        compression library - runtime
ii  zlib1g-dev:amd64                    1:1.2.11.dfsg-2ubuntu9.2                     amd64        compression library - development

zlibはインストールされています。

sudo apt --reinstall install zlib1g zlib1g-devしてみます。変わりません。

エラーログファイルをみる

build/wasm32-unknown-wasi/head-wasm32-unknown-wasi-full-js-debug/ext/zlib/mkmf.logをみます。 build/wasm32-unknown-wasi/head-wasm32-unknown-wasi-full-js-debug/ext/zlib/mkmf.log · GitHub

15~16行目

conftest.c:3:10: fatal error: 'zlib.h' file not found
#include <zlib.h>

echo '#include <zlib.h>' | gcc -o a.out -E - は成功します。 zlib.hが見えなくなる条件があるようです。

ビルドの出力を確認

コンパイルオプションぽいものをさがします。

HEAD is now at aabc019 Skip VM_CALL_BLOCKISEQ for Ruby < 3.3
echo aabc019684d8b4a1ed66c2a1ca48da7bbb18dcc0 | /bin/sh /home/ledsun/ruby.wasm/build/checkouts/head/tool/ifchange /home/ledsun/ruby.wasm/build/checkouts/head/.bundle/.timestamp/typeprof.revision -
/home/ledsun/ruby.wasm/build/checkouts/head/.bundle/.timestamp/typeprof.revision unchanged
        BASERUBY = /home/ledsun/ruby.wasm/build/x86_64-pc-linux/baseruby-head/opt/bin/ruby --disable=gems
        CC = /home/ledsun/ruby.wasm/build/checkouts/head/tool/wasm-clangw /home/ledsun/ruby.wasm/build/toolchain/wasi-sdk/bin/clang
        LD = /home/ledsun/ruby.wasm/build/toolchain/wasi-sdk/bin/clang
        LDSHARED = /home/ledsun/ruby.wasm/build/toolchain/wasi-sdk/bin/clang
        CFLAGS = -fdeclspec -O3 -fno-fast-math -g -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wundef   -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_PROCESS_CLOCKS
        XCFLAGS = -DWASM_SETJMP_STACK_BUFFER_SIZE=24576 -DWASM_FIBER_STACK_BUFFER_SIZE=24576 -DWASM_SCAN_STACK_BUFFER_SIZE=24576 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fno-strict-overflow -fvisibility=hidden -DRUBY_EXPORT -I. -I.ext/include/wasm32-wasi -I/home/ledsun/ruby.wasm/build/checkouts/head/include -I/home/ledsun/ruby.wasm/build/checkouts/head -I/home/ledsun/ruby.wasm/build/checkouts/head/enc/unicode/15.0.0
        CPPFLAGS = -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_PROCESS_CLOCKS
        DLDFLAGS = -Xlinker --stack-first -Xlinker -z -Xlinker stack-size=16777216 /home/ledsun/ruby.wasm/build/wasm32-unknown-wasi/wasi-vfs-0.1.1/libwasi_vfs.a @/home/ledsun/ruby.wasm/build/wasm32-unknown-wasi/head-wasm32-unknown-wasi-full-js-debug-ext/js/link.filelist @/home/ledsun/ruby.wasm/build/wasm32-unknown-wasi/head-wasm32-unknown-wasi-full-js-debug-ext/witapi/link.filelist /home/ledsun/ruby.wasm/build/wasm32-unknown-wasi/head-wasm32-unknown-wasi-full-js-debug-ext/extinit.o
        SOLIBS = -lcrypt -lm -lwasi-emulated-mman -lwasi-emulated-signal -lwasi-emulated-getpid -lwasi-emulated-process-clocks
        LANG = C.UTF-8
        LC_ALL =
        LC_CTYPE =
        MFLAGS =
        RUSTC = rustc
        YJIT_RUSTC_ARGS = --crate-name=yjit --crate-type=staticlib --edition=2021 -g -C lto=thin -C opt-level=3 -C overflow-checks=on '--out-dir=/home/ledsun/ruby.wasm/build/wasm32-unknown-wasi/head-wasm32-unknown-wasi-full-js-debug/yjit/target/release/' /home/ledsun/ruby.wasm/build/checkouts/head/yjit/src/lib.rs
clang version 13.0.0 (https://github.com/llvm/llvm-project fd1d8c2f04dde23bee0fb3a7d069a9b1046da979)

インクルードパスっぽいものをさがします。

-I. -I.ext/include/wasm32-wasi -I/home/ledsun/ruby.wasm/build/checkouts/head/include -I/home/ledsun/ruby.wasm/build/checkouts/head -I/home/ledsun/ruby.wasm/build/checkouts/head/enc/unicode/15.0.0

ここにzlib.hがないのでしょうか?

locate zlib.hを実行すると

ledsun@MSI:~/ruby.wasm[1]►locate zlib.h
/home/ledsun/.cache/node-gyp/17.0.1/include/node/zlib.h
/mnt/c/Ruby30-x64/msys64/mingw64/include/bzlib.h
/mnt/c/Ruby30-x64/msys64/mingw64/include/szlib.h
/mnt/c/Ruby30-x64/msys64/mingw64/include/zlib.h
/mnt/c/Users/led_l/.cargo/registry/src/github.com-1ecc6299db9ec823/libgit2-sys-0.12.26+1.3.0/libgit2/deps/zlib/zlib.h
/mnt/c/Users/led_l/.cargo/registry/src/github.com-1ecc6299db9ec823/libz-sys-1.1.3/src/zlib/zlib.h
/mnt/c/Users/led_l/.cargo/registry/src/github.com-1ecc6299db9ec823/libz-sys-1.1.3/src/zlib-ng/zlib.h
/mnt/c/Users/led_l/AppData/Local/Programs/Python/Python311/Doc/html/library/zlib.html
/mnt/c/bm311/psql/include/zlib.h
/usr/include/zlib.h
/usr/include/node/zlib.h
/usr/share/doc/nodejs/api/zlib.html
/usr/src/linux-ibm-headers-5.15.0-1034/include/linux/zlib.h
/usr/src/linux-ibm-headers-5.15.0-1035/include/linux/zlib.h

/usr/include/zlib.hがあります。 コピーしたらいいのかな?

cp /usr/include/zlib.h /home/ledsun/ruby.wasm/build/wasm32-unknown-wasi/head-wasm32-unknown-wasi-full-js-debug/.ext/include/wasm32-wasi /.してみます。 変わりません。

ファイルが見つかってないわけじゃないのかなあ?

CIはどうなっているの?

Nightly release · ruby/ruby.wasm@d0e1445 · GitHub

GitHub Action上でのエラー

同じエラーが起きています。 成功しているジョブもあります。

Ruby 3.2は成功しています。

Ruby 3.2ならビルドが通るのでは?

rake npm:ruby-3_2-wasm-wasiしてみます。

rake npm:ruby-3_2-wasm-wasi をしたときのエラー

headとおなじエラーがでます。 CIとは環境のちがいもあるのかもしれません。

WebAssembly用語集

ruby.wasmのソースコードを読んでいるとWebAssemblyに関する用語が出てきます。用語を間違って読みとるとソースコードも間違って読んでしまいます。ソースコードを読むのに無駄に時間がかかります。そういうわけで、ソースコードを読みながら調べた用語をここにまとめておきます。

キーワード 説明 追加情報
WebAssembly Webブラウザで実行できるバイナリコードの仕様
wasm WebAssemblyの略称。Webのwとアセンブリのasmをくっつけたもの。「わずむ」と発音します。
BytecodeAlliance Moziilaが中心になって立ち上げたWebAssemblyの仕様を決める団体。Fastly、IntelRed Hatも参加しています。 詳細 
WASI WebAssembly System Interfaceというインターフェース仕様。「わじ」と読みます。WebAssemblyからホストの機能を呼び出すときのインターフェース。BytecodeAllianceで決めています。 詳細
wasmtime WebAssemblyをWebブラウザ以外で実行するランタイム。BytecodeAllianceが作っています。 詳細
wasmer WebAssemblyをWebブラウザ以外で実行するランタイムとそれを作っている会社 詳細
WASIX wasmerが提唱するWASIの拡張。スレッドとかソケットを使えるようにしたいみたい。BytecodeAllianceとは独立した動きです。 詳細
Emscripten WebAssemblyが考えられるずっと前から、C/C++ソースコードWebブラウザで実行しようとするプロジェクトとそのためのコンパイラ。WebAssemblyが生み出されるきっかけになりました。いまはWebAssemblyを生成するコンパイラの一つです。 詳細
WIT Wasm Interface Type の略。WebAssemblyとホストの間のインタフェースを定義するためのIDL 詳細
wit-bindgen WITからバインディング(ホストとゲストのメソッドを呼び出すコード)を生成します。bytecodeallianceが作っています。 詳細
ABI Application Binary Interface の略。バイナリ間でお互いに呼び出せるためのインターフェース。WebAssemblyに関わらずあるプログラミング言語から他のプログラミング言語を呼ぶときに使う用語です。同一のプログラミング言語内でAPIというところをプログラミング言語を跨ぐとABIという感じです。

とちぎRubyの勉強会 拡大版で発表しました

とちぎRubyの勉強会 拡大版 で発表しました。

発表資料はこちらです。

自分がプルリクエストをつくるときの手順を例にハッキングの仕方を整理しました。我ながら、現時点のruby.wasmのハッキングの仕方の説明としてなかなか上手くできたと思います。

先週のTokyuRuby会議14にてLTをした - @ledsun blogに続いての発表です。資料をつくりながら、僕の気持ちを表現するのにぴったりな「ブラウザでRubyをキメると気持ちいい」*1というフレーズを思いついたのが良かったです。

途中気分転換に

を読んでいたら「謎を一つ解くだけの話は中ダレするので、最初に提示した謎を解いたら次の謎が出てくるようにすると良いと」ありました。参考にして、前半で知った情報プラスアルファで、後半の謎が解けるようにしてみました。上手く行ったでしょうか?

帰り道で「次の機会に、こんなことを聞いてみたい」を思いついたのでメモしておきます。

  • 山口さんに、2000年代のQA組織(開発プロセスの最後尾にいて出荷判定するイメージです)と現代のQA組織の役割のちがい
  • よしおりさんに、世の中ではどんなテストをしているのか。発表では聞き手にわかりやすくするためにRubyRailsを例にしていました。それ以外の分野ではちがったテストをしていそうだな?と思いました。
  • しおいさんに、発表がいつも面白いので、どうやって資料作ったり練習しているのか。

*1:RubyKaigi2008のMatzの「Rubyをキメると気持ちいい」の本歌取りです