 defensive coding. To introduce myself, my name is Sam Fiffen. Hi, people. I'm Sam Fiffen on Twitter, Sam Fiffen on GitHub. You can take a look at my profiles there if you're interested in finding out more about me. If you do take a look at my GitHub profile, you'll find that I spend most of my time on GitHub committing as a member of the RSpec core team. And that's one of the reasons why I'm here today. I think it's really important that RSpec is well represented in community events like this, and people who use the framework every day get the opportunity to talk to someone who works on the framework full-time. So if you have any questions or feedback about RSpec, if you're loving it, if you're hating it, please do let me know because, like, person-to-person feedback is one of the ways we influence the future of the framework. We actually just dropped a new release of RSpec, 3.4 this week, and it contains a couple of really powerful new features, so that might be worth checking out. I work for a company called Fun and Plausible Solutions. We're a consultancy based in the UK. We tend to work on helping our clients with their Rails applications, do performance optimizations, database analysis, and stuff like that. And if that's of any interest to you, I love coming over here to do work as well, so maybe we can find a way to work together. Before I get too far into the talk, I wanted to just sort of take a sample of the conference. Who's enjoying themselves so far? Everyone having a great time? Right. So I have a slightly more person in question than just getting you all to clap, cheer, and applaud, which is who is here for their first time, who hasn't been to a RubyConf or a RailsConf before. So that's, like, the vast majority of you. And given that you're all having fun and you're all enjoying the conference, I thought that I'd just give a shout out to the Scholarship and Guiding Program, which is something I've participated in now for RailsConf this year and this conference as well. I think the Scholarship and Guiding Program is an excellent feature of these conferences. It encourages new people, like the vast majority of people in this room, to come into our community to be greeted and have a really, really great time. And so if you're enjoying yourself and you've enjoyed this conference a lot, when it comes time for RailsConf, if you're attending or if you're planning on attending RubyConf next year, I would thoroughly suggest that you volunteer to become a guide, sorry, and help the program get bigger. It's something that our community is applauded by other communities for having. I've seen people from a very, very wide range of different language and technology communities say our Scholarship Program is great, and so I would encourage you all to take part in that. So with all of that sort of front matter out of the way, let's get into talking about coding and stuff. So I wanted to start this talk with a story, and the story actually comes from one of the question and answer sessions from another one of my talks. My talk was about deep internal architecture of how RSpec mocks actually works, how the pieces all fit together and stuff like that. And as the close of the explanation of the talk, I said that most of the code was there to enable us to deal with complicated user objects that override default methods provided on kernel or object. And as I was beginning to answer the questions from the developers, more senior and deep technical questions got out of the way, I was asked a question by a junior developer that absolutely flawed me that I wasn't actually able to give a reasonable and complete answer to. And this is one thing I absolutely love about working with less experienced developers. Because they don't have all of this baseline built up implicit knowledge, you're forced to explain things in a great level of detail and clarify your own understanding of what those things are. The question that I was asked is when is it okay to override the methods on object? And I said that sometimes it's okay and sometimes it's not, but I hadn't really given a clear explanation for when that was the case. And so I sort of ummed an odd. I gave half explanations. I really wasn't able to fully clarify my thoughts. And this is so often the way when you get asked a basic question that you think you totally understand, you actually can't explicitly put the knowledge into words and say it. And this is again one of the things that I love working with newer developers. So eventually I came to an answer to do with consistency. When overriding methods on object is sometimes a good idea and sometimes it isn't. And the answer to do with consistency was well do it when it makes your object more consistent with the things that already exist in Ruby not less. And I could give lots of examples of this. I could talk for hours and hours and hours about this subject, but that is not the focus of the talk. So instead I'm going to give a quick example and move on. So for example, let's imagine you have some collection object that you've implemented that might be a bit like a hash or a string or a set or something like that. Well, if you don't override the equals equals method on that object, then it's going to just perform object identity comparison, but that's not very Ruby-like. Object identity comparison is fine in some cases, but when you have a clearly defined collection of things, what most of the Ruby standard library does is ask all of the things inside it if they match all of the other things inside the other collection. And so doing an override on this equals equals there is totally reasonable. And this is a way that you make your object more consistent, not less. So I'm going to go through the rest of this talk, and it would be great if you could hold on to that story as we're walking through it, and we'll visit it back at the end of the talk. So I want to posit the question, what makes a gem good? Now, this is a hotly debated topic in our community, and I certainly can't provide you with a universal panacea, but I can provide you with what I think makes a gem good, and you can decide to agree or disagree with me as you want to. So what about the internals? Does it matter to us if the internals of a gem are crazy in order for a gem to be good or not? Well, I don't really think so. We all work with a lot of very complicated libraries every day, but it's very rare that we look inside them. I suspect most people in this room aren't intimately familiar with the source code that makes up Ruby on Rails, or Sinatra, or Rake, or Aspect. So clearly, like the internals don't matter to us that much day to day when we're picking which gems we're going to use. I suspect most of you don't read the source code of all the gems that you use. But what about the interface? Well, to me, I think this is a winner. I think most of us go after gems that have nice interfaces that are going to work well with our applications, and to some degree, it's something to do with convenience. I think a gem is good if it's more convenient than I were to do the equivalent implementation within my own site application myself. I mean, I would like to think I'm a pretty capable Ruby developer, and so given any arbitrary problem, I can probably get it done in some reasonable amount of time. So if I'm going to pull a pre-packaged piece of code off the shelf, then it has to be more convenient to fit within my application than if I was going to do it myself. There has to be a time-benefit trade-off there. It has to stay convenient. As my application grows, my architectural patterns are going to change the way that my team works and functions is going to be different and so on and so on. A gem that you pull into your application now may seem like a great idea, but in six months or 12 months, you may absolutely hate yourself, and I'm sure some of you have made that decision. Teams are not homogenous in terms of skill. We have senior developers. We have junior developers. We have people who have been programming other programming languages than Ruby for longer than they've been programming Ruby. And so if that's the case, like we need our gem and our interface to be understandable by everyone on our team, powerful enough to do what the senior developers want and easy enough to understand for the juniors. Now, this one might be slightly further out, but I suspect some of you have applications that don't just run on MRI. Some of you will run on JRuby or maybe Rubinius or maybe you'll have a hybrid Ruby deployment in different parts of the cloud or whatever. And so if you're going to pull a gem off the shelf, it needs to work with a wide range of Ruby code bases. It needs to work with different Ruby interpreters. It needs to do a lot of different things. So I've just come up with a really long shopping list of criteria for what I want the gem to be. But it wouldn't be fair to have that without comparing it with the gem that I write and maintain and see how it stands up to everything that I've just asked for. Now, I could stand here and tell you that RSpec is definitely always convenient, but that would be a lie. I'm sure every single person in this room who has used RSpec at some point has decided they absolutely hate it and don't want to use it anymore. I certainly know I have on multiple occasions. So that's fun. So if we're going to talk about RSpec, let's pick a specific feature, go through it and see how it stands up. And to do this, I'm actually going to do a user story. Now, I'm sorry to get all agile on you just before lunch, but here we are, and hopefully it will be fine. As an RSpec user, when I stub an object, I want the original method to be on that object after the example so that my objects aren't broken by my test suite. Now, that was a little fast, and I'm sure not all of you spend all day mocking back and forth in RSpec. So let's go through that again with a slightly more detailed explanation. As an RSpec user, that's about 70% of you in the room according to the last year's Ruby survey. When I stub an object, that's the process of doing, like, allow object to receive foo, or if you're still on legacy RSpec, object.stub foo or whatever. And that's what that's going to do is replace that method and change it so that instead of invoking whatever the original implementation of that method is, it calls into fake RSpec code so that you don't have to execute whatever that object's implementation is. I want the original method to be back on that object after the example, so that is when we're done stubbing an object inside RSpec, we put the original implementation back, and so here we're actually talking about the process of moving methods around in Ruby. I'll explain how that works in just a minute, so that my objects aren't broken. I would contend that most of you don't have RSpec stubs moving around in your production systems, and therefore, like, it's not reasonable for RSpec to leave a stub on an object after your test is done executing, because that leaves the object in inconsistent state with how it would be otherwise in the system. So we need to do that reset. Okay, so we're talking about moving methods around taking them off objects and putting them back on. How does that work? Well, just in case you aren't doing this every day, the syntax that we're talking about here in RSpec is the allow cat to receive meow syntax. Allow to and receive there are the RSpec keywords, and I'm making up some hypothetical object called cat, which has a hypothetical meow method. And what this is going to do is it's actually going to remove the meow method from the cat, save it somewhere else, execute your test, and then put the original method back on the cat object. So during the execution of the test, you'll have the fake meow method, and when the test is done, you'll have the original implementation back. So, again, we need to pull another layer back and talk about how you actually save methods and move them around in Ruby. So if you head over to the standard library documentation, and you look really, really close at the documentation for objects, you'll find this method called method. The definition of method, according to the documentation, is method is a method which takes a symbol and returns a method. So far so cryptic. Let's read the doc string and see if that helps. Looks up the named method as a receiver in obj returning a method object or raising name error. The method object acts as a closure in obj object instance, so instance variables and the value of self remain available. Everyone clear on that? Exactly. So what we're talking about here is the concept of a method object. Ruby doesn't have first class functions, and so we can't simply treat methods on objects as data values. The method method allows us to do that. When we invoke it with a symbol, it returns to us an object which represents that method on that particular object instance. You can pass the method object around as if it were any other object in the Ruby system. The method object has a public method called call on it, which allows you to invoke the method provided, and you pass the arguments to call that you would pass to the method. This basically allows you to pass the method around as an object in Ruby, which is great for our spec, because it means we can put that method somewhere else, execute our test, and then put the method back at the end of the test by using the method object and basically shoving it back on with the method using the method object, and then we can return it later. I wish I could tell you that that was the whole story. I wish I could tell you that it really was that simple, but unfortunately that would be a lie, and this would be the shortest talk at the conference. Now I have to talk about defensive coding, and this is where it's going to get slightly mad. In our spec, we like to design abstractions over common tasks in Ruby where we need to fix bugs and basically provide places to hang user facing behavior. For the rest of this talk, we're going to be discussing a method called aspect support method handle for. This is basically our wrapper around the method method to catch a bunch of user error cases. Basically, if you want to work in Ruby to pull method objects off user objects, simply invoking the method method is not good enough in a lot of cases. And so what follows is an evolution of aspect support method handle for through our Git history, roughly altered and made linear for various branching reasons and cutting a couple of scenes out. This is the roughly true evolution of how that method came to be, but it's not the absolute truth. Please dive the Git history if you're interested in finding out more or you want to tell me that I'm wrong. So, the first implementation of aspect support method handle for, you can find anywhere in the aspect source history looks like this. Aspect support method handle for is defined to take an object and a method name. It simply invokes the method method on that object with the method name. Basically what we've done is we've just wrapped the method method in an aspect method and actually in the place where the code actually was this was just object method method but story. So, the first problem we have here is that users lie. How many of you have ever had to do anything to do with HTTP in Ruby? Right? That's a ridiculous no don't put your hands up that's a ridiculous question. If you look inside the source code for basically any HTTP library anywhere in Ruby, you'll find a method that looks like this. We're going to define a method called method which returns the string get or post or maybe it's a symbol or maybe it sets the method on an object like something like this. But what the user has done here is they've blown away the original implementation of the method method. We no longer can just really invoke that method in order to get method objects back. And one of the truths about Ruby is that users can and will redefine any method at any time for basically any reason. And that's fine. They should be allowed to do that. Like the word the word method means something in the language of HTTP. That is a domain specific word in HTTP that like definitely should be allowed to be defined by clients. As it turns out, it's actually not too hard to deal with this case. So the second definition of aspect support method handled for you see anywhere in the git history looks like this. We define a constant called kernel method method which gets assigned to it the value of the expression kernel.instance method method. So we've seen the method method already but let's talk about the instance method method. The instance method method is like the method method except that it returns instance method objects not method objects. An instance method object is basically the implementation of a method on a class without any specific object instance in order to receive the method. You can basically think of it as like the class's blueprint implementation of the method with no specific object upon which to be invoked. It's the method object without the target. Instance method objects come from classes and modules instead of specific object instances. The difference here being that you can pass them around and give them an instance to be invoked on later. So what we're doing here with our kernel method method is we're grabbing the kernel definition of the method method which is always correct. It's always going to give us a method object instance back. Then we simply bind the implementation of the kernel method method to the object that's passed and then we invoke it. We're basically entirely bypassing the user's definition of the method method at this point. We're saying we don't care like if you're an HTTP library or whatever the hell it is you've defined it as, we're just going to invoke our own implementation which is always correct. The point here is that users can and will redefine core methods at any time. And that's fine because in a lot of cases you can just grab kernel's implementation of the method and use that instead. Nobody screws with kernel don't screw with kernel like you can define stuff on your own objects that's fine but please don't redefine stuff on kernel. The next one is that Ruby interpreters lie. We have this problem where there are a bunch of Ruby interpreters out there and our spec has to support all of them. Some objects don't have kernel in their inheritance chain and the alpha nerds in the room will have had their brains immediately jump to basic object which is true. Basic object doesn't have kernel in its inheritance chain but there's an even weirder class of objects in the Ruby standard library which don't have kernel in their inheritance chain and they look like this. They include a dupe of kernel as the first thing they do. Firstly I'm sure that most of you have never seen anyone actually dupe a module that's a really weird thing to do. The basic reason for this is so you don't have the name kernel in your inheritance chain but you do have all of the behavior of kernel. So looking at this script it's going to work the aspect support method handle for definition which currently looks like this. Now if I run this script on MRI latest I get the exact correct result out I get exactly what I'm expecting but if I switch to JRuby and run it and actually I should say now this is fixed in JRuby this talk was written a while ago before the current release of JRuby I'm not trying to be mean but so I get this explosion it says bind argument as to be an instance of kernel bind at whatever inside JRuby and basically what's going on here is we don't have kernel in the inheritance chain but we're trying to apply one of its methods to an object. This is called rebinding module methods and it's a thing that you're kind of supposed to be able to do but sometimes not really on different Ruby interpreters. As I said aspect has to support basically every Ruby interpreter under the sun. We support MRI versions 187 plus all stable versions of JRuby and even REE how many of you have still got REE apps in production? There's always one, two whatever, basically no one but still enough that we have to support it. One thing that's worth noting here is that aspect does not support Rubinius and that's not of a lack of trying. On both sides we've had conversations with Rubinius team they've had conversations with us it just turns out that like as Charlie Nutter once put it the aspect mocks test suite is like a stress test for the metaprogramming of any Ruby VM and as it turns out Rubinius is like still trying to work out some of those internals and it's not quite there yet. So if you know lots about Rubinius and you're interested in making aspect work there I would be really interested to have a conversation with you, please come find me. Aspect also works on Ruby versions on Windows. This is a Windows spaced CI on the Azure cloud and it totally works which is nuts. Basically we really really try to support basically everything you're going to do so yeah you have a lot of Ruby interpreter support. So module methods in order to support this the aspect support method handle for definition grew to look like this the first thing that's important to note here is that we're actually conditionally defining methods based on the result of this Ruby features support through binding module methods method which actually itself uses the same trick but switching on Ruby interpreter features. Basically we know at Ruby interpreter boot time whether or not we have the features we need to be able to do this and so this is the function that does that if we're running on MRI we can do this if the Ruby version is greater than or equal to two but otherwise we have to create a new anonymous module define a method inside it pull the instance method object out of that module and then try and bind it onto an object which doesn't include that module if that works we define the method to return true but if we get an exception we define it to return false. So anyway coming back up here to Ruby features support through binding module methods if we do then we can simply use the kernel method rebinding trick but otherwise we have to check if kernel is in the inheritance chain of the object and if it is we can use our trick otherwise we have to directly invoke the method method ideally we wouldn't have to do that but at this point there's nothing else we can do. The point here is that Ruby interpreters behave differently and if you're building a gem which gets deployed to everyone it's a really good idea to make sure it works with them. Just to show you this is real there's an object inside standard library called simple Delegator which totally has all of the kernel behavior but does not have kernel inside its inheritance chain. All right moving swiftly on sometimes users don't lie. Try not to actually read this code just instead follow along with the points so here I have an object which has a single public method and on another object I define its respond to and method methods to delegate onto that object if and only if the method that's being invoked on the outer object is defined on the inner object. Basically here I have correct implementations of metaprogramming to delegate method calls down onto other objects if they define those methods. Hi Avdi No no no anyway moving on basically what's going to happen here is because we've overridden the method method rebinding the kernel implementation to it is going to give us an exception because the kernel implementation doesn't know about this delegation logic. Avdi is looking really confused can I keep going is that okay so anyway when this happens we have this weird catch22 situation where we can't deploy our kernel method trick because it doesn't know about the user's implementation of the method method but we can't directly invoke the method method on most user objects because they might override the method method and then we'd explode. So the solution here is a trust but verify kind of situation and our eventual implementation looks something like this. Basically we initially try the kernel method method trick and if that fails it raising a name error we invoke the method method on the user object check it returns a method handle and if it does we return it and otherwise we raise the original exception so that's it that's everything you need to know to pull a method object off a user object in Ruby it looks like this this is the complete implementation of aspect support method handle 4 again don't try and read it that's not the point here so to wrap up this is why I love aspect. I think this is one of the things that is to the credit of the aspect framework beyond many of the others. We will go to extreme lengths to ensure whatever you do to your object our framework does something reasonable when you pass it in that is an extremely hard guarantee to give but it's also one that's totally worth it because it enables aspect to be used in all kinds of legacy code situations it otherwise wouldn't basically if you're struggling with aspect and you're not sure whether or not it's our fault it probably is and please file a bug. We'll be as nice as we can and if it turns out not to be a problem inside of aspect we'll fix up our documentation because it clearly means that you were confused by something that we wrote your gem should be defensive like users are going to redefine core methods on object you at any time and when that happens you should expect it code will be run on interpreters that aren't MRI and interpreters that aren't MRI behave differently to MRI you should expect that too Ruby users are weird. Look at us as a collective group we're strange strange people and that's great but it also means that you have to trust and verify what they're doing so at the beginning of this talk I asked the question what makes a gem good so what about the internals well I would argue that the explanation that I just gave made absolutely no sense I would argue that understanding the internals of aspect requires you to spend literally years of your life learning about how Ruby metaprogramming works but that's fine because most of you don't need to understand the aspect internals what about the interface well you just type allow cat to receive meow and you get the engine coming to life you get everything inside the aspect framework thousands and thousands of lines of metaprogramming code activate in order to ensure that whatever you're trying to do it's just going to work you probably didn't even know all of that nonsense was there until I just showed you it and that's fine because we have a really good interface so what about gems well to me the point of a gem is to provide a strong abstraction boundary between what it is you're trying to do and the internal implementation of how that works and like as Simon Matt met says the wrong abstraction can be really really expensive the worst gems cause an extreme mess you have to monkey patch into them in order to get anything to work and that can be a giant pain in the ass done correctly you can hide huge amounts of complexity behind really well defined barriers and part of that is ensuring that when a user passes an object into your gem it doesn't explode when they've rewritten some core underlying piece of tech right defensively and your user is trying to know what's inside the box they're not even going to look it doesn't matter to them so remember the story at the beginning of this talk where we had our confused junior developer well for me this talk is really born out of like the power of ruby and what you can do with it it's about making sure that our apis and our code is defensive in order to be prepared against the mistakes of tomorrow I mean this is a comparison this is the entire source code of mini test mock as of yesterday don't even bother reading it it's 169 lines of code I just showed you more code than that inside our spec and that's not even a complete feature and that's a trade off there are definite trade off there you can understand mini test way more easily than you can understand our spec but there are differences in what you can and cannot do when it comes to mini test, Ryan is nodding which means the thing I just said is correct so I just showed you more code than that for a tiny complete feature that isn't a mocking library and that's fine like these are differences of opinion and you should understand and consider them before picking any of the trade off so I'm done I just have to do one slightly gross please check out a thing I'm working on thing I'm making a SAS called browser path it's about load testing and front-end performance web application stuff so please check that out if you're interested that's it I'm done peace out let's have some questions right so the question is when kernel is the inheritance chain can't you just grab the method of kernel and not care if it's in the inheritance chain the answer is it depends on which Ruby interpreter you're using if kernel is not present on the inheritance chain of your object on some Ruby interpreters you cannot rebind methods taken directly off kernel onto those objects the other problem that you have is like you really don't want to be searching backwards in the inheritance chain of the object because you're going to end up finding all who knows what like especially with how active records and stuff are built so the solution that we have we do our very best and then if it doesn't work we give up turns out to be fine on nearly every Ruby interpreter now and it's the case that like the number of people who are still running on aging Ruby interpreters is going way down so it's not too much of a concern but at the end of the day like our solution is the most reliable way I have ever seen to pull a method object any object in Ruby so it works if you can find a way to break it please let us know because we'll try and fix it so yeah it's reliable until you can prove that it isn't alright thanks very much