$shibayu36->blog;

株式会社アンドパッドでエンジニアをしています。プログラミングや読書のことなどについて書いています。

YAPC::Kyoto 2023に参加しました

久々のオフライン開催ということで、YAPC Kyoto 2023に参加してきた。

久々に大量の知り合いと話せて、とにかく楽しかった!

YAPCは昔から参加していたので、大量の知り合いと久々に会えた。オンライン開催だと立ち話とかもあまりできなかったので、オフラインで「最近どうですか」から色々話せるのは楽しいんだなと思い出せた。 僕は、好きなことがある人が無限に最近やっている話をしてくれるのを聞くのが好きなので、カンファレンスで久々にそういう体験ができた。これだよこれ〜〜という気分になった。

前日祭の後も飲み会を開催したら10人くらいきてくれたり、その後はてなのオフィスに行ったらいろんな飲み会から集結して数十人くらいで集まれたのも楽しかったな〜。

本編で印象に残った話

ar_tamaさん

あの日ハッカーに憧れた自分が、「ハッカーの呪縛」から解き放たれるまで - Speaker Deck

最近僕もお悩み中だったので刺さった...僕もはてなに入るまではほとんどプログラミングをしたことがなく、周りに優秀なエンジニアがいる状況で劣等感も感じながら気合いでついて行くみたいなキャリアだったので、共感できるところが多かった。「ベクトルが自分に向いているうちは何をしてもだめ by 南場さん」というワードも、はい...という気持ちになった。

ちなみに技術に特化できなかった人間がバランスが大事っていうのは良くないみたいな話もあるけど、僕は最近はそう思わなくなった。それはストイックすぎるし、そう思いすぎるとめちゃくちゃ辛くなる。もちろん技術に特化している人を軽んじてはいけないと思うし、それはまた別の話。

moznionさん

ソフトウェアエンジニアリングサバイバルガイド: 廃墟を直す、廃墟を出る、廃墟を壊す、あるいは廃墟に暮らす、廃墟に死す - Google スライド

廃墟のメタファー分かる!いろんな廃墟の対応法があった上で、その時に起こり得ることがまとまっていて良かった。どうしてもいろんな理由で廃墟になってしまうことはあるし、その時にただ愚痴るのではなく、ちゃんと対応策を練れるエンジニアになりたい。

nekokakさん

エンジニアからマネジメント・CTOのキャリアとしてどういう気持ちでやってきたかが伝わってくる発表だった。誰かが変えてくれるということは絶対ないという言葉に共感した。課題と感じる場所には突っ込んでいく・タスクフォースで協力してやっていこう・責任は自分で取るという流れで行くと、結構みんな協力してくれるというのは参考になったし、それをずっとやり続けて行くのならそれは信頼されるだろうなあと感じた。

onishiさん

YAPC::Kyoto 2023 Keynote - Speaker Deck

エモかった。発表中に泣きそうになった。

僕も初めてYAPCに行った時にantipopさんとかに連れられて著名な人と会話したり、それきっかけでハッカソンに参加させてもらったりと良い経験ができた。またYAPCではいくつか登壇させてもらい、いろんな人と知り合いになれた。その経験がなかったら今の自分はなかったと思う。YAPCは最高。

またはてなにいた時はonishiさんが作ってきた組織にいたという感覚が強かったし、その中でめちゃくちゃ成長できたと思っている。今回の発表を見て、いろんな人が作ってくれた環境や組織に助けられながら仕事ができたなあということを再実感した。

発表を聞いていて、onishiさんがモブ...?はて...?という気持ちにはなった。けれど僕もいつも自分のことをなんとか食らいつくモブという気持ちで日々を過ごしているので、まあみんなそれぞれ自分なりに悩みながらやってくしかないなという気持ちになった。

僕も来年に35歳になるし、自分なりに何かできることを見つけていきたいなあ。

運営のみなさんありがとうございました

オフライン開催、とても楽しめました!!運営のみなさん大変だったと思いますが、開催していただいてありがとうございました!!

vscode-rdbg(debug.gem)でのRubyデバッグが便利すぎる

最近Rubyを学び直したり、アルゴリズムの基礎練をしたりしているのだが、debug.gemおよびvscode-rdbgが便利すぎるので紹介。

