00:00:12.240
Thank you for coming. I'm Sandi Metz. I was a programmer for thirty-five years and I am a woman of a certain age. I wrote a book a couple of years ago, and when it came out, it felt like a bomb went off in my life. I had to quit my day job because I was so busy doing other things.
00:00:22.960
Because I quit my day job, I needed a way to make a living, and people kept asking me to teach. So, I finally broke down and agreed to teach short object-oriented design classes. Then, I had to create a curriculum, which was incredibly challenging.
00:00:38.840
I sat down to figure out what I thought were the most important lessons I could teach in three really intense days. When I created the curriculum, I believed that the lessons were unrelated—good things, yes, but not the same idea. After teaching that course for the last year and a half, I have finally come to realize that I was really teaching one simple idea all along.
00:01:05.439
Today's talk will cover everything I've learned in the last year and a half in just thirty minutes. This will involve a lot of slides. Based on my calculations, I will change slides approximately every four and a half seconds. If you want to change seats now, it’s not too late to move over to the sides. You've been warned!
00:01:30.720
This talk is divided into four parts, and it will build up from the bottom. The first part is inspired by the realization I had after my book was published: I found that I had a different idea about objects compared to many people in our community. I was curious about why that was, and upon reflection, I realized it was because I had been influenced by Smalltalk.
00:02:00.000
I've been writing Ruby since 2005, but I had not written Ruby for as long as I had written Smalltalk. I say 'infected' because it's humorous, but I feel inoculated by Smalltalk. I want to show you just one small aspect of Smalltalk today that will clarify how I think about objects.
00:02:14.400
In Ruby, there's a method called 'send' that allows you to send a message to an object. You might not be familiar with it, but it invokes the method named in the symbol passed to it. This mechanism illustrates the fundamental truth about Ruby: at the lowest level, we are just sending messages to objects.
00:02:38.760
For example, consider the expression '1 + 1.' What Ruby does behind the scenes is send a message to the number one, and the operation is just syntactic sugar for message sending. This special syntax is what makes Ruby friendly; its surface appearance resembles math, but it's actually about sending messages to objects.
00:03:03.959
Now, when I send '1 == 1,' what I receive back is 'true,' an instance of the true class. This class knows various pieces of information, and for the sake of this talk, let’s simplify the concept and treat 'true' as just an object that we communicate with through message sending. Similarly, we can treat 'false' in a comparable way.
00:03:36.840
When I first arrived in Ruby from Smalltalk, the fact that 'true' is treated as an object wasn’t surprising to me. However, I was taken aback by Ruby's specific syntax for handling 'nil.' It's baffling within an object-oriented language that there's a unique syntax for 'nil,' just as there's for boolean values.
00:04:04.360
In Smalltalk, there are only a few keywords, enforcing a clear and straightforward message-sending structure. Yet, when I encountered Ruby, I found it had this additional special syntax concerning boolean conditions, which seemed unnecessarily complex to me.
00:04:29.280
In Ruby, we evaluate an expression, and based on the result—whether it evaluates to true or false—we execute one of two code blocks. This introduces a type check that, in an object-oriented context, feels counterintuitive. Instead of making type checks, I prefer to simply send messages to objects without concerning myself with their types.
00:05:08.680
If you transitioned to Ruby from a procedural language, writing extensive conditions might feel customary. However, the presence of keywords in Ruby encourages a procedural mindset, which hinders learning object-orientation and taking full advantage of the power of objects.
00:05:46.200
To illustrate how unnecessary this keyword is, let's pretend we change Ruby to have Smalltalk-like syntax for conditions: we could implement our own logic for handling true and false messages. When we break down this logic, we realize we really can send messages directly without needing this special syntax.
00:06:13.240
The core point I want to convey is that we should focus on sending messages rather than considering the types of objects we work with. In object-oriented design, the message-centric approach helps maintain cleaner code. Once we adopt this mindset, we can simplify our interactions with boolean values.
00:06:49.679
Let’s say we have an animal class with a factory method 'find' that returns an object or nil if not found. If you’re handling an array and calling 'animal.find' on each of them, you might receive nil, leading to a situation where your application could crash due to a nil error when messages are sent.
00:07:22.520
My point here is that while nil can sometimes be treated as nothing, when we send a message to a nil object, it represents something, and we need to handle it properly in our code to avoid crashes. Often, we add cumbersome conditions in our code to check for nil values, which results in a lot of extra complexity and verbosity.
00:07:43.440
This ranges from simple checks like saying 'no animal' when there's a nil value to deeper nested conditions that complicate our logic. As Ruby programmers, we want to simplify this mess. Instead of these lengthy conditionals, we should leverage Ruby’s truthy functionality, realizing that nil can also stand for something valuable in our design.
00:08:26.160
Furthermore, if you're using the 'try' method, it may seem convenient, but it tends to convolute the underlying logic. It's simply a shorthand that dusks over the fundamental principle of messaging. That said, we need to foster an environment where we focus on sending messages instead of complicating our logic with type checks.
00:09:01.600
Now, consider that conditions reproduce; if you spread that string throughout your code base, changing that one part can lead to extensive changes across your system, leading to what’s known as shotgun surgery. I despise these conditions and wish to promote a message-centric approach to coding.
00:09:33.520
The core issue here is that sometimes you might receive an object that has an attribute you expect, but at other times, you won't. When you receive objects that conform to different APIs, you risk crashing code that's not prepared to handle those discrepancies. What we aim for is the establishment of a null object, or a default placeholder that stands in when an object is missing.
00:10:05.560
This design pattern is known as the null object pattern, which was beautifully termed by Bruce Anderson as the 'active nothing.' Implementing this pattern within your code results in a clear improvement, allowing you to remove conditions while simplifying your logic.
00:10:36.800
Now that we've established a null object, we can create behavior without owning various responsibilities. By wrapping untrustworthy external APIs in a reliable object, we can manage conditions in a single place while interacting with multiple objects in a uniform manner.
00:11:06.840
The lesson I’ve extracted from the past 18 months is that the null object pattern is just a small instance of a grander abstraction. This talk builds towards that abstraction, and as a stepping stone, let’s switch examples to further illustrate this concept.
00:11:44.800
Consider the tale of 'The House That Jack Built.' It’s a cumulative story where each line builds on the previous one. If I were to create Ruby code that generates this tale without duplicating strings, I would likely use an array to store the various bits and have methods to manipulate those strings accordingly.
00:12:28.360
To produce the lines of the tale, I would iterate through my array, generating each line dynamically. However, let's say the spec changed. Now, they want to introduce a 'random house' where the order of lines is randomized rather than simply recited in sequential order.
00:13:12.560
This alteration necessitates a degree of refactoring in my existing design, as I need to ensure nothing from the original house class is broken. Moreover, I must implement a solution without using any conditional statements, inspiring me to consider inheritance to solve this dilemma.
00:14:01.040
By overriding the existing data method with one that shuffles the order, I can create a new subclass that successfully fulfills this requirement. This subclass needs to be deftly engineered to ensure that original functionality remains intact.
00:14:38.639
The next task is to implement an 'echo house,' a variant where each line gets duplicated as it goes in for extra emphasis. This requires additional refactorings since the methods will undergo various responsibilities, making it difficult to change without careful thought.
00:15:14.560
Eventually, I reach a point where extensive code duplication is occurring simply to manage this echo effect. I'm forced to reevaluate my approach, recognizing the importance of keeping code clean and free of redundancy.
00:15:51.960
To counteract the challenges of implementing additional variants of 'house,' I realize that inheritance creates complexity that can become unmanageable. Introducing a solution based on composition, where I define roles like 'order' and 'format,' becomes paramount.
00:16:39.560
By injecting order and format objects into the processes I need to maintain, I can effectively streamline the structure of my code base and avoid cumbersome inheritance hierarchies. This leads to a more elegant extension of functionality without introducing intricate conditional checks.
00:17:22.760
The final message I want to convey is the inherent value in finding useful abstractions within your codebase. The underlying object-oriented principles allow us to see beyond mere function, revealing deeper relationships and structures that empower robust design.
00:18:05.960
While navigating conditions might yield temporary solutions, I encourage you to embrace principles like the null object pattern and dependency injection. Doing so will yield greater flexibility in coding practices and help you avoid pitfalls associated with conventional inheritance methodologies.
00:18:54.720
Our journey through object-oriented design should focus on clarity and adaptability. Remember to prioritize communication between objects and avoid assumptions tied to specific class hierarchies; this agility enhances the quality of your code. Enough of this back-and-forth messaging—let's make something beautiful!
00:19:39.680
So, as we explore the boundaries of object-oriented programming, consider that every small detail in your design—no matter how seemingly insignificant—is actually foundational. Our challenges, whether big or small, contain important lessons and opportunities for growth, and those lessons will serve you well.
00:20:16.800
In shared experiences through code, we uncover truths that elevate our capabilities. Whether it's navigating the intricacies of inheritance or reaping the benefits of composing our designs through abstraction, the hidden gems emerge when we practice intentionality and diligence.
00:20:49.720
As I conclude my talk, I hope you leave with a renewed sense of wonder and possibilities that lie within code. Remember that every 'nothing' holds the potential for 'something,' and everything can become part of our evolving narrative as programmers and designers.
00:21:21.200
Thank you for listening. I’m Sandi Metz, and I wrote that book. Please sign up to find out more about my upcoming projects, including a new book I am writing. I rarely teach public courses, but I will have offerings in New York City, so sign up for my website if you're interested in that as well as stickers and tattoos. Come find me later, and I hope you enjoyed this presentation! Thank you!