00:00:12.259
Okay, so it's 4:30, and we're going to get started. Hi, this is Practical Debugging.
00:00:19.380
If you're here for some other things, then I'm sorry, but you're going to learn how to debug.
00:00:24.869
Debugging in Ruby is fun; it's a fun thing.
00:00:30.000
When you get a stack trace, what do you do? I ask my co-workers, and this is the best response I got:
00:00:36.420
Usually, they cursed audibly, frightening my co-workers. That was the best I got.
00:00:42.270
Then there was silence in the Slack channel for a solid two days. You would think people don’t know how to debug in Ruby.
00:00:49.520
They pull in tools and all kinds of dependencies they usually don’t need. You get what you need when you just download Ruby.
00:00:57.570
So, the standard practice when you have a question is to ask: 'What would Stack Overflow do?' This is Stack Overflow.
00:01:02.640
You may come across hashtags like #WWSD. Stack Overflow has two main suggestions:
00:01:08.040
It suggests to use Pry and Byebug. Stack Overflow really likes to suggest these tools.
00:01:13.049
They often say 'Use Pry,' even though some comments suggest that Pry is more efficient than IRB.
00:01:20.460
At the end of the day, people want bug-free code. They don’t care how it got that way. You could care less.
00:01:27.840
But this can be really hard for me personally because I live and die by my syntax.
00:01:33.869
I don’t know if you guys are like that. Not a lot of people tend to be, but I just love clean, efficient syntax.
00:01:40.619
I really don’t care if it’s functioning correctly; I want it to look good. I just wanted it to look good.
00:01:46.740
So I have problems because when I'm debugging, all that code is going away.
00:01:51.780
You have the freedom to say, 'It doesn't matter; just delete the code. It's going to go away anyway.'
00:01:59.700
You can do all kinds of crazy stuff, so you have to get over yourself. You have to get over the fact that the code is going away.
00:02:06.990
You can do whatever you want. My point in giving this talk is that the Ruby standard library has every tool you need to debug effectively.
00:02:12.090
You have Pry; you can use it. I don’t care if you use Byebug; feel free; it’s wonderful.
00:02:17.909
On the other side, there are tools that are criminally underused, and I’m going to walk you through a couple of those.
00:02:24.540
The point of this talk is not to give you every single possible tool to solve every single possible problem.
00:02:30.840
The point is to give you a diving board, to just show you the places where you can start your journey through debugging.
00:02:36.030
These processes and to walk you through a couple of the tools that I use pretty often to solve these problems.
00:02:41.220
So, the first question you ask yourself—oh, I’m sorry. For example, I needed an application to debug over the course of this talk.
00:02:47.720
So I wrote a little Minesweeper. Is anyone familiar with Minesweeper? Yes? No? Maybe?
00:02:53.489
I’m going to show it to you live. Demo time.
00:02:59.329
Here's Minesweeper; it's in Ruby. Can everyone see that? Kind of? You click to tell where the mines are, and if you click a mine, you lose. It also lets you specify the width, height, and number of mines.
00:04:05.190
Let’s try that again. You click, and then it says, 'You win.' Much better!
00:04:11.130
What are the odds? You can never tell, and there’s no way to find out.
00:04:17.940
This talk goes pretty quickly and has a lot of code examples. I’m going to run through them quickly. Don’t panic; all of the examples are available on the GitHub repo I’ll give you the link to later.
00:04:28.130
So, pay attention now, and you can go back to redo all of them. It provides tons of links and references.
00:04:35.690
Now, the first question you need to ask is: 'What kind of problem are you solving?'
00:04:43.020
I’m going to define three kinds of problems that we’ll work through: the first is an interface problem, the second is a state problem, and the third is a flow problem.
00:04:50.440
Interface problems occur when you don’t understand the dependency structure of methods or constants.
00:04:57.150
Methods have structure. They have a return value that is a type; they sometimes have multiple types. They have parameters and the order of those parameters.
00:05:03.220
Constants also have methods defined on them, their ancestry, and their descendants.
00:05:09.610
So, interface problems occur when you don't understand the dependency structure.
00:05:15.170
You'll be making certain assumptions each time you call a function or reference a concept.
00:05:21.340
Interface problems might answer questions like, 'Why is this thing nil?' This is the most common issue.
00:05:27.410
It boils down to why I can’t call the methods I want or ‘What can this object see and do?’
00:05:34.050
Now, let’s discuss an example. In the Minesweeper code, we get the error: 'No implicit conversion from nil to integer.'
00:05:40.190
Right here, we see the error is on this line. It’s trying to coerce nil into an integer.
00:05:46.160
Neighbor is nil because it attempts to reference an index inside the array.
00:05:52.110
We ask, 'Why does neighbors have nil in it?' We follow the code logic back to neighbors four.
00:05:58.090
Neighbors four calls index four, defined here, and we quickly see the problem.
00:06:04.120
This giant return nil if statement is where it fails. It assumed that integers were being returned.
00:06:11.350
In Ruby, we define methods with 'def' and have no compile-time verification of type.
00:06:19.420
So, use .compact to get rid of all nil values, making everything great.
00:06:26.170
Next question, 'Why can’t I call the method that I want?'
00:06:32.370
If we encounter a rescue or method missing error, this means we’re trying to call something that doesn’t exist.
00:06:39.430
We check the first line of the stack trace and see our method is in the build status label.
00:06:46.280
We might want to add debugging code to print out available methods.
00:06:52.880
Using the methods method gives us an array of symbols representing all callable methods.
00:06:58.990
Using pp to print allows us to see this in a structured form.
00:07:05.020
We want to keep feedback loops quick; we don’t want our app just running.
00:07:11.710
Lastly, we grep through the methods in our terminal to find what we need.
00:07:18.200
We realize that changes in self have altered the available methods.
00:07:24.100
We discover how lexical scope references local variables that affect the outcome.
00:07:30.320
Good debugging is all about using the tools effectively.
00:07:36.780
Now, let's consider: 'What are the constants like?'
00:07:45.220
This one's intriguing because people forget that constant lookup is different from inheritance.
00:07:52.490
Imagine we refactor our cells with different classes. We decide on certain hierarchies.
00:08:00.320
If we run our code, it might say it can't initialize due to referencing the wrong base class.
00:08:06.430
Using Module.nesting can show us exactly which namespaces we have access to.
00:08:12.320
From this, we realize we only have access to top-level constants.
00:08:19.690
We refactor the constants and run it again, allowing us to access the base class appropriately.
00:08:25.600
Next, we want to ask: 'What can this object see and do?'
00:08:31.810
There are many introspection tools you can use. This is a simple method to help understand constants.
00:08:39.950
Print out the constants and use Constants to guide through the available objects.
00:08:45.680
Running it gives a good insight into what’s available in the project.
00:08:53.450
It becomes useful when dynamically handling situations like hot reloading.
00:09:01.050
You can even find the instance methods available in the context of this object.
00:09:08.560
This information helps you when debugging effectively.
00:09:15.260
Then, we address the question of, 'Oh god, what is this gem even doing?'
00:09:21.580
Imagine you encounter an ambiguous option error.
00:09:29.100
You check the code locally and find it calls methods in the gem.
00:09:36.160
Use the source location method to trace the origins of where this function is defined.
00:09:44.340
After locating it, inspect the input parameters to understand how they’re passed.
00:09:51.430
This will reveal more about what the gem is doing.
00:09:58.100
By executing these trace commands, you can find specific areas of concern in your code.
00:10:06.320
When debugging, you don’t want to leave it in a messy state.
00:10:12.920
Using the command bundle exec gem pristine cleans everything up.
00:10:18.500
Now, for lessons learned from interface problems: account for every return type.
00:10:25.060
You don’t have a compiler, and mistakes become apparent.
00:10:31.380
You must handle these types explicitly.
00:10:37.529
Also, understand constant method lookup by recognizing your lexical scope.
00:10:43.220
Your ability to introspect your code quickly is a key asset in debugging.
00:10:49.200
And take advantage of the fact that gems aren’t byte code; you can see how they solve problems.
00:10:56.230
State problems occur when your assumptions about the current state are incorrect.
00:11:04.390
This might seem broadly general, but it breaks down the assumptions made inside your program.
00:11:11.860
They answer questions like 'How does this value change at this point?'
00:11:18.620
Or like, 'What has been initialized, and what’s altering its state?'
00:11:25.640
Considering how values are mutated requires a different set of debugging tools.
00:11:31.480
The value changing at this point can be traced intuitively.
00:11:37.260
Let’s assume button.text changes unexpectedly.
00:11:43.640
We need a way to introspect to understand how the value changes.
00:11:50.800
Let's print out all instance variables and retrieve their values.
00:11:58.049
Creating a hash with instance value information can be simple and efficient.
00:12:04.700
When executed, it outputs a clear state of the debugged object.
00:12:10.440
While this might seem trivial, it only takes a few lines of code to implement.
00:12:16.990
Using instance variables and getters is easy to remember.
00:12:23.340
Another common issue is with finding out what has been initialized.
00:12:30.250
If you run into an error like 'undefined method count for nil class,' you start digging in to find out why.
00:12:36.840
Could it be that it wasn’t initialized properly? Or was it mutated during runtime?
00:12:42.710
What you want to gauge is the current state of the variable.
00:12:49.290
Methods like binding.IRB allow you to localize techniques without much hassle.
00:12:56.860
Binding retains access to local stack variables and provides useful debugging capabilities.
00:13:04.980
Tracking memory allocation may not be relevant but is necessary to know.
00:13:12.260
You create tests around methods that are slow and understand why.
00:13:19.420
Understanding methods not properly implored and GC impacts becomes essential for performance.
00:13:25.180
So, apply GC to get a grasp on the memory allocation and analyze results effectively.
00:13:32.380
Now, you see, when you run the code ten-thousand times, look out for trends in memory usage.
00:13:40.320
Lastly evaluate and make adjustments as necessary.
00:13:46.990
Lessons learned on state problems emphasize quick feedback, flexibility, and easy access.
00:13:53.990
Finally, let's reflect on flow problems, which can pose serious troubles.
00:14:01.240
These are the ones that email notifications from monitoring tools like Honeybadger concern.
00:14:09.100
Flow problems occur when you don’t know how the application reached a particular state.
00:14:15.540
Questions like 'How did the interpreter get here?' come into play.
00:14:22.970
Understanding that requires a level of introspection and good code practices.
00:14:30.420
To triangulate how interactions occurred, leverage tools that can visualize traces.
00:14:38.140
Create trace points to observe function calls more clearly.
00:14:45.360
Using trace points helps you visualize and understand the flow of your Ruby interpreter.
00:14:52.900
Adding if statements can help us manage what’s evaluated until you're satisfied.
00:14:59.440
Going deeper into object space allows effective tracking.
00:15:06.790
Utilizing insights gained from dynamic analysis becomes pivotal.
00:15:13.700
Trace allocation information reveals patterns.
00:15:21.580
When debugging, identify references to instance variables.
00:15:29.180
It quickly brings clarity to what may be causing issues.
00:15:36.220
Finally, learning when variables have been set becomes essential for solving problems.
00:15:43.620
Asking if allocations are set correctly is a key question.
00:15:51.100
Observing whether instances change drives home understanding in Ruby.
00:15:57.980
Remember to iterate through your traces and evaluate lines consistently.
00:16:05.430
Flow problems require effort to understand the underlying code requisites.
00:16:12.860
Identify and track through execution lines effectively.
00:16:19.740
Then wrap things up by emphasizing trade-offs in Ruby.
00:16:27.140
When someone comes up to you and says, 'Ruby’s dead, don’t use Ruby,' just remember you go ship your app to production.
00:16:33.620
Take in the feedback. You’ll enjoy the flexibility Ruby provides.
00:16:42.140
Thank you!