 Welcome to the session. We're going to talk about obligatory programming. The title is Sounds Worse Than I Actually Meet It. But, you know, you guys are all here so it worked. I'm Mark Sonobom. I'm a performance engineer at Acquiat. You might wonder why a performance engineer is so interested in this type of stuff and it comes back to the idea that you can't. It's really difficult to make something more performant that no one understands. And so the understandability and the simplicity of a system is very important when you're actually trying to make it perform. And I just like things that make sense. So, just a quick overview of where we are. Like Drupal 7, we had some classes. The APIs were pretty much procedural. We all know it was not very unitestable. And we all know it was pretty tightly coupled. And it's also very complex. I think everybody could agree on that. There's been better things written about the complexity of Drupal. In Drupal 8, we adopted some symphony components. We have many more classes. We're actually still not unitestable. We're still tightly coupled. And we're actually somehow more complex. And this type of stuff sort of scares me because we have existing contributors who shouldn't be as scared of D8 as they appear to be. So in general, when people talk about object-oriented programming, it talks as a tool to manage complexity. But we somehow took that and made it more complex. And that's what we're talking about here. Though I really like this quote from Martin Fowler. The key to controlling complexity is a good domain model. That's not really a word we say that often here. And every time I say model, somebody says this. But just because you may not have the M in the C doesn't mean you don't have the M. Everyone has the M. If you're doing anything with objects, you need a domain model. A domain model, it isn't a literal thing. There are lots of criticisms of object-oriented programming because it doesn't always map to real-world things. An object doesn't have to map to a real-world thing. It has to map to some abstraction that makes sense in your domain. We have nouns that we use for these things all the time. Those are essentially what our domain objects end up being. So they're usually nouns. I won't go too much into ubiquitous language, but if you read the book Domain Driven Design, I highly recommend it. He goes into it much more. But the basic idea is the words we use when we're speaking to each other and the words we use when we're in code should be... There's an overlap there, and that's our ubiquitous language. When we talk about blocks, blocks are a very clear domain model. Yet when we're in our C, we talk about block entities and block plugins, and no one can understand each other. That's why that's part of it's sort of important. Here's the most obvious, triple models. We usually think of these as entities. We just got in a patch to where all the main Drupal entities have interfaces now, which is great, but that's roles interface. This suggests to me that a role has no business logic, but I don't think that's exactly true. If you start looking around, you start seeing things like this. I would say that changing permissions is well within the domain of a role. That's something that maybe it should be responsible for. That's a really simple refactoring. You just stick that on the role object. There's that one method, but then I'm implying two more with grant permissions and revoke, that however it needs to do that job doesn't really matter. Those methods should just exist on that object. Comments look similar. Here's just a shortened list of its properties. Notice the comments on a bunch of those properties. The names are really ambiguous. What is mail on a comment? That doesn't really make a lot of sense, but the comments tell you what it is. In most cases, if you look through Drupal code and see a comment, think about if that could be a better method name or something that describes that thing better. Don't be afraid of using multiple words. Long variable names and long method names are totally fine. If they say exactly what they do. That's always better than a comment. The first thing I would do to refact this is I would add descriptive accessor methods. It would end up looking something like that. But now there's another code smell. Whenever you're working with one object, if another noun starts appearing in a bunch of methods, that's a good indication that you probably need another object. People who understand the entity API are probably freaking out now because they're like, you can't do this. That may be true, although I don't know if I actually believe that, because still these are the internal properties to comment, and comment can instantiate its values however it wants to. I don't see a reason why we couldn't say, make a comment author object. This isn't exactly a user. This isn't our normal user object. This is a comment author. It may inherit from user and may not. It doesn't seem like it has the same responsibilities, right? But just having something that says a comment author is a thing that we have and the thing we refer to in this domain tells you a lot about what's going on. So here's another instance of hidden business logic and procedural functions. This function prepares the author property we were talking about before. There's not really a reason that needs to exist outside. But it also suggests something else. So if you look at that actually, it's saying entity create with a UID of zero. We all know that UID zero means anonymous. But that's really just an implementation detail. There's no reason we need to just code that all over the place with that assumption and have all of our code know that zero means anonymous. I don't see why we wouldn't just have an anonymous comment author. It can inherit from comment and just hard code its UID to zero. And then that code I think reads much better. So this is from the actions module. This one is pretty rough. And this should stay there, right? So it's the actions module. It's this action that's not going to go anywhere. But it knows how to find the subject within the comment, which would be one thing if it was just subject, but it's actually subject value, which is a little strange. It also knows how to get to status and knows the constant to compare it to. Now you could say there's an argument that like that's sort of okay because these are within the same package of comment module. And I would believe you. But there's really no reason it needs to do that. So when you look at the code around an object, you should always remember it doesn't have to know how this object is going to do something. It just needs to say what it wants. It just needs to tell it to do something. So this is what this code I think should look like. You just call a publish method or you just call a subject method. It doesn't matter where that comes from. The contract here is just I'm going to call subject on you and you're going to return me a string. And then it's up to comment module or comment class to do that. And so here's another instance of like knowing those constants. And in this case, it's actually looking if it's published or not. And when I see that code, I say, okay, there's a business case for having an isPublishMethod. And then we just add that. And you probably recognize that publishing is actually not unique to comments at all. And when we see things like that, those are basically roles that those objects are playing. And I would say that's like a publishable roles. And when you see those, we need to extract those out into interfaces. So we could have a publishable interface with those three methods. And then comment and node can just implement those. And then the methods that actually handle publishing, they could potentially just type in publishable if that's all they do. And then that tells you a lot about that code. That code says, I need an object that I can call these three methods on. It's a very clear contract. I get told this once in a while. And so for those who haven't heard, the single responsibility principle is about not ever making a class with more than one responsibility. The problem with that is it's a really difficult thing to reason about. I really like this quote from Eric Evans again, domain-driven design. Basically suggest there's complexity in doing that. And the more you do that, the more you get away from the actual fundamental concept of object-oriented programming, which is you have, you couple the data to the behavior that acts on that data. I'm not saying that single responsibility principle is wrong. It is absolutely not wrong. But don't go nuts with it. You can really take it too far. And like reasoning about what a responsibility is is actually much more difficult than you'd think. The more you think about it trying to narrow down what a class is doing, the more I think it's sort of a subjective thing sometimes. I think it's easier to think about it in terms of a reason a class would change. If you have two methods that sort of look like they're maybe doing something that doesn't belong in the same class, but they would always change together, I don't think that's a big of a deal. But if a change somewhere else would cause one of those methods to change, maybe that's a sign that it should get moved out into its own class. So there's a balance I think between single responsibility principle and a rich domain model. And we just need to find that. And one indicator we don't use enough is pain. The way I like to code is I like to start with the simplest possible thing. I don't worry about what rules I'm breaking. I just do it how it makes sense to me. And then the first time I encounter pain, every factor. And we sort of default to pain. We cause ourselves pain because we over abstract, and we may never actually use that abstraction. And then we end up with more complicated code that's painful to use all the time. So sometimes, and I understand we can't always wait because we're getting something into a product that's going to ship, but we have pretty long release cycles. So it's not really that bad to get something in that's less abstract, let three years go by, see if something else needs it, and then if something else needs it, you extract it then. So there's a bunch more cases of hidden business logic in procedural functions. It's a really, really easy thing to fix. You just have to go find it and then stick it on the appropriate class. If it makes sense that you need a new class, just make a new class. Not every class in Drupal needs to be what's called a framework object, right? It doesn't need to be a config entity or an entity. We can just have classes that don't inherit from anything, that just play a single role, and that's totally fine. So just really quickly, I want to talk about types of domain objects. I think this comes from the domain-driven design book. I think it's pretty helpful way to think about things. So entities, in the sense of outside of Drupal, defined as having an identity. So generally anything with a UUID is an entity. They're treated as mutable, and they have state. They're usually nouns. Drupal entities and config entities are a good example of actual entities. And this is the important thing. This is how you spot it. All of the attributes can change on an entity, but it's identity remains. It's still that entity. On the other side, you have value objects. They have no identity. They're immutability. I know that's not something we really deal with that much, but you can still treat an object as immutable. It has state, usually nouns. The fields, I think, are a good example of value objects. And the important thing about them is the only way you can identify is by its attributes. If you change its attributes, it's a different object. And ideally you shouldn't change its attributes. You should just instantiate a new object with those attributes. So you have those two things, then you have services. Services should be stateless. They're usually verb-ish. I ask that you try to make entities and value objects before you make services. I thought we were actually a lot worse about this than we were. The more I look through the services we have, most of them are actual services. And I think that's because most of our business logic is actually still in procedural code. But as we move that stuff over, it would be very easy. I mean, we talk about services all the time. We love services. It would be very easy to start taking functions and say, like, okay, or the comment prepare author. And somebody makes the comment prepare author service. That doesn't make a lot of sense. That's actually just putting behavior into a service that should be on the object that it actually acts on. And I like these quotes about it. You should just use them sparingly. Don't let them take over behavior from the objects that should have them. And the second one really hits home because you realize the more you do that, you're slipping towards procedural programming. When you end up with dumb data objects that have no behavior and then a bunch of services, it's not that different from just data structures and functions. And if you don't read this, the Martin Fowler article, the anemic domain model, I highly recommend it. I think that describes essentially where we are right now. And basically, when you don't do this, you're robbing yourself of the benefits of object-oriented programming, which I think is one of the reasons that things are so complex right now and no one can understand this new code and there's people rejecting the idea of object-oriented programming. It's because when you leave out the domain model, you don't get the benefits of it. And so then you just have a more complex version of what we had before. The naming. Anybody who knows me knows I like to talk about naming. It's really, really important. Everyone likes to talk about cash validation and naming and how it's so hard. Naming is not hard. Naming classes and methods with poorly defined roles and responsibilities is hard. So if you find yourself having a hard time naming a class, think about what that class is doing. It might be doing too much. Or what it's doing may not make sense. If you distill down and basically you describe, okay, look at the comment on top of a class. There's quite a few cases where the comment actually says what it does. But the class itself doesn't suggest that in any way. So here's one example I found in Core. I apologize to Kat Bailey for this. But I think a lot of this is actually just like legacy, like coming from the original procedural design that this was just like the first iteration. This is actually totally fine. Like we take the procedural code, we move it into classes. We do it however it works. But we don't stop there. We have to then refactor and figure out how it makes more sense. So let's take a quick look at these method names. There's some strange stuff going on. The first thing that I notice is that those three terms are used to describe the same thing, right? Paths that are not aliases we don't have a good name for. What's the difference between get path alias and look up path alias? I don't know. If you look at this, you see the only thing this function is doing is it's getting the default language and then it just delegates to look up path source. I would refactor that to take out that language behavior into its own method. All of a sudden that's only one line. And then just copy the rest of that function into this original one. And then you don't have these two methods that basically say they do the same thing, right? But that didn't tell the story because there's something else happening. So it is better but the vocabulary is still a little inconsistent. This is the way I would name them. I like using find because I think that's actually what's happening here. And I think path is actually totally fine. If you just say path, that means path. If we say alias, that's a path alias. We could argue about path that's totally fine as long as we settle on one, right? And so I think it makes sense to say find path by alias or find alias by path. Because that's all those methods are doing. We still have some of these other methods here. Get path lookups and preload path lookups. They're actually only called by this decorator. And so I would say if they're only called by that one decorator, they should actually just go into those and just refactor them out into that class. But we saw this cache clear method which is a little odd because if there's a cache decorator, why does any cache anything happen in this class? I would say that probably shouldn't happen and we should refactor that into that cache decorator. And then those are gone. Then there's path alias whitelist rebuild. That's also only called by cache clear. And if we remove that, we can remove this. Now there's also this class. This is essentially what it looks like with the code taken out. So the path class manages crud for path aliases. There's a responsibility overlap with alias manager because it's the thing managing aliases. Even though a manager actually means nothing and it's more of a repository. I would say just stick this stuff in alias manager. If one class says I can find this by a given criteria, why shouldn't that class... That class already has a database connection. It already knows about these objects, knows how to find them. Why shouldn't it also just save them and load them? I also changed these method names because before they were load and delete and then they took conditions. So I think that's probably better reflected in the method signature. And so I named it findware and deleteware. I don't remember where I got that. It may have been from backbone model. But if you look at backbone model or active record or... What's the PHP one? Doctrine, they have all kinds of these find by attributes, findware type classes and it's always good inspiration for these things because that's what the rest of the world uses. So another problem we have is emphasizing systems over the domain. What I mean by that is this. So if you were to find our user model, it's buried in here under plugin core entity user. So if I want to understand the user model, I also have to understand entities and plugin. This is actually what it could be now because Tim Plunkett is awesome and we got in this custom entity type annotation so that now the annotation on them is no longer plugin, which enables this so we can just have entity type. That makes a whole lot more sense. If it was possible, I'd actually just prefer to get rid of that and just have user and somebody saying, oh, but how do you find the things? I'd prefer that we always do the best design. And without regard to performance or anything else, do what makes the most sense. Let's decide that's the way we should do it. There are tons of smart people in this community that will solve the technical problems with it. So we shouldn't design with how it's going to work or how it's going to perform in mind all the time. There are cases where that's an exception. But in this one, I don't think there is because we came up with this idea of finding plugins based on namespaces and then getting that from the container, which if you've ever looked at the container and how it does that is really crazy. But there's other ways to perform that job that don't poison our namespaces like this. And so we're already on the right track to fixing this, but there's just one example. We like to talk about everything in terms of plugins and entities and we talk about the system it uses before we talk about it. It's really sort of an ancillary thing. It doesn't matter that block uses entities or plugins. It just matters that block is block. So here's a slightly less bad example, but I'm still not crazy about it. So custom block controller calls entity manager. Custom block controller knows about custom blocks. Why should it know anything about entity? I think it could just be that. And this is sort of more like the active record approach, which I'm sure somebody will argue out of, which is fine. If you need to be fancy, you can have it injected and just have a custom block repository. And that could inherit from the entity manager, which I think is more of an entity repository. It could share all of that functionality, but I think it is important to just subclass that and have that object live in its own domain. The more your classes talk to itself, well first the best thing is talking to yourself, calling methods on self. The next thing is calling methods on objects that you own. Custom block controller is in the same package as custom blocks. It's always safe to call methods on those. You won't end up with refactoring issues because they're in the same package. Just a way of protecting yourself against what goes on outside. The controllers. Sort of a new thing for us. Although really they're just page callbacks in terms of how we're using them. Here's one example in the aggregator where it's actually calling a database and it's getting a list of aggregator items. That's not really, or that's actually business logic in itself. That doesn't belong in a controller. When we find stuff like that, that means it needs to be moved to some domain object. Just as a random example, I know Larry is cringing at my new. But let's just call it there's an aggregator feeds class, right? That's essentially what it's doing. That's what it's getting back. Yeah, I said aggregator items before, it's feeds. I always like to create simple accessors for domain objects within sort of like glue code objects like controllers. And so it actually do this. It's like what I said before, it's always best to call methods on self, right? And so you're not actually dealing with that class or even an injected thing. Admin overview only calls this feeds method. It knows it gets an object back that it could call find all on. And then this feeds method actually handles the instantiation of that. And so it only does this new hard coded class if it isn't set previously. Larry's also going to hate this, but this is actually a reasonable way to manage dependencies in some cases. If you do this, I can still unit test that class. Because one, I can inject that feeds variable, so the hard coded one will never get instantiated. But even if I wasn't doing that, I can actually subclass it, replace that method with a stub and test that stub. You don't want to do that all the time. Like I think dependency injection is a really good thing and we should be doing that most of the time. But this is an option. You can totally do this and it's okay. So I would say dumb down a controller until it's not even worth testing. Because we really shouldn't be testing controllers. And if we have a logic in there that needs to be tested, we should just move it out. So it touched a bit on unit testability and coupling previously, but I want to take you through my experience trying to test this map class. Map is a class in type data. It's essentially a hash for people who think of them as hashes. It should be a pretty simple thing to test. So I just took the existing tests from our unit test base class, the type data thing, and just made a simple PHP unit test. All this does is it iterates over it and then it counts how many times it iterated. And then it should equal the number of items that I passed into it. I passed two items into it. And if you notice, I instantiated this map class with that value. So it looks like it has everything it needs. So this is the first message I get. It failed. It didn't work at all. I then discovered I had to pass set value and give it the value again. Just a little strange, but okay. That got me going. And then the next error I got was an undefined function called a cache. So that's in cache decorator for plugins. Just to get to the next thing I went in there and just hacked it out. Now I get this. Hacked that out. Now that. This is a special example because I feel like we just default to using format string all over the place or check plane, which is fine when you need to use check plane, but for format string, this is actually using an exception. I don't really know why we ever use that in exceptions. We could just use sprintf. If I'm wrong, we can talk about that. But let's figure out something because we shouldn't be calling procedural functions in exception messages. And now I get this. And this got called somewhere within the type data, like magic functions. And so I hacked that out as well. And that's how I hacked it out. Finally, after all that, this test passes. So the thing to understand here is that inheritance is hard coding and dependency. We're very sensitive in code to seeing a class's name. And in general, that's not a great practice. But we inherit all over the place. And the thing I want everyone to know is that it's the same thing. It's actually, this is a tighter form of coupling to that class. There's no way I can mock this. There's nothing I can do. It is essentially like taking on that class. If you're a base class, and we're saying, these are our base classes, everyone should inherit them. You have a responsibility to not screw your subclasses. Right? Just this simple tiny map class had problems from, like two different problems from these two different systems. These systems should not make other code untestable. Yeah. And so, touching on what I was mentioning before, here's another example of just calling a dependency directly in a method like this. So sure, this should be injected. I think there's maybe issues right now with injecting things like this into entities. And if that is the case, and you can't do that, the bare minimum, just hide every dependency in a method. As I was saying before, that's still testable. I can replace that. And you just don't want that save function. That save function, its responsibility is to save things. It shouldn't have any knowledge outside of that. And this is a super, super simple thing to do. I don't think there's any reason to not ever do this. Whenever you have a dependency, just hide it in a method. I mean, I even do this when they're injected. It'll save you having to refactor the thing that has the logic in it. So I actually think single responsibility principle is more important when applied to methods. And it's a really easy thing to apply. So if you have a method that has more than one responsibility, like it's fetching something from the database, it's applying some business logic in it, say calling a wash dog, you can pretty easily refactor that into separate methods. Those separate methods will be pretty easy to name because they just do that thing that you need done. And then that class is going to be simpler to understand and easier to refactor. So our code is still, as I saw before, very tightly coupled. If you can't use PHP in it to test your class, you cannot claim that your code is loosely coupled. There's a lot of talk in Drupal 8 about how, like, more loosely coupled things are, but there are not a lot of PHP unit tests. And for those of you who don't know, we got PHP unit in a few months back. You can now test any class in Drupal with PHP unit and prove that it is decoupled. Drupal unit test base is not a unit test. And actually, unit test base is not a unit test. Here's an example of a unit test base. A unit test should not have a database connection. So please, I know, like, I don't actually think that none of our code got more loosely coupled. I know it is, but please just go prove me wrong by writing tests. I really want to be wrong about that. And the more we write tests, the more we will find these issues like I found with MAP because it's very easy to ignore when all the code is loaded all the time. So if you write a PHP unit test, no code is loaded except for what's auto-loaded. And so get through the test, get it passing with that, and then you know that class has no hidden dependencies. So I think there's actually still time to do this. I don't think any of these, or any of the stuff I'm showing is necessarily that time consuming in terms of refactorings, because really, like, they are just refactorings. There's not changing functionality. It's just taking our existing code and making it simpler and moving things around. So that's really all I want to do. I just want to make Drupal simpler, and I think everyone wants that. And so there's a sprint Friday. If you're interested in working on this type of stuff, I will be there. Happy to help people out with it. And just a couple of books I recommend. I feel like there's some good design books out there that have really helped me understand this stuff, especially if you're new to object-oriented programming. For if you're into this stuff and you feel like you have a really good foundation, I love small talk best practice patterns, not the easiest book to read, because all the examples are in small talk. This is actually a really great book, and a lot of the patterns apply to Drupal. And I'll put these slides online, so don't worry about copying them down. Refactoring is also another great one. Domain-driven design I quoted heavily in this. This is actually the bulk of what I'm talking about here. And this is actually one of my favorite new books. It says it's about Ruby, but it's really just about object-oriented design, and it's explained in like the simplest, easiest to relate to terms. And it talks about everything in terms of like the benefit, like you do this and this is the benefit that it gets you. And I think anybody in the Drupal community who feels sort of new to object-oriented programming and doesn't feel like they understand it, I highly recommend that book. It's really great. Thanks. So, are there any questions? And if you have a question, you're supposed to come up to that, Mike. Or anybody who wants to talk with me or tell me that I'm wrong. That is not much of a core conversation. All right. Even though I usually love to tell you you're wrong, I thought you were mostly right on here. So, a question I guess in terms of core development, how much do we need to block release on the developer experience being good? That's a great question that I can't answer. Yeah, question is like how much would we like block release on the DXB and VAT? I don't like to think in terms of blocking since that's clearly not my call. But I don't think there's, I mean we think like, okay, we don't want to block on this. So that means maybe we don't have time to do it. I think that maybe just encourages people not working on it. I would say let's assume we're going to ship D8 in a state that makes sense. So we should just continue to work on this stuff until we release. Sort of a dodge, but I think I said something. You were talking about Ruby and Active Record earlier, and I think the way that they do domain modeling is very intuitive. They have all of their static finder classes and saving classes on the model object, the entity object itself, right? I think in the PHP world, the preferred way to do that is to inject dependencies. Oh, hey. Oh, yeah, it helps to turn the mics on, I guess. Sorry. So should I repeat what I was saying? Keep going. I'll summarize it. Okay. So the preferred way, I think in PHP instead, is to inject dependencies into like a manager class, right? And that does the finding and saving, so, you know, et cetera. But I think that results in a more complex domain model, right? And like an uglier looking code to, like, end users who are using your interfaces. What do you think about that? Okay. Yeah, happy to talk about that. So the question is about, like, if you ever looked at Rails and the Active Record pattern, like, if you had a user class, you would call it user static method find, and then you'd get users back. You don't have a separate object. Like, that's the same user class that you would then instantiate. And the Active Record pattern, it's where you have, I mean, this is going to be a gross oversimplification, but it's where you have the persistence layer and the domain object, like, all in the same object. And so you mentioned, like, the PHP world, we tend to do it in another way, where you have, like, repositories and stuff. Those two patterns, actually, they're in the... They're in this book. That actually has the Active Record pattern. This book is actually sort of the secret manual to Rails. And then there's also the data mapper pattern, which I think is what you're talking about. A doctrine from what I've seen looks like it does the data mapper pattern. I don't think it's actually bad, because the basic idea is you split it up into your domain models, and then you have repositories. Repository has a place in your domain. I think when you call it that, it says, actually, what it is. If we call it a manager, it's more ambiguous. But a repository is a domain object that I talk to to get instances of this type of object. It's certainly more complex. I think if we actually did something that looked like Active Record, we'd be totally fine. But I don't really want to make that argument. So I'm fine with doing, like, a more data mapper-style thing. So I wanted to ask again, actually, about the question about whether we should block release on DX problems. I actually have... It's part of my job to try to help answer that question. So Code Freezes July 1st, and the idea is that five weeks from now we can't change our public APIs anymore. And so the question becomes, how much refactoring can we do that's not going to change our current public API? Do we consider namespaces part of our public API for July 1st? Because if we do, then, you know, that sort of reduces what... Like, question, so can you, like, talk about what you think we can and can't do, or do you have any idea? Okay, so what I'm talking about would definitely involve public API changes. But they don't necessarily have to involve public API... BC breaks. Right? So say, like, in the example, we're showing, like, the comment-prepare author. We could move that in. We could create that new method on comment. But we could leave that function and then just move the guts of that function just passing through to that class. We've done that in a few areas. Like, there's a lot of... If any of you have been in the CorkU following this stuff, like, there's lots of patches that are just moving these functions into classes. Some get rid of them. Some mark them as deprecated. Some just leave them. I like leaving them and marking them as deprecated. We have procedural code. We're going to have it for a long time. We have .module. That's what that is. There's... People are going to want or need to call those procedural functions, and that's totally fine. You just can't do it in a class. I probably should have said that earlier. You can't call a procedural function in a class. That's, like, violating a boundary that introduces hidden dependencies and makes that class totally untestable. So we can do that and leave those functions. We just can't actually use them inside of classes. So I think we can actually do this, in most cases, without breaking VCU. And in terms of, like, public, it's really important distinction. We don't need a default to making everything public. If there's no reason to... Or for... If a method is intended to only be called within that object, just make it protected. And then we don't have something that could potentially break VCU. Because everything that we make public, I like Martin Fowler has a concept of a published interface. It's like a step beyond public. It's like we have our public interface, and then we release. Now, we're really on the hook for never changing those. And so if we think it might change, if we feel like it's unstable, let's just make it protected. We can always change that if it ends up being an issue. But that puts us in a safer spot. So, for the record, I don't actually disagree with 95% of what you said. Sweet. Really, the only thing I'd add is, Drupal does have something of an upper bound here, simply because so much of what Drupal does is by design runtime dynamic that does limit your ability to use method names to document what's going on, and what's going on is going to vary depending on what the user configured in some form over there. So there's a limit to how far we can go with this, but in a general sense, I mostly agree with you. So if anyone thinks that there's a blood feud here, no, there isn't. And to comment on that, though, I think there are definitely cases where you can say, like, yeah, it would be nice to have this method that says what this thing is, but in this context, I don't have the knowledge about that because it's some user configured thing. I would say in that case, I mean, it's clearly not appropriate, but it gives us a better understanding of what our domain is to understand that because our domain is not solving, like, these business problems that sites have. Our domain is making software that builds websites. Right? And so that generic part of it, of just getting information that a user configured and then doing something with it, like, that's our domain problem. And so methods that reflect that are very informative. So they made up, you know, find by query object that the user configured somewhere, you know, find by view or something like that. Yep, and that explicitly states, this is the lowest level of abstraction that we can deal with. Yeah. Anybody else? Okay, thank you very much for showing up.