テストを実行してRubyの型情報を集めるやつを作った

イントロダクション

「テストを走らせて型情報を収集すればいいんじゃない?」そのアイデア自体は話題に上がることが多かったかと思われますが、観測範囲では前例がないように見えます。そこで、実際に作ってこそ見える世界があると思い動くものを実装してみました。

Orthoses::Trace

github.com

orthosesはRBSを生成するための機能を作るフレームワークで、この機能の一つとしてOrthoses::Traceというミドルウェアを実装しました。

例題として、rack-testというgemのRBSを生成したいとします。 その場合の生成コードをOrthoses::Traceを使って以下のように準備します。

https://github.com/ksss/orthoses/blob/db80d506c5fb02dadaa0ae303e0761ba0a543f6f/examples/rack-test/generate.rb

require 'pathname'
require 'orthoses'
require 'fileutils'

include FileUtils

out = Pathname('out')
out.rmtree rescue nil
Orthoses.logger.level = :warn
Orthoses::Builder.new do
  use Orthoses::CreateFileByName,
    base_dir: out.to_s
  use Orthoses::Filter do |name, content|
    name.start_with?("Rack::Test")
  end
  use Orthoses::Trace,
    patterns: ['Rack::Test*']
  run ->(){
    cd "src" do
      load "spec/all.rb"
      Minitest.run
      # avoid to run on at_exit
      module ::Minitest
        def self.run args = []
          0
        end
      end
    end
  }
end.call

このコードを実行すると、rack-testのリポジトリ内のテストコードを走らせ、RBSを生成することができます。

Orthoses::CreateFileByNameミドルウェアの働きによってclass毎にファイルができます。

その一部を見てみましょう。Rack::Test::UploadedFileの場合は以下のような出力を得ることができます。

class Rack::Test::UploadedFile
  attr_reader tempfile: Tempfile?
  attr_reader original_filename: String
  attr_accessor content_type: String
  def self.finalize: (Tempfile file) -> Proc
  private def initialize_from_file_path: (String path) -> nil
  private def initialize: (String content, ?String content_type, ?bool binary, ?original_filename: nil) -> void
                        | (StringIO content, ?String content_type, ?bool binary, ?original_filename: String) -> void
                        | (String content, ?String content_type, ?bool binary, ?original_filename: String) -> void
                        | (StringIO content, ?String content_type, ?bool binary, ?original_filename: nil) -> void
  private def respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool
  def method_missing: (Symbol method_name, *Array[untyped] args) ?{ (*untyped) -> untyped } -> (Integer | String)
                    | (Symbol method_name, *Array[Encoding] args) ?{ (*untyped) -> untyped } -> File
  def append_to: (String buffer) -> nil
  def self.actually_finalize: (Tempfile file) -> bool
  private def initialize_from_stringio: (StringIO stringio) -> StringIO
  def path: () -> String
end

テストコードを走らせて、わりとそれっぽいRBSを得ることができました。

実装方法

TracePointを使いました。基本的にはTracePointのcallイベントで引数の型を、returnイベントで返り値の型を、それぞれテスト実行時に集めておくという発想です。

コード例で説明します。

def foo(a)
  a.id
end

みたいなコードがあったときに、このままではa.idが何を返すのかわかりません。 そこで、実際にコードを実行し、fooが呼ばれたときのaの値を見て引数の型を、返り値を見て返り値の型をfooの型として蓄積しておきます。

結果として

  • 引数aはRecord型, 返り値はIntegerだった。
  • 引数aはRecord型, 返り値はnilだった。

という情報を集めることができます。

これらの情報を統合して

def foo: (Record) -> Integer?

というRBSに落とし込む、というのが基本的な発想です。 コードの実行が必要ではありますが、コードを実行する最高のサンプルとしてテストコードを使用すれば、既存の資産を流用しつつ0からよりも遥かに効率的にRBSを記述することが期待できます。

既存の手法

静的な型ファイルを自動生成する既存の手法としては以下の前例があります。 これらの前例と今回の手法を比較し、どのようなメリットがあるのかを考えます。

rbs prototype

その名の通り、RBSのプロトタイプを作成するためのコマンドです。 静的解析と動的解析の両方があり、コマンドひとつで生成できるので、RBSのプロトタイプ生成として最も有名な方法だと思われます。

typeprof

静的解析によってできるだけ型情報を収集する手法です。かなり精度も高く「そんなことまで分かるのか!」と驚きます。

rbs-dynamic

RubyKaigi2022で発表されたツールで、コードを実行して型情報を得るものです。 今回実施した手法にかなり近いのですが、今回のように既存のテストコードをまるごと回す用途ではエラーが発生し動かせなかったので今回は使用していません。

比較

rbs prototype rb

ソースコードに対して実行します。簡略化のためコメントは除きます。

$ rbs --version
rbs 3.0.4

$ rbs prototype rb src/lib/rack/test/uploaded_file.rb | grep -v '#'
module Rack
  module Test
    class UploadedFile
      attr_reader original_filename: untyped

      attr_reader tempfile: untyped

      attr_accessor content_type: untyped

      def initialize: (untyped content, ?::String content_type, ?bool binary, ?original_filename: untyped?) -> void

      def path: () -> untyped

      alias local_path path

      def method_missing: (untyped method_name, *untyped args) { () -> untyped } -> untyped

      def append_to: (untyped buffer) -> nil

      def respond_to_missing?: (untyped method_name, ?bool include_private) -> untyped

      def self.finalize: (untyped file) -> untyped

      def self.actually_finalize: (untyped file) -> untyped

      private

      def initialize_from_stringio: (untyped stringio) -> untyped

      def initialize_from_file_path: (untyped path) -> untyped
    end
  end
end

rbs prototype runtime

$ rbs prototype runtime -r rack/test/uploaded_file 'Rack::Test::UploadedFile'

rbs prototype rbとほぼ変わらない結果となったので割愛

typeprof

$ typeprof --version
typeprof 0.21.7

$ typeprof src/lib/rack/test.rb src/spec/rack/test/uploaded_file_spec.rb
# ...snip...
class UploadedFile
  attr_reader original_filename: String?
  attr_reader tempfile: String | StringIO
  attr_accessor content_type: String
  def initialize: (String | StringIO content, ?String content_type, ?bool binary, ?original_filename: String?) -> void
  def path: -> untyped
  alias local_path path
  def method_missing: (:must_respond_to | :pos | :read method_name, *Symbol args) -> untyped
  def append_to: (untyped buffer) -> nil
  def respond_to_missing?: (untyped method_name, ?false include_private) -> true
  def self.finalize: (String | StringIO file) -> ^-> untyped
  def self.actually_finalize: (untyped file) -> untyped

  private
  def initialize_from_stringio: (String | StringIO stringio) -> (String | StringIO)
  def initialize_from_file_path: (String | StringIO path) -> void
end
# ...snip...

比較考察

出力は一部しか示していませんが、全体的な比較として以下のようになりました。

Tool メリット デメリット 備考
rbs prototype 実行が非常に手軽だった ほとんどがuntypedだった なし
typeprof 実行が手軽、かつある程度型がある。インスタンス変数も対応している。 untypedはある rbs collectionで型情報を追加できる可能性がある
Orthoses::Trace 型情報が多い 実行のためにコードを書く必要がある rbs collectionで型情報を追加できる可能性がある

手軽さと正確性にある程度のトレード・オフの関係があるように見えます。

もう少し細かく比較してみましょう。

実行の手軽さ

圧倒的にrbs prototypeやtypeprofが手軽です。Orthoses::Traceではちょっとしたスクリプトレベルのコードを書く必要があり手軽とは言えません。

型の正確性

方法によって考慮されている型情報に差はありますが、どの手法でも最終的な型情報はプログラマーの理想のものになるはずです。よっていずれの手法でもある程度の手直しは必要そうです。

対応範囲

typeprofは定数やインスタンス変数も対応しているのに対して、Orthoses::TraceではTracePoint軸なのでフックが設定でず対応できません。typeprofすごい。 また、Orthoses::Traceでは、呼ばれていないメソッドは情報がないので型生成できません。typeprofは呼ばれてなくても類推できます。typeprofすごい。

テストツールごとの対応

Orthoses::Traceではコードを書くことでテストを走らせ、型情報を得ることができることがわかりました。 テストツールは様々にあり、minitestやtest-unit、rspecなどが有名です。コードを書く以上、それぞれのツールに対応したコードを書く必要があります。 ここではOrthoses::Traceを使った場合のテストツール毎のコードの書き方を示しておきます。

まずテストコードを含むソースコード全体が必要なので、ソースコードgit clone等でダウンロードしておきます。 以後説明のため、このソースコード置き場のディレクトリー名を仮にsrcとします。

minitest

最初に示したrack-testがminitestを使っています。 minitestを使っている場合、autorunを使っていなければload "test/run.rb"みたいな感じでテストコードを読み、Minitest.runでテストを走らせることができます。しかし、大抵はautorunを使っているので工夫が必要です。 autorunはat_exitで実行されるので、そのタイミングだとorthosesで解析するProcの外になってしまいます。 なのでProc内でテストを実行しつつ、autorunを消すためにメソッドをまるごと上書きしています。Minitest.runはexit codeを返すインターフェースなので0を返すのがミソです。

test-unit

多分こんな感じだと思います。

