RailsConf 2017

Practical Debugging

Practical Debugging

by Kevin Deisz

In the presentation "Practical Debugging" by Kevin Deisz at RailsConf 2017, the speaker addresses the challenges developers face when debugging in Ruby and promotes utilizing the built-in tools provided by the Ruby standard library instead of relying on heavy third-party libraries. The talk is structured around various debugging techniques applicable to different types of problems: interface, state, and flow problems. Key points discussed include:

  • Interface Problems: These occur when there's confusion about the dependency structure of methods or constants. For instance, when dealing with nil values or undefined methods, Deisz emphasizes checking method signatures and using methods like methods to introspect available methods on an object.
  • State Problems: These arise from incorrect assumptions about the internal state of objects in memory. The speaker demonstrates how to use instance_variables and binding.irb for quick introspection to identify what has been initialized or how object values change over time.
  • Flow Problems: Discussed as the most serious, these problems occur during runtime when developers are uncertain about the control flow leading to a specific state. Deisz introduces tools such as caller, TracePoint, and ObjectSpace to track function calls and the lifecycle of objects, which assists in understanding how an object arrived at its current state.

Throughout the talk, Kevin showcases a Minesweeper application as a practical example to illustrate these concepts. By walking through real code, he addresses common debugging scenarios, emphasizing the importance of Ruby's flexibility and the advantages of introspection. The overarching takeaway is that while debugging may require effort and careful analysis, Ruby's inherent debugging capabilities are robust and should be leveraged effectively. In conclusion, Kevin encourages developers to embrace Ruby’s tools to write cleaner, bug-free code and advocates against the dismissal of Ruby as a viable programming language, highlighting its unique advantages in rapid application development and debugging.

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!