rubyday 2020

Cool, But... Why?

Cool, But... Why?

by John Crepezzi

The video titled "Cool, But... Why?" by John Crepezzi, presented at RubyDay 2020, delves into the rationale behind common Ruby coding conventions often accepted as established norms. This talk challenges developers to critically analyze these conventions and to understand the underlying reasons rather than simply following them without thought.

John discusses various topics that new and experienced Ruby developers often grapple with, emphasizing the importance of asking 'why' instead of just accepting conventional wisdom. Key points discussed include:

  • Skepticism in Learning Ruby: Newcomers often question Ruby's efficacy, especially regarding performance, and may feel confused by its syntax. This skepticism often evolves into excitement once they become familiar with the language's features.
  • The Myth of "Bad Ruby" Practices: John highlights that certain coding practices labeled as 'bad' in Ruby are often based on outdated or misunderstood norms. For instance, the use of for loops is critiqued for lacking flexibility; however, he argues it should not be outright avoided without considering the context.
  • Benchmarking Performance: John introduces the Benchmark tool, demonstrating how different implementations of iterations, including for loops and each methods, can be compared. Through benchmarking, he reveals the nuances in performance, such as for loops being slower than the each method because Ruby translates for to each.
  • Method Missing: He explores method_missing as both a powerful and risky feature, showcasing its complexity and the potential performance overhead it incurs, along with implications for code maintainability.
  • Defining Methods: John touches on the functionality of define_method, explaining its complexities and the reasons why it may introduce performance issues. He argues for caution in its utilization, particularly how it can affect garbage collection.
  • Best Practices and Common Misconceptions: John advocates for using symbols over strings for performance, clarifies the implications of using 'and' and 'or' versus their counterparts, and emphasizes the importance of documentation through comments.

In conclusion, John encourages developers to foster a mindset of inquiry and experimentation to become more proficient in Ruby. The main takeaway from the talk is that questioning accepted practices and understanding the ‘why’ behind coding standards can significantly contribute to individual growth as a developer and mentor.

This talk is a call to action for Ruby developers to not just follow trends but to critically evaluate and experiment with their coding practices.

