358: Class Methods
The Bike Shed - Podcast tekijän mukaan thoughtbot - Tiistaisin
Kategoriat:
Inspired by a Slack thread, Joël invites fellow thoughtbotter Aji Slater on the show to talk about when you should use class methods and when you should avoid them. Are there particular anti-patterns to look out for? How does this fit in with good object-oriented programming? What about Rails? What is an "alternate constructor"? What about service objects? So many questions, and friends: Aji and Joël deliver answers! Backbone.js collections Query object Rails is a dialect Meditations on a Class Method Why Ruby Class Methods Resist Refactoring Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined by fellow thoughtboter Aji Slater. AJI: Howdy. JOËL: And together, we're here to share a little bit of what we've learned along the way. So, Aji, what's new in your world? AJI: Yeah, well, I just joined a new project, so that's kind of the newest thing in my day-to-day work world. I say just joined, but I guess it was about a month ago now. I'm on the Liftoff team at thoughtbot, which is different than the team that you're on. We do more closer to greenfield ideas and things like that. So there's actually not much to speak about there in that project just yet. Rails new is still just over the horizon for us. So I've been putting a lot of unused brain cycles toward a side project that is sort of a personal knowledge base concept, and that's a whole thing that I could probably host an entire podcast about. So we don't have to go too deep into my theories about that. But suffice it to say I've talked to some other ADHDers like myself who find that that space is not really conducive to the way that we think and have to organize ourselves and our personal knowledge stores. So sort of writing an app that can lend itself to our fast brains a little bit better. JOËL: Nice. I just recently recorded an episode of this podcast talking a little bit about note-taking approaches and knowledge-base systems. So, yeah, it's a topic that's very much top of mind for me right now. AJI: Yeah, what else is going on in your world? JOËL: I'm based in New England in the U.S. East Coast, and it is fall here. I feel like it happened kind of all of a sudden. And the traditional fall thing to do here is to go to an orchard and pick apples. It's a fun activity to do, and so I'm in the middle of planning that. Yeah, it's fun to go out into nature, very artificial space. AJI: [laughs] JOËL: But it's a fun thing to do every fall. AJI: Yeah, we do that here too. There's an orchard up north of us where my wife and I live in Chicago that we try to visit. And Apple Fest in Lincoln Square is this weekend, and we've been really looking forward to that. Try another time at making homemade hard cider this season, I think, and see how that goes. JOËL: Fun. When you say another time, does that mean there was a previous unsuccessful attempt? AJI: Yes. Did the sort of naive approach to it, and there is apparently a lot more subtlety to cidermaking than there is home-brew beer. And we got some real strong funk in that cider that did not make it necessarily an enjoyable experience. Like, it worked but wasn't the tastiest. JOËL: So it got alcoholic. It was just terrible to drink. AJI: Yeah, I would back that up. JOËL: So recently, at thoughtbot, we had a conversation among different team members about the use of Ruby class methods, when they make sense, when they are to be avoided. What is their use case? And different people had different opinions. So I'm curious what your take on class methods are. When do you like to use them? AJI: Yeah, I remember those conversations coming up. I think I might have even started one of those threads because this is something that comes up to me a lot. I'm a long-time listener, first-time caller to The Bike Shed. [laughs] I can remember awaiting new episodes from Sage and Derek to listen to on my way to and from my first dev job. And at one point, Sage had said, "Never put your business logic in something that you can't call .new on." And being a young, impressionable developer at the time, I took that to heart, and that seems something that just has been baked in and stayed very truthful to me. And I think one of the times that I asked that and got some conversation started was I was trying to figure out why did I feel that, and like, why did they say that? And I think, yeah, I try to avoid them. I like making instances of things. What is your stance on the Class Method, capital C, capital M? JOËL: I also generally avoid them. I have sort of two main scenarios that I like to use class methods, first is as an alternate constructor. So new is effectively a class method that's built into Ruby's object model. But sometimes, you want variations on your constructor that maybe sets values by default or that construct things with some slightly different inputs, things like that. And so those almost always make sense as class methods. The other thing that I sometimes use a class method for is as an alias for newing up an instance and then immediately calling an instance method on it. So it's just a slightly shorthand way to call some code. AJI: That's usually been my first line defense of when there's someone who might feel more comfortable doing class methods that sees me making an instance and says, "Well, you don't need an instance, just make a class method here because it'll get too long if you have to .new and then dot this other thing." And so I'll throw in that magic little trick and be like, here you go. You can call it a class method, and you still get all the benefits of your instance. I love that one. JOËL: Do you feel like that maybe defeats the purpose? In terms of the interface that people are using, if you're calling it a class method, do you lose the benefits of trying to do things at the instance level instead? Or is it more in the implementation that the benefits are not at the caller level? AJI: I think that's more true that the benefits are at the instance level, and you're getting all of that that goes along with it. And you're not carrying along a lot of what I see as baggage of the class method version, but you're picking up a little bit of that syntactic sugar. And sometimes it's even easier just to conceptualize, especially in the Rails space because we have all of these different class methods like, you know, Find is one I'm sure that we use all the time to call it on a class, and we get back an instance. And so that feels very natural in the Rails world. JOËL: I think you could make an argument that that is a form of alternate constructor. It's a class method you call to get an instance back. AJI: Yeah, absolutely. JOËL: The fact that it makes a background request to the database is an implementation detail. AJI: For sure. I agree with that. I had a similar need in a recent project where the data was kept on a third-party API. So I treated it the same way as, instead of going out to the database like ActiveRecord does, made a class method that went off to the API and then came back and made the object that was the representation of that idea in our application. So, yeah, I wholeheartedly agree with that. JOËL: So in Rails, we have the scope keyword, which will run some query to get a collection of records. But another way that they're often implemented is as class methods, and they're more or less interchangeable. How do you feel about that kind of use of class methods on an ActiveRecord object? Does that violate some of the ideas that we've been talking about? Does it sort of fit in? AJI: I think when reaching for that sort of need, I sort of fall into the camp of making a class method rather than using a scope. It feels a little less like extending some basic Rails functionality or implying that it's part of the inherent framework and makes it a little more like behavior that's been added that's specific to this domain. And I think that distinction comes into my thinking there. I'm sure there are other reasons. What are your thoughts there? Maybe it'll spark an idea for me. JOËL: For me, I think I also generally prefer to write them as class methods rather than using the scope keyword, even though they're more or less the same thing. What is interesting is that, in a way, they kind of feel like alternate constructors in that they don't give you an instance; they give you back a collection of instances back. So if we bend the rules a little bit...these are not hard and fast rules but the guidelines. If we bend the guidelines a little bit, they kind of fit under the general categories for best uses of class method that we discussed earlier. AJI: Yeah, I can definitely see that. I tend to think, or at least I think when you had first brought up the term of alternate constructors, my first thought was of one instance; you ask for a thing, and it gives you this thing back. But it's the same sort of idea with that collection because you're not getting just one instance; you're getting many instances. But it's the same kind of idea. You've asked the larger concept of the thing, the class, to give you back individuals of that class. So that totally falls in line with how I think about acceptable uses of these class methods the way that we've been talking about them. JOËL: Rails is something really interesting where a lot of the logic that pertains to a single item will live at the instance level. And then logic that pertains to a group of items will live at the class level. So you almost have like two categories of operations that you can run that semantically live either at the class or the instance level. Have you ever noticed that separation before? AJI: I think that separation feels natural to me because I came into programming through Rails. And I might have been colored in my thinking about this by the framework. The way that I conceptualize what a class is being sort of this blueprint or platonic ideal of what an individual might be and sort of describing the potential behaviors of such an individual. Having that kind of larger concept be able to work across multiple instances feels, yeah, it feels sort of natural. Like, if you were to think about this idea of a chair, then if you went in and modified what a chair is to mean, then any chair that you asked for later on would kind of come with that behavior along with it. Or if you ask for several chairs, they would all sort of have that idea. JOËL: I think similar to you; I had that outlook on that's almost like a natural structuring of things. And then, years ago, I got into the hot, new JavaScript framework that was Backbone.js. And it actually separates...it has like a model for individual instances, and then a separate kind of model thing for collections. And that kind of blew my mind. But what was interesting, then, is that you effectively have instance methods that can deal with all things collection-related, any sort of filtering, any sort of transformations. All of those are done, which you have an instance of a collection, basically, that you act on. And I guess if we were trying to translate that into Rails, that's almost like the concept of a query object. AJI: Hmm, it's sort of an interesting way to think about that. And Backbone, I feel like I did a day of that in bootcamp. But it has been some time, so I'm not sure that I've worked with that pattern specifically. But it does sort of bring up the idea of how much do you want to be in one model class? And do you want it to contain both of these concepts? If you have a lot of complex logic that is going to be dealing with a collection, rather than putting that in your model, I think I would probably reach for something like a service object that is going to be specifically doing that and sort of more along that Backboney approach maybe like a query object or something like that. JOËL: Interesting. When you use the term service object, do you mean something that's not a Rails model, just in general? Or are you talking specifically about one of these objects that can respond to call and is... I've heard them sometimes called Command objects or method objects. AJI: Yeah, that's an overloaded term certainly in the real space, isn't it? Service object, and what does that mean? I think generally, when I say it, I'm meaning just a plain, old Ruby object like something that is doing its one thing. You're going to use it to do its implementation details. They're all kind of hidden behind in private methods and return you something useful that you can then plug into what you were doing or what you need going on in some other place in your app. So it, to me, doesn't imply any specific implementation of, like, do you have call? Do you use it this way? Do you use it that way? But it's something that's kind of outside of it is either a model, a view, a controller, and it encapsulates some kind of behavior. So whether that, like we're saying, is a filtering or, you know, it's going to wrap that up. JOËL: I see. So, for you, a query object would be a service object. AJI: Yeah, I think so. You know, maybe this is one of the reasons why I generally don't like the overuse of the term service object in our space. I don't know if that's a hot take, and I'm going to get emails for this. But -- JOËL: Everybody send your angry tweets @Aji. AJI: Yeah, do it to @Aji on Twitter because I've been trying to get that three-letter handle for years. No, but if you want to talk to me, I'm @DoodlingDev. But, yeah, certainly, it does feel sometimes like an overloaded term, and I just want to go back to talking about plain, old Ruby objects. JOËL: So, service object is definitely an overloaded term. It's used for a lot of things. One thing that I've often seen it referring to are objects that respond to call. And just to keep away the confusion, maybe let's call them Command objects for the purposes of this conversation. AJI: Sounds good. JOËL: I commonly see them done where the implementation is done with a class method named call. Sometimes it delegates to an instance that also has call. Sometimes it's all implemented as a class method. How do you feel about that pattern? AJI: I don't mind the idea of a thing that responds to call. It, in a way, sort of implies that the class is sort of named as an action, which I don't like. It has an er name, and that kind of has a class named as a pattern. And that always sort of bugs me a little bit. But what I hope for when I open up one of those sorts of classes or objects is that it's going to delegate to an instance because then you're, again, picking up all of those wonderful benefits of the instance-level programming. JOËL: You keep mentioning the wonderful benefits of instance-level programming. What are some of those benefits? AJI: One of the ones that sort of strikes me most visibly or kind of viscerally when I see it is that they're very easy to understand. You can extract methods pretty easily that don't turn into kind of clumsy code of a bunch of different class methods that all have four arguments passed in because they're all operating on the same context. And when you're all operating on the same context, you have really a shared state. And if you're just passing that shared state around, it just gets super confusing. And you get into the order of your arguments, making a big impact on how you are interacting with these different things. And so I think that's sort of the first thing that comes to mind is just visually noisy, which for me is super hard to get my head around, like, well, how am I supposed to use this thing? Can I extend it? JOËL: Yeah, I would definitely say that if you have a group of class methods that all take, commonly, it's the first argument, the same piece of data and tries to operate on it, that's probably a code smell that points to the fact that these things want to be an instance that lives around it. This could be a form of primitive obsession if you're passing around, let's say, a hash, all of these, and maybe what you really want is to sort of reify that hash into an object. And then all these class methods that used to operate on the hash can now become instance methods on your richer domain object. AJI: Yeah. What do you say to the folks that come from maybe a more functional mindset or are kind of picking up on the wave of functional programming that's out there in the ethos that say that you've got a bunch of side effects when you don't have everything that your method is operating on, being passed on or passed in? JOËL: I think side effect is a broad term. You could refer to it as modifying the internal state of an object. Technically, mutation is a side effect. And then you have things like doing effects out in the outside world, like making an HTTP query, printing to the screen, things like that. I think those are probably two separate concepts. Functional programming is great. I love writing functional code. When you're writing Ruby, Ruby is primarily an object-oriented language with some functional aspects brought in. In my opinion, it's very, you know, a great combination of the two. I think they've gotten the balance well so that the two paradigms play nicely together rather than competing. But I think it's an object-oriented language first with some functional added in. And so you're not going to be, I mean, I guess you could; there is a way to write Ruby where everything is a lambda or where everything is a class method that is pure and takes in inputs. But that's not the idiomatic way to write Ruby. Generally, you're creating objects that have some state. That being said, if an object is mutating a lot of global state, that's going to become problematic. With regards to its internal state, though, because it is very much localized and it's private, nobody else gets to see it; in many ways, an object can mutate itself, and that chain stays pretty local. AJI: Yeah, absolutely. You've tripped onto another one of my favorite rabbit holes of idiomatic code, and, like, what does that mean, and why should we strive for that? But I absolutely agree that when Ruby is written to conform to other paradigms that aren't mostly object-oriented is when it starts to get hard to use. It starts to feel a little off. Maybe it has code smells around it. It's going to give me the heebie-jeebies, whatever that might mean for you or for different developers. I think we all have our things that are sort of this doesn't feel right. And you kind of dig into it, and you can sort of back that up. And whenever Ruby starts to look like something that isn't lots of little objects sending messages, is when I start to get a little on edge, maybe. JOËL: It is worth, I think, calling out the fact that Ruby is a very expressive language. And there are effectively many...you could call them dialects of it. You have sort of your pure sort of OO approach. You have what's typically written in Rails, which has some OO things. But Rails is also, in many ways, it's very DSL-heavy and, in some ways, very class method-heavy. So writing Rails is sort of its own twist on Ruby. And then, some people will try to completely retrofit a functional approach onto Ruby, and that's also a way that some people like to write their code. And some of these, you can't necessarily say they're not valid, but they're not what you'll mostly see in the wild. And they're not necessarily the approach that I would recommend. AJI: Yeah, that's the blessing, and the curse of both programming in general and such an expressive language like Ruby is that there are many different valid ways to do it. And what are your trade-offs going to be when you make those choices? I think that falls kind of smack dab into that idiomatic conversation. And it comes up for me, too, as a consultant because I try to tend towards that idiomatic, those common patterns and practices because I'm not going to live with this code forever. I need to hand this off. And the closer it is to what you might see out there in the wild more commonly, the easier it will be for the next Ruby developer to come pick it up and extend it. JOËL: So you'd mentioned earlier some of the benefits of instance programming. One of the things that I find is maybe a little bit weird when you go heavily into the class method approach is that there is only one instance of the class, and it is globally available. AJI: Are you talking about a singleton there? JOËL: Yes. And, in fact, your class is effectively a singleton, potentially with globally mutable state. I hope not, but potentially with all of the gotchas and warnings that that entails. And so, if you think of your user instance, you need a reference to it, and there can be multiple of them, and you can call methods on them. If everything is happening at the class level, there is a single user class in memory shared by anyone who wants to use it. It's globally accessible. You can all call methods on it. Yeah, in many ways, it does act like a singleton. AJI: And let's not even get into the Ruby chestnut of everything's an object. So it is an instance of a class in and of itself. JOËL: Yes. AJI: But, absolutely, it can start to act that way. But the singleton it's enshrined in the Gang of Four book of patterns. Like, so what's wrong about a singleton? I hope you can understand over the airwaves the devil's advocate that I'm playing here. [laughs] JOËL: Yes. There are little horns that have sprouted on your head right now. I think part of the problem with singletons is that, generally, they are globally accessible. There's the problem of global mutable state again. There was a time, I think, when the OO community went pretty wild with singletons, and people realized that this was not great. And so, over time, a consensus evolved that singletons are a pattern that, while useful, should be used rarely and in moderation. And a lot of warnings have been shared in the community, like, be careful not to overuse the singleton pattern or don't build your system out of singletons. And maybe that's what feels so weird about a system that's built primarily in terms of class methods for me is that it feels like it's built out of singletons. AJI: Yeah. When I think of object-oriented programming, I kind of fall back to maybe one of the ideals of it is that it represents the world more accurately or maybe more understandably. And that sort of idea doesn't fit that paradigm, does it? If you're a factory that is making widgets, there's not the one canonical widget that all of your customers are going to be talking to and using. They are going to each have their own individual widgets. And those customers can be thought of like the consumers of your methods, your objects. JOËL: The idea being the real-world thing you're simulating normally, there are multiple actors of every type rather than a single sort of generic one that stands in for everybody. AJI: If this singleton is going to be your interface or the way that you interact with each of these things that are conceptually different, like a user or something like that, then differentiating between which user becomes a lot harder to do. It takes a lot more setup and involved process in referring to this user when and that kind of thing and creating the little instances. Then you've got more kind of direct reference to a single concept, a single individual. JOËL: So what you've described is a very sort of classic OO mindset. You find the data and the behaviors that go together. You try to oftentimes simulate the world, model it in terms of actors that give and receive messages. In many ways, though, I think when you're building a system out of class methods, you're thinking about the world in an almost different paradigm. In many ways, it feels almost procedural. What are the behaviors that need to happen in my app? What are the things that need to be done? You'd mentioned earlier that oftentimes these classes or the methods on them will end up with E-R; they're all verbs. You have a thing-doer, a thing executor, thing manager. They all do things rather than having domain concepts extracted and pulled out. Would you say that that feels somewhat procedural to you as well? AJI: Yeah. I think a great way to divide it is the way that you have right there; it's these sorts of mindsets. Do you have collections of things that have behaviors, or do you have collections of behaviors that might refer to things? And where you're approaching the design of a system, either from that behavior side or from that object side, is going to be a different mindset. Procedural being more focused on that kind of behavior and telling it what to do rather than putting... I think this is probably a butchered Sandi Metz example, but putting your roommate who hates cats and a cat that doesn't want its tail stepped on in one room, and eventually, things will happen accordingly. And those two mindsets are going to end up with very different architectures, very different designs, very different ways of building these applications that we make. And, again, does that come back to...Ruby, potentially to a lesser extent but still in the same camp, is object-oriented language, and it sort of functions best when considered and then constructed in that mindset. And I often wonder sometimes if language developers and language designers make anti-patterns sort of purposefully awkward to use. Like, if you want to hide a lot of class methods, you can do the class shovels self version of things or have private_class_method littered all the way through your file. And it seems to me like that might be a little bit of a flag that, like, hey, you're working against the system here. You're trying to make it do a thing that it doesn't naturally want to do. JOËL: Yeah, because you'd mentioned this private_class method thing because, by default, it's hard to get class methods to be private. You have to use a special keyword. You can't just write private in the class and then assume that the methods below it are going to be private because that does not apply to class methods. AJI: Exactly. And that friction to making an object that has a smaller interface, that kind of hides its implementation, seems as though it's a purposeful way that Ruby itself was designed to maybe nudge us, developers, into a certain way of working or suggesting a certain mindset. JOËL: There's a classic Code Climate article titled Class Methods Resist Refactoring. And it mentions different ways that when you're relying heavily on class methods, it's harder to do some of the traditional refactors things like just extract method because it is clunkier because you can't have private methods as easily. You can't share state, so you have to thread variables through. I guess, technically, you can share state with things like class variables and class instance variables, but if you do that, you will probably be very sad. AJI: [laughs] Yeah, you're opening yourself up to a whole world of hurt there, aren't you? And, yeah, you're opening yourself up to a whole world of hurt there with that, aren't you? Sort of sharing data so dangerously around your app. JOËL: So I'm a big fan of test-driven development. And one of the things that TDD believes in is that test pain should help guide the design of your system and that, generally, things that are easier to test are better designed. AJI: Yeah. JOËL: It's often easier to test class methods because they are globally available singletons. I can easily stub a class. Whereas if I need to stub an instance, I need to do some uglier things like stub any instance of or stub the constructor to return a double, or do some other kind of dirty tricks like that. Does that mean that TDD would prefer a class method-based approach to writing code? AJI: I think that a surface-level reading of that might say that it does. And I think that maybe the first pass on things, if you're thinking about I want to get this thing done that's right in front of me right now and just move forward, might kind of imply that. But if you start to think about or have come back to something that was implemented in that way, anytime that sort of behavior is going to grow or change, then it's going to start to...the number of backflips that you have to do become a lot more complicated and a lot higher when you've got class methods. Because I find that, yes, you might have to stub out or pass in a created object or something like that. But if you've got a class method, especially if it is calling other class methods inside it, then all of a sudden, you have in your test this setup that looks completely unrelated to anything that you're running and testing, that you have to have all of this insight or knowledge of what those classes are doing just to set up your test framework before you can even run that. Another thing that is looked to as an axiom when writing tests that can imply this class approach is that you shouldn't change your code just for the test. If you're doing dependency injection or something like that, passing around little objects, then you're making your code more complicated to make your tests look a certain way. JOËL: That's interesting. So maybe I'm reacting to some test pain by trying to change my tests first. So I'm trying to deal with some collaborators, and it is tricky to do. And so I decide, well, the thing I want to do is I want to reach for stubbing. But then that's hard to do because it's instances. So in order to make already that compromise in my test work better, now I change the code to be nicer for the test to use mostly classes because those are global. Whereas maybe the correct path to take initially is, say, oh, there isn't test pain here because I'm trying to isolate an object from its collaborators. Maybe we need to pass an object in as an argument rather than hard coding it inside the class. AJI: Yeah, absolutely. JOËL: So I guess you follow the test pain, but maybe the problem is that you've already kind of gone down a path that might not be the best before you got to the point where you decided that you needed a class method. AJI: And I think that idea of following the test pain can be, again, there are only shades of gray; there is no black and white. It can be sort of taken in a lot of different ways. And the way that I think about it is that test pain is also sort of an early warning sign that there's going to be pain if you want to reuse this class or these behaviors somewhere else. And if it was useful somewhere, it's likely it's going to be useful in another place. And there are many different kinds of tests pain. The testing is a little easier with a class method because you're not stubbing out any instance of. You're just stubbing; really, what's the difference between stubbing out any instance of or stubbing out the class? Is that just a semantic difference? Is that -- JOËL: Because someone on the internet said that stubbing any instance of is bad. AJI: Ooh, right, the internet. I should have read that one. The thing that you can do with passing around instances or sending messages to instances as you do when you're calling a method is that you can easily swap in a different object if you need to stub it. It's similar to how you can change the implementation under the hood of an object and pass in an object that responds to the same messages and kind of keep moving forward with your duck typing. If you can go into your tests and pass it sort of an object that's always going to return a thing...because we're not testing what that does; we just need a certain response so that we can move forward with the pathway that is under test. You can do that in so many different ways. You could have FactoryBot, for instance, give you a certain shape of a thing. You can create a tiny, little class right there in your tests that does something specific, that can be easily understood what's going on under the hood here. And instead of having to potentially stub out or create all of these pathways that need to be followed that are overwriting logic that's happening in different class methods or different places otherwhere in the application, you can just pass in this one simplified thing to keep your tests sort of smaller and easier to wrap your head around all in just one go. JOËL: I think what I'm getting here is that when you design your code around instances, you're more likely to build it in a modular way where you pass objects to other objects. And when you build your code using class methods, you're more likely to write it in a hard-coded way. Because you have that globally available class, you just hard-code it and then call it directly rather than passing things in. And so things end up more coupled and, therefore, high coupling leads to more test pain. AJI: Yeah, I think you've really kind of hit on something here that the approach of using class methods is locking that class into kind of a single context or use case. Usually, it is this global thing that is this one way, and that's even kind of backed up by the fact that class methods are load-time logic instead of run-time logic. And it really kind of not only couples but it makes it more brittle and less amenable to kind of reuse. JOËL: That's a really interesting distinction. I often tend to think of runtime versus load time in terms of composition versus inheritance. Composition, you can combine objects together at runtime and get behaviors built on the fly as the code is executing, whereas inheritance sort of inherently freezes you into a particular combination of behaviors at the time of loading the code. It's something that the programmers set up, and so it is much less flexible. And that is one of the arguments why the Gang of Four patterns book recommends composition over inheritance in many situations is because of that runtime versus load time dichotomy. And I hadn't made that connection for class methods versus instance methods, but I think there's a parallel there. AJI: Yeah, absolutely. The composition versus inheritance thing, I think, goes very hand in hand with the conversation that we're having about putting your behavior on a class versus an instance because...and I don't know if this is again yielding my thoughts to 'the internet said' in that composition is preferable to inheritance. But without unpacking that right there, that is certainly something that I strive for as well. And while it might have, much like TDD, some kind of superficial, short-term complexity, it has long-term payoff in that flexibility and that reuse, and that extensibility, and all of those other buzzwords that we developers like to throw around. JOËL: So you've shared a lot of thoughts on the use of class methods. I think this could branch into so many other aspects of object-oriented design that we haven't looked at or that we could go deeper, things like TDD. We could look into how it works with the solid principles, all sorts of things. But I think the big takeaway for me is that class methods are very useful, but it's easy to use them as our single hammer to every problem being a nail. And it's good to diversify your toolset. And some tools are specialized; they're good to be used in very specific situations that don't come across very often, and others are used every day. And maybe class methods are the former. AJI: Absolutely. That hammer-and-nail metaphor was right where I was headed for too. Love it. JOËL: Well, thank you so much, Aji, for joining the conversation today. Where can people find you online? AJI: Yeah, anywhere you want to look for me: Instagram, GitHub, Twitter. I'm @DoodlingDev, so just send all your angry emails that way. JOËL: And with that, let's wrap up. The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at [email protected] via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeee!!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Support The Bike Shed