如何定位Scala REPL中抛出异常的代码行?
这个问题我之前也碰到过,Scala REPL的:paste模式确实会因为代码包装逻辑导致栈追踪行号和你实际粘贴的代码行不匹配,下面几个实用方法可以帮你快速定位真实的异常抛出点:
方法1:使用:paste -raw粘贴代码
这是最直接的解决方案。默认的:paste会把你的代码包裹到一个自动生成的匿名类中,这个类自带的初始化逻辑会占用一些行号,导致栈追踪的行号偏移。而-raw参数会让REPL直接把代码当作脚本执行,跳过包装步骤,栈追踪的行号就会和你粘贴的代码完全对应。
示例操作:
$ scala Welcome to Scala 2.12.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_171). Type in expressions for evaluation. Or try :help. scala> :paste -raw // Entering paste mode (ctrl-D to finish) if (math.random > 0.5) { throw new Exception() } else { throw new Exception() } // Exiting paste mode, now interpreting. java.lang.Exception at $line3.$read$$iw$$iw$.<init>(<console>:1)
可以看到,栈追踪里的行号变成了<console>:1,和你粘贴的代码行完全一致。
方法2:给代码添加明确的异常标识
如果因为某些原因不能用-raw模式,你可以在代码里给不同分支或行添加带有明确信息的异常,直接通过异常消息定位问题点,不用依赖栈追踪行号。
示例代码:
scala> :paste // Entering paste mode (ctrl-D to finish) if (math.random > 0.5) { throw new Exception("异常来自大于0.5的分支") } else { throw new Exception("异常来自小于等于0.5的分支") } // Exiting paste mode, now interpreting. java.lang.Exception: 异常来自大于0.5的分支 ... 28 elided
这样不管栈追踪行号是多少,你都能从异常消息里直接知道是哪段代码抛出的异常。
方法3:查看REPL生成的类文件(进阶)
如果你想深入理解行号偏移的原因,可以通过javap命令查看REPL生成的临时类的行号映射。执行代码后,用:javap -c -l加上类名(比如栈追踪里的$line3.$read$$iw$$iw),就能看到LineNumberTable部分,这里会显示生成的类的行号和原始代码行的对应关系。
示例操作:
scala> :javap -c -l $line3.$read$$iw$$iw
输出里的LineNumberTable会列出每个字节码指令对应的行号,你可以对照这个表把栈追踪的行号转换成你实际的代码行号。不过这个方法比较繁琐,适合需要深入排查的场景。
为什么会出现行号偏移?
简单来说,Scala REPL在处理普通:paste的代码时,会自动生成一个包装类,把你的代码放到这个类的初始化块中。这个包装类会包含一些额外的代码(比如导入语句、REPL的内部逻辑),这些代码会占用前面的行号,导致你的代码在生成的类里的行号比原始行号大,所以栈追踪显示的是包装后的类的行号,而不是你写的代码的行号。
内容的提问来源于stack exchange,提问作者Yaroslav

