
はじめに
PLEXJOB開発チームの栃川です。
勝手に「当たり前を当たり前に説明する」シリーズというものをはじめてみました。 続くかどうかは後続のエンジニア次第です(笑)
先日、Rakeタスクの実装をしている際にLocalJumpErrorという見たことのないエラーに遭遇しました。 本ブログでは、このエラーから学んだことについて深ぼってみます。
発生したエラー
私のチームでは定期バッチ(Cron Job)をRakeタスクで実行しています。 ある日、Datadogのログ上に下記のようなエラーが発生していることを発見しました。

このエラーが発生した原因としては、 下記のようなreturnで早期リターンを実装していたためのようでした。
task sample: :environment do return unless valid? puts "終了" end
正しくは next を使ってあげる必要があります。
task sample: :environment do next unless valid? puts "終了" end
では、なぜ nextでは問題なく、returnではうまくいかないのか?? このことについて、自分なりに掘り下げて考えてみたいと思います。
この記事で話すこと
- LocalJumpErrorとは何か
- なぜ Rakeタスクはブロックなのか
- なぜ next は使えるのか
- 他に使えそうな早期リターン手段は何があるのか
結論
Rakeタスクは「メソッド」ではなく「ブロック」です。
そして Ruby においては
- return は「メソッド」を抜ける
- next は「ブロック」を抜ける
という明確な役割の違いがあります。
つまりRakeタスク内で return を使うと、抜けるべきメソッドが存在しないためLocalJumpError が発生します。 一方で、Rakeタスク内では next を使うことで、ブロックの処理を途中で打ち切りつつ、タスク自体は正常終了させることができます。
そもそもLocalJumpError とは何なのか?
今回発生したLocalJumpErrorは、 Ruby における「制御フローのジャンプ先が見つからなかった」場合に発生する例外です。
Ruby には、
- return
- next
- break
- redo
といった 処理の流れを強制的に移動させる構文が用意されています。 これらは単に「処理を終わらせる」のではなく、「どこまで処理を巻き戻すか(=どこにジャンプするか)」が厳密に決まっています。
Ruby における「ジャンプ」は、例えば return は、Ruby にとって「現在実行中のメソッドを終了し、呼び出し元に制御を戻す」 という意味を持ちます。
これは Ruby の仕様として明確です。
def sample return end
この場合、return のジャンプ先は sample メソッドの呼び出し元です。
問題は、「ジャンプ先が存在しない状態」でreturn が実行された場合です。
Proc.new do return end.call
このコードでは、return は「メソッドを抜けたい」が、今いるのはブロックであるため抜けるべきメソッドが存在しないという状態になります。
Ruby はこの状況を、「想定されていないジャンプが発生した」と判断し例外を投げます。
それが今回発生したLocalJumpErrorというわけです。
Rakeタスクが「ブロック」であるとは
Rakeタスクが「ブロックである」とはどういうことなのでしょうか。 Rakeタスクは DSL として提供されているため、 普段 Ruby や Rails を書いていると「特別な構文」のように見えがちです。
task sample: :environment do puts "hello" end
しかし、この task は Ruby の構文ではなく、ブロックを受け取るただのメソッドです。このことは内部実装を見ると理解しやすいです。
def task(*args, &block) Rake::Task.define_task(*args, &block) end
Rakeタスクで書いたdo ... end の処理は&block として受け取られています。 そしてブロックとして受け取られたあと、Rake::Taskクラスのexecuteメソッドでcallされています。
def execute(args=nil) # 略 @actions.each { |act| act.call(self, args) } end
つまり、Rakeタスクとは「特別な処理」ではなく、実行タイミングを Rakeに委ねたブロックの集合にすぎません。 この意味で、「Rakeタスクはブロックである」と言えます。
returnが使えなくてnextが使える理由
ここまででRakeタスクの正体は、内部的には次のような構造になっていることがわかりました。
- task はブロックを受け取る DSL メソッド
- つまりタスク本体の処理はブロックである
- ブロックは call されることで初めて動く
ここで、最初の話に戻ります。
task sample: :environment do return unless valid? puts "終了" end
このreturnを Ruby は、「現在実行中のメソッドを終了しよう」と解釈します。 しかし、今いるのはメソッドではなくブロックの中です。 ゆえに抜けるべきメソッドが存在しないため、ジャンプ先が見つからないLocalJumpErrorが発生するという流れになります。
一方で next は、「このブロックの実行をここで終える」という意味を持つため、 ブロックの評価を終了するだけで済み、エラーにはなりません。
next以外で実装方法はあるか?
ここまでで、Rakeタスク内の早期リターンには returnではなくnextを使うべきということを見てきました。
では、next以外に使えそうな手段はあるのでしょうか。 Rakeタスク内で使われがちな制御構文を整理してみます。
raise
task sample: :environment do raise "invalid state" unless valid? end
異常終了として止めたい場合に使います。 タスクは失敗扱いになり、スタックトレースが出力されます。
Datadog や CI で検知しやすい「この状態はおかしい」「失敗として扱いたい」という場合はこちらが適しています。
ただし、「異常終了」の意味なので「早期リターン」とは少し意味合いが違うため、代替案にはならなさそうです。
abort
task sample: :environment do abort "invalid state" unless valid? end
明示的にタスクを中断したい場合に使います。 raise よりシンプルで、エラーメッセージだけ出して止めたい場合に便利ですが、 例外として扱われないため、エラーハンドリングが必要な場合は raise の方が向いています。
とはいえこちらも、「異常終了」の意味なので「早期リターン」とは少し意味合いが違うため、代替案にはならなさそうです。
exit
task sample: :environment do exit unless valid? end
Rake プロセス自体が終了します。ensure や後処理が走りません。
原則として非推奨です。意図せず他の処理に影響を与える可能性がありますので、明確な意図がない限り、使用は避けたほうが安全です。
break
task sample: :environment do break end
こちらは使えません。breakはループ専用の制御構文です。 returnと同様に、使う文脈が違うためエラーになります。
以上、nextに変わる他の代替案を調べてみましたが、結局、nextを使うのが一番良さそうでした。
まとめ
今回のエラーをきっかけに、Rakeタスクの正体や各メソッドの意味を一段深く理解できました。 AI が当たり前になった今だからこそ、普段何気なく使っている技術を掘り下げることで、理解の解像度が高まり、AIとの付き合い方も良くなると感じています。
小さなエラーは仕組みを知るための入り口なので、 これからも見過ごさずに拾い上げ、学びとして積み重ねていきたいと感じました。
最後に
PLEXではエンジニアを募集中です。 興味ある方はぜひカジュアル面談からお話できれば嬉しいです〜🫡
エンジニア募集
100兆円規模のインフラ産業の課題解決に挑戦|業務支援SaaSのテックリード - 株式会社プレックス
急成長する業務支援SaaSのソフトウェアエンジニア・リードエンジニア - 株式会社プレックス
インフラ領域で日本を動かす仕組みを作るスタートアップのエンジニア - 株式会社プレックス
オペレーションの効率化によって事業成長に貢献するコーポレートエンジニア - 株式会社プレックス
インフラ産業の人材課題を解決 | フロントエンドの技術を牽引するテックリード - 株式会社プレックス
弊社について