Ruby

There are no rules in Ruby

There are no rules in Ruby

by Max Jacobson

The video titled "There are no rules in Ruby," presented by Max Jacobson at RubyConf 2017, explores the flexible and dynamic nature of the Ruby programming language, highlighting how many commonly accepted "rules" in programming can be viewed more as guidelines or norms. Jacobson shares his personal journey as he navigates learning Ruby, aiming to provide insights into how assumptions about the language can lead to unexpected behaviors in code.

Key Points Discussed:

  • Introduction to the Topic: Jacobson introduces the idea that Ruby does not have strict rules akin to those in more rigid programming languages. He encourages viewers to embrace Ruby's flexibility as part of their programming experience.
  • Understanding "Rules": The speaker contrasts traditional rules with those that are more permissive, using the analogy of washing hands in a restaurant only if one chooses to do so. He posits that one can approach Ruby with a similar mindset.
  • Code Examples: Jacobson provides a simple code example with a Dog class, illustrating how expectations about method behavior, such as the function get_dog, can lead to assumptions that may not hold true. He demonstrates how unexpected outputs can arise when changing method implementations.
  • Limitations of Assumptions: He discusses how one cannot always rely on the expected behavior of methods, pointing out that methods may not operate as initially thought, leading to runtime errors and unpredictable results.
  • Learning Through Exploration: The presenter emphasizes the importance of testing and checking the behavior of code as critical practices in Ruby programming. Observing lower-level code can offer insights into Ruby's dynamic nature and the behavior of classes and instances.
  • Comparison with Rust: Jacobson draws a comparison between Ruby and Rust, a language known for its strict rules and safety features. He explains how Rust's expectations on behavior lead to fewer unexpected crashes and errors, advocating for a robust error-handling approach even within the flexible Ruby framework.
  • Software Development Practices: The speaker encourages the importance of error tracking, comprehensive testing, and code reviews in managing Ruby's flexibility to maintain code quality.
  • Final Thoughts: He concludes by reiterating the idea that while Ruby does not impose strict rules, understanding the language's dynamics and embracing its flexibility can lead to creative coding outcomes. Jacobson expresses his passion for Ruby, advocating for a balanced approach to writing Ruby code that is both creative and responsible.

Main Takeaways:

  • Ruby is more about guidelines than rigid rules, promoting exploration and creativity.
  • Assumptions in coding can lead to unexpected behaviors; thorough testing and understanding the language's nuances are essential.
  • Practices such as error handling and code reviews are crucial in ensuring code reliability, especially in a flexible environment like Ruby.
