 It's 4.30, so we're gonna get started. So hi, this is practical debugging. 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. 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 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 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 by bug. Stack Overflow really likes to suggest using these tools. 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. More of the story, at the end of the day, 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 into bugging. This one is actually really hard for me. I live and die by my winter. 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. 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. 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. You have pride. You can use pride. I don't care if you use pride. 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, 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. 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 mind-sweeper. Is everyone familiar with mind-sweeper? Yes? No? Maybe? Okay, I'm gonna show it to you. Live demo time. So, here's mind-sweeper. It's in Ruby. Can everyone see that? Kind of, maybe. And you click, right? And then you can tell them where the minds are. And if you click a bad mind, 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 and the number of minds. So this only has one, so you should be fine. And then. Thank you. Thanks for coming. Yeah? Okay. Let's try that again. We click. And then it says you win. There we go. Much better. Okay. What are the odds, right? What are the odds? You could 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. Don't panic. All of the examples are up on the GitHub repo that I will give you the link afterward 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? 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. 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 an ancestry. They have descendants. They have all kinds of things. So, interface problems occur when you don't understand 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? This is the most common thing that I find. Undefined method, blah, blah, blah for nil. So, we're gonna walk through that. Why can't I call the method that I want? 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 mind supercode. We get this no implicit conversion from nil to integer right here. 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 course that into an integer that it can reference inside of the array. And so, we say, okay, neighbor is nil. So, neighbor is an element of neighbors. Why is neighbors have nil in it? We go to this neighbor's four. Neighbor's four is defined here. Neighbor's 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, the neighbor's 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. 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. And then later down the road, 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 dot 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. Total lie. 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 and 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 one of the stack trace 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. 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 respectively 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 with as quick as possible, right? You don't want your application to just keep running. 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, we get derivative already debugging information, and we can find out that in fact, self has changed in this block. That kinda makes sense. We could have 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 referenced the local variables when the block was defined. So we take that text, mine's, mine's left, and column span width, 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 tend to forget that constant lookup is not the same as ancestry inside of Ruby, it's, 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 the cell neighbor and the cell empty, and they're all gonna take care 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, mine's super-based. 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 within this current lexical scope. So if you run this code, we're gonna get mine sweeper, 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. So in this case, you go to that first definition, class, cell, mine descends from base. Base is not mine sweeper base. Base is mine sweeper 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 mine sweeper, cell, and mine sweeper. So you can see from this code 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 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 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, especially when you're dealing with reloading, like hot reloading with Rails and something hasn't been loaded, think STI where you wanna 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. This right here, this instance methods function is gonna give us an array of symbols that represent those methods. And we can use, 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. Dash r is gonna require it. Dash e is gonna evaluate it. 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 characters worth of typing. Try to get that with other languages. It's not the easiest. 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. 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, we go in and we find out, okay, this is actually calling, if you remember from the lexical scope example earlier, the tkliblgrid method. So we go into our command line and we say, all right, let's get that tkmethod grid and then let's get the source location. Source location method, instance method, 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, this is calling out to the tkgrid 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? 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. 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 that when you downloaded the gem originally. You can also do dash dash all and that will just clean your whole gem file. So lessons learned from interface problems. Account forever 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? 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 that you have for having chosen Ruby as your language of choice. And finally, take advantage of the fact that gems aren't bytecode, 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. 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 are right there. It's great. So use them. State problems. So, state problems. 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 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 say, okay, button.text is changing. And let's say it's changing to something that we don't like. It's changing to some, I don't know, foobar 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, 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 with the keys being the instance bars, the values being the actual value of those, and the state being outputted. Well, 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, right? 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. 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 nail 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. 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. It might look similar to binding.pri. It's very similar. It's like the same thing, except it doesn't have 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. 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. 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. Now we're gonna go and complete this whole debugging example in a minute, but suffice to say at this point, you can use binding IRB 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 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 and status.txt 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, 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 wanna 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 so I'm gonna create some buttons. You see where this is going. Doesn't that just like hurt your eyes a little bit, right, I mean you're just like 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, 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 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. We go back into our code and this is the block because we were controlling for that inside of the code. 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 like intense GC optimization, memory optimization is not useful. You have bigger fish to fry, right? If your page is taking three seconds to load this is not going to help. That being said it's a good example and let's say this is like the most critical path. Like 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 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 50,001 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. 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 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. So, flow problems. Our last class of problem. 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 the location and code and it's associated state. 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? Where did the interpreter go from here? Where 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. If you 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 this. It tells us the whole back trace. That's great, that's pretty good but it's kind of frustrating because now we have all this code that we don't wanna look at anymore because we still have all of the external gems. Let's get that out of there. Real quick back trace cleaner without pulling in a third-party gem, regex. This is a new regex. It creates a regex from the current working direct or from the directory in which this file is defined. And we can grep through caller like that. Pretty effective back trace cleaner. We go and re-run it and we get that. Those are just the lines in our code that executed to get to this point, right? It created, mind super start was called that called board.start, that created cells, the cells called toggle mine, that called update and everything broke. That's our back trace. 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 and say, okay, from the very beginning we want to know everything. We don't know anything about this code base. We just want to 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, 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, like path and line number. 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. We can walk through line by line as this thing is going. Again, we want to 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 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 going to 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, I'm sorry, args of a 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 when these things get de-allocated. 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 the object space knows to start tracing allocations because obviously you don't wanna trace that in production. But in this case we can call allocations source file and allocations 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.rb949. We go and we find out, okay, this is exactly where it's allocated. You can go anywhere in code, find out where all those objects came from 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 lifecycle. Where is the subject 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. 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 cell's array. Something is mutating it over the course of it going from its birth to where we are trying to access it. Cells.freeze. Cells.freeze is gonna say, okay, anytime this array would be modified, throw an error. And immediately 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.rbline32, we go and we see, okay, 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. 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, 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. They were assuming that cells was initialized. So let's go in, okay, we've run it and it says zero minds 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 so that we can change build status label to be after that. So let's bust out our old print trace point. 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 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 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 on objects from other classes, don't ever do that. We can assume it's in within this file. Next, TP.binding.eval self.class equals equals self.class. What the heck am I doing there? So, binding.eval, right? 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 cell points debugging. 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. And we're gonna say TP.binding.eval, the instance variable cells, not equal the 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. We get the line of code, and we get the file in which that instance variable changed. It tells us one line. If we go back to this code, this is nine lines of code. Nine lines of code to attach to the Ruby interpreter, 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. 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, 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 when we use Ruby and we're debugging Ruby. We're trading compiler niceties for introspection. 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, 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 and you're 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 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 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, the state problems and maintenance costs for startup costs, 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.