Orthoses::Builder.new do
  use Orthoses::Trace, patterns: ['Foo*']
  run ->(){
    cd "src" do
      load "test/foo_test.rb"
      Test::Unit::AutoRunner.run

      class ::Test::Unit::AutoRunner
        def self.run(*, **)
          0
        end
      end
    end
  }
end

rspec

あんまりrspecを使ったライブラリーを見つけられなかったのですが、おそらく以下で動くと思います。 ただし、テストが成功しないとexitで強制終了してしまうのですが、テストが失敗しているということは型は間違っている可能性が高いのでこれでいいのかも。

require "rspec/core"
Orthoses::Builder.new do
  use Orthoses::Trace, patterns: ['Foo*']
  run ->(){
    cd "src" do
      load "spec/foo.rb"
      RSpec::Core::Runner.invoke
    end
  }
end

めんどい

テストライブラリーに合わせて柔軟に対応できる分、自分でコードを書かなければいけないのがめんどいので、もう少しサポート方法を考えたいところですね。。。 しかしながらリポジトリ毎にテストの走らせ方は千差万別で、gemの依存関係もそれぞれ異なってくるので統一された方法 は難しいと考えています。

思うところ

TracePoint#enable(target: )問題

attr_accessor系のメソッドがRubyVM::InstructionSequence.ofに対応してくれるともう少しコードが簡単になるのですがレアケースそう……。

実装の詳細

実装面はかなり端折りましたが、実は面白い工夫が盛りだくさんです。TracePointに興味がある人は覗いてみてください。

typeprofすごい

ということでOrthoses内でtypeprofを呼ぶことができる拡張も作りました。

https://github.com/ksss/orthoses-typeprof

これまでにOrthoses内で蓄積した情報をtypeprofに渡して型の参考値にすることができます。

ちなみに今回の出力結果は以下にまとめたので比較してみてください。

Orthoses::Trace: https://github.com/ksss/orthoses/tree/main/examples/rack-test/out/rack typeprof: https://github.com/ksss/orthoses-typeprof/tree/main/examples/rack-test/out/rack

感想

これでライブラリーのRBS追加が少し楽になるかも……?どうだろう、typeprofで十分なのかもしれない……。

とりあえず作ってみる事はできたので、今後は使ってみて便利かどうか確かめる。ですかねえ……。

そもそも何かしらやりたいことがあって、そのときに思いついた手法なのですが、思いついて実装してブログを書くのに5ヶ月ぐらいかかってしまい本来の目的を見失いました。お後がよろしいようで。

思いついたとき。

YARDタグからRBSを生成する

YARD

YARDはドキュメンテーションツールです。 Rubyのコメントに

# @param [String] a
# @return [void]
def foo(a)
end

みたいな記述を見たことがありませんか? この@paramがYARDのタグ名、[String]がタグのもつ型情報です。aは引数の名前ですね。

YARDではこの型情報を元にリンクを貼ったwebページを生成したりできます。

rubydoc.infoがまさにYARDによって生成されていますね。

RBS

RBSRuby公式の型を記述する書式、およびツールの名前です。

こんなかんじです。

def foo: (String a) -> void

YARD to RBS

では

# @param [String] a
# @return [void]
def foo(a)
end

とかかれたソースコードを読み込んで

def foo: (String a) -> void

とかかれたRBSを出力できたら便利だと思いませんか?

orthoses-yard

というわけで実装したのがorthoses-yardです。

github.com

orthoses-yardはorthosesの拡張です。 orthosesが何かは、RubyKaigi2022で紹介した資料を御覧ください。動画はもうすぐ出ると思います。*1

orthoses-yardではこのorthosesの仕組みに乗っかって、任意のpathのRubyをパースしたYARD情報からRBSを生成します。 「アプリケーションの一部だけしかYARDドキュメント書けてないんだよな」という場合でも柔軟に対応できます。

基本的には@paramタグを引数として、@returnタグを返り値として解釈し、メソッドの型定義を生成しています。 また、@yieldparamタグと@yieldreturnタグにも対応し、ブロックの型表現も可能です。

これ(Rubyコード)から

class Foo
  # @param [String] a
  # @return [Integer]
  # @yieldparam [Symbol] b
  # @yieldreturn [Boolean]
  def foo(a)
  end
end

これ(RBS)を作れます。

class Foo
  # @param [String] a
  # @return [Integer]
  # @yieldparam [Symbol] b
  # @yieldreturn [Boolean]
  def foo(String a) { (Symbol b) -> bool } -> Integer
end

orthosesは柔軟なので、アプリケーションだけでなくgemのRBSもorthoses-yardを使って生成できます。手始めにyard gem自体の型定義をYARDタグを元に生成してみました。yard gemならYARD記法を使いこなして広いパターンでの挙動確認ができそうだったのが選択理由です。