00:00:48.559 Cool, but why is our next talk by John of Italian origin? If I recall correctly, hi John! Thank you, lovely. It has been a great day—tiring, but generally a grand day. I wasn't expecting it to go so smoothly after the first few hiccups in the morning.
00:01:07.840 But good thing that everything is working much better right now. Yeah, how are you?
00:01:22.400 I'm good, yeah! I've tried to make the lighting in here reasonable. This is my first time doing a remote conference, but I'm excited.
00:01:27.920 Same here! That's part of the excitement and stress—what can go wrong today? Thank you so much for joining us.
00:01:42.240 For our attendees, this is going to be a live talk.
00:02:06.079 Thank you! I assume you can all see my screen at this point.
00:02:17.680 So, if you're like a lot of people when you're first learning Ruby, you're going to go through a few stages. Let's see if these sound familiar. You start out skeptical. Maybe you've heard that Ruby is slow, and all of your friends say that it can't be used for writing real software. They'll send you links to articles about sites built in Ruby that had to move off because of scaling problems.
00:02:41.519 You'll almost be convinced until you realize that every article you're reading was literally written in 2008.
00:02:54.560 Next, you might be confused, having never seen a language that looks like this before. You're wondering, where did all the punctuation go? Wait, array iteration is just another method call? Oh God! Then you're thinking: wait a second, am I having fun programming? Maybe! As the language wins you over and the code starts to flow, you're naturally going to get excited.
00:03:22.959 Everything's an object. The standard library has so many great things, and there are sensible names everywhere! Why don't all languages just use sensible names? And the community—you all are amazing! Now you're writing real code, things are coming together, and you're loving it.
00:03:40.799 This is about the point where some cranky senior programmer walks in the room, looks at your work, and says something like, 'This code is not very Ruby.' What does that even mean?
00:04:00.239 You may even find yourself in this position later, saying the same old stuff to other newer Ruby developers. We just don't use that feature; they were writing Python. Or, my personal favorite, and probably the one you hear most often: 'It's just not very idiomatic, is it?'
00:04:36.720 While often, the advice you receive originated in a good place and with good intentions, more often than not, the person giving it doesn't have the backing of why. They're just repeating something that they heard. They can often be wrong, misguided, or have assumptions based on evidence that just isn't the case in modern Ruby anymore.
00:05:18.880 I'm here to say: don't be a parrot!
00:05:24.160 Unless you are an actual literal parrot, in which case, please just continue to be a parrot. Okay, so everyone repeat after me at home: don't be a parrot!
00:05:36.720 If you just repeated it, then you're not getting it yet. Today, I'm going to dig into some common things that are seen as 'bad Ruby.' I'm going to talk about why and I'm going to go through some code examples to demonstrate. I don't need you to say, 'We don't use that anymore.' I just need you to say, 'We don't use that because...'
00:05:55.199 The things that I want you to take away here are a set of practical tools as a starting point and a mindset that you can use to become a better developer and a better mentor.
00:06:16.480 Before we get too deep into this, I should introduce myself. My name is John Crepezzi, and my internet handle pretty much everywhere is @cjohnrun. I live in New Jersey in the United States with my family.
00:06:39.120 No Italiano—Italian imperfecto! I love Italy, and I wish more than nearly anything that this pandemic was over so that I could be over there with you in person. A few years ago, my wife and I were at a bar in Milan, and we overheard an American couple. It was a cocktail bar, and as our cocktails came out, we heard this couple at the next table say, 'No one drinks Campari in Italy anymore.' Well, as soon as I'm able to, I will be back to disprove that statement.
00:07:19.200 I'm a staff software engineer at GitHub. I've been at GitHub for a bit over three years and I've worked on a ton of really fun features there with some amazing people, including Eileen and Aaron. I've been working in Ruby professionally since 2009. I've worked on a bunch of open source software, and I've worked at Patch, Genius, and Square. I've also started a few startups along the way.
00:07:45.600 This is all just to say that I've definitely put in my 10,000 hours at this point, but I don't consider myself an expert. That's primarily because I think that experts—there's so much to know about software, even a relatively minor topic like Ruby, and it's constantly evolving. So, the only thing that you should trust is known, and even then, only temporarily is your own experience.
00:08:05.440 So, let's get back to that. And maybe at this point, you're thinking, 'So, if we shouldn't trust what people tell us, and we don't want to be parrots, then maybe we shouldn't trust you, and we shouldn't listen to you.' And it's like, first off, rude! You know what? No, you shouldn't trust me. Just for that, I'm going to sneak one lie into this presentation, and you get to try to spot it.
00:08:31.040 So, just a fair warning up front here: I'm about to show and talk about a ton of code. It's going to be fast, but I'll share a link to all of my examples at the end of this presentation. I encourage you to check them out locally, experiment with them, and then come to your own conclusions.
00:08:57.360 Let's get started, and I'm going to start with for loops. This is one of the most common topics for developers coming from other languages who are new to Ruby.
00:09:08.480 For loops provide a simple way to iterate over any enumerable. You can see in the bottom example that you can even do a handy destructuring with for, similar to how you'd use each on a two-dimensional array. But we're told not to use them. We're told they're not Ruby. Cool, but why?
00:09:35.040 Let's start with the reason that you maybe hear from people: that's because when you're using each, you're creating a new scope. You can see here inside of each that I'm making a new variable called 'new_var,' and then outside of the block, if I try to access it, I no longer have access to that variable, which produces a name error.
00:09:52.000 Contrast that with using for. When we define new_var inside of a for loop, it isn't created inside of a new scope. These variables stick around and continue to be accessible outside of the for loop. And if you're like me right now, you might be thinking: who cares? That seems like a pretty weak reason not to use something.
00:10:09.760 After all, if those other keywords that we use pretty often, like while, until, and case, work the same way... You might be surprised to see that, due to the way that the Ruby parser works, you don't even need to use these variables for them to be defined. But that's probably a topic for another day.
00:10:28.160 Yes, despite these other keywords working the way they do, we don't hear many people proposing something crazy like implementing if using blocks—although looking at it now, I kind of don't hate it.
00:10:50.000 Let's learn a new tool real quick. This is Benchmark! It's part of the Ruby standard library, and it gives us a quick way to compare multiple implementations of something. We require the benchmark and then write multiple versions to see how they perform. In return, we're going to get out this list of the versions compared to each other on several different dimensions.
00:11:26.160 For our purposes today, the one that's really easy to conceptualize is the last one: it's the real category. This one measures what you kind of expect—it's wall time—how much time that version actually took to run on a real clock.
00:11:54.959 If we run a simple benchmark on that funny if block version that I showed previously, we're going to see that the runtime of this block form is about two times slower than the keyword version. So maybe that's a reason not to use these block versions. And then maybe you see where I'm headed.
00:12:11.680 Next, if is faster than the if block predicate version. It would stand to reason that for should probably be faster than each. Here, I've written six separate implementations of a basic for loop. Let's see how they do against each other first.
00:12:45.200 And not too surprisingly, wall and until are nearly identical. Also not surprising based on the performance of times and up to—are also very similar. Even each is very similar to times and up to, and that makes sense; they're all methods that make a call and then yield to a block.
00:13:00.080 But what about for? Well, here we can see that each in this benchmark is actually faster than for, and that's pretty weird, right? Actually, if we include the iteration inside of the loop but run the loops multiple times in the benchmark, we'll see the numbers diverge even more.
00:13:23.680 How can this be? This is the point where we should all be happy that Ruby is open source; we don't have to wonder because we can go look at the source code.
00:13:38.960 So we go deep—6,500 lines down into compile.c—and this is the C code that handles implementing for. I don't need you to know C, but if we look right here, we can see that Ruby's implementation of for literally sends to each.
00:14:17.760 For is implemented in terms of each, just like our if predicate method was implemented in terms of the keyword if. So now the evidence is stacking up quite a bit. You shouldn't avoid for because its syntax is confusing or because it's ugly, or because it's not Ruby.
00:14:31.920 But you might choose to avoid it because it's an unhelpful abstraction on top of a more flexible method called each. I realize the technical content here is pretty dense, so I'm going to mix in some photos of my favorite European Ruby conferences from last year.
00:14:56.640 This one was in February of last year. This was Ruby on Ice in Tegernsee, Germany. We had the opportunity to stay at what I think is probably the nicest hotel I've ever been in. It was directly on the lake, and afterward, I took a week off, which is something I pretty much never do.
00:15:12.880 My wife and I went over to Salzburg for a week. This was a really nice trip, and going outside was awesome.
00:15:36.480 Now, let's look at something completely different: method_missing. This is a feature that people love to hate, often embraced by people who like to feel clever. Method_missing is both a blessing and a curse, and it's also typically seen as a code smell.
00:15:49.600 But why? Here's the default implementation of method_missing that exists on BasicObject. Again, don't worry too much about the specifics, but the Ruby equivalent is essentially raises NameError. Because the Ruby gods have exposed this method on BasicObject, that means subclasses can override it and supply custom behavior for what happens when a method isn't defined.
00:16:10.239 Here's a simple illustration. In this example, we've created a custom implementation of method_missing that lets you call any method and then returns the name of the method you've called backward as a string.
00:16:34.000 To make a slightly more useful example, here's a quick and dirty way that we could abuse method_missing to take a hash and make its members accessible via method calls. This is a very dumbed-down version of something like the Hashie library that you may have used in the past.
00:16:52.639 The thing everyone knows for sure, and what probably has a few of you squirming in your seats right now, is that if you define method_missing, you also need to define respond_to. But why? The reason you need to define respond_to is that if you don't, anything trying to determine if a certain method exists is going to receive the wrong answer.
00:17:12.960 Does something that's defined by hash respond to 'a'? No, it doesn't. But if you define a customer respond_to, well, now it will. So at this point, everything is perfect; respond_to returns the right thing, we can call the method.
00:17:39.840 We can even look up the—oh damn it! The thing is that since the method was never explicitly defined, Ruby doesn't know how to find it. Luckily, Ruby provides a way to fix this. Instead of defining respond_to, we can define respond_to_missing, and now our method appears correctly.
00:18:02.480 Of course, things like public methods still won't include these method methods, but that's understandable. Is something maybe tingling in your brain, though?
00:18:18.720 Why did we need to define a custom respond_to_missing? Why wasn't defining respond_to enough to get method to behave correctly? And more importantly, what does the fact that it wasn't enough to define just respond_to imply about the behaviors of these methods?
00:18:40.000 Let's bring our benchmarking tool back out here. We're comparing calling respond_to on a real defined method versus calling respond_to asking about a method that's only defined through method_missing.
00:19:07.840 No surprise—the real method call is slightly faster! Not a very convincing reason to not use method_missing, though. It's when actually calling the method that the true difference is going to start showing itself.
00:19:32.800 Now we're seeing more of a difference in call times, and it should be pretty clear why. The first example can find the method and call it directly, while the second example goes through a level of indirection to get to the right place.
00:19:59.040 So, let's just draw that out to really illustrate the point. On the left side, we have a regular method call—we call the method, we look it up, we find it, and we call it.
00:20:08.720 On the right side, when we call a method, we have to look it up, and now it's missing. Now, instead of being able to call method_missing, we have to look up method_missing, and then when we find that, we can call it.
00:20:24.320 It's this extra step that makes method_missing definitions a little bit slower. Okay, so we've settled on maybe one reason to choose not to use method_missing unless you really need to.
00:20:49.840 And that's because it's, pound for pound, slower than a defined method to actually call.
00:21:00.480 But that's probably not enough of a reason. There's another problem, though. Let's take a look at what happens if there's a failure somewhere inside of a method_missing call.
00:21:19.440 You can see down here in the bottom error backtrace that we've called this method apple—it's errored—but the backtrace contains no mention of the method that we've called. That's going to be pretty confusing to try to debug.
00:21:33.840 Now, can you fix it? Sure! But we're getting closer to the primary reason that using method_missing is typically avoided, and that's because the method_missing implementation is super complicated.
00:21:55.040 Bordering on impossible to implement correctly in a way that doesn't hinder developer ergonomics or cause significant performance overhead. Rails, for example, goes to intense lengths to give developers a good version of method_missing that feels natural.
00:22:11.040 Wondering how they do that? Here it is! You can see that when you call dynamic_matcher in Rails, it literally defines the method and then immediately calls it. This keeps the backtrace clean and avoids the performance penalty on subsequent calls that would typically come with method_missing.
00:22:29.200 So is this a good option for developers? How do we define methods in Ruby? Are there any drawbacks here?
00:22:49.680 Well, you probably saw where this was headed. We're going to dig into define_method and we'll figure out what the deal is in there. But first, it'd be crazy not to talk about last year's Eruko conference! This was June of 2019, and I mean, come on, it was on a giant ship!
00:23:17.360 The right side here was a brewery nearby that we spent a lot of time at, and it was called Caps. Just like going outside, it was really nice.
00:23:30.160 Okay, back to define_method. Imagine you want to create a class with two methods that are identical except for their name. Define_method is a way to define their behavior quickly and with less repetition.
00:23:43.360 You can see that I'm iterating through an array of two symbols, foo and bar, and for each, I'm defining a method that just returns the same symbol. It's tempting, but you're often going to get pushback trying to put code like this into production.
00:24:12.960 Cool, but why? For all meaningful intents, these methods are just like all the other methods that you might define. Calling respond_to on an instance of something returns true; calling the method works fine.
00:24:37.760 Looking up the method works fine, even trying to list the methods will contain the correct symbol. They behave just like methods! So we should be able to compare these two side by side, and we expect them to perform the same, but they don't!
00:25:00.960 Okay, so defined methods are slower to call. Why? Clearly this isn't just the method. If we drop a pry debugger into the middle of all of this, we can get a hint of what's going on.
00:25:25.920 We look up the definition of foo, and we get back the defined_method block instead of receiving back the actual method definition. This kind of hints to us that maybe the thing we're defining isn't a method after all.
00:25:49.760 Resorting to some old tricks here, let's go to the relevant part of the Ruby source code for define_method. Okay, so define_method is actually pretty complicated because it can take in more than just blocks.
00:26:11.200 So how about we just zoom in here on the relevant piece? In this part of the definition, we can see how define_method actually works. It creates a proc copy of the body that was passed in, turns it into a lambda, and then it defines the Ruby method that you're defining in terms of the proc that it just created.
00:26:28.240 Here's a roughly equivalent piece of Ruby code that should help illustrate what's going on and why methods created via define_method are slower than normal methods to call.
00:26:51.600 They're slower because instead of calling the method directly, you'll always be calling what essentially amounts to an instance of a valid copy of the original block.
00:27:12.400 But it doesn't stop there. There's another issue with our typical uses of define_method that's problematic.
00:27:25.920 So I'm going to break my format a little bit here to look at another class of problem that might not be super obvious, but one that you should be keeping in mind when using define_method. And in fact, as we'll see, all other methods that take a block.
00:27:44.640 When you're using something like define_method when setting up a class inside of the block, you'll have access to any objects that are currently in scope. You can use them inside of your block.
00:28:09.680 Here, I'm using a local variable called greeting inside of a define_method called greet. Unfortunately, that comes at a cost. You see, Ruby uses reference counting to keep track of garbage collection. Normally that works really well, and you don't have to think about it too much.
00:28:29.760 But when you introduce a block like the one for define_method when creating a class, since Ruby can't tell what things you might reference inside the block, it needs to keep all of them accessible.
00:28:41.680 That means that it can't free any of the objects that could be reached from inside of that block until the block is freed. In this example here, of course we can all agree that greeting doesn't get garbage collected, but it might be surprising to hear that neither will other_var, despite the fact that it's literally never used.
00:29:05.760 Just saying that is all well and good, but you probably expect me to prove it, and that's exactly what I'm going to do.
00:29:24.880 Ruby comes with a useful library called ObjectSpace. Just like Benchmark, we have to require it to get access to these methods, which, by the way, should only ever be used in development.
00:29:42.720 I encourage you all to take a full tour of this module yourself, but just to get you started, I’m going to shout out some of my favorites. So, count_objects_size will give you essentially a hash back that has all of the types for Ruby and how many bytes they're currently taking.
00:30:03.840 memsize_of_all will take a class in and give you back the consuming memory total of all the living objects of that class type.
00:30:23.760 And each_object will actually let you put in a class and you can get, you can pass a block that will receive each of the current alive objects of a given class.
00:30:38.480 So, given those tools, it should be pretty straightforward to put together an experiment here. This is a quick and dirty method that just runs garbage collection, counts the total memory size of a certain class, yields to some code, and then reports back the difference after the block is over, after running garbage collection.
00:30:55.440 So, essentially by wrapping code in this block, we're able to see how many bytes of memory were created for a given class inside of the block but then not able to be released.
00:31:11.520 If we run this, and we create a string inside but we don't reference it anywhere, we can see what we expect: zero bytes. That means that the string 'str' was able to be garbage collected before our block was over.
00:31:27.680 Now we make a quick modification. So what we've done is we've defined the string to be scoped outside of the block, and we can see what we expect here: again, the string doesn't get garbage collected, and we get back 40 bytes of unreleased string.
00:31:45.840 It looks like everything is working with our experiment, so let's jump back to where we were before. We're going to wrap our greeter class definition in our test method and you're going to see exactly what I was saying before.
00:32:04.080 Ouch! 10,081 bytes of a string that won't be garbage collected despite the fact that it's not being used.
00:32:22.160 Now for a more meaningful illustration. Imagine that we're loading a bunch of data in order to populate a list. We open a file, we pull some data out of it, and we put it into a constant called band_users.
00:32:42.960 Later on in the class, we use define_method. Now it's very obvious that band_users is not going to be garbage collected. But here, by mistake, we've actually taken away Ruby's ability to garbage collect.
00:33:02.960 In this case, we even close the file pointer for the file that references or was referenced by banduser file.
00:33:20.480 As a result, we've retained 8,424 bytes of unreleased file. And just to be really clear, this isn't just a problem at the class level; it can happen anywhere that a block is passed to a method that holds on to a reference to the block.
00:33:41.040 In this example, we're calling parse and we're calling add_callback, and the file handler defined by file will not be released until the actual user object itself is garbage collected.
00:34:01.680 If you nail out these references, you're going to let Ruby's garbage collector continue to do its amazing job. And this is how you have to fix this issue if it's something that you're encountering in your application.
00:34:19.760 So, define_method isn't free, and that doesn't mean don't use it, but it does mean that you're going to pay a constant cost on every call to a method that was created via define_method.
00:34:35.760 And that you have to be careful of what objects you're unintentionally retaining. But is there a way to avoid that?
00:34:50.800 There is, but you might not like it! Class_eval has the benefit of creating real methods, and due to the way that they're evaluated, they can't hold on to any closure context from the surrounding scope.
00:35:07.440 So, in this case, other_var would be garbage collected as soon as foo is finished being declared, and garbage collection runs.
00:35:28.000 So just to be really exhaustive here, here's a benchmark showing that the methods created via class_eval are 100% identical to real Ruby methods. They have a higher upfront cost to be created, but they incur no extra cost per method call.
00:35:46.400 If you've ever seen a bunch of dynamic code, class_eval for a hot code path—this is why! In fact, Rails's define_method that I showed you earlier uses class_eval to define dynamic matchers.
00:36:03.440 It's tempting to overuse Ruby's meta-programming features. It can result in less code and, more importantly, less duplication.
00:36:18.080 But you really need to know what you're doing, and you need to be aware of the potential trade-offs that you're making.
00:36:34.160 On the flip side, every path doesn't need to be fully optimized. Things like class_eval definitely make code harder to reason about— they make it harder to search, and ultimately harder to maintain.
00:36:49.280 You need to make the choice that's appropriate for your use case.
00:37:05.440 So, okay, you caught me. These are not from a Ruby conference! This is just our summer vacation last year. This is at a VS day down in Puglia, and the water is just so clear.
00:37:21.440 These are my daughters, and this daughter's name is Ruby.
00:37:33.040 Okay, let's go for a lightning round! I'm not going to go all the way on these; I'm just going to start you off with some things to think about.
00:37:48.960 Use symbols over strings whenever reasonable. Cool, but why? Historically, the reasoning is that strings are mutable, so they're new objects every time that you create them.
00:38:04.160 You can see here that if I create two strings and I compare the object IDs, they'll be different. Symbols are immutable, and due to that, they always point at the same objects, thus they cause less memory churn.
00:38:19.440 You can see here I'm creating two symbols with the same value, and the object IDs are the same. That's the reason you're going to hear from people. But a lot of these once compelling reasons are less so now that frozen strings exist.
00:38:36.720 By the way, do your own benchmark, but frozen strings are just as fast as symbols when it comes to lookup. Still, many developers choose to use symbols as a signaling device of immutability, and standardizing on them, especially in control flow circumstances, avoids unnecessary markup and reduces confusion about what type of object you're handling.
00:38:54.080 Did you ever get an object and say, 'These are not equal, but I expect them to be,' and it turns out you were holding onto a string when actually you were comparing to a symbol?
00:39:11.840 Also, somewhat obscure markup tip: on the bottom here, you can create symbols with interpolated values like this: interpolation is not a reason to have to use strings.
00:39:32.680 The next one is always use attr! Cool, but why? You'll often hear to use things like attr_reader, attr_writer, and attr_accessor. I say use whatever version feels most natural for your use case.
00:39:53.680 But just to really set your brain off a little bit and get your mind spinning, I will mention that the attr definitions are slightly faster. So why don't you go find out why?
00:40:11.920 You'll also hear: don't use keywords like 'and' and 'or.' Oh wait, that's confusing to say! Don't use keywords 'and' and 'or'—okay, never mind. Cool, but why?
00:40:29.280 Okay, so a regular double ampersand behaves like you're used to in all programming languages. The entire right-hand side of the statement here is evaluated before assigning to foo, and foo becomes three.
00:40:53.440 The keyword 'and' is a lower precedence version of double ampersand, so the behavior is pretty wildly different. For illustration, you can think of the keyword version here essentially as first evaluating foo equals two and then ending with three.
00:41:07.760 Even that isn't quite right, because the 'and' version returns two, while the double ampersand version returns three. This is why people avoid using it. It's said the same way out loud, yet it behaves very differently and can introduce very subtle bugs.
00:41:23.440 This one I pretty much agree with—just don't use them.
00:41:33.920 Another one you're going to hear pretty often is: don't write comments. Cool, but why? You know what, actually, I take that back— not cool! Write comments, please, and if you want to, tell people they tell you not to. Find something else to worry about.
00:41:48.400 Phew! Okay, we are nearing the end here. And if I can have you only take away one thing from this talk, it's that hashes should not have spaces inside of them! I mean, really just think about it.
00:42:12.960 But if I can have you take two things away from this talk, then the second one would be that questioning accepted wisdom is the fastest path to growing as a developer.
00:42:34.720 Reading the headline of an article shouldn't be enough to convince you of something. In fact, even reading the full article shouldn't be sufficient.
00:42:50.720 We're engineers, and the way that we learn is through experimentation. Through this, you're going to grow as a developer, and when you do, you're going to help the other people around you do the same.
00:43:07.200 I hope that in this talk, I've given you some tools to help on that journey and pointed you in some new directions to explore.
00:43:15.240 Thank you for having me, and enjoy the rest of the conference.
00:43:30.800 Hello, you're going to help! I was waiting for the... and your line that didn't come!
00:43:39.360 So, this has been great! They're only super fun. If you look at the chat afterward, everybody has had a ton of fun.
00:43:47.680 Thank you so much! Great to be here, making us experience again a little bit of that joy in finding why.
00:44:02.760 I don't see many questions, besides one that is like: which was the lie? But that's not something you should answer, to my understanding.
00:44:12.160 Although I do want to introduce the possibility that the lie was that I was going to tell a lie, so just, you know, let your mind wander!
00:44:25.440 Let's see if someone else has questions. Amazing performance! I'm reading so good job.
00:44:54.320 We have a bit of a delay between the streaming and what is shown. Uh, there it is: the question I was waiting for. Should we prescribe this dog when writing a Rubocop config?
00:45:07.040 I think it's a bit of drawing.
00:45:14.760 But yeah, I know that that makes a lot of sense. I think the two things that stuck with me were the ones about the symbols and the and or thing.
00:45:47.920 When I was in my previous company, we had, I swear to God, it was like a meeting with 20 developers in a room with our team lead, and the topic of the day was: should we go for single quotes or double quotes?
00:46:03.680 And mind-blowing!
00:46:12.960 But yeah, so that was also a remembrance of that conversation, because there was no real reason for really sticking with either one or the other all the time.
00:46:21.760 That brought me to look a little deeper into this and or or conundrum.
00:46:37.040 Early in my Ruby experience, I was very into single quotes as a way to indicate that there was no interpolation in the string.
00:46:51.760 But do you really need that hint as a way to do things? You can pretty much see!
00:47:05.760 And then I remember back then someone publishing, almost jokingly, a benchmark about single quotes versus double quotes.
00:47:16.960 Just trying to point out how ridiculous the argument was.
00:47:24.720 At GitHub, we use double quotes for everything just as a matter of consistency, and it honestly kind of annoys me a little bit. But it's not worth a fight.
00:47:35.120 Yeah, definitely! That is the other thing: if we are talking about performance at this level, again, probably the most costly part of the whole application is us.
00:47:45.600 So just short-circuit everything else and go for one, because it's not worth the fight!
00:47:59.360 Definitely! There is a question from Aaron: if you use define_method with a lambda, does that make it a blocking method?
00:48:10.720 Yeah, it was a very valuable meeting, Antonio—to leave back and forth. People are asking this. 'Cool, but why?' is already a meme! We can create one.
00:48:30.720 I'd like that! That sounds great.
00:48:40.320 Half the keystrokes, twice the speed!
00:48:51.680 Good point! You see, we can keep debating about this for the rest of the day.
00:48:58.880 I think anyways, John, it has been great to have you here. I just have to laugh so much and to learn, actually, so much after 10 years of Ruby development.