debug.gemやvscode-rdbgとは

debug.gem( https://github.com/ruby/debug )とは最近のRubyのモダンなdebugger。これまでlib/debug.rbやbyebug、debaseなどがあったが、それらのいくつかの課題を解決したdebuggerとなっている。Ruby 3.1 の debug.gem を自慢したい - クックパッド開発者ブログ に背景や基本的な使い方が詳しく載っている。

またRubyKaigi 2022のruby/debug - The best investment for your productivity - RubyKaigi 2022でも紹介された。Scriptable Breakpointsという仕組みを使えばデバッグ体験をシェアできるという話に感動したことを覚えている。

vscode-rdbgとは、debug.gemの機能をVS Code上で使いやすくしたもの。これの体験が良すぎると思ったので今回記事を書いている。

最初に設定しておくと便利なこと

vscode-rdbgを使うにあたって、settings.jsonに以下のような設定を入れておくことをお勧めする。これを入れておくことで、どのワークスペースでもrdbgを通したrubyスクリプト実行ができるようになる。またuseTerminalをtrueにしておくことでSTDINを使いたいプログラムにも対応できる。

  "launch": {
    "version": "0.2.0",
    "configurations": [
      {
        "type": "rdbg",
        "name": "Debug current file with rdbg",
        "request": "launch",
        "script": "${file}",
        "args": [],
        "askParameters": true,
        "useTerminal": true
      }
    ]
  }

ワークスペースでlaunch設定を共有したい話についてはVSCodeで全ワークスペースで使うdebug launch設定をする - $shibayu36->blog;にも書いた。

続いて最近公開したRubyで迷路探索を幅優先探索で解く - $shibayu36->blog;のプログラムを参考にvscode-rdbgを使ってみる。

VS Code上でブレークポイントを設定してデバッグする

まずは単純にブレークポイントを設定してデバッグしてみる。VS Codeで他言語のデバッグと同様のインターフェースでブレークポイントを設定すればデバッグできる。これだけでも便利。

Watchを設定し、見たいものに着目しながらデバッグする

さらにWatchの部分で見たいものを設定しておけば、そこに着目しながらデバッグができる。見たい部分だけ集中して見れるので、状況がわかりやすい。

rescue any exceptionを設定しておけばエラーになった時にdebuggerを起動できる!

この機能が超便利。Rubyスクリプトにバグがあって落ちてしまった時に、その時点でdebuggerが起動するので落ちた時の状況が把握しやすい。

例えば以下のように境界チェックを忘れるというバグを入れてみる。

diff --git a/ruby/lib/maze.rb b/ruby/lib/maze.rb
index 263e546..a4fa272 100644
--- a/ruby/lib/maze.rb
+++ b/ruby/lib/maze.rb
@@ -35,8 +35,8 @@ class SolveMaze
         next_y = cur_y + dy
 
         # outbound
-        next if next_x < 0 || next_x >= @maze_width
-        next if next_y < 0 || next_y >= @maze_height
+        # next if next_x < 0 || next_x >= @maze_width
+        # next if next_y < 0 || next_y >= @maze_height
         next if @maze_struct[next_x][next_y] == '#' # wall
         next if @maze_dist[next_x][next_y] # already visited
 

そうするとスクリプトを実行すると以下のように落ちる。

/Users/yuki.shibazaki/development/src/github.com/shibayu36/algorithms/ruby/lib/maze.rb:40:in `block in calc_by_bfs': undefined method `[]' for nil:NilClass (NoMethodError)
        from /Users/yuki.shibazaki/development/src/github.com/shibayu36/algorithms/ruby/lib/maze.rb:33:in `each'
        from /Users/yuki.shibazaki/development/src/github.com/shibayu36/algorithms/ruby/lib/maze.rb:33:in `calc_by_bfs'
        from /Users/yuki.shibazaki/development/src/github.com/shibayu36/algorithms/ruby/lib/maze.rb:85:in `<main>'

この状態でrdbgを通してスクリプトを実行すると、以下のようにdebuggerが立ち上がり、状況を把握しやすくなる。

Debug Consoleで詳細に追いかける

VS Codeでdebuggerが立ち上がった状態で、Debug Consoleを見るとdebug.gemと直接やりとりができる。直接やりとりできる分、debug.gemのすべての機能が使えるので便利。

まとめ

debug.gemおよびvscode-rdbgを使うと非常に便利で、最近の開発に不可欠となっている。特に境界がややこしいコードとかを書くときに大活躍している。非常に便利なのでみんな使いましょう。

参考

Rubyで迷路探索を幅優先探索で解く

最近は仕事ができない感から完全脱却してみる|牛尾 剛|noteに触発されて基本に戻ろうと思い、アルゴリズムの勉強をしている。今回は迷路の最短経路を幅優先探索で解くというのをやってみた。

やりたいこと

迷路を以下のような文字列で与えられたときに

.#....#G
.#.#....
...#.##.
#.##...#
...###.#
.#.....#
...#.#..
S.......

最短経路の手数と、その経路を出力する。

16
.#****#G
.#*#.***
.**#.##.
#*##...#
**.###.#
*#.....#
*..#.#..
S.......

実装

以下のように実装した。アルゴリズム理解のためなので再利用性や設計の綺麗さなどはあまり考えていない。

class SolveMaze
  VECTOR = [[0, 1], [0, -1], [1, 0], [-1, 0]]

  def initialize(maze_struct)
    @maze_struct = maze_struct
    @maze_width = @maze_struct.first.size
    @maze_height = @maze_struct.size

    # The distance between start to @maze_dist[x][y]
    @maze_dist = Array.new(@maze_height) { Array.new(@maze_width, nil) }
    # The previous position of @maze_dist[x][y]
    @maze_prev_links = Array.new(@maze_height) { Array.new(@maze_width, nil) }

    @search_queue = []
    @maze_struct.each_with_index do |row, x|
      row.each_with_index do |val, y|
        if val == 'S'
          @maze_start = [x, y]
          @search_queue.push([x, y])
          @maze_dist[x][y] = 0
        elsif val == 'G'
          @maze_goal = [x, y]
        end
      end
    end
  end

  def calc_by_bfs
    until @search_queue.empty?
      cur_x, cur_y = @search_queue.shift
      return @maze_dist[cur_x][cur_y] if @maze_struct[cur_x][cur_y] == 'G'

      VECTOR.each do |dx, dy|
        next_x = cur_x + dx
        next_y = cur_y + dy

        # outbound
        next if next_x < 0 || next_x >= @maze_width
        next if next_y < 0 || next_y >= @maze_height
        next if @maze_struct[next_x][next_y] == '#' # wall
        next if @maze_dist[next_x][next_y] # already visited

        @maze_dist[next_x][next_y] = @maze_dist[cur_x][cur_y] + 1
        @maze_prev_links[next_x][next_y] = [cur_x, cur_y]
        @search_queue.push([next_x, next_y])
      end
    end
  end

  def get_route
    route = []
    cur_x, cur_y = @maze_goal
    until cur_x == @maze_start[0] && cur_y == @maze_start[1]
      route.unshift([cur_x, cur_y])
      cur_x, cur_y = @maze_prev_links[cur_x][cur_y]
    end
    route.unshift(@maze_start)
    route
  end

  def print_route
    route = get_route
    maze_solved_struct = Marshal.load(Marshal.dump(@maze_struct))
    route[1..-2].each do |x, y|
      maze_solved_struct[x][y] = '*'
    end
    maze_solved_struct.each do |row|
      puts(row.join(''))
    end
  end
end

maze_str = <<~STR
  .#....#G
  .#.#....
  ...#.##.
  #.##...#
  ...###.#
  .#.....#
  ...#.#..
  S.......
STR
maze_struct = maze_str.split("\n").map { |line| line.split('') }
maze_solver = SolveMaze.new(maze_struct)
puts(maze_solver.calc_by_bfs)
maze_solver.print_route

maze_str2 = <<~STR
  S..#......
  .#...##.#.
  .##.#...#.
  ...#..##.#
  ##..#.#...
  ...#.#..#G
  .#....#..#
  ..#.#..#..
  .#.#.#..#.
  ......#...
STR
maze_struct2 = maze_str2.split("\n").map { |line| line.split('') }
maze_solver2 = SolveMaze.new(maze_struct2)
puts(maze_solver2.calc_by_bfs)
maze_solver2.print_route

実装してみての気づき

参考資料

これまでの学習履歴