Extremely Defensive Coding
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 66 | |
Author | ||
Contributors | ||
License | CC Attribution 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor. | |
Identifiers | 10.5446/37523 (DOI) | |
Publisher | ||
Release Date | ||
Language | ||
Producer | ||
Production Place | San Antonio |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
Ruby Conference 201552 / 66
8
10
11
14
18
19
21
22
23
25
26
27
30
31
35
36
38
40
41
42
46
47
48
51
52
54
55
57
60
61
62
64
66
00:00
Coding theoryMachine codeFeedbackEvent horizonSoftware frameworkProfil (magazine)Multiplication signCore dumpSampling (statistics)DatabaseInformation technology consultingOscillationClient (computing)WebsiteWindowPoint (geometry)TwitterPower (physics)Mathematical analysisUniform resource locatorMathematical optimizationComputer animation
01:50
Computer programmingMereologyDifferent (Kate Ryan album)Software developerRange (statistics)Formal languageLevel (video gaming)Multiplication signPlanningComplete metric spaceDefault (computer science)Object-oriented programmingClosed setObject (grammar)Machine codeQuicksortComputer architecturePhysical lawFitness functionLecture/Conference
04:30
ConsistencyCASE <Informatik>Object-oriented programmingWordIdentity managementString (computer science)Set (mathematics)ConsistencyBitLibrary (computing)Hash functionFocus (optics)Function (mathematics)Pairwise comparisonStandard deviationSoftware developerTotal S.A.Right angleVideo gameCausalityComa BerenicesGoodness of fitComputer animationLecture/Conference
06:39
Interface (computing)Range (statistics)Wind tunnelMilitary baseDifferent (Kate Ryan album)Range (statistics)Machine codeInterpreter (computing)Pattern languageInterface (computing)MereologyHybrid computerSoftware developerCartesian coordinate systemTerm (mathematics)Functional (mathematics)Multiplication signImplementationProgramming languageSource codeOrder (biology)Library (computing)Electronic mailing listScaling (geometry)CASE <Informatik>Uniform resource locatorFitness functionNetwork topologyWebsiteDegree (graph theory)Point cloudPower (physics)Computer animationLecture/Conference
10:04
MultiplicationLie groupPoint (geometry)Lecture/Conference
10:45
Suite (music)Object-oriented programmingImplementationObject (grammar)Suite (music)Process (computing)State of matterSoftware testingProduct (business)CASE <Informatik>Machine codeConsistencyPhysical systemLecture/Conference
12:47
Computer-assisted translationCASE <Informatik>Key (cryptography)WordSoftware testingOscillationSpherical capObject-oriented programmingMathematicsMessage passingImplementationHypothesisComputer animation
13:38
Algebraic closureInstance (computer science)Variable (mathematics)Library (computing)Standard deviationObject (grammar)Algebraic closureStatistical dispersionObject-oriented programmingInstance (computer science)Online helpSymbol tableString (computer science)Cost curveLecture/ConferenceComputer animation
14:28
Object-oriented programmingSocial classObject (grammar)Instance (computer science)Software testingParameter (computer programming)System callPhysical systemOffice suiteSoftware bugLie groupMachine codeMathematicsFerry CorstenInternet service providerComputer animationLecture/Conference
15:48
Coding theoryDemosceneTask (computing)Software bugAbstractionCASE <Informatik>Object (grammar)DemosceneImplementationSource codeEvoluteWrapper (data mining)Error messageVotingObject-oriented programmingComputer animationLecture/Conference
17:11
Library (computing)Object-oriented programmingSource codeMachine codeLie groupMathematicsRight angle
17:56
Multiplication signWordOrder (biology)CASE <Informatik>ImplementationClient (computing)Object (grammar)Formal languageExpression2 (number)Instance (computer science)BitComputer animation
18:53
Instance (computer science)Module (mathematics)Kernel (computing)ImplementationExpressionInstance (computer science)2 (number)Kernel (computing)MassLatent heatObject-oriented programmingSocial classImplementationException handlingDifferent (Kate Ryan album)MultilaterationModule (mathematics)Object (grammar)Order (biology)Keyboard shortcutMathematicsGame controllerComputer animationLecture/Conference
20:19
Core dumpImplementationKernel (computing)Library (computing)Kernel (computing)ImplementationObject-oriented programmingPoint (geometry)Core dumpLie groupCASE <Informatik>Object (grammar)Lecture/ConferenceComputer animation
21:06
Object (grammar)Kernel (computing)ChainInheritance (object-oriented programming)Object (grammar)ChainGame controllerObject-oriented programmingInheritance (object-oriented programming)Standard deviationLibrary (computing)Sinc functionKernel (computing)Social classAlpha (investment)Computer animationLecture/Conference
21:37
Kernel (computing)Inheritance (object-oriented programming)Object-oriented programmingParameter (computer programming)Instance (computer science)Keyboard shortcutOcean currentScripting languageExpected valueChainModule (mathematics)WebsiteLecture/Conference
22:43
Module (mathematics)BuildingHydraulic jumpMenu (computing)Ultraviolet photoelectron spectroscopyInterpreter (computing)Different (Kate Ryan album)Stability theorySpecial unitary groupRevision controlMobile appData conversionProduct (business)Computer animationLecture/Conference
23:21
MetaprogrammierungStress (mechanics)Software testingData conversionSuite (music)Reduction of orderComputer programmingWind tunnelInformation securityPoint cloudWindowSpacetimeLecture/Conference
23:58
Physical lawModule (mathematics)Point cloudWindowRevision controlOrder (biology)Interpreter (computing)ResultantLecture/ConferenceComputer animation
24:24
Inheritance (object-oriented programming)ChainKernel (computing)Instance (computer science)Module (mathematics)BootingKeyboard shortcutInterpreter (computing)Object-oriented programmingFunctional (mathematics)Revision controlResultantException handlingReverse engineeringBuildingIdeal (ethics)Point (geometry)Source code
25:38
BuildingPoint (geometry)Interpreter (computing)MathematicsObject-oriented programmingStandard deviationLibrary (computing)Real numberInheritance (object-oriented programming)Kernel (computing)Lie groupShared memoryDependent and independent variablesComputer animationLecture/Conference
26:09
Object (grammar)Object-oriented programmingDependent and independent variablesPoint (geometry)Computer programmingImplementationMathematics
26:47
ImplementationFormal verificationException handlingObject-oriented programmingKernel (computing)ExplosionObject (grammar)Computer animationLecture/Conference
27:35
Object-oriented programmingException handlingImplementationKernel (computing)Complete metric spaceReading (process)Point (geometry)Lecture/Conference
28:08
BlogMachine codeSoftware frameworkComputer fileLengthObject-oriented programmingOscillationComputer animationLecture/Conference
28:57
Machine codeFormal verificationMultiplication signMachine codeInterpreter (computing)Object (grammar)Core dumpMachine codeGroup actionGoodness of fitWind tunnelLecture/ConferenceComputer animation
29:31
Interface (computing)Kolmogorov complexityOrder (biology)Line (geometry)Computer-assisted translationInterface (computing)MetaprogrammierungType theoryVideo gameMachine codeSoftware frameworkComputer programmingOscillationWind tunnelProgram codeLecture/ConferenceComputer animation
30:34
Boundary value problemAbstractionKolmogorov complexityAbstractionPoint (geometry)Boundary value problemImplementationCuboidCore dumpObject-oriented programmingOrder (biology)MereologyComplex (psychology)Patch (Unix)Matching (graph theory)Computer animation
31:27
Machine codePower (physics)Entire functionOrder (biology)QuicksortSource codeMachine codeSoftware testingLine (geometry)Pairwise comparisonComputer animationLecture/Conference
31:53
OpcodeLine (geometry)Library (computing)Different (Kate Ryan album)Line (geometry)Web 2.0Machine codeObject (grammar)Software testingFront and back endsSoftware as a serviceStructural loadLibrary (computing)Web applicationWeb browserPlastikkarteLecture/ConferenceComputer animation
32:54
Cellular automatonKernel (computing)Inheritance (object-oriented programming)NumberInterpreter (computing)Object-oriented programmingRow (database)CASE <Informatik>ChainPoint (geometry)Right angleWave packetObject (grammar)Lecture/ConferenceComputer animation
34:41
Storage area networkVideoconferencing
Transcript: English(auto-generated)
00:16
defensive coding. To introduce myself, my name is Sam Phippen.
00:21
Hi Avdi. Hi people. I'm Sam Phippen on Twitter, Sam Phippen 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.
00:41
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,
01:01
please do let me know because 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.
01:22
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.
01:43
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 pertinent question than just getting you all to clap, cheer, and applaud,
02:00
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.
02:23
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,
02:40
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
03:02
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,
03:22
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,
03:42
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
04:03
that absolutely floored 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,
04:21
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? I'd said that sometimes it's okay and sometimes it's not, but I hadn't really given a clear explanation
04:41
for when that was the case. And so I sort of ummed and ahhed, 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
05:02
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,
05:22
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 gonna give a quick example and move on.
05:41
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.
06:02
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.
06:25
So I'm gonna 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,
06:40
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?
07:00
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
07:22
that makes up Ruby on Rails, or Sinatra, or Rake, or RSpec. So clearly 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.
07:41
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 gonna 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
08:01
than I were to do the equivalent implementation within my 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
08:22
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,
08:40
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,
09:02
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.
09:24
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, like, a hybrid Ruby deployment in different parts of the cloud, or whatever,
09:42
and so, if you're gonna 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,
10:01
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.
10:22
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,
10:41
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'll be fine. As an RSpec user, when I stub an object, I want the original method to be on that object
11:02
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,
11:24
according to the last year's Ruby survey. When I stub an object, that's the process of doing allow object to receive foo, or if you're still on legacy RSpec, object.stub.foo, or whatever,
11:40
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, instead 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
12:01
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
12:23
in your production systems, and therefore 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 an inconsistent state with how it would be otherwise in the system, so we need to do that reset.
12:41
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,
13:01
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,
13:22
and then put the original method back on the cat object. So during the execution of the test, you'll have the faked 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.
13:43
So if you head over to the standard library documentation and you look really, really close at the documentation for object, you'll find this method called method. The definition of method according to the documentation
14:00
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's object instance, so instance variables and the value of self
14:22
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.
14:42
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,
15:03
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 RSpec, because it means we can put that method somewhere else,
15:20
execute our test, and then put the method back at the end of the test by using the method object and basically just shoving it back on the object. We've saved the method using the method object, and then we can return it later. Now, I wish I could tell you that that was the whole story. I wish I could tell you that it really was that simple,
15:41
but unfortunately that would be a lie, and this would be the shortest talk at the conference, so now I have to talk about defensive coding, and this is where it's going to get slightly mad. So in RSpec, we like to design abstractions over common tasks in Ruby where we need to fix bugs
16:04
and basically provide places to hang user-facing behavior. For the rest of this talk, we're going to be discussing a method called RSpec support method handle four. This is basically our wrapper around the method method to catch a bunch of user error cases.
16:24
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 RSpec support method handle four
16:43
through our git history, roughly altered and made linear for various branching reasons and cutting a couple of scenes out. So 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.
17:04
So the first implementation of RSpec support method handle four you can find anywhere in the RSpec source history looks like this. RSpec support method handle four is defined to take an object and a method name. It simply invokes the method method on that object with the method name.
17:23
Basically, what we've done is we've just wrapped the method method in an RSpec method, and actually in the place where the code actually was, this was just object dot method method, but story. So the first problem we have here is that users lie.
17:41
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 gonna define a method called method
18:01
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 directly invoke that method in order to get method objects back.
18:22
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, oh. The word method means something in the language of HTTP.
18:41
That is a domain-specific word in HTTP that definitely should be allowed to be defined by clients. And as it turns out, it's actually not too hard to deal with this case. So the second definition of RSpec support method handle for you see anywhere in the git history looks like this. We define a constant called kernel method method
19:04
which gets assigned to it the value of the expression kernel dot instance method method. So we've seen the method method already. Let's talk about the instance method method. The instance method method is like the method method
19:21
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
19:41
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
20:00
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 implementation 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
20:21
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 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
20:42
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,
21:02
that's fine, but please don't redefine stuff on kernel. Okay, so the next one is that Ruby interpreters lie. We have this problem where there are like a bunch of Ruby interpreters out there and RSpec has to support all of them. Some objects don't have kernel in their inheritance chain.
21:21
And like 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
21:41
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 gonna actually invoke
22:02
the RSpec 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.
22:21
This talk was written a while ago before the current release of JRuby. I'm not trying to be mean. So, I get this explosion. It says bind argument must 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
22:42
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, RSpec has to support basically every Ruby interpreter under the sun. We support MRI versions 187 plus,
23:02
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. There's like basically no one, but still enough that we have to support it. One thing that's worth noting here
23:21
is that RSpec does not support Rubinius, and that's not out of a lack of trying. On both sides. We had conversations with Rubinius team, they had conversations with us. It just turns out that like, as Charlie Nutter once put it, the RSpec mocks test suite is like a stress test for the meta-programming
23:42
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 RSpec work there, I would be really interested to have a conversation with you. Please come find me. RSpec also works on Ruby versions on Windows.
24:03
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.
24:20
In order to support this, the RSpec 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.
24:43
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,
25:00
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
25:22
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
25:40
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.
26:03
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
26:23
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 meta-programming to delegate method calls down onto other objects if they define those methods. Hi, Abdi.
26:42
No. Sorry. No. Anyway, moving on. Basically, what's gonna 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
27:00
doesn't know about this delegation logic. Abdi is looking really confused. Can I keep going? Is that okay? So anyway, and when this happens, we have this weird catch-22 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
27:22
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,
27:40
and if that fails at 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
28:00
of RSpec support method handle four. Again, don't try and read it. That's not the point here. So to wrap up, this is why I love RSpec. I think this is one of the things that is to the credit of the RSpec framework beyond many of the others. We will go to extreme lengths to ensure whatever you do to your object,
28:22
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 RSpec to be used in all kinds of legacy code situations. It otherwise wouldn't. Basically, if you're struggling with RSpec
28:41
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 RSpec, 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
29:02
on object out from underneath 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.
29:21
Look at us. As a collective group, we're a 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
29:41
that I just gave made absolutely no sense. I would argue that understanding the internals of RSpec requires you to spend literally years of your life learning about how Ruby meta-programming works, but that's fine, because most of you
30:00
don't need to understand the RSpec 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 RSpec framework, thousands and thousands of lines of meta-programming code activate in order to ensure
30:21
that whatever you're trying to do, it's just gonna 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
30:42
between what it is you're trying to do and the internal implementation of how that works. And like, as SaniMet 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,
31:00
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.
31:21
Write defensively, and your users are never gonna 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 the power of Ruby and what you can do with it.
31:41
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, just to sort of quickly draw a comparison, this is the entire source code of Minitest mock as of yesterday. Don't even bother reading it. It's 169 lines of code.
32:02
I just showed you more code than that inside RSpec, and that's not even a complete feature, and that's a trade-off. There are definite trade-offs there. You can understand Minitest way more easily than you can understand RSpec, but there are differences in what you can and cannot do when it comes to mocking objects in Minitest. Ryan is nodding,
32:21
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. These are differences of opinion, and you should understand and consider them before picking any of the trade-offs. So I'm done.
32:41
I just have to do one slightly gross please check out a thing I'm working on thing. I'm making a SaaS called Browser Path. It's about load testing and front-end performance, web application-y stuff, so please check that out if you're interested. That's it. I'm done. Peace out. Let's have some questions.
33:09
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.
33:21
If kernel is not present in 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 you really don't want to be searching backwards in the inheritance chain of the object because you're going to end up finding
33:42
oh, who knows what, especially with how active records and stuff are built. So the solution that we have, where 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,
34:00
and it's the case that 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, our solution is the most reliable way I have ever seen to pull a method object off any object in Ruby,
34:21
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. All right, thanks very much.