Add Yard gem by ksss · Pull Request #217 · ruby/gem_rbs_collection · GitHub

既存のYARDドキュメントを元にメソッドや定数の型を決定することができています。

RBS自動生成の肝

RBS自動生成最大の問題はmethodの型定義です。

orthosesによるRBS自動生成では、classの名前や継承関係、includeしているmoduleを自動的に洗い出したりするのは得意ですが、 methodの型定義を決めることは困難なのです。

なぜならmethodの型は基本的には人間が決めるべきであり、人間が書くべきです。 正しいものが何なのかわからないと、methodの実装自体が型チェックができないですしね。

def foo
  if cond
    "foo"
  else
    :bar
  end
end

上記例だと静的解析すればfoo: () -> String | Symbolとなりますが、 実際はStringだけを期待していてSymbolが返るのは誤りになるケースも多くあるでしょう。 ましてやattr_accessorだとさらに難しくなります。

methodの型は手で書くしか無いのでしょうか……。

でも自動化したいですよね。

もし既存の資産であるYARDドキュメントを利用できれば、methodやattr_accessorであっても型ファイルを生成できます。

このYARDからの生成によって、このmethod定義問題に良いアプローチができるんじゃないかと期待しています。

YARDを書けばRBSを生成できるならYARDを書くモチベーションを上げることもできますし、YARDとRBSで二重管理になることも防げます。

補足

ちなみにRubyの型界隈に詳しい人は、似たようなツールとしてsordを思い浮かべたと思います。 確かに今回実装したものはsordによく似ていますが、orthosesの拡張なので他のorthosesの機能と組み合わせられるという点が大きなウリとなっています。 例えば「Railsプロジェクトで一部だけYARDを使っている」という場合でも、orthoses-railsrails拡張と組み合わせてプロジェクト全体の型定義をコントロール可能です。

YARDの限界

YARDは非常に歴史あるプロダクトであるため、途中から追加されたkeyword argumentsの対応が弱いように感じます。YARDタグだけではそれがキーワード引数なのかどうか判別できません。もちろんYARD的には問題ないのですが、型定義を生成する場合は情報が足りません。

# どっちも同じ書き方

# @param [String] key
def bar(key)
end

# @param [String] key
def foo(key:)
end

orthoses-yardではMethodオブジェクトからparametersをとってYARD記法とマッチングさせることによってこの問題を解決しています。*2 また、RBSでのoverloadが表現できないことも若干ネックかなと思います。

# YARDだと表現できない
def foo: (Integer) -> Integer
       | (String) -> String

これから

大まかな機能はできましたが、まだまだ細かいTODOが残っているので機能を拡充してプロダクトで使ってみて更に磨きをかけていこうかと思います。

*1:動画では「orthoses-yardが欲しいんだけど誰か作らない?」と言ってますが自分で作ってしまいました。プログラミング楽しい。

*2:引数を移譲するメソッドではこの方法では対応できないので別途対応を考え中

RubyKaigi2022で発表した

rubykaigi.org

speakerdeck.com

RubyKaigi2022で発表した。オンライン参加で、発表は録画だったので、発表したかどうかは奇妙な感じだけど、ともかくできるだけのことはやりきった。

僕にとって、RubyKaigiは2013が最初の参加だった。以来「いつかは発表を……。」と思ってはいたけど、ネタがなかったり、CFPを出したけどRejectだったりだった。

今回、"Rubyに関する国内最大級の国際会議"に初登壇できたのは万感の思いだ。本当に嬉しい。

一方、採用率(採用数 / CFP数)は実は2倍もない。つまり誰にでもチャンスはあって、やれる覚悟があるかどうかが重要なのだ。みんなも発表やってみよ。

KPI

"GitHubリポジトリのスター100個"を目標にしてたけど、結果としては30くらいだった。

https://github.com/ksss/orthoses

やはり何をするものか伝わらなかったり、どうやって使うか分からないから試せないなど、色々と足りないと思うので目標に向かってがんばっていきたい。

トークの反応

良かったのか悪かったのかはよくわからない。なにせ何人の人が会場の席に座ってたのか分からないし、何人がオンラインで観ていただけていたのかも分からない。

Twitterの反応はそこそこいただけたと思う。本当に嬉しかった。Twitterに書いていただいた感想は最後に乗せるが、良さげな反応をいただいて光栄だった。

トーク内容

技術的な詳細(TracePoint系やRBSのマージ系)はRubyKaigiっぽいと思いつつも省略した。実装はコード見て。

代わりにミドルウェアの仕組みや書き方を多めに解説し、エコシステムを作る目標によせた。発表は宣伝効果が高いはずなのでこれを狙った。

実は伝えたかったこと

エコシステムを作りたい

自分にしては大きな規模の構想なので、エコシステムを作って人を巻き込んでより大きなことをやってみたい。モデルとしてはfluentdだ。あんな感じでコミュニティで拡張gemを作ったり使ったりするようになったらいいなあ。

gem_rbs_collection問題

railsアプリケーションの型付けという目標を達成するためには、既存のrails gem用の型では不十分で、大きく直さなければならない。

また、gem_rbs_collectionのメンテナンスコストを下げることも目標の一つなので、orthosesでrails gem用の型を生成する運用を狙って導入PRも作ったが、反応がない。ちょっとやり方が唐突かつ差分が大きかったのが原因かと思うので、これを改善してなんとか導入に繋げたい。

オンライン参加

直近、家族の病気が続いているのもあって間違った選択だったとは思っていない。

思ってはいないけどインパーソンが羨ましい気持ちは十分にある。せっかく"発表者"という話しかけられやすい状況なのに交流ができない。ノベルティは何一つもらえないので、物で思い出が残らない。。。

伊勢海老カレーはいただけたのが救いだった。味も美味しかったけど、作るのも楽しかった。ESM様ありがとうございます。

録画配信

壇上発表ではなく録画配信は初めてだったけど、これはこれでメリットがあるように思った。 トーク以外に力が割ける。

視聴者様の理解に少しでもつながればと事前の予習記事を用意してスライドも当日朝に公開できた。 トーク中もTwitterを覗いてlikeをつけてまわった。質問ぽいのはリプライもした。自分のトークを観てくれて反応までしてくれているのは嬉しい。尊い。 「ちなみに」みたいな補足情報を投げれるのも録画配信のおかげだった。

体調を考慮しなくてもいいので確実に発表できるし、録音もちょっとずつ撮ってそれぞれをつなげるYouTuber方式ができた。

録画方法

録画はKeynoteiMovieを使った。Keynoteには録画機能があるので、これで頭からスライドに合わせて声を撮り、かんだりしたらやり直しつつ少しずつ前に進む。完全に声優のアフレコ現場だ。 Keynoteの発表者ノートに話す"セリフ"を完全に書き出す。これが台本だ。 マスクをしてポップノイズを減らし、ゲーミングヘッドセットのマイクで録音する。ノイズを減らすためにクーラーの音が入らない締め切った部屋で撮った。汗だくになって二晩かかった。途中でスライドを足したりしたけど、時間にして6時間くらい。 Keynoteで録画ができたら、iMovieで編集作業。 編集作業は、間を調整したり、クーラーや洗濯機や風の音によるの背景ノイズを除去したり、音レベルを調整したり、咳払いをカットしたり。Keynoteである程度NGとかはなくなっているはずなので一晩で終われた。 最後にmp4動画とKeynoteファイルを提出。keynoteファイルには日本語での"セリフ"が入っているので翻訳しやすくなったはず。

英訳

本番中に英語の放送も気になって見てみたが、「自分の話したことが英訳される」という経験したことのない感動があった。英語版もYouTubeに上がったらいいなあ。

しゃべり

何年も子供に絵本を読み続けて身につけた技能として、「色々な声色で子供にも分かりやすく書いてある文字を読む」というスキルを身に着けていることに気が付き、これを活用したかった。

もっと堂々とした引き込まれるような分かりやすい発声を心がけたが、実力不足でボソボソとしてしまい怪しいセミナーみたいな謎の雰囲気が出たっぽい。

別セッション

自分の動画はイヤというほど観ているので裏番組のSyntax Treeのセッションも観ようと思ったけど、Twitterで思いの外反応をいただけてこちらの反応に忙しくて観れなかった。

スポンサー

僕の表現方法がまずくて、まるでorthosesプロジェクトにGitHub社とWantedly社がスポンサーとして付いているかのような表現になってしまった。 正しくは、2社とも個人のとしてGitHubスポンサーをいただいているだけで、orthosesプロジェクトとは関係ないです。なんなら個人スポンサーがついた理由も分からずにいる。

もしものときのために

もし通信障害が起きたときのためにYouTubeに動画をuploadしてた。こちらは使う機会がなくてよかった。配信チームに感謝。

Railsアプリケーション

やっぱりRailsアプリケーションの型生成例を出したほうがインパクトがあるとは思うので、これはなんとか提示したかったところ。手元では作れているけど、適当なRailsアプリケーションを探したりとかまでできなかった。そのうちやりたい。

雑談

このまま誰とも話さずRubyKaigiを終えるのは辛かったので雑談に加わった。 めちゃ楽しかった。

トーク中のTwitterの反応

さいごにトーク中のTwitterの反応を掲載します。 無許可掲載すみません 🙏

去年ブログを作っていた

Next.jsで作ったプロジェクトをvercelに乗せる形でブログを作っていた。2021年の3月に。