00:00:10 Hi everyone, I'm Max Jacobson. Hello!
00:00:17 I am here to talk about how there are no rules in Ruby. What does that mean? What am I talking about? I started thinking about this idea a few months ago when I learned something new about Ruby that really surprised me.
00:00:33 The effect it had on me was that I kept hearing this phrase come to mind: "There are no rules in Ruby." It just kept popping into my head, and I had to write it down. So that's what this talk is about.
00:00:54 To give you a bit of context, I'd like to thank the organizers for making this event accessible for me. This is my first conference talk, and I feel very honored to be here. Just a little bit about myself: I'm from New York. I wish I had gotten a haircut before I came because I hear they're filming this, and I'm a software engineer at Code Climate in New York.
00:01:22 I have some stickers if anyone wants any later. Today, we are going to discuss Ruby and how it works, along with our thought processes while writing Ruby code. I want to clarify that this isn't about whether Ruby is dead or anything like that; it's more of a personal journey as I've been learning about it.
00:01:46 This talk revolves around the awkward phases I’ve experienced along the way. I’ve certainly had plenty of awkward phases in my life— I was a little too into Blink 182 for a bit too long. So, this talk is about an awkward programming phase that I’m currently in, and I'm beginning to gain some perspective on it.
00:02:03 If anyone here is beyond it, I would love for you to tell me what it's like on the other side. An alternate title for this talk could be: "How I Learned to Stop Worrying and Love the Fact That There Are No Rules in Ruby." Before jumping into some code, I want to discuss what I mean by rules.
00:02:50 Here’s an example of a rule: "Employees must wash their hands before returning to work." I like that one. Now, in an alternate reality, we might say: "Employees may wash their hands before returning to work." The small difference matters—it’s not quite a rule if it’s not a ‘must’ statement.
00:03:14 I recently started feeling like Ruby might be more like the second sign rather than the first. What if working with Ruby felt like being in a restaurant that has the sign saying you may wash your hands? I'm going to elaborate on that thought and, before diving into code samples, give you a glimpse of my childhood dog, Milo.
00:03:38 When I learned that conference talks often feature photos of pets, I realized I don’t have one right now—so I wanted to share Milo, who was a Cairn Terrier, like the dog from The Wizard of Oz. He was a great example that I have used in my code examples.
00:04:07 Let’s jump into some code examples to understand better how we can think about rules in Ruby. This is a small program where we define a Dog class. Your programs might be longer or more complex, but we’ll start here. When we run it, as expected, we see that Milo is indeed a dog. We can execute this countless times, and it will always produce the same result.
00:04:51 Now, let’s make a couple of small changes. First, we’ll modify the code to have a method called get_dog that pulls a line out. This method would allow us to obtain a dog based on a name we provide. Next, we will hide the implementation of the get_dog method.
00:05:38 You could think of this as an example from the documentation of a library you’re reading. You see there is a get_dog method that returns a dog, but you might not always examine the source code. You incorporate these classes and methods into your projects while assuming they function correctly.
00:06:14 So, are there any guarantees about the things that we can trust to be true, given that we don’t know everything about our system anymore? Is there an implementation of this method that could cause the program to crash when run? It turns out our assumptions can be misleading.
00:06:56 For a moment, we can live by the rule: the method get_dog returns a dog. Who would trust that rule? Can I see some hands? It’s a trick question; I can’t really see anyone's hands here! Well, it seems that everyone’s hands are up.
00:07:39 However, this assumption isn't entirely true. Let’s inspect an implementation of get_dog that suggests this rule might not hold. It could raise an error instead of returning a dog, indicating that our understanding of the rule was flawed.
00:08:02 So, we might say it either returns a dog or raises an error, and we can consider this a new rule we can follow in our programs. I won't ask you to raise your hands again, but the idea is to contemplate whether this sounds reasonable to you.
00:08:26 I can see how that would make sense, yet it raises another question: what happens when a method doesn't perform as expected? Imagine our method instead returns a string that apologizes for not finding the dog, which could also lead to a run-time error.
00:09:05 Let’s reset to the state before we established our new rule. Perhaps we could still assume that when I have a dog instance and call the method to retrieve a name, I will get back the same name that we initialized it with, which is a comforting thought.
00:10:04 Let’s think about this for a moment though; is it satisfying? Well, maybe not, as defining a singleton method could alter that behavior, allowing it to return a completely different value instead.
00:10:36 This doesn't crash the program but, it does cause unexpected behavior. If you redefine the method to return 'sorry,' the output then becomes ‘sorry is a dog.’ I find that delightful for reasons I can't entirely articulate.
00:11:12 At this point, I don’t intend to suggest how you should write your code. It's about exploring the boundaries of what can happen in your Ruby systems and programs as we uncover how to verify behavior in classes.
00:12:09 Even if you check the source code, there are no guarantees you’ll see the full picture since boundaries are flexible and you can change instances or classes at runtime. Let's explore another example—the method name may seem true so far.
00:12:38 When I have a dog instance, it will have a method name because we initially defined an attribute reader. However, it’s entirely conceivable that it could have a method named differently, leading to a state where we crash due to an 'undefined method' error, even if all other dogs have a name attribute.
00:13:06 Let’s reset once more. We noted that while an instance lacks a readable method for 'name', the underlying instance variable still exists. This leads me to propose another rule: when I have a dog instance, it will have an instance variable ‘name’ defined internally.
00:13:32 Ruby provides methods to check whether this variable is defined and to retrieve its value. However, it’s not ironclad; just as you can undefined methods, you can also access and modify instance variables, removing them entirely.
00:14:03 It's becoming clearer that we can’t rely on the complete certainty of Ruby’s behavior in this instance. Now, consider a final example: when I create an instance of a dog, I assume the code in the initialize method must have run correctly.
00:14:49 However, it’s possible to allocate a dog instance without invoking the initialize method. It's a neat concept, but I’m still unclear on specific use cases. Let's briefly discuss what happens when we call 'Dog.new' versus 'Dog.allocate.'
00:15:16 When you call 'Dog.allocate', you receive an instance of the Dog class that hasn’t been initialized. This shift piqued my curiosity and made me reflect on how I approach my understanding of Ruby.
00:15:50 So, I want to clarify the nature of my explorations. I've reviewed some lower-level code to understand what actually transpires when initializing a new object—specifically in the Ruby source code where these mechanisms reside.
00:16:29 I also want to conclude by suggesting a rule that I feel comfortable stating at this point: when I have an instance of a dog, it's a good dog. That's the standard I feel works best for me.
00:17:05 I sincerely enjoy Ruby; I'm not criticizing it. We’re discussing how we think about the workings of our systems. Due to all these possibilities, we must establish more robustness in our code before deployment.
00:17:51 Having error trackers is one way to ensure we can identify issues when they arise. This helps in understanding the unknown possibilities of crashing mechanisms and fosters resilient code.
00:18:36 You should also write comprehensive tests to build your confidence and define expectations for how your system behaves. Even essential practices like conducting code reviews will ensure you catch potential issues.
00:19:13 Learning to comfortably read through source code is a valuable skill. This applies to your code, your teammates' code, the libraries you depend on, and even Ruby itself. This exploration of understanding brings clarity.
00:19:56 Now, let’s venture into my personal programmer journey for a brief moment. I started learning Ruby around five years ago at Flatiron School’s Ruby on Rails program. By 2015, I began exploring Rust.
00:20:43 In 2017, I felt a sense of panic as I navigated rapid changes. What is Rust? Rust is a systems programming language known for its speed and safety against segmentation faults and guarantees thread safety.
00:21:17 It had its 1.0 release in 2015 and was crowned the most loved programming language in the Stack Overflow Developer Survey of 2016 and 2017. The praise it receives might reflect its stringent rules.
00:21:53 In contrast, Ruby lacks such strict rules. I've rewritten a simplified version of the dog example in Rust, showing them in a similar structure—defining a struct dog instead of a class.
00:22:27 Upon running it, we get the same output, but can we define the rules we intend to follow? For instance, in this Rust program, the function return is expected to be a dog.
00:23:09 If we attempt to alter it to return a string instead, the compiler gives us an explicit error, indicating a mismatch in the expected return type. It’s reassuring to know that certain invalid paths are blocked outright.
00:23:36 Check this out: If we try writing an immutable field in Rust and reassign a different string, we also receive an error. It's clear that Rust promotes immutability by default, instilling confidence in your data integrity.
00:24:10 Let’s move forward and explore how Rust handles error management differently. In general, Rust's methods require you to consider how to handle potential errors upfront rather than waiting for the circumstances to change unexpectedly.
00:25:02 This provides a familiar structure where results can return either a successful response or an error case to be addressed. Effectively, you’re formally defining what should happen if things go wrong.
00:25:44 Much like Ruby's file-reading functions, you must prepare for different potential outcomes from the results. If we attempt to manage this in Ruby, we can employ tries and exceptions, but often that part is not entirely clear.
00:26:12 Conversely, in Rust, “Result” encapsulates success or failure, forcing a user to consider what to do next if the outcome is unsuccessful. This structure promotes resilience by establishing firm pathways for defining outcomes.
00:26:58 Ultimately, transitioning back to Ruby, while it doesn’t inherently lend itself to automatic error management like Rust, it still allows for thoughtful precautions during error handling when structuring code.
00:27:34 After exploring Rust, I've come to realize that although Ruby accommodates a broad range of writable code, it does not fully prevent the potential for crashing. What we must be aware of is how any method could break.
00:28:16 However, I think that ultimately we retain the directive to ensure we're clear about expectations. Allowing for open-ended determinations around values ultimately leads to a less desirable program.
00:29:05 To summarize, it’s essential to be careful that even with all of Rust's rigidity, we must still diligently manage how we handle the flow. Learning to determine what can behave unexpectedly in Ruby can strengthen your programming.
00:29:59 With that insight in mind, I’ll wrap up. There are a variety of skills we can build throughout our journeys. Our paths continually yield insights into others' experiences as we learn and grow in the Ruby community.
00:30:26 As I reflect on the very concept of rules in programming languages, it is essential to find joy in the process and utilize rules as tools for enhancement rather than rigid barriers.
00:30:48 Ultimately, if Ruby's inherent flexibility promotes creativity and exploration, then embracing the notion that there are no strict rules could be a boon for programmers navigating their journeys.
00:31:16 Learning that you can make the rules work for you instead of against you fosters a positive mindset. So, thanks for your time while I’ve been discussing the beauty and freedom found within Ruby.