9
Sep

RailsConf 2014 – Debugger Driven Developement with Pry by Joel Turnbull


JOEL TURNBULL: Thanks for coming. My name is Joel Turnbull. I’m a developer for Gaslight in Cincinnatti,
Ohio. I also head up and coordinate a blog over
there, in case anybody ever wants to talk about that. But today, I’m here to talk about debugger
driven development with Pry. Most Rubyists I know
don’t use debuggers. Rubyists I know, when faced with
a problem, would prefer to ponder code than pop open
a debugger and poke around. This is crazy to me. I’ve always thought it was, because to me, trying to solve a problem by pondering code is like trying to find your keys in the dark, when you’re holding a flashlight, but you’re consciously deciding not to use
it. So why is this? I, I used to think it might be about egos or culture or something like that, but really it’s pretty simple.
I think we’ve had a lack of really good tools up to this point. But ultimately, my talk isn’t
about using debuggers in a traditional sense to
fix software, but using debuggers as a tool in your workflow to build software. And, so why do I think we can do this right now? I feel like we finally have a tool that we can use to explore this debugger driven, debugger driven workflow.
And that tool is Pry. Can I get a show of hands of who uses Pry? Awesome. Like, everybody. All right.
Cool. So I’m talking about debugger driven development
with Pry. Conrad Erwin gave a talk not even like six months ago called REPL Driven Development with Pry.
I swear to god I had no idea. But, and both are accurate, I think. You know, but I think both terms kind of undersell what the power of Pry is. So here’s my favorite definition. Pry is an IRB alternative and
runtime developer console. So if we take the first part of that and we think about Pry as an IRB alternative, you know, anything that you can
do with, with, both, both are REPLs, right. And, and
a debugger is a REPL, too. Anything you can
do in Ruby, you can do in IRB. Anything you can do in IRB, you can do in Pry. What makes both of them powerful, is they
both leverage this idea of runtime. And, to me,
runtime is all about immersion. It’s about being immersed
in a live system, where you can play with code and you can look at your objects and, and all that kind of thing. You can validate your implementations. Everything you need to do.
And it’s like, looking for your keys with a flashlight. So, given that Pry and IRB are both REPLs and they both have this idea of runtime, why should you use Pry instead of IRB? It’s got a couple vital workflow features right out
of the box. Syntax highlighting and tab completion.
Both super handy. But, what I want to talk about today is some of the bigger, game-changing features
of Pry. The first one is enhanced introspection. Here’s
our friend again. Introspection is the ability to, of, of a
language, where you can ask a language questions about
itself. And it’s built into Ruby, and that’s awesome. If you’ve ever asked a class what method you can call on it, or you’ve asked an instance what class it is, you’re doing introspection. What if you want to go deeper? Like, what if you want to know what the class methods are versus the instance methods? You know,
what if you want to know what methods are inherited,
and from where? What if you want to know what state an instance holds onto during its life
cycle? You can answer all these questions with plain
Ruby and IRB. But the problem is, is that, it’s, the, the amount of effort involved is non-trivial.
I would classify it as daunting. So. Given that. You know. I would, I would classify this as DRTFM. This is what I, this is what I point to. I mean, this is the workflow that I like. I like to take the things out of the box. I like to get a feel for what the pieces are. I like to play around with them. I like to try to put it together without reading the manual, and if I get stuck, then I read the manual, right. The second really game-changing feature of
Pry, to me, is extendability through plugins. And the
best way that I can show this is to just demo some of my favorites for you. So, I’m gonna show you a Rails app. Instead of calling it in a normal way, like rails s, I’m gonna call it like this. Under the umbrella of Pry rescue. And I’ll show you
why in a minute. But here we are. This is an app I’ve been working on late, late nights, you know.
Please don’t steal this. This is a bowling score
count, tracker, right. You can push any number after
you bowl and it will record what you, what, how many pins you knocked down, right. So, the first thing I’m gonna show you about Pry, for those who aren’t necessarily familiar,
is how you invoke a runtime at any point in your application, where you’re running Ruby, right. Here, I’ve inserted a couple binding dot prys.
One into my controller action and one into my
template. On the lower left we have our model. OK, so let’s rerun it with that in mind. Let’s go back to our running server, and we’ll see that we’ve halted our execution here,
and we’ve been dropped into a run time. And we can do things in here like look around. We can play lines of code. Let’s play the line that sets the bowling game. And then we can look again. When we exit from this, we’ve returned from
our controller. We’re starting to render our template,
and we’ve hit our next binding dot pry. You can put binding dot prys inside your erb tags, right. Same drill. We can look at bowling games.
We can, we can step into our next, our, our, our implementations of our methods. Here we’ve
stepped into the frames method of our bowling game model. Same drill. We can look around here. We can look at ourself. We can go to the next, or we can continue. So, you can see how handy that would be if things aren’t necessarily blowing up, but,
you know, something’s not quite right, either. So what
if things do blow up, though? Right. So, here I am. I’m gonna, I’m gonna spark an exception here by bullet, trying to bowl
the next ball of my next frame. I’m gonna bowl a big nine. I’m gonna come back here and we’ve been dropped into a runtime, because
an exception was thrown. That’s what being under the watchful
eye of Pry rescue is gonna do for us within this context here. So, some interesting, interesting things we
can do here. We can call the show-stack command of the
Pry stack explorer plugin. And we’re seeing the
whole stack here. this is like caller, but it’s alive,
right. We can move up the stack. We can move up the stack nine frames. We can move down the stack. We can take a look at the state of any object here at any level of our stack trace. We can look at our stack again just to see where we’re at. We’re on frame ten. But this is a big hairy kind of Rails stack trace, and we’re not getting a whole lot of value out of that right now. So let’s call the cd-cause command of Pry
rescue. And see if it can track down what caused this problem in the first place. There we go. This looks more familiar, right.
Here we are in our template. And I will guess that on line thirteen we have a problem with frame one. And that’s true. So we just need to add a little bit of a guard in here, and say, you know, if we have pins on frame one, let’s render that. Otherwise
let’s just render a dash. And we get feedback that that’s going, that’s
what’s gonna happen, right. We know that this is
a working implementation. So let’s copy our history. This is another
plugin called Pry clipboard. That’s gonna copy that
implementation into my clipboard. I’m gonna edit a file where
the last exception was raised. It drops me right
where I need to be. I’m gonna paste in that implementation, drop back in, and I’m gonna
ask Pry rescue to try it again. And we’re back. And we’ve got our dash. So, and we can continue to bowl, right. That’s
legit, right. A five and a six. OK, cool. So, what do we do here? We used binding dot pry to invoke a runtime anywhere in our app where we’re doing Ruby code. We used the pry-debugger gem to give us our step, next,
and continue functionality that we expect out
of our debugging tools. We used pry-rescue and ran our Rails
app under the umbrella of pry-rescue to drop us
into a runtime when things go wrong, so that we can poke around. We used the pry-stack_explorer
gem to navigate the stack and explore state at any
level. A few commands we saw were cd-cause in pry-rescue that took us to the, the root of our problem. We used play and we used copy-history
to not mess around with, you know, copying things
with our mouse and pasting them into our REPL,
which can be a pain. We used the edit command with the e flag to take us to the file where the exception occurred so that
we could fix it. And then we used pry-rescue, try-again,
which under this, under the context of Rails, just
replayed our request. We didn’t have to reload our,
our page, or do any of that nonsense. Reload the whole environment or anything like that, right.
So it was fast. So, the, that’s, that’s great, and having
demoed Pry in a debugger context, you know, I can show some of those things. But, really, what I
find interesting is this idea of Pry as a runtime developer console, right. So there’s some really awspect- really awesome
aspects of Ruby, right. It’s introspective, which we’ve
talked about a little bit and we’ll talk about more. And
it’s a dynamic and it’s reflective, which means,
you know, we don’t have to compile it and we can change things on the fly in its runtime. And I think we, we take advantage of these things, you know, a lot in the code that we write, whether we’re doing metaprogramming
or monkeypatching or, or anything like that. But we, we haven’t
really taken advantage of, of these things in the
tools and in our workflow yet. So, when I talk about workflow and our problems with our workflow, I see big problems with
the traditional workflow in, in Ruby development
that I, that I see. Right, I mean. By that I mean we write some code in an editor. We save it. We hop over to a terminal or we pop over to a webpage and reload it and run it to see if it worked and, if it worked, we go back and continue on. If it didn’t, we fix it, we save. We go back. We reload it, rerun it. Rinse, repeat,
right? What, what are, what problems do I see with this? First of all, it’s disruptive and it’s
distracting to keep switching back and forth, right. Some
amount of context-switching is always gonna be imminent,
right. But any effort you can make to reduce that is just gonna be a huge win for your flow, you know, and your focus. The second problem I see with that is it’s just guess work. We just write some code.
Oh, yeah, I think it’s gonna work. We save it. We go over. We run it to see if it works. We’re just taking shots in the dark, you know. You can ask this guy. Taking shots with a flashlight is much more
accurate. And recommended, apparently. So do that. And, to me, it just seems backwards, right.
We, we’re like solidifying and codifying our code
into our code base just in an attempt to see if it works. It should be the opposite. Our code base, it should be the, the record of code that we’ve, we’ve already validated, right.
And I, I mean these things are just kind of things
that we accept and, and we, we take for granted, right. But I mean, I think other languages have,
have been more effective and intentional about
integrating this idea of a runtime into the, their workflow, you
know. And blurring this line between static and
running code. Clojure comes to mind, right. But, we’ve talked about the awesome aspects
of Ruby, and there’s really nothing that restricts
or limits us from doing the same, right. I mean, I think the Ruby language is, the Ruby language enables
it, you know. It’s just that up to this point, we really haven’t had the tools to do so. So, we talked about these workflow problems.
How does Pry solve these workflow problems, right?
I see that, you know, the introspection and the documentation
and the source code browsing, which we’ll see in a
minute, that’s, that’s baked into Pry, gives us ninety
percent of the information that we need to write code right now, you know, in most cases. It has a runtime that you can throw your code against and validate it and get feedback
on whether it, whether it works or not, immediately.
And it’s smart about editing. You don’t have to
think about what file you need to open. Pry usually knows what file you want to edit and usually knows exactly where you want to edit it. Which is really nice. So, when I talk about this idea of runtime development, I’m really talking about being
immersed in this, in this environment that, you know, loves
us and can give us feedback, and, and is alive, right. So we want to spend the majority of our development time there, and we want our editor
to just be an after thought. And that’s just
a place where we just push working code when
it’s done, right. So, let me demo to you a little bit about how I see that working, right. So. Our mission is to write an empty class definition, OK. This is just a script. No
Rails involved here or anything. But given a class
like name like BowlingGame, we want to create a
file, bowling_game dot rb, and write a class definition
to it, like class BowlingGame empty space end.
Right, and I’ve created a little skeleton here of, of
how I think that might work, right. We’re gonna, we’re gonna read in a string
from the command line, we’re gonna pass that string
into a method called file_name_for_class and get
a file name. We’re gonna pass that string into a method
called class_definition for class and get a class
definition, and then we’re gonna create that class by writing
that class definition to that file. Right. So let’s step out here and let’s just run that. So, we get an expected error, right.
We haven’t implemented anything called file_name_for_class,
yet. So we could jump into our editor and start coding and
all that, but why don’t we see what this is like. Why don’t we use Pry rescue and leverage that exception to drop us into a runtime and start this runtime development process. So we do
that. We get the same error, but the difference is,
we’re inside of a runtime now, OK. So, the first problem is pretty easy. We know that we need to define file_name_for_class. And we do so,
right. The difference is in here, I’m gonna raise,
in the implementation of this class, and drop
back out. And I’ll show you why. When we ask Pry to try again, we get in here. We’re right where we need to be to define the implementa- working implementation of this class. Although,
I already know that I have forgotten to pass in the BowlingGame string, like I always do. OK. Now we have everything we need. So we have something like BowlingGame, right, and
we’re looking for something like bowling_game dot rb, OK. So
I’m just gonna preview my favorite Pry command of all
time here. ls. When I call ls, and give it the class or the object I’m working with, I immediately
know I’m working with a string and I’m seeing all of the methods that are available to me on that string. And when I say all of the methods, I don’t mean all of them. Notice
that the object methods are not in here. And, you know that if you’ve ever used IRB to get the method, you always do, thing dot method
minus object dot methods blah, blah, blah. The, that’s just noise, right, in most cases.
So the default is just to leave that out. The developers of Pry have thought of that, right.
So we can try to class downcase, which gets us so close, but yet so far away. We don’t really know where to put that underscore. But, as Ruby devs, Rails devs, we know we have things like ActiveSupport that can help
us out with that. So we include that, and now when we ls our class, we’ve got some extra stuff in here, right. Demodulalize, deconstantize, right.
That kind of stuff. And if it, that’s hard to see, we can just ls ActiveSupport::Inflector itself and see
what’s available to us there, right. So we need an und, we needed an underscore. I see a method called underscore there. Tab
completion is nice. All right. So now we’re getting a lot closer. All we need to do is stick on our file exception, and I think we’re done, right. So that, there’s our filename. So let’s copy
that. Let’s drop back into our file. Right here,
exactly where we need to put the implementation, let’s
put it in there. And let’s save it and continue on. OK, I know, I’m not gonna continue through this whole thing, in the interest of time,
but I do want to, to implement this last method inside of the create_class, so I can talk
a little bit more about what I love about introspection, right. Let’s just reflect on what we did there, though, with the workflow, OK. So we used, the, we used pry-rescue to leverage the power, you
know, to leverage that exception, to pop us into a
runtime, and we used the runtime of the debugger not to fix code, but to drive our development
process, right. So. We’re in here. We’ve raised inside of
our create_class method. We try again. We’ve got
our file name, and we’ve got our class definition.
And we have this file class, right. And, and I know about this file class, and I know this is exactly what I need to get the job done. But, I mean, this is exactly what I, what happened to me. I don’t really remember exactly
what I need to do here, OK. So I can pop out to StackOverflow or I can pop out to, to Ruby-docs or something
and take a look. Or I can just ls file, right here. And just take a minute to look at this, and think about all of the low-level Ruby instrospection acrobatics you would have
to go through to get this brain dump of information right
here. We’ve got all of the methods that you can call on instances of file. We’ve got all the methods you can call on the file class. We’ve got all of the methods that file inherits,
and, from its superclass, and we know what that
superclass is. IO. We also know all the constants involved and
if there was state involved in class or instance
variables. We would see that here as well. So, all of a sudden, I’m informed about, about everything
I need to know about this class, and I have a pretty good idea, just from a, from a moment’s notice, of how I can use it, or what I can do to play around with it. So let’s. I see the right method. And I don’t really want to write anything or mess
with anything right now, so I’m just gonna look
at the documentation for it. OK. It opens the
file. It optimally seeks to the given offset. Writes
a string, then returns the length written. Write
ensures that the files close before returning. This sounds
like what I want. I page down. I see some examples there. Great. I’m ready to go. Alternatively, I can do the same and call
show-source, OK. Which isn’t doing a lot for me here, but when I’m in my, my own code base, this is what I lean on more often, right. So, let’s get our bearings again. And let’s give it a try, right. File.write(file_name.class_definition).
OK. Something happened. It seemed to work, right.
How do I know? Really easy to shell out in Pry. You get really nice output. Exactly what you
would expect, which is, I can’t say the same for IRB in that, in that case. I have this thing called bowling_game in there.
Let’s take a look at it. That looks correct to me. So let’s just do that again. I’m gonna copy the history again. And then I’m gonna
remove bowling_game, just to make sure that my script
is doing what it promises, right. Oops. And it’s not there anymore. So let’s go back. Let’s put in that, let’s put in that implementation. And drop back in. Let’s ask Pry to try again. No more exceptions. Do we have our
bowling_game? We do. OK. So, what happened there, right? We used
the debugger and the runtime as, as, as a tool for driving our development process and build
our implementation. Not just to fix software, right. We validated our implementation before codifying
it, and we reversed this traditional workflow that I’ve
got so many problems with. We explored and we informed
ourselves about how to, how to use our classes without context switching. Right, we stayed focused in one
tool. And then we didn’t have to reload our, our libraries and our environment every time we wanted to
run code, so it was fast. So, and, you know. We’re in the testing track here, so I should probably talk about testing
a little bit. And, that might have felt a little bit like TDD to you. I mean, it does to me, right. As, as practitioners of TDD,
you know, I may not, I’m, I’m, I love TDD. I think most, many Rubyists do. But I mean, what I love about it most is it keeps me focused and, and, and, having a test suite allows me to aggressively refactor
my code, you know, and have the confidence to
do that. You know, and I’d rather do that upfront than after the fact, right. But, as practitioners
of TDD, you know, we’ve learned to love failure,
you know. TDD is all about making something fail, then
writing the code to make it pass. When we have bugs, we want to exercise that exact piece
of code that’s giving us the error and get that error, you know, before we then go, cause
we know we’re about to do something cool and
fix it, right. So, we love to see red. And so a, a practice that’s, that’s centered around
failure is naturally suited to a debugging tool, right. I mean,
that’s what a debugging tool does. It’s meant to
handle failures, catch failures, and then give you
the tools you need and enable you to fix them as quickly as you can. And we’ve seen how awesome, you know, a debugger Pry can be. And, so, the promise of this talk is that I’m going to, you know, deliver a, a, a Pry-enabled TDD workflow. But before I do
that, I mean, I mentioned that, you know, I’ve had
a, kind of specific experience that made me a
believer that this workflow even happens. I mean, I’ve
actually done this before. I’m not creating this, right. And, and that specific experience was this.
Does anybody know what this is? Smalltalk. Yeah. This is
a screenshot of a Farrow Smalltalk IDE, right.
And I spent about a year or so in the 2000s writing a web application in the C-side framework
in Smalltalk for a company in New York City with, with a team of guys. And, really delving into object-oriented programming,
and learning so much about it, I really learned to love this, right. But what first, what first struck me
when I saw Pry and I saw the ls command was it reminded me of this, right. Right here, we are looking at a ZnClient class in a Zinc HTTP package. We see the class definition
right there at the bottom, with all of the instance variables that this class makes use of listed
out. On the right hand side, we see all the methods that we can call on this, on a, on an instance of class, and we can click on any of those to see the source code of that method. I mean, that’s all, that’s
all you need, as far as I’m concerned. I mean, that gets you ninety percent of the way there almost every, like, almost every time, right. So, I wanted to, I wanted to demo, like a, a Smalltalk TDD workflow, cause that’s
the other really cool thing about Smalltalk and different
thing about Smalltalk is that it really, like, enables
this awesome TDD workflow. And I don’t think I’m gonna
have the time to do that unfortunately. But the TL;DR is this, basically. OK. So if I write a test, and I try to exercise a class that doesn’t exist, like BowlingGame
here, Smalltalk’s, when I save this, Smalltalk’s gonna say, that
doesn’t exist. Do you wanna, what do you want to do? And the first option there is define new class. You just click it and it does it, right. When I try to call a method on that class that doesn’t exist, Smalltalk asks me
if I want to implement that method, and when I
say yes, it drops me into a debugger, right. A runtime, where I have everything available
I need to create the implementation of that class, and
I can expect all the things I need to do so. Right, I’m not in a static code editor here. This is like, living system, right. So I can do so. Just want to return zero. And when I save, my tests pass. So that’s. That’s,
that’s the idea, right. And, and it, it’s small,
but, you know, small changes in your user interface,
you know, can really turn, like a daunting experience
into a really motivating flow, right. It doesn’t take much. So, when I talk about, you know, this power of Pry and, and we’ve talked about some of the plugins and how powerful they are and
how, you know, Pry is so easy to extend, you know, even I can do it. And, and so, I’ve kind of gotten the ball rolling. I pushed a little code up to GitHub. It defines a new command called define-it in
Pry. Right now, it’s just a PryRC, and you can define Pry commands in your PryRC or you can do gems or whatever, and that’s, that’s where
I want to move it. But, this is my attempt to kind of get this workflow going in, in Ruby, right. So,
all right. So here it is. Here’s my PryRC. I’m creating a command called define-it, and then
I’m implementing – every Pry command implements this method
called process, and that is what is going to happen when you call the command from within the Pry REPL, right. And so I’m kind of using this idea that we demoed in the last demo of, when I hit a name error, I just want to automatically generate an empty class definition and move
on. What it will also do is when I hit a NoMethodError, it’s gonna generate an empty
method definition and put you right in there where you need to be to implement it, right. So let’s take a look. So, once again. Oh, let me just show you this real quick, though. So here is a spec and a set of tests that exercise this BowlingGame class,
right. The first test just verifies that BowlingGame is a thing.
The second test says if I ask a new BowlingGame for its score, it should return zero. The
third test says, if I bowl a four then the game’s score should reflect that. And the last test says, if I bowl twice, you know, the, the score should reflect that,
that sum. So, when I run rSpec under pry-rescue,
pry-rescue, you know, it does different things in different
contexts, right. So just like before, pry-rescue’s gonna
break on unhandled exceptions, just like it always
has done, but in the context of rSpec, it’s also gonna break on assertion failures, all right. So you can
poke around and get those fixed. So, here we are. We. We’ve got our first exception, right. It’s the name error. Uninitialized
constant BowlingGame. So let’s define it. All right. Didn’t see it, but it did it. Next. Believe me. All right. So next. Next, we’ve got our next exception here right. Undefined method ‘score’
for BowlingGame. Our NoMethodError. So let’s define-it. You guys
didn’t believe me, but there it is. All right. There we go. We’re in our empty method. Let’s TDD, right.
We just need to make the test pass so we return zero out of there. Ask pry-rescue to
try again. We got another exception here. NoMethodError:
undefined method ‘bowl’ for BowlingGame. So, let’s define-it. Now I feel like we’re
gonna need to keep track of a little state, so let’s. We want to make that the fixnum. We want to return @score out of here now, instead. And let’s initialize @score to be
zero when you create a new BowlingGame. Let’s save that
and let’s try again. All right. That worked. We’re in our last
test here, and we’ve hit our first assertion error,
right. It expected nine and it got five, cause we’re not doing any of the, the totaling yet. So let’s, this is another way you can call edit. You can just ask it to edit the class, and it’ll know what file to drop into. So I feel like we’re gonna want to keep track of our scores now. So let’s just push those fixnums on a scores array. Let’s get our score by reducing those values. That’s how you keep
score in bowling, right. You just add everything
up. All right. Cool. And let’s, let’s initialize it with an array
with initial value of zero as well. Let’s try again. Something’s wrong there. There it is. All right. Four examples, zero failures. rSpec
thought we didn’t fail at all, you know. It’s pretty
cool. So. So what happened there? All right. I mean, basically, you know, we. We, again, we used
Pry to drive our development process. We used
our own Pry plugin there to kind of make that a little bit easier, and within one runtime,
we fixed all of our assertions, exercised our class
and implemented the whole solution, right. No reloading, no nothing like that. So, in conclusion, embrace your runtime and
all things. Don’t read the effing manual. Use a kickass flashlight. If you’re fixing software, use a debugger.
If you’re building software, use a debugger. And when
you use a debugger, use Pry. Thank you very much.

Tags: , , , , , , , , , , , , , , , , , , ,

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *