Practical Debugging
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Part Number | 56 | |
Number of Parts | 86 | |
Author | ||
License | CC Attribution - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/31272 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
RailsConf 201756 / 86
1
8
10
12
18
19
23
30
35
42
49
52
53
55
56
59
65
74
77
79
82
83
84
00:00
Computer animation
00:30
Dependent and independent variablesStack (abstract data type)Computer animation
01:22
Stack (abstract data type)Buffer overflowSoftware developerProtein foldingMultiplication signForcing (mathematics)Software bugCore dumpArmFlow separation
02:00
CodePoint (geometry)Multiplication signWhiteboardRight angleData miningSpring (hydrology)Casting (performing arts)CodeLibrary (computing)Cartesian coordinate systemNumberProcess (computing)BackupDemo (music)Sound effectClique-width
04:50
GodFunctional (mathematics)Optical disc driveLogical constantDataflowData structureType theoryMultiplication signParameter (computer programming)Data miningCodeLink (knot theory)Order (biology)State of matterInterface (computing)2 (number)Pointer (computer programming)Inheritance (object-oriented programming)Object (grammar)Software repositoryXML
06:36
Electronic signatureIntegerBitStatement (computer science)Type theoryPointer (computer programming)Computer configurationData conversionError messageElement (mathematics)Subject indexingLie groupSystem callCodeTotal S.A.CASE <Informatik>Vector spaceLine (geometry)Forcing (mathematics)Natural languageXML
08:21
InformationSystem callBlock (periodic table)Function (mathematics)Variable (mathematics)Local ringData miningSymbol tableError messageFeedbackWhiteboardLine (geometry)Ferry CorstenCartesian coordinate systemCodeExistenceBuildingLetterpress printingRadical (chemistry)Array data structureNumberTheoryRight angleFactory (trading post)WritingOvalContent (media)Text miningXMLUML
10:13
Logical constantSocial classCodeDemosceneCASE <Informatik>Program slicingMultiplication signSpacetimeEndliche ModelltheorieDifferent (Kate Ryan album)NamespaceWeightConnected spaceHierarchyObject (grammar)Self-organizationInheritance (object-oriented programming)Data miningModule (mathematics)BitCode refactoringXMLUMLComputer animation
11:47
1 (number)Instance (computer science)Row (database)Line (geometry)Symbol tableLogical constantFunctional (mathematics)Dot productSocial classStructural loadCodeDirectory serviceModule (mathematics)Library (computing)InformationType theoryFeedbackGoodness of fitObject (grammar)System callNatural languageOrder (biology)VarianceEndliche ModelltheorieSingle-precision floating-point formatIterationRight angleBit rateInheritance (object-oriented programming)Projective planeXMLUMLComputer animation
13:50
GodFunctional (mathematics)Error messageState of matterObject (grammar)Line (geometry)Fiber bundleUniform resource locatorComputer configurationSource codeInstance (computer science)Computer fileProcess (computing)MetadataCodeSoftware bugPhysical systemDifferent (Kate Ryan album)Interface (computing)Game controllerVideo gamePoint (geometry)Right angleParameter (computer programming)Social classComputer programmingUMLXMLComputer animation
16:05
Logical constantSemiconductor memoryRight angleState of matterObject (grammar)CodeNatural languageProgrammer (hardware)Type theoryPoint (geometry)BytecodeAxiom of choiceChainComputer fileMathematicsCompilerPhysical systemComputer programmingComputer animation
17:57
InformationInstance (computer science)Key (cryptography)State of matterCodeVariable (mathematics)Macro (computer science)Single-precision floating-point formatObject (grammar)MultiplicationCompilerHash functionLine (geometry)Electronic mailing listFunction (mathematics)Symbol tableAutomatic differentiationComputer animation
19:26
File systemDrop (liquid)CodeVariable (mathematics)System callPoint (geometry)Object (grammar)Block (periodic table)Utility softwareInstance (computer science)Keyboard shortcutState of matterPointer (computer programming)InformationTable (information)CountingLocal ringNetwork topologyBeat (acoustics)Social classRight angleComputer animation
21:11
Object (grammar)Touch typingArithmetic meanMaxima and minimaExpert systemPoint (geometry)CodeHash functionStatisticsLine (geometry)Instance (computer science)WindowClient (computing)SpeicherbereinigungMereologySemiconductor memoryBitState of matterMultiplication signSystem callData miningKey (cryptography)Whiteboard2 (number)Web pageBlock (periodic table)Bit rateSlide ruleQuicksortMathematical optimizationCharge carrierInterpolationLatent heatString (computer science)Computer animationXML
24:22
Communications protocolObject (grammar)Uniform resource locatorCodeInterpreter (computing)Variable (mathematics)FeedbackDataflowInstance (computer science)State of matter1 (number)Computer programmingEmailLine (geometry)InformationInterface (computing)BitPoint (geometry)Cartesian coordinate systemMultiplication signError messageProduct (business)Set (mathematics)Water vaporRight angleTemporal logicSocial classFrequency
26:00
Computer fileCodeKernel (computing)Directory servicePoint (geometry)Regulärer Ausdruck <Textverarbeitung>WhiteboardLogicLine (geometry)Sound effectOcean currentGraph coloringDivisorVector space3 (number)Computer animation
27:11
Sheaf (mathematics)BitPlotterLine (geometry)CodeObject (grammar)Statement (computer science)Module (mathematics)Functional (mathematics)System callMultiplication signWrapper (data mining)WindowMetadataInformationInterpreter (computing)Single-precision floating-point formatNetwork topologyNumberKernel (computing)Right anglePoint (geometry)Computer animationXML
28:33
Object (grammar)WindowSource codeInstance (computer science)CodeOptical disc drivePositional notationSingle-precision floating-point formatSheaf (mathematics)Variable (mathematics)Memory managementSpacetimeResource allocationProduct (business)Line (geometry)Right angleReal numberSystem callInformationGreatest elementXML
29:42
Video gameEntire functionObject (grammar)Pointer (computer programming)CodeCycle (graph theory)Codierung <Programmierung>Social classError messageMultiplication signXMLUML
30:29
Pointer (computer programming)Line (geometry)WhiteboardSocial classArmXML
31:00
Variable (mathematics)Instance (computer science)CodeArithmetic meanSoftware developerData miningSocial classComputer programmingPointer (computer programming)CountingXMLComputer animation
31:53
CodeVariable (mathematics)Instance (computer science)InformationComputer fileSocial classMathematicsObject (grammar)Line (geometry)Point (geometry)Hecke operatorMultiplication signBuildingInterpreter (computing)System callProduct (business)NumberCondition numberMereologyStatement (computer science)Line codeBlock (periodic table)Keyboard shortcutTask (computing)Ferry CorstenSound effectAreaXMLComputer animation
34:59
Compilation albumSoftware maintenanceDataflowMultiplication signCompilerTypprüfungState of matterBlogPoint (geometry)Type theoryInformationTracing (software)Single-precision floating-point formatBitMobile appDependent and independent variablesProduct (business)Projective planeBuffer overflowCodeVariable (mathematics)Arithmetic meanWordCuboidCompiler constructionRight angleFunctional (mathematics)AreaLine (geometry)User interfaceElectric generatorLevel (video gaming)Computer animation
37:21
XML
Transcript: English(auto-generated)
00:00
So it's 4.30, so we're gonna get started. So hi, this is Practical Debugging.
00:20
If you're here for some other thing, then I'm sorry. But you're gonna learn how to debug. So debugging in Ruby, it's fun, it's a fun thing. This is what happens, you get a stack trace. You get a stack trace and what do you do? I ask my coworkers and this is the best response that I got.
00:40
Usually curse audibly frightening my coworkers. That was the best I got. And then there was silence in the Slack channel for like a solid two days. And then someone asked about, I don't know, something else completely different. And I think this is actually a kind of a good reason
01:00
for this talk. People don't know how to debug in Ruby. They pull in tools, they pull in all kinds of dependencies, they pull in all kinds of things that they usually don't need. You get what you need when you download Ruby. So standard practice when you have a question
01:21
about what to do, what do you do? You remain calm and you ask what would Stack Overflow do? This is Stack Overflow driven development or hashtag WWSOD. And Stack Overflow says to do two things. It says to use Pry and it says to use ByBug. Stack Overflow really likes to suggest using these tools.
01:43
They suggest it all the time. Use Pry, use Pry, use Pry. I especially like this one that says use IRB. And then the first comment is, Pry is more efficient than IRB. Just because, I guess, I don't know. Moral of the story, at the end of the day,
02:01
people want bug-free code. They don't care how it got that way. You could use, I don't care, it doesn't matter. And you don't get style points in debugging. This one is actually really hard for me. I live and die by my linter. I don't know if you guys are like that. Not a lot of people tend to be,
02:21
but I just love clean, efficient syntax. I really don't care if it's functioning correctly. I want it to look good. Just want it to look good. And so I have problems because when I'm debugging, all that code is going away and you have the freedom to say, fuck it, it doesn't matter. Just delete the code.
02:40
It's gonna go away anyway. And so you can do all kinds of crazy stuff. And so you have to get over yourself. You have to get over the fact that the code's going away, you can do whatever you want. My point in giving this talk, my whole thing, is that the Ruby standard library has every tool you need to debug effectively.
03:01
You have Pry. You can use Pry. I don't care if you use Pry. Feel free, it's wonderful. On the other side of that, there are tools that are available to you that are criminally underused. And so I'm gonna walk you through a couple of those. The point of this talk is not to give you every single possible tool to solve every single possible problem. The point of this talk is to give you a diving board,
03:23
is to show you the places where you can start your journey through debugging these processes. And to walk you through a couple of the tools that I use pretty standard, pretty often, to solve these problems. So, first question you have to ask yourself.
03:41
Oh, I'm sorry, backing up. I needed an example application just to debug over the course of this talk, so I wrote a little Minesweeper. Is everyone familiar with Minesweeper? Yes, no, maybe? Okay, I'm gonna show it to you. Live demo time. So, here's Minesweeper.
04:03
It's in Ruby. Can everyone see that? Kind of, maybe. And you click, right? And then you can tell them where the mines are. And if you click a bad mine, then you lose. It says you lose. And then you can do the same thing. I allowed you to specify the width and the height
04:22
and the number of mines. So this only has one, so you should be fine. And then. Thank you. Thanks for coming. Yeah, okay. Let's try that again.
04:42
We click, and then it says you win. There we go. Much better, okay. What are the odds, right? What are the odds? You can never tell, no way to tell. So, this talk goes pretty quickly. It has a lot of code examples, and I'm gonna run through them pretty quickly.
05:02
Don't panic. All of the examples are up on the GitHub repo that I will give you the link afterwards so you pay attention now. And you can go back and review all of them, and it gives you tons and tons of links and tons and tons of references. So, don't start scrambling, but I am gonna go through these examples pretty quickly. So, first question you ask, what kind of problem are you solving?
05:22
I'm gonna define three kinds of problems that we're gonna work through. The first is going to be an interface problem. Second is gonna be a state problem. Third is gonna be a flow problem. So, interface problems. First kind of problems. Interface problems occur when you don't understand the dependent structure of methods or constants. Methods have structure.
05:40
They have a return value that is a type. They have, or sometimes multiple types. They have parameters. They have the order of those parameters. They have the types of those parameters. All things like that. Constants have methods defined on them. They have ancestry. They have descendants. They have all kinds of things. So, interface problems occur when you don't understand
06:00
the dependent structure of methods or constants. You have to know these things in your code, and you're making certain assumptions every time you call a function, or reference a constant for that matter. Interface problems answer questions like, why is this thing nil? Right, this is like the most common thing that I find. You know, undefined method, blah, blah, blah for nil. So, we're gonna walk through that. Why can't I call the method that I want?
06:22
What are the constants I can reference? What can this object see and do? And, oh God, oh God, what is this gem even doing? And in true millennial fashion, I can't even. So, first question, why is this thing nil? So, we're in the mine super code. We get this no implicit conversion from nil to integer right here.
06:42
So, let's go into this code a little bit. The error is on this line, and it says no implicit conversion from nil into integer. So, neighbor is nil, because it's trying to coerce that into an integer that it can reference inside of the array. And so, we say, okay, neighbor is nil.
07:01
So, neighbor is an element of neighbors. Why is neighbors have nil in it? We go to this neighbors four. Neighbors four is defined here. Neighbors four calls index four, and index four is defined here. And pretty quickly, you can see the problem. It's that giant return nil if statement. And, you know, while this is a little bit contrived,
07:23
the neighbors four method assumed that integers were being returned. The index four method does not have that signature. Now, in Ruby, we get def. In other languages, you might have option int, or optional int, or maybe int, or whatever int. In this case, you get def.
07:40
That is what you get. And it's on you. The onus is on you to take care of this type signature. And it's implicit. It's not there. You are trading this off. You get to say def, and you don't care what gets returned. In most cases, this is wonderful. This is quick. This is very, very fast. This gets you to market.
08:01
And then, later down the road, you know, like a year, you get a honey badger, because you didn't take care of the whole type signature. So, in this case, the fix is actually pretty easy. We had .compact there. It gets rid of all the nils, and everything's great. So, basically, if everything is ever nil, just add a compact, you're fine. It's a total lie.
08:21
Moving right along, why can't I call the method that I want? Let's see if we get this error. And we get this, and this is our clue. Rescue in method missing. You might also get undefined method. Either one. This tells you, okay, we're trying to call something that doesn't exist. So, let's go to the first line of the stack trace
08:40
that we control. We go to the board. We get this build status label method. This is where our error is coming from. We have that adder reader there, and we're like, hey, we should be able to call this thing, because it exists. Right, so, what are we gonna do? So, we're gonna add some debugging method, some debugging code. We go in here, and we're gonna call the methods method.
09:01
It's gonna give you an array of symbols that's gonna tell you everything you can possibly call. We're gonna print that out with P. P is a great method. It's effectively this, but instead this, right? It puts inspect, but instead it's, okay, you got it. We're gonna exit immediately. You wanna keep the feedback loop as quick as possible. Right, you don't want your application to just keep running.
09:20
Just get the information you need immediately. And finally, we're gonna grep through the methods and try to find the method that we need, and we're just gonna output that all to the terminal. So, running that code, we get this. The first array is the array where we were looking for the thing, and the second array is all the methods. Obviously, we didn't write all these methods, so clearly the scope inside of that block has changed. We go back to our code.
09:41
We get through our debugging information, and we can find out that, in fact, self has changed in this block. That kinda makes sense. We could've been clued in by the fact that text and grid are not methods that we've actually defined. We take this lexical scope inside of Ruby has reference to the local variables when the block was defined, so we take that text, mines, mines left,
10:01
and comps, bandwidth, we move them out into local variables, and everybody's happy. Now, this example thing to remember, we use the methods method, right? Found everything we need. Real quick, good debugging. Excellent, moving on. What are the constants I can reference? This one is kinda fun, because people,
10:22
people tend to forget that constant lookup is not the same as ancestry inside of Ruby. They're different things, so let's look at this example. Let's say we refactored our cells to have different hierarchy, and we decided, okay, we're gonna have these cell mine, and this cell neighbor, and this cell empty, and they're all gonna take care
10:41
of their own needs and whatever. In this case, we think this is great, and we go and we run the code, and we say, uh-oh, uninitialized constant minesweeper base. So we go back into our code, and the tool that we're gonna reach for this time is module.nesting. Module.nesting is a method that's going to tell you exactly what namespaces you have access to
11:01
within this current lexical scope. So if you run this code, we're gonna get minesweeper, and that's all we're gonna get. We go back into this code. What this is saying is that we only have reference to the top-level constants, which you can always reference, and the constants directly defined underneath the module namespace.
11:21
So in this case, you go to that first definition, class cell mine descends from base. Base is not minesweeper base. Base is minesweeper cell base. So we do a little bit of refactoring. We move these guys on in, move that up, run it again, and you get minesweeper cell and minesweeper. So you can see from this code
11:40
how you can now access the base class and the other definitions. Everything's good. So what can this object see and do? This one's fun. So there's all kinds of introspection tools that are relatively available to you. This is just something that's a couple lines of code to tell you every single constant
12:00
that's defined in a project. We do a couple things. So we can print out the name of the constant. We can look at the constants. Dot constants is a method that's defined on constants, on modules for that matter. We can iterate through them, and we can call const get to get something by a symbol. It'll give us an object. We can then iterate through that recursively and get all of our information,
12:20
and we run it on our project. It's pretty boring. It tells you three constants. But you can do things like run it on active record and get every single active record constant there is. Probably not immediately apparent what the value of this is, but being able to iterate through constants very quickly is very, very useful,
12:40
especially when you're dealing with reloading, like hot reloading with Rails, and something hasn't been loaded, think STI, where you want to iterate over the descendant classes, and not all of them have been loaded. This is very, very useful for that kind of debugging. We can also find out everything that we can call on an object. We can list its instance methods, right?
13:02
This right here, this instance methods function is gonna give us an array of symbols that represent those methods. We don't even have to load up code. We can do this on the command line. That dash I is gonna add the lib directory to our auto load path, or to the load path for Ruby. The dash R is gonna require it. Dash E is gonna evaluate it,
13:22
and we get all of this goodness. We can do the same thing for instance methods that are just defined on this class and not inherited. Same thing for private instance methods, and the same thing for private instance methods that are just defined on this class. All this information is available to you from the command line. It's like 40 lines worth of typing, 40 characters worth of typing.
13:42
Try to get that with other languages. It's not the easiest, right? This is quick introspection, and this is some of the fastest feedback loop you're gonna get. Finally, we get to my favorite, oh god, oh god, what is this gem even doing? Let's say we get this error message, ambiguous option column.
14:02
Nevermind the fact that you probably can spell, but we're gonna go with this example for a minute. Let's go to, so what we could do, we could go to the code from our side. We can also go to the code from the vendor gem side. So we're gonna do that. So first line we control.
14:21
We go in and we find out, okay, this is actually calling, if you remember from the lexical scope example earlier, the TK label grid method. So we go into our command line, and we say all right, let's get that TK method grid, and then let's get the source location. Source location, method, instance method.
14:40
That will give you back a method object. The method object then has metadata on it that you can get, right? Source location is going to tell you, okay, this is the exact file on your system where this function is defined. And this is the exact line inside of that file so you can go quickly and find it. We find, okay, let's, this is defined here.
15:03
This is calling out to the TK grid method. So let's go and get that and repeat the process. Only difference here is that it's not an instance method. It's a class method. So we use the method method instead of the instance method method. Are you tired of hearing me say method yet? And we can repeat the same process. We can go into the code and we see, okay, this is what it's actually doing, right?
15:23
Try to do this with a jar. Enjoy yourself. This is, we know that the error comes from this line and we know that params is being passed into this so let's just go ahead and print out params. And hopefully at this point, it's now become evident that column is misspelled and you can go into your code and fix the bug.
15:44
Yeah. At the end of the day, when you're done messing with all of your vendor gems, you don't want to leave them in that state, bundle exec gem pristine will restore that to the state when you downloaded the gem originally. You can also do dash dash all and that will just clean your whole gem file.
16:05
So, lessons learned from interface problems. Account for every return type. You don't have a compiler. You don't have something telling you, okay, this can return these kinds of types and you need to go one by one and take care of them. There are no sealed traits here, right?
16:22
Account for constant and method lookup. You need to make sure that you know what lexical scope you're in. You need to make sure that you know what you can reference On the other side of that, take advantage of constant and method introspection. You have these tools at your fingertips. You can use these very quickly and when you don't use them, you're giving up one of the greatest assets you have
16:41
for having chosen Ruby as your language of choice. And finally, take advantage of the fact that gems aren't byte code, right? They're not compiled. You can do anything you want to them. You can go and delete files and see what happens. If nothing else, you can use them as an educational opportunity just to go in and see what kind of problems they were solving and to see how they solved those problems.
17:01
And you know, I mean, this is one of the best ways to learn Ruby. You have all those files already downloaded to your system. They're right there. It's great. So use them. State problems. So state problems.
17:21
State problems occur when the assumptions you made about the current state of the program are incorrect. That might seem overly generically broad, but what I'm basically saying here is that the internal state of every object in your system that's living and breathing inside of your memory as it's been allocated, what assumptions are you making at each point in code
17:41
that may not be correct? They answer questions like, how does this value change at this point? What has been initialized at this point? And how many objects are allocated in this method or within this lexical scope? So first one, how does this value change at this point? If we go into this code and we see, okay, button.text is changing,
18:01
and let's say it's changing to something that we don't like. It's changing to some, I don't know, FUBAR or something that we don't know why. We can go in and figure out exactly what's happening very, very quickly, and we're gonna add some introspection code here to figure out exactly how. So we're gonna use the instance variables method. That goes and lists an array of symbols, again,
18:21
defining every single instance method that's been initialized on this object. We can then map over that and use instance variable get to get the value. We can then create a hash out of that and put it out to the command line, and finally we can get the current state of the text and the new state of the text and get all that information. When we run it, we get a hash
18:43
with the keys being the instance vars, the values being the actual value of those, and the state being outputted. While this may seem relatively trivial, just to get this information, that was like six lines of code to tell you exactly the state of your object, and it was painless to put in.
19:02
I used two methods that I had to know about, instance variables and instance variable get. That kind of information is relatively easy to remember. You don't need to think about some kind of introspection API with multiple objects and try to use some kind of compiler macro to try to get your information available to you.
19:23
This is very, very quick. What has been initialized at this point? Similar to the last one, let's say we get undefined method count for nil class. We go into our code and we see, all right, cells is nil. Why is cells nil? It could be nil for two reasons.
19:41
One, we forgot to initialize it, or it wasn't yet initialized at this point. Two, it was already initialized and it got mutated somehow and now it's nil. Either way, it doesn't really matter. For state problems, we're just interested in finding out exactly the current state of this code. So we go in and we can use binding.irb.
20:02
Might look similar to binding.pri. It's very similar. It's like the same thing. Except it doesn't have like the ls or the cd or all the stuff that makes you treat objects like file systems that are kind of funny. This is brand new in Ruby 2.4. If you're not using Ruby 2.4, you should use Ruby 2.4.
20:21
So first off, let's talk about binding. Binding is inside of the current lexical scope of any kind of block inside of Ruby. It's gonna tell you the table of local variables and the call stack, as well as a couple other utilities I'm not gonna mention. IRB, right, is what happens when you're getting the REPL for Ruby.
20:41
So when you require IRB, IRB adds an IRB method to the binding class, which will then drop you in. So if you go and you run this code, let's make that a little shorter, it's gonna drop you into this IRB console right at that point in code. You can introspect on the instance variables. You can determine that in fact cells is nil.
21:02
And we're gonna go and complete this whole debugging example in a minute, but suffice to say at this point, you can use binding.rb to get all of that information very quickly. How many objects are allocated at this point? So I wasn't gonna include this example just because it's such a minimal touch on GC,
21:20
but I'm gonna do it anyway, just because it's good to know that you have these tools available to you. I by no means claim to be a GC expert, there are so many people more qualified to speak about this, but I'm going to just kind of give you the beginner's guide to what you need to know. So let's say we have this code,
21:41
and status.text is getting updated, and we've determined through massive amounts of profiling on our part that this method is slow. We don't know why it's slow, and so we're gonna do a little bit of profiling on our own for just this specific method. So we're gonna create a board. Now before I add the rest of the code to this slide,
22:02
remember what I said earlier that you don't get style points in debugging. This is going to be egregiously bad code that you should never use, okay? So we're gonna create a board, and I don't want to deal with the TK status, the TK label, so I'm just gonna create this struct and instance eval that in there. Then I'm gonna create some cells, and oh, the cells need TK buttons,
22:21
so I'm gonna create some buttons. You see where this is going. Doesn't that just hurt your eyes a little bit, right? I mean, you were just doing bad things to Ruby. But it's okay, we're gonna delete it all. We're gonna find out exactly what's going on, so let's add some code. We're gonna say, okay, GC, this is your window into the garbage collector. This is gonna tell you what it's thinking about,
22:42
what it's measuring, what it has done, and everything. The stat method is gonna give us back a hash that's gonna give us the current state of the garbage collector. The total allocated objects key within that hash is going to tell us exactly what we've, how many objects have been allocated.
23:00
And finally, if we look at this code, we're gonna run update status 10,000 times and see what happens. If we run this code, we get 70,001. So thinking back, we ran this thing 10,000 times, so seven objects are being allocated per method call. You go back into our code, and this is the block because we were controlling for that inside of the code.
23:22
This is the two lines that get executed. So you can do a couple things here, and I'm gonna put this little caveat out here. Normally, this kind of intense GC optimization, memory optimization, is not useful. You have bigger fish to fry. If your page is taking three seconds to load,
23:41
this is not going to help. That being said, it's a good example, and let's say this is the most critical path. You have to know how many mines are left at all times, like just 10 million clients connecting and they have to know, I guess. You can do this. So we can say, okay, we don't need to select
24:01
and then count, we can just count. And we don't need to do string concatenation, we can do string interpolation, we fix that. We run it again, we get 5001, we deleted two objects. That's a very minimal GC example, it's just to show you that, right, GC, you have that kind of introspection available to you at any given time.
24:23
Lessons learned from state problems. Take advantage of Ruby's quick feedback loops and flexibility. I was able to instance eval status and that button in there and they were just structs and it didn't matter because it's not checking the API, it's not checking what you conform to, it's not checking that you have some kind of
24:40
marker interface or protocol saying, okay, these objects can be substituted for these other objects. And your ability to see into your program state at any point is one of your strongest tools in debugging. Right, you can go into your code and run this at any point, you can put it into your code anywhere inside of your whole application, you can get this information quickly.
25:01
So, flow problems, our last class of problems. These are the most serious ones, these are the ones that you hit in production, these are the ones that Honey Badger sends you an email at 3 a.m. Flow problems occur when you don't know how the Ruby interpreter got to or left a location in code in its associated state.
25:20
It's a bit of a mouthful, but what is that basically saying? When you have your application code and you say, okay, at this line specifically, I have an error and I have no idea what's going on. You need to know how it got there, where it's going, the objects inside of that state, where they came from, where they're going. So it answers questions like, how did the interpreter get here?
25:41
Where did the interpreter go from here? Where is this object? How does this object get here? Where is this object being mutated? And finally, where does this instance variable get set? Right, these are all temporal problems. These are having to do with over the course of a longer period of time. So they're necessarily a little bit harder. How did the interpreter get here? Fortunately, this one's pretty easy to solve.
26:00
If we look at this code and we say, okay, it says we win, but maybe we got the logic wrong, and it says we win after the first click. We can find out how to do that really quickly. We can say, okay, puts caller. Caller is a method from kernel. It's gonna tell you the stack trace up to this point. We go and we run this code, and it gives us all of this. It tells us the whole backtrace.
26:21
That's great. That's pretty good. But it's kind of frustrating, because now we have all this code that we don't want to look at anymore, because we still have all of the external gems. Let's get that out of there. Real quick backtrace cleaner without pulling in a third-party gem, regex. This is a new regex.
26:42
It creates a regex from the directory in which this file is defined. And we can grep through caller like that. Pretty effective backtrace cleaner. We go and rerun it, and we get that. Those are just the lines in our code that executed to get to this point. It created, mine sweeper start was called,
27:02
that called board.start, that created cells, the cells called toggle mine, that called update, and everything broke. That's our backtrace. Where does the interpreter go from here? This is where we start to pull out the hammers. This section is a little bit harder, so we have to pull out the bigger tools. So this is our very start,
27:22
and say, okay, from the very beginning, we wanna know everything. We don't know anything about this code base. We just wanna see where it goes. TracePoint's our friend. TracePoint is a module that will walk you, it's a window into what the interpreter is seeing. Right, it's actually technically a wrapper around the set trace func, I believe,
27:42
from kernel, which is a little more functional. So this is like an object-oriented way of looking at that. So that's TracePoint. The new call is gonna say, okay, every single time a Ruby function is called, execute this block, and give us this TP object. That TP object has certain metadata on it,
28:01
like path and line number, and if we go and run this code, and finally we'll enable it. We go and run this code, we get all of this information, right? We can walk through line by line as this thing is going. Again, we wanna clean that up just a little bit. So let's go and add an if statement that says basically the same thing as we did last time. We go and we run that code again,
28:20
and it says exactly the lines of code that we've defined that it's gonna walk through. This is quick information to get, and it's just gonna show you exactly every single line that you control that you can change that has executed already. How did this object get here? Another hammer we're gonna bust out. Let's say we have this args of,
28:41
I'm sorry, args of button, and that's an instance variable that we're gonna set, and that's coming in. We have no idea where it came from. Let's bust out object space. So object space is a window into the Ruby heap. It's a window into what's been allocated, and actually it also allows you to access
29:01
when these things get deallocated. Couple things you can do with this, and what we're gonna do real quickly, that little section in the bottom right has to be required. That's the only reason I put it at the bottom. You have to require that first to make sure that object space knows to start tracing allocations, because obviously you don't wanna trace that in production. But in this case, we can call allocation source file
29:21
and allocation source line, and we can put that information out, and we can find exactly the line in code where this object was created. We go and we run that, and we get board.rb 949. We go and we find out, okay, this is exactly where it's allocated, right? You can go anywhere in code, find out where all those objects came from,
29:41
and now on the other side of that, where is this object being mutated? We have an object in code. We know exactly where it came from. We can now find the entire life cycle where is this object being mutated, and the easiest way is actually surprisingly simple. So undefined method click for nil class. Let's go in and say, okay, we have these cells.
30:02
We know they were initialized correctly, and one of them is nil. Why are they nil? So we go here, and clearly something is mutating this cells array. Something is mutating it over the course of it going from its birth to where we are trying to access it. Cells.freeze.
30:22
Cells.freeze is gonna say, okay, any time this array would be modified, throw an error. And immediately when we run that, instead of getting undefined method click for nil class, which is several lines deep, we get can't modify frozen array. Board.rb line 32, we go and we see, okay,
30:42
somebody was reading that bang methods are more performant. They got really excited, and they said, okay, we're always gonna use bang methods. That's bad. And it can be refactored to be this. Cells no longer gets modified, and everybody's happy.
31:01
Finally, last example. Now this last example is the trickiest one, so stick with me. When does this instance variable get set? Undefined method count for nil class. We go into our code. This was the previous example. We said we were gonna finish, right? This is where we left the code. Now the problem here is that cells should be set,
31:23
but it's not. So let's make it set. And we're gonna assume that the developer who did this was not an idiot. By the way, that's just a good assumption to make in general. The guy or girl who made this was not an idiot, and this code should have worked.
31:42
They were assuming that cells was initialized. So let's go in. Okay, we run it, and it says zero of mine's left, but the program runs. This is good. It means that we can keep working. We go back into our code, and we say okay. Now we need to find the line of code in this file where this instance variable would have been set
32:00
so that we can change build status label to be after that. So let's bust out our old friend TracePoint. This is getting complicated, but it's not as bad as it looks. First of all, this is going to give us this block, this TP object at every single time
32:23
the interpreter hits a new line in code, and it's gonna enable it. Then it's gonna say for if some condition that we'll go through, put out the path that you're at, and the line number minus one. The reason we're saying line number minus one is because we're gonna try to create an if statement
32:42
that says okay, if cells changed, then the previous line must have changed it. First part of the if statement. Are we within the same file? This might not be relevant, but for us it's easy because it's an instance variable, and assuming you are not setting instance variables
33:02
on objects from other classes, don't ever do that, we can assume it's within this file. Next, TP dot binding dot eval self dot class equals equals self dot class. What the heck am I doing there? So binding dot eval, right,
33:21
when you're within the trace point block, when you have the TP object, you have access to the binding at that point in code. You can access all the variables that that binding could have seen, and you can just call eval on it. Remember how you don't call eval in production? Please don't call eval in production. You can do that in debugging. You don't get style points in debugging.
33:41
It's gonna go away anyway. So the point of this line is because earlier, remember we had that lexical scope issue where we were inside of that block? That's still inside this file, so we need to make sure we're still evaluating within that code, within this class. And finally, this is the money one. We're gonna create a local variable that we're gonna have access to within this block,
34:01
and we're gonna say TP dot binding dot eval, the instance variable cells, not equal to cells. That is to say if this instance variable changed, then we wanna know about it, and we wanna immediately exit, and that's gonna give us the information. So we run it, and that's what we get.
34:23
We get the line of code, and we get the file in which that instance variable changed. It tells us one line. Now if we go back to this code, this is nine lines of code. Nine lines of code to attach to the Ruby interpreter,
34:41
walk it through every single line that it evaluates, and evaluate an instance variable, and tell you exactly when these things changed. You go in, and it turns out this is a relatively easy fix. This is the line we wanna change, and we do that, and everything's fixed. So lessons learned from flow problems.
35:02
For large projects, it requires effort to follow the flow. This is just a thing. With larger projects, it's gonna be harder to walk through the stack trace. You're gonna have thousands and thousands of lines, so you need to be very, very careful about how you clean it and how you walk it through. On the other side, for new projects,
35:20
stack traces are easy to generate and follow. My point in saying this is, it's going to be easy at the offset, and we know this from Rails. We have generators. We have quick and easy 15-minute blog tutorials. It will get you up to speed very, very quickly. It only becomes hard down the road. So all that is to say, we're taking some trade-offs
35:42
when we use Ruby, and we're debugging Ruby. We're trading compiler niceties for introspection, right? Compilers will tell you when you haven't taken account for every single type that's gonna be returned from a function. On the other side of that, Ruby is going to give you the ability to introspect on any point in code, every single variable, every single constant,
36:00
every single state of anything that's going on, and that information is available to you very quickly. If you don't use that as a tool in your debugging, then you're giving up one of the best advantages of using Ruby. We're trading out-of-the-box speed for flexibility. If we had taken the out-of-the-box speed, then we would not have been able to take that status struct
36:22
and stick it in for a TK label. We wouldn't have been able to trade those things out because the compiler will yell at you. It will say, no, I need to ensure that everything is correct before I run. And finally, we're trading maintenance costs for startup costs. This is for flow problems. This is for when you're trading off the fact
36:41
that it's going to be very easy to get to market to ship this to production, and down the road, it's gonna be a little bit harder to maintain. In other words, these are interface problems for the compiler versus the introspection, for out-of-the-box speed versus flexibility, there's state problems and maintenance costs for startup costs.
37:00
These are flow. So the next time someone comes up to you and says, Ruby's dead, don't use Ruby, you can say, actually, I'm gonna go ship my app to production, and I sleep fine, and enjoy your type safety. Good night. Anyway, thank you.