使用 REPL 增强开发 - 实用指南
您是否曾经遇到过这样的情况:无需调试,解决方案就立刻浮现在脑海中?如果没有,您并不孤单。在本文中,我将介绍一种方法,在您解决问题时,为您创建的函数提供实时反馈。毕竟,我们当中没有人像 ThePrimeagen 那样,总能一次成功。
目录
好的,但是 REPL 是什么?
REPL 本质上是一个连续循环,它接受一个命令,执行它,显示结果,然后等待另一个命令重复这个循环。REPL 的便利之处在于它能够记住前一个命令的上下文。为了亲身体验这一点,您可以立即打开 shell 并启动它irb
来尝试 Ruby 函数。
$ irb
irb(main):001> 1 + 1
=> 2
irb(main):002> raise 'Some error'
(irb):2:in `<main>': Some error (RuntimeError)
from /Users/cherryramatis/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.8.0/exe/irb:9:in `<top (required)>'
from /Users/cherryramatis/.asdf/installs/ruby/3.2.2/bin/irb:25:in `load'
from /Users/cherryramatis/.asdf/installs/ruby/3.2.2/bin/irb:25:in `<main>'
irb(main):003>
正如你所看到的,我们得到了一些简单操作的输出,1 + 1
比如异常抛出,很酷吧?这是一个很棒的特性,因为我们可以快速评估复杂的操作,帮助我们理解正在编写的算法。
我为什么要用这个?
如果您是 Ruby 开发者,您很可能已经用它完成irb
过一些基本任务,比如计算1 + 1
(毕竟我们是程序员,不是数学家)。但是,我们如何才能从使用 REPL 作为计算器,转变为改变我们编写软件的方式呢?为了说明这一点,让我们考虑以下场景:您正在开发一个遵循简单服务层架构的 API 应用程序,并且您刚刚开始编写服务的第一个实现。现在,您将如何快速执行该服务并测试您的逻辑是否接近?
嗯,你现在可能有两个选择:
- 1. 您按照 TDD 哲学编写测试:TDD 哲学在 Ruby 社区中广泛传播,并且确实非常有用,但是这有一个问题(就像编程中的任何事情一样),那就是在开发功能时需要编写的大多数测试在完成服务实现后就会被丢弃,因为测试用于验证业务逻辑和代码契约,而不是验证语法是否正确。
- 2. 使用 curl 编写 HTTP 层的其余部分进行测试:大多数时候,我这样做(编写 JavaScript 代码时)只是为了能够有一个 curl 命令,我可以反复运行它,同时用日志填充代码,直到完成实现。但这并非 http 层的目的,理想情况下,你应该将 http 层作为实现的最后一部分来编写,因为它只是一个通信部分。
在“REPL 驱动开发”场景中,您采用完全相反的方法,因为您在创建文件和编写函数后立即开始测试您的服务,这非常棒,因为我们不再考虑运行测试或使用 curl 进行测试作为调试步骤,而是开始进行验证,检查您的解决方案是否与初始目的同步(在测试或文档中描述)。
需要强调的是,我并不反对 TDD 理念,我发现它非常有用,并且我希望在测试有用的地方使用测试,也就是检查我的解决方案是否符合最初的目标。我们不应该用测试来检查代码是否运行正常。
新的循环又开始了?
当我们开始将 REPL 视为技术经验的一部分时,另一个与 TDD 周期并存的周期开始融合。在这个周期中,我们只有在程序运行无误时才会进行测试,这使得测试用例更加简洁,并与最初的目的紧密相关。我们也不需要继续编写只运行服务的包装器,而是继续运行命令curl
来检查日志。
不幸的是,据我所知,有几种语言实现了可用的设置以在应用程序上下文中使用 REPL ,其中一些是:ruby、elixir、haskell、clojure(以及各种其他 lisps)。
虽然讨论理论部分和思考新的哲学很酷,但我想帮助您将所有这些付诸实践,所以让我们去追求实际的好处好吗?
如何在 ruby 应用程序中设置一个简单的 REPL?
在我的所有应用程序教程中,我首先设置一个应用程序级 REPL,它基本上是一个console
加载项目内所有文件的脚本,如果您使用Ruby on Rails或Hanami之类的框架,那么通过运行命令您已经拥有一个控制台console
。
设置起来非常简单,您只需要在里面创建一个文件bin/console
并要求您想要在 REPL 上使用的所有文件,大多数时候我们使用像zeitwerk这样的 gem来提供自动要求,但如果您想手动执行此操作,请参考下面的示例:
#!/usr/bin/env ruby
# Ensure tht the 'lib/' directory is in the load path
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
# Load all *.rb files in the lib/ directory
Dir[File.expand_path('../lib/*.rb', __dir__)].each do |file|
require file
end
require 'irb'
IRB.start
如果您想要一个使用自动要求和依赖注入的更复杂的示例,请参阅我的文章:https://dev.to/cherryramatis/creating-a-sinatra-api-with-system-wide-dependency-injection-using-dry-system-10mp
引入 pry,一个更简洁的实现
我最近的所有教程和项目主要都是使用默认的 Ruby REPL 进行管理的irb
,不得不说它简直太棒了。然而,最终促使我转向Pry 的原因是它提供了更好的默认设置。但这究竟意味着什么呢?让我来演示一下:
查看课程文档
运行 时pry
,您会发现两个用于探索类详细信息的重要别名:$
和?
。
使用 时?
,实际上是ri
在方法上调用命令来查看其 YARD 文档。然而,pry
提供了一种更加用户友好的方法。您?
几乎可以在任何类或方法上使用,而不必担心它是否能按预期运行。
$ bin/console
[1] pry(main)> ? MonadicExceptions::Result.from_exception
From: /Users/cherryramatis/Repos/monadic-exceptions/lib/result.rb:12:
Owner: #<Class:MonadicExceptions::Result>
Visibility: public
Signature: from_exception(callback)
Number of lines: 16
This static method act as a bridge between exception to Result, it
supress any raises a method invokes and transform into a valid Failure
return.
param callback [Proc]
return [Failure({error: Symbol, where: String, orig_exception: Exception, message: String}), Success(data)]
def self.from_exception(callback)
result = callback.call
Dry::Monads::Result::Success.new(result)
rescue => e
exception_name = MonadicExceptions::ResultSanitizer.new.treat_exception_name(e.class.to_s)
Dry::Monads::Result::Failure.new({ error: exception_name,
where: callback.source_location.first, orig_exception: e,
message: e.message })
end
[2] pry(main)>
如果您只是想查看源代码,建议使用$
:
[2] pry(main)> $ MonadicExceptions::Result.from_exception
From: /Users/cherryramatis/Repos/monadic-exceptions/lib/result.rb:12:
Owner: #<Class:MonadicExceptions::Result>
Visibility: public
Signature: from_exception(callback)
Number of lines: 10
def self.from_exception(callback)
result = callback.call
Dry::Monads::Result::Success.new(result)
rescue => e
exception_name = MonadicExceptions::ResultSanitizer.new.treat_exception_name(e.class.to_s)
Dry::Monads::Result::Failure.new({ error: exception_name,
where: callback.source_location.first, orig_exception: e,
message: e.message })
end
[3] pry(main)>
如何可视化异常
在迭代开发过程中,遇到错误异常几乎是不可避免的,能够可视化和理解这些错误至关重要。
在 Pry 中,您可以使用该命令wtf
(是的,您读得对)查看最后触发的异常的详细信息:
[7] pry(main)> MonadicExceptions::Result.method_raising
RuntimeError: A error
from /Users/cherryramatis/Repos/monadic-exceptions/lib/result.rb:29:in `method_raising'
[8] pry(main)> wtf
Exception: RuntimeError: A error
--
0: /Users/cherryramatis/Repos/monadic-exceptions/lib/result.rb:29:in `method_raising'
1: (pry):3:in `__pry__'
2: /Users/cherryramatis/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:290:in `eval'
3: /Users/cherryramatis/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:290:in `evaluate_ruby'
4: /Users/cherryramatis/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/pry-0.14.2/lib/pry/pry_instance.rb:659:in `handle_line'
[9] pry(main)>
加分点:如果您附加?
到命令,它将根据?
插入的数量显示有关异常的更多详细信息。
列出方法并编辑它们
我经常用这个——用 REPL 作为主要数据源来迭代代码真是太棒了。你可以使用ls
、cd
、 和等命令,轻松地根据上下文浏览代码库edit
。
要列出一个类的方法,只需将其应用于ls
它即可:
[13] pry(main)> ls MonadicExceptions::Result
MonadicExceptions::Result.methods: from_exception method_raising
MonadicExceptions::Result#methods: blau blau2 teste
[14] pry(main)>
如果您想要将模块或类中的方法作为入口点,您可以首先cd
进入它:
[14] pry(main)> cd MonadicExceptions
[15] pry(MonadicExceptions):1> ls
constants: Result ResultSanitizer
locals: _ __ _dir_ _ex_ _file_ _in_ _out_ pry_instance
[16] pry(MonadicExceptions):1> ls Result
MonadicExceptions::Result.methods: from_exception method_raising
MonadicExceptions::Result#methods: blau blau2 teste
[17] pry(MonadicExceptions):1>
最后但仍然重要的是,您可以edit
随时直接在线打开您的代码编辑器:
免责声明:是的,它也适用于来自 gem 或 ruby 源代码的代码!
重新加载代码以继续迭代
这就是我为什么更喜欢 Pry 而不是 IRB 的真正原因,那就是 Pry 提供了一整套 gem 生态系统,可以增强 Pry 的使用体验。关于“重新加载”代码,我将介绍 Pry Reload 这个 gem。
pry -reload gem 提供了reload!
智能地重新加载已更改代码的命令,以便您可以继续迭代解决方案,而无需关闭并重新打开 REPL,如下所示:
额外奖励:使用 REPL 调试测试
除了 pry REPL 周围的各种 gem 之外,我们还有pry-rescue,它允许我们在测试失败后立即启动调试 REPL,这样我们就可以在等待所有其他测试运行之前调查并修复它:
$ rescue rspec
From: /home/conrad/0/ruby/pry-rescue/examples/example_spec.rb @ line 9 :
6:
7: describe "Float" do
8: it "should be able to add" do
=> 9: (0.1 + 0.2).should == 0.3
10: end
11: end
RSpec::Expectations::ExpectationNotMetError: expected: 0.3
got: 0.30000000000000004 (using ==)
[1] pry(main)>
在该会话中,您可以使用诸如和之类的命令try-again
来break
重新play
运行测试、添加断点或在断点的上下文中运行特定的行或方法。
如果您想了解有关使用 pry 作为 REPL 的更多信息以及有关 pry-rescue 的更多详细信息,请查看此会议演讲:https://www.youtube.com/watch?v =D9j_Mf91M0I
结论
我希望我已经有效地传达了这样一个理念:使用 REPL 可以成为增强代码理解的强大工具。当我探索 Clojure 和 Elixir 等语言时,这个概念确实让我惊叹不已。它极大地增强了我处理复杂算法的信心,因为它让我能够可视化实现过程每一步的输出。
好了,今天就到这里,如果有什么需要我帮忙的,欢迎留言!愿原力与你同在🍒
文章来源:https://dev.to/cherryramatis/enhancing-development-with-repls-a-practical-guide-2fij