https://ksss.ink/

いろいろ手作り感があるけど、無料でお手軽に自分のサイトを作ってデプロイするという塩梅はおもしろい。

Next.jsは体験がよく、JS初心者にとっては考えることが少なくてとっつきやすかった。vercelもGitHubと連携するだけでデプロイで来てDNSレコードまで管理できる。無料で。すごい。

いろいろガシガシ記事を書くかと思いきや1年くらい新規記事が書けずに停滞している。難しいね。ブログ。

メリットは"自由"という他ないんだけど、デメリットとして様々なサービス連携ができない点がはてなブログ等と比べるとある。はてブ数で"発信力"を決めるサービスとか。

とりあえず作ったしここに記録を残しておく。1年前くらいに書いておくべきだったなあ。

2021年なにしたっけ

くらし

二人目の子供が大きくなっていき、それにつれて一人目の子供も刺激されて成長していくさまを見ながら一緒に暮らしていた。 子供のことは生活のほとんどを占めるが、プライベートの詳細には立ち入らないのであんまり書くことがない。

しいていうなら睡眠の重要性を再確認した。 夜ふかしした次の日は何をやってもダメダメだし、子供の対応でどうせしっかりとは寝れない。 かといって早寝が続くとやりたいことが何もできないので鬱憤が溜まっていく。 今は夜ふかしと早寝を交互に繰り返している。

買い物

洗濯機

洗濯機がドラム式になった。 ドラム式がいいらしいと聞いていたり、なにかと天気に左右される生活を改善したいという考えが夫婦間で大きくなってきたのでエイヤと買ってみた。 こういうのは二人共苦手としているが、がんばってよかった。 今は夜に洗濯・乾燥する生活に落ち着いている。

ハンドディスペンサー

子育てしていると、とにかく手を洗う。コロナ禍もあってさらによく手を洗う。 こんなに手を洗うなら、もしかして自動で泡が出てきたら便利なんじゃないかと思って買ってみたらやはり便利だった。 ただ、最初に買ったのは作りがイマイチで、泡が電池部分に入り込んでしまい、最終的に半年くらいで故障した。 二個目は電池部分に泡が入り込まないように完全に分離しているタイプに変えてみた。電池が大量にいる。

Kindle Paprewhite

もともと7年くらい使っていたし、画面には傷がついていてライトが付けられないし、typeCになったし、多分早くなってるだろうと思って買い替えてみた。 あいかわらず漫画はiPadで見ているが、小説にはやはりよい。これで三体を読み切った。

iPhone13 pro MAX

10からの買い替え。ひょんなことから10万円分のAppleカードをもらったので使うタイミングを伺っていた。 これまでは裸で使っていたが、考え方を変えてケースとAppleCareをつけた。高いもんなんだから保護もコストを掛けていい。 Apple純正のケースにMagsafeでくっつくリング用のベースをつけてさらにリングを付けてる。 充電もMagsafeのやつを買ってくっつけてる。このくっつけるのが楽しいので充電が捗る。

いろいろ最初だけ読んでは途中でほったらかしというパターンが多かった。 読み切れたのは三体ぐらいで、しかも半年ぐらいかかった。読み切れた事自体は達成感がある。

読み終わったあとも三体のことについて、しいては宇宙、人生について考えている……。

来年はもう少し読みたい。

健康

実は5月に全身麻酔の手術入院をしていたんだけど、この出来事をまとめようまとめようと思ってずるずる年が終わってしまった。 病気というわけではなくて、耳の一部を小さく切り取っただけ。いつかブログにまとめたいが、しない気もする……。 ともかく全身麻酔の体験は興味深かった。痛みというより意識に対する麻酔という感じ。 この手術の直前に「全身麻酔の原理は解明されていない」という記事を見て大変興味深かった。

全身麻酔が効く仕組みから「意識の発生源」が見えてきた - ナゾロジー

OSS

仕事が一段落した頃、Rubyの型についてキャッチアップしようと触ってみたらドハマリした。 これは実用的に使えるツールで開発環境を大きく変えると直感した。 仕事のRailsプロジェクトにも入れて便利に使った。 PRもいくつか送っていて、8月くらいから寝る前のちょっとした時間を使ってmergeされたのもされなかったのも合わせて60個ぐらい送っていたらしい。 仕事用のコードでエラーになっている部分のうち、これはおかしいのでは?と思った部分をひたすら改善していった。なので実用性を重視した改善が多いと思う。いやまあ興味本位での手当たりしだいというPRも多いとは思う。。。 使い始めた頃に比べたらだいぶ使いやすくなったきがするけど、まだまだ発展途上なのでもっと便利にしていきたい。 作業時間の捻出が課題。

来年

睡眠を大事にがんばりすぎない感じでがんばっていきたい。

三体 / 劉慈欣

三体の感想を書くことは不可能だ。


単純に三部作で相当な分量があり、しかも一部毎にジャンルが違うと思うくらい多様な話が盛り込まれている。三体の感想を一言で述べるということは、言葉を原子に見立てるならば、原子を無限に圧縮することになるため途中で核融合が発生してしまう。そのため永遠に到達できない。

よってこれから語るのは三体の感想ではない。ただの個人の日記である。


2021-12-23。僕は三体Ⅲを読み終えた。

僕は三体を2021年5月頃に読み始めた。Ⅰだけでも歴史あり、科学あり、VRあり、サスペンスあり、人類賛歌ありと盛りだくさんだった気がする気がするというのは、現実での時間の経過もあるが、本編の話のスケールが大きすぎて、Ⅰは遠い過去の話のように感じるのだ。

途中間も空いたりしているが、Twitter検索によるとⅡの上を読み終えたのが10月となっている。Ⅱを読み終えたのは11月末だ。

そう考えるとⅢは1ヶ月程度と、僕にしてはかなり早く読んだことになる。僕は「年内に読み切る」と目標を立てた。

目標とは、他の余計なことをしないために自分にかける呪いだ。この呪いのおかげもあってか、それとも三体の魅力もあってか、加速的に読み進めていった。


これだけの壮大なスケールにも関わらず、三体は読みやすい。難しい表現は出てこず、かなりわかりやすさに配慮しているんじゃないかと感じた。表現は淡々としていて、それでいて壮大なシーンも僕でも難なく思い描けるように書かれている。それゆえ、壮大なシーンがダイレクトに精神に届くため、〈水滴〉のシーンや〈紙切れ〉のシーンは精神的に落ち込んだ。


僕個人は、壮大な人類の歴史にとってほんの小さなひと粒の粒子でしか無い。そう考えると生きる意味についても、どうしても考えさせられてしまう。

僕は今年いっぱいでMICINを退職し、来年1月からREADYFORへ転職する。しかし、そんなことは人類にとっても宇宙にとっても些細なことだ。僕がこの人生で何をしたのかなんて、多く見積もっても百年と残らないだろう。宇宙規模で見れば、全ての個人の営みは等しく希薄化され、一切意味がないかのように思える。


はたしてそれが真実なのだろうか?


本書ではこうも語られている。

”きょうを楽しめ”というのがいつだって正しい道だったんだからね。

私達の目では原子は目に見えないほど小さい。一つ欠けたところで大して変わらないだろう。代わりはいくらでもいる。


しかし世界はその原子でできている。


宇宙規模で見ればちっぽけな命でも、宇宙の歴史はその生命の一つ一つで紡がれている。

宇宙は大きい。でも、生命はもっと大きい。

生きる意味なんてどうでもいい。生きることが意味なのだから。

Rails MVCしか知らなかったバックエンド開発者が、最近のフロントエンド開発を学んで得た知見


これは、これまでRailsの古き良きMVCな開発体制しか知らなかったバックエンド開発者が、環境が変わってフロントエンド開発を学ばざるをえなくなった者の記録です。

歴史的に正しい事実を書いたものではなく、私個人の理解を整理するための妄想日記です。

 

私はこれまではWebアプリの開発ばかりやってきて、RailsでHTMLテンプレートエンジン使ってviewを作るスタイルでしか開発してきませんでした。

 

f:id:ksss9:20210221232753p:plain

しかし、ネイティブフロントとWebフロント両方があるアプリケーションが開発されているところを見て、ある事を思いつきました。

 

「Webフロントもネイティブフロントのように開発できれば、バックエンドエンジニアはバックエンドに、フロントエンドエンジニアはフロントエンドに分業できて、開発しやすくなるのでは?」

 f:id:ksss9:20210221232937p:plain

この気付きが超重要でした。このイメージを持てたおかげでフロント開発の意義がスルスル入ってきました。

 

Railsフルスタックフレームワークなので、RailsでHTMLを生成して返すことができますが、その開発体制だと、「バックエンドエンジニアは、得意なバックエンドを沢山書いて、不得意なフロントエンドをちょっと書く。フロントエンドエンジニアは得意なフロントエンドを沢山書いて、不得意なバックエンドをちょっと書く。」ということが起こっていたように思います。

だんだんと、「ちょっと不得意な人が書いたコード」が増えていきます。レビューで指摘しあえれば良いのですが、なかなか完璧にはいきません。

 

もし、RailsのV部分を引き剥がして、フロントエンドが得意なエンジニアに開発を任せることができれば、RailsAPIだけでよくなり、ネイティブフロントもWebフロントも同じようにAPIを提供する形で開発できます。

 

どうやらこれを実現するのがSPA(シングルページアプリケーション)という事なのかなと思います(多分間違ってる)。

だからこそRailsAPIモードができたり、PWAだとかいう話が出てきたのかなと想像します。

 

そしてSPAを作りやすいReactやVueが盛り上がったと予想します。このSPAをS3とかなんかいい感じのプラットフォーム(これがNetlifyとかなのか?)にデプロイすれば、サーバーを管理せずとも簡単なアプリなら作れるし、Lambdaとかと組み合わせれば結構なことまで出来ちゃいます(これがAWS amplify?)。いわゆるLinuxサーバーを立てたりしなくてもマッシュアップサービスとかが作れてしまうので、サーバーレスとかも叫ばれてきたのかなあ?

 

このSPAも細かく言うと色々やりようがあるようです。

 

例えばブラウザでページを開いたときにJSが起動してHTMLを構築するシンプルな方法をCSR(クライアント サイド レンダリング)と言うそうです。だいたいのことはCSRで行けるし、create-react-appが出てきて学習コストも下がってきたので、今も結構使われてるのかなと予想します。

 

しかしながらGoogleクローラーがJSを実行するかしないか微妙な時期があったのか、最初のJS実行時のラグを気にしてか、サーバーサイドでReactなりVueなりを実行してHTMLを生成する手法が出てきました。これがNext.js(React)とかNuxt.js(Vue)ということかなと思います。これがSSR(サーバー サイド レンダリング)と言うそうです。

 

Next.jsの方をちょっと触っただけですが、動的画像生成を実装した経験からも、next/imageだけでもかなり便利そうです。

これは、libvipsを使って動的に画像を生成して、ブラウザ毎に最適な形式でファイルを作り、キャッシュ用のヘッダーもいい感じにして、なんなら専用サービス(Vercel?)にデプロイすれば生成したファイルをキャッシュしてくれます。

これだけでも導入する動機になりそうです。触ってみた感じ、S3のファイルとかでもドメインさえ設定すればなんでもいけそう。

ただし、この画像生成機能はビルド時ではなく画像へのリクエストがサーバー上に来たときだけに使えるので、SSRでは使えても後述するSSGでは使えないようです。

 

さて、そんなSSRも、GoogleクローラーがJSを実行してくれるようになったり、CDNが一般的になったり、そもそもサーバー側の要素が増えるので、実装が複雑になってしまいがちでした。そこで新たな策として、予めビルド時(デプロイ時)にDBなりを見てもいいからHTMLを生成しておいて、これをCDNなりで静的ファイルを配信すれば、サーバーのプロセスもいらないし、なんならDBもいらないし、既にレンダリングされているから表示も早いしでいい事ずくめとなったのがSSG(スタティック サイト ジェネレーター)なのかなーと予想します。

これがNext.jsの今の売りっぽいです。CSRに似ていますが、HTMLのあらかじめ生成度合いが、CSRが0だったのがSSGで80〜100になったイメージです。もちろんAPIをクライアントサイドから叩いて動的コンテンツを追加することもできます。なんかこの辺からJamstackと言うらいしいっぽい感じがします。ブログとかほとんどSSGでいけそうです。

 

更にISGやISRなんてのもあるっぽいですが、まだまだキャッチアップ中です。。。 

 

さてさて、こんなことを最近学んでみて、また、少しNext.jsアプリケーションを作ってみて、昔ながらのRailsアプリケーションに対する考え方が変わり、ようやく時代に追いつけてないことが自覚できたレベルにはなれたかも?と思いました。

これまで作ってきたものは、サーバーサイドでほぼ同じHTMLを作って返すものなので、最近のフロント事情からしたら無駄が多いのかもと思えてきました。

また、Next.jsをcreate-next-appで作ってみると、驚くほど簡単にコンテンツが作れてしまいます。フロントはからっきしだった私が、簡単なアンケートサイトみたいなやつなら作れたので確かです。

Next.jsはルーティングも簡単に(ファイルパスを切るだけ)できるので、最早VもCもフロントでできてしまいます。

そして、どうせSSRするなら、そのnodeサーバーからprismaなりでDBを叩いて動的コンテンツを返せれば1プロセスだけでいいし、認証もFirebase AuthenticationやAuth0なんかで作れてしまいます。

そうなると「もはやRailsはいらない!」みたいな思考になる人が現れてもおかしくないなと思う気持ちも理解できます。

もちろんRailsでHTMLを生成する方法も、状況によってpros/consあるかとは思いますが、自分が見ないようにしていたものの大きさを急に知って、驚くばかりです。

っていうかもはや私としては、バックエンドエンジニアとは?自分の存在価値は?みたいな気持ちです。

 

これから、改めて「どうやってアプリを作るのか」を考え直してみたいと思います。