 Thank you. Good morning. It's so nice to see all of you. This is literally my favorite thing to do is to stand in front of a bunch of brilliant, talented, beautiful Rubyists and talk to you about programming. This is my happy place. I'm also really, really excited to be sharing this stage with amazing speakers. I feel like this lineup is kind of ridiculously good and ridiculously intimidating. It's kind of awesome. Also, I'm super psyched to notice that 33% of our speakers are thought about employees, which I think is kind of cool. And if I can just recruit one more before the end of the day, that's 50%. So, Linda, I mean, think about it, Sandy, you know, Tom, anybody, talk to me at the break. So, I, like Linda, am a higher education dropout and mediocre programmer. But I'm trying to get better. I'm trying to become a better programmer. And one of the ways I'm doing that is to focus on the fundamentals. So, this talk is going to be a live coding talk. We're going to write some code here. I'm going to refactor it in front of you. But the point of all this is actually not for me to reach the end. It's not for me to, for you to just sit there and listen. The point is interaction. Imagine that we are all pairing. I'm driving. And you are navigating. So, please talk. Please give feedback. Please ask questions. I want to hear from you. My bar for success of a talk is how much back and forth we have. I want to have conversations. I want to stop and talk about things and delve into little side things. I love that. It's my favorite thing. So, let's do that. Let's get started. Let's look at some code. This is, by the way, live coding in the bath. Can I have a monitor? There it is. Excellent. Reason Vim, by the way, obviously, right? Any Vim fans in the audience? Yes. Excellent. Any E-max fans in the audience? Wow. Okay. I kind of like E-max too. It's nice. Okay. So, please read this code. I can bump the font just a little bit. So, here's something I realized. It looks like when I run my tests. At this font size, it actually works. But if I bump up the font even more, it doesn't work. So, I'll prove it to you. So, here's a fail. And when my tests fail, it looks like this. You can see at the bottom, there's the failure. And here's what happened. Let's do that again. I'll get rid of the fail. When they do work, that goes away. But I'm going to add the fail back in there. And I'm going to bump my font one more time and see what happens. So, unfortunately, this is the max font size right here. I'm really sorry. This is kind of one of those things I don't know how I could have anticipated this exactly. So, please bear with me. My apologies for any hardness to read. But, please squint and read this code. I'm going to scroll down for you two once you get the gist of that. So, just a quick overview. This is a class that sends invitations. It takes a CSV of people who want to invite somewhere and a message that we want to send each of them. For each of the recipients, which we'll get to, we create a mailer. We send a mail to their name and their email. We include the message and then we deliver it. And the way we get the recipients is by parsing the CSV that comes in and turning it into a hash. So, how do we feel about this code? Any comments? Initial impressions? What's that? What's that? Ouch! Is that your response? Often code I wrote makes people say, ouch, yeah. What's ouchy about it? Yeah, so one feedback goes, what if parsing the recipients fails? And I'm sort of in the middle of delivering, so I'll send like 50 emails and then I'll hit a parse error. And then like, what happened to the rest? Well, who knows, you know, air break exceptions. Not sure. Yeah, that's good feedback. That's good. So, this is a man who sent a lot of email, I imagine, probably through background jobs with retriable jobs, I hope. Other thoughts on this code? Yeah. You're about 25 minutes ahead of exactly where we are, so nice job. His feedback was, he expected CSV to be an injected dependency and he thought it was weird to be sending in a CSV full of names and then parsing it in there. Is that a fair summary? That is exactly what I was hoping you would say. So, to me, this is not ridiculous code to me. This is code that I actually probably would write as a first pass. So, my general approach to solving any problem is to first make it work, make the test pass, and then make it beautiful later. So, I would look at this and say, okay, we've taken a CSV full of unparsed data, we parse it, and then we send email for each of those things. And that sounds like very much like we have multiple jobs in this class, right? There's multiple responsibilities here. And we all know that we want to follow something called SRP, single responsibility principle. We shouldn't say we all know. You might consider following SRP. It sometimes can make your code better if your classes have a very focused job. And so often, I will find myself in this position where I'll muddy up a class with another responsibility, and I'll say, okay, now it's time to extract it. So, let's extract this code. And we're going to do the extract class we're acting right now. We're going to do it sort of slowly, and I'm going to try to do it as cleanly as possible. And this is how I try to do it day to day. Again, we're focusing on the fundamentals. It's going to get a little more complicated soon, but we're going to start with some simple stuff. So what's the first step in the extract class we're factoring? Anybody? Name the class that's reasonable. Yeah. The parsing of the recipients. What does that mean for a first step? Okay, so there's an instance variable for the recipients instead. Yeah. So for me, the thing I like to think about for my first step is I like to sort of write the code I wish I had. This is kind of a classic TDD trick, and we are going to be TDDing this. By the way, I have tests. This is what it looks like when the tests run and they pass, all the thing pops up and it goes away. If they pass, you don't need to see anything, right? That's just no information needed other than success. When they fail, that little box down there will stay with the error message. And by the way, notice that it takes me two key strokes and basically no time to run my tests. And that's important. If you're doing TDD and the way you run a test is you switch over to a different terminal or a different team accession or a different whatever, type in like RSpec and then you tab complete a file name and then you type a line number and enter. You're kind of doing TDD, but not really. People that tend to do that will abandon TDD and say it's too slow. And it's like, well, TDD isn't too slow. What you were doing is too slow. So running tests should be, do not leave the error activity. It should be in the error activity. So let's start writing what code we wish we had. I wish we had a parser. Whenever I have to parse something and something else, I always want parsing to be on its own. There's one of those great tasks you can just always pretty much draw a box around it and say parsing happens over here. Great job for a single isolated class. So I wish we had a parser. What do we do now? Run the tests. Correct answer. What happens? On a new slide is constant parser. That's pretty fair. I mean, I just made it up on the spot. So let's make a parser. Is the next task, is the next thing we do, do we make a parser? What's that? Just the class. I'm going to go even one step a little bit more. I'm going to move an even smaller step. I'm going to make the spec first. I know I'm going to have a parser. I want targeted specs for the parser. So the next thing I'm going to do is make some specs. Spec, parser, spec. I'm going to run this again. I get the same failure. This is actually great. Because I'm extracting a class from an existing class, the unit tests for the invitations class are now basically an integration test. The integration test, this invitation class and the new parser class I'm creating. And so when I start to create a, when I'm writing code in general, I start from an integration test. I let that fail and then I start writing a unit test and watch and make those past. And we're going to jump back and forth between the tests for these different files and take turns and make sure that the message in them stays similar. So we have a test for our parser. We get this uninitialized constant parser. I'm going to vertically split this. We'll see how that works. We don't have a ton of resolution to play with. But here's our parser. This is how I work most of the time in like 640 by 480. It's pretty awesome. Okay. That right there, that was that test passing. See how it went away out of our face? That means, okay, now there's in fact a constant called parser. And we found it. So now let's go back to here and rerun the test for invitation spec, for the invitations. So I run the invitation spec and those passed. Those happened to stick around. Okay. So now we're green again. So again, next step, write the code we wish we had. Now that we have this parser, it doesn't do a lot, but we have it. I wish in recipients I could just ask the parser to handle the recipients like that. So let's run our spec again and see what we get. No method error. Undefined method recipients for parser. I should give you a second to read these errors. I've done this talk a couple of times. I might be able to read them faster than you. No method error. Undefined method recipients on parser. That's reasonable. It's a brand new class. It has not that many methods. Except what Ruby gives us and certainly does not have a recipients method. So now we need to drive out the behavior of the recipients method on our new parser class. So at this point, we basically are moving, we're in the middle of extract class as a refactoring. But we're actually going to now descend one level of abstraction. Or I guess, yeah, sort of. We're going to push one level down the stack into the refactoring of move method. We basically want to move the contents of this recipients method into the parser class. So I actually kind of have a test for this already. So here's the tricky thing. There's a difference between move method when you move a public method and when you move a private method. You okay? Cool. And because we're moving a private method, we actually only have tests that sort of indirectly test that behavior. So I write direct tests for all my public methods on my classes. But for my private methods, I do not test them directly. I let them get tested implicitly as part of testing the public methods. Are you okay with that? Yeah. Sandy says yes. Yes. It's nerve wracking. Sandy is sitting right there. I know if I mess up, she didn't come yell at me. She's an aggressive person. You don't know that better. Like physically, like, she'll hurt you. You test a private method directly. Sandy will seriously like, I've had it happen. Okay. Just kidding. She's super nice. She's the nicest. Ridiculously nice. Okay. So we are going to, so here's what I'm going to do. I'm going to do every programmer's favorite thing and we're going to copy and paste. It's going to be great. So I'm going to use the tests for the deliver method and invitations. This one right here, which is only public method to drive out the behavior of this private method that we're pulling out into our new parser class. So let's go look at the tests. This, by the way, are the tests for invitations. This should be, oops. This is actually deliver. That's embarrassing. Cool. So it sends emails to all the inviities. We first stuff out the mailer. We'll come back to that. I make a CSV. By the way, this little dot strip here doc thing, I stole this from ActiveSupport. It's super handy. Have you ever had a here doc and you have to jam it all the way over to the left and it looks totally goofy? You familiar with this idea? If you're not, look up here docs. And then once you look up here docs, look up strip here doc and you'll have an awesome pair of wonderful power. Okay. So we grab a CSV. We throw that CSV into the invitations class with a string and then we expect the mailer to receive two method calls for invitation for each user in our CSV. So again, now that we're extracting a parser, it's kind of become a like a component test. It's become a test that tests multiple classes at once. And so I'm going to copy over this test and I'm going to fiddle with it so that we have something that works for the tests are our recipients method directly. So let's grab this. I use visual mode, which is a total smell and you shouldn't do it, but I did it because my screen is too small. Let's print this. Yeah, there we go. The mailer is here too. Juniel, vim master extraordinaire. There's a intimidating audience to write code in front of using vim and doing OO in front of Juniel and standing mats. All right. So let's change this around. We're not going to stop the mailer. It doesn't happen anymore. We still have this CSV string. I still want to do that. But now we're not going to check if messages get sent. We're in fact going to check the result. So we're going to say, Hey, parser, here's your CSV and I want you to call the recipients method and then sort the result off. And then I'm going to expect this result to equal an array of hashes. So let's say that's going to look like name. And that goes to user one and your email. I have to use string keys here. You'll see why in a moment. One at example. Okay. So that's our first hash. There should be two. This should be to user two. That should be to user two. Let's lose that. And if all has gone well, which probably hasn't, we should get an error and not a syntax error. Ha, just kidding. We need to close the equals and whatnot. Good. Believe it or not, that's what we want. Okay. Run over arguments. So just a recap. So I'm calling, I have a CSV, a string that represents a CSV and CSV pretty simply has an email and name in it. This email and name and that email and name. We ask the parser to parse it given that string. And we expect the result to be an array of hashes with name to name and email to email. And we have a failure. So just a quick recap. We had an integration type test. We pulled it in here. We modified it. So we're testing this method more directly now. Let's now fix this argument error. We have a wrong number of arguments, one for zero. Anybody know how to fix that? Yeah. Confidence. Someone's like, yep. I got it. How do we fix it? What do we do? He's like, oh, I have to actually answer the question. What's that? We need an initializer. Absolutely. So this class needs to take a, whoops. I have a snipper for this. It's my favorite thing. CSV. What? Hold on, Vim. Get it together. There we go. We'll come back there. I will turn this talk around. Okay. We're going to rerun our test. I just did something stupid. I'm going to be harping on these fundamental little things, by the way. All I did was have that, I had an error which said, hey, I'm calling initialize with one thing and you don't accept that. And so all I did was say, okay, initialize takes that one thing. And then I just throw that result on the floor and move on with my life. Because all I want to do is get that message to change. That's it. I want that message I got from my test to be a new message, to give me something to tell me the next thing to do. So I'm intentionally moving in very small pieces, very small steps. Next, unify method recipients for parser. Okay, no problem. I don't want to make that work. Actually, let's do it this way. So now I could go right this recipients method, but what I'm actually going to do is I'm going to steal the recipients method from parser or from invitations. But I'm going to comment it out. Because the test just told me there is not a method called recipients right now. And so I'm just going to make a method called recipients and then let the test tell me what it actually needs to do. And here we have our first actual test failure as opposed to test error. We expected the result to be an array of hashes that look like the array of hashes I typed up there. But we got nil because the method has no body. So we had to comment this. We ran our test and it blows up. Can anybody know why that blew up? This is not a great error. Can you figure it out? It's admittedly a little bit confusing. Just shout it out. Exactly. Yeah, we're calling. See, we have at CSV in here. And because we made that small step up here, we didn't actually assign it. But if we do that, hey, green. Excellent. So we have just extracted. We're working on extract class. As part of extract class, we did move method. We're making some good progress. Now let's see. Let's come back to this is parser spec. We're good. Invitations. We're good. Now I want to come back to the invitation spec. But first questions, comments, concerns. How are you feeling? How's your mental state right now? As my pair, I need to know how you are. Yes. We have a woo. Yes, sir. We're still not using the parser. Totally right. We should come back and use the parser. I think that should do the next thing. Other comments, concerns, thoughts. Yeah. Great question. Would I inject a double instead of using the actual class or object? And we were going to do exactly that in just a moment. That's a great question. Perfect question. Awesome. So let's actually use our parser. Crazy idea. Let's use the Kobe row. So let's get rid of this. And let's run the invitation spec, which is over here. And it blows up. And we know why. Or I know why. Because of that. Great. And now we're green again. We're using our parser. We've extracted it. Hopefully your mental state is now great because we are now actually delegating to the parser correctly. Here's your test. Here's the parser. Things are going well. This is good. So let's go. There's a quick change we can make to invitations, which is this. We don't need to store off CSV anymore, right? Because we pass it right into the parser. And that's actually a little bit weird. Let's go look at our tests now for invitations. We'll lose the parser for a moment. Come back to here. Invitation spec. You know what I just had the impulse to do that was really strong? Anybody know? Commit. You know? It's like, if things go all bad, okay. So no matter how much trouble we get into, we know we can get back to there. Tests are green. We're feeling pretty good. So let's make sure. Yep, green. Everything's green. Okay. So if we get in a lot of trouble, we know we can rescue. I do that when I'm working by the way. WIP commits work in progress. WIP are underrated. They can save you. Okay. So let's look at our tests right now and see how they make us feel emotionally. I think the tests are a little smelly. So right now, this is still basically an integration test. I think component test is maybe the better word. Component test meaning testing multiple components simultaneously. I'm pretty sure that's the right terminology. I might refer to them as integration test again. But we are testing the parser through the invitations now. And we already have unit tests for the parser. So what I'd actually like to do now is isolate the invitations from the parser. Because right now they're very much, they're joined together as we test them. And I like to have the invitations tested independently of its collaborators. Now we're already doing this with this stub mailer business up here. So here we're creating a double mailer. And we're allowing it to receive invitation and return this double. So we've already isolated the invitation from mailer. And that lets us do expectations like this or assertions like you must have received this message. And I want to have a similar situation for parser. So let's start out by extracting a stub parser method from our tests. And some people I think don't actually realize that you can extract methods from our specs. I do it all the time. This is my default first refactoring that I go to to clean up duplication and whatnot. So let's call a stub parser method. And into it we will yank this. Let's grab these five lines and throw them here. And what do we need to do? We need to isolate invitations from the parser. So we need to create a parser double. So here's our CSV. We have a parser. This is going to be a fake parser that we'll stand in for our real parser. We are going to say, hey, parser, when someone asks you for your recipients, we're actually just going to give a canned response of recipients. So actually we're going to change this. We're going to steal from the parser spec. Oops. Let's steal our beautiful array of hashes. Don't worry about that styling. Cool. Recipients equals an array of hashes. That's some nice indentation. Good job. Good job, Vim. That's the automatic indentation that Vim has chosen. Close enough. So for now. This is the kind of thing you come back to before you push it up and ask for the review. It's cool. We make a mess first and then we clean it up. That's my style. Now I need to say, hey, when the invitation class uses this parser, use this fake one. I have a snippet for this. We're going to allow parser to receive new. My snippets are a little bit off. And return the parser. So when I, let's wrap this. When someone calls parser.new with CSV, return parser. And we need a CSV, which is a little bit weird, but we're going to talk about that in a second. And then, okay. So now, if I want to use this, let's see what happens. Here is a smell that we have just bumped into. Undefined local variable or method CSV. Where is that coming from? Well, it is coming from right here. So my invitation needs a CSV, which is kind of actually kind of goofy and was pointed out by someone very astute in the fourth row over here. But for now we need to give it a CSV. So let's actually, for now, just return a CSV from here. Okay. And then let's be fancy. You guys like being fancy? Do CSV. And that'll be here. And that should bring it. If you want to tap, this is going to return the CSV at the end. It lets us refer to it in here and then returns the CSV at the end. So cool. Back to green. So we extracted our stub parser method. But this is extracted. This method has, I think, revealed us interesting smell, which is that we need the CSV and the CSV is basically unused inside invitations, right? That is going to become important. So let's, but let's talk about what we just did. We isolated invitations and parser. Now, why is this good? Who has reasons for why it might be good to stub out a collaborator like we just did there? Yeah. It lets you unit test them separately. Other benefits. Why might you stub out collaborators like this? Why am I going through all this work? Yeah. You can swap out CSV. Yeah. We can swap out messages. So, for instance, I have, it makes it really easy to specify exactly what I want the parser to say. So before I had to give it a literal CSV string and let the parser parse it out. And now I can just say, here's a fake parser to stand in for the real one. And when you ask it what its recipients are, here's a prerecorded answer. Now that's nice. That's nice now. And it's nice at other times, particularly because it lets you make something that's not deterministic, deterministic, right? For recipients, recipients is always going to return the same thing if you give it the same string. But let's imagine this was something that used time or hidden external service. These are all really good reasons that you would want to stub those things so you can always have the same response come back. This is kind of the idea behind something like VCR or fake web or mock web or something like that. Yeah. Right? Web mock. Web mock. There it is. Yeah. Awesome. Okay. So, this is nice now. We've extracted a class. We've moved a method. We've isolated this. I think we're steadily marching towards, like, better code. I think things are good. But there's sort of one nice thing that we can do now. And this is, in fact, just kind of almost the point of the talk is to get to this point, which is I would like to invert control. And this is what was roughly suggested earlier in the talk, which is it's actually kind of weird that we pass in CSV at all two invitations. Now we're passing in CSV. Let's go check out invitations. So here are all the instances of CSV. This should move to the parser. But here, just two. We use it to build the parser. Here's the CSV. And it's like, thanks. Making it a parser. Cool. Wouldn't be great if we just got a parser. It would be great if we just got a parser. There'd be some definite benefits for that. And the process of instead of, so right now, here's what our control looks like. So I'm going to go, let's move up a level to the invitations controller. So we create an invitations controller. This is a fake controller. By the way, I'm totally like pretending this is Rails. It's totally not Rails. I got you guys good. You should see what's an application controller. Jokes galore. This is all online. You can check it out later. But this is fake Rails. This is not real Rails. So here's how control is structured now. We have invitations controller, which creates invitations, which creates a parser, which uses CSV to parse things. So everything kind of points down. This is normal control, like top down control. It's kind of a standard way of thinking about programming. Inverting control flips that around and says what if you instead built your dependencies at the top level and then pass them down to lower levels so that you actually don't see the word new very often in your lower levels. It's an interesting way of building and this is part of solid. You've heard of solid? The solid principles? This is the I. I is inversion of control. This is something that confused me when I first heard of it. Inversion control sounds weird, sounds funky. Not super hard. One way to accomplish inversion of control is with dependency injection. So this just says, rather than giving invitations the CSV and then having invitations build a parser, what if we just build a parser and give the parser to invitations? That is using dependency injection to accomplish inversion of control. And that's going to be our final refactoring step. How's the mental state? Yeah, question. What if we just gave invitations the recipients? What if we just gave invitations the recipients? Yeah. I like that even better. Version two of the talk will in fact probably just pass in recipients directly. We may even get there. We'll see. Maybe we can get that. That could be our final refactoring if we're speedy and awesome. Other questions? What other thoughts, concerns? I said a lot of like large three word things there that are potentially a little scary. How are you feeling? What questions do you have? Great question. Yes. So in the specs, I extracted methods. Could I use before? I could. I don't generally reach to before until later. So my first pass of refactoring specs is always to use normal Ruby things. Use methods. Methods work great. I occasionally will reach for let's and before's, but pretty rarely. Those are like kind of my like last resort things. What I typically find is when I pull stuff into a before, what I typically get is like I add a new test and the new test doesn't really want exactly the world that's in the before. It wants something a little bit different. So it kind of modifies the world a little bit before it runs. And then we have kind of this weird thing where I need to like go like read the test, see the changes of the world, go read the before, figure out what the world is. Okay, see changes here. And I find like bouncing around a lot in my tests and it sort of creates this complexity. So I will occasionally put stuff into before, but it's sort of my like every once in a while refactoring and not a routine refactoring for me. And I find it tends to make my tests a little bit cleaner. Okay. So let's interrupt control real quick. And this is actually going to be pretty straight, pretty quick. So last page. Let's do this in our tests for invitations. Instead of passing a CSV, let's pass a parser. So let's say we're going to get back a parser instead of a CSV. Actually, and then here instead of this, we get a parser. And then we can actually lose this whole CSV thing, which is beautiful. And we can lose a little of indentation. We can stop stubbing parser. And in fact, just return this parser right here. Let's see if we're green. We're so close to green. This is again a weird and crazy error message because of, this is CSV, the built-in standard library CSV, not being super helpful. But basically what we're doing now is passing in a parser here. And then in the class, we're treating that as a CSV. So we're passing a parser to a parser, which is very interesting from a computer science standpoint. And it reminds me of a Tom Stewart talk about decidability. But for our purposes, it's not super helpful. So let's instead just say, hey, actually this is just a parser now. And if we rerun our spec, something blows up. Thank you. This is why it's great to pair with like 400 people simultaneously. Because all errors are shallow when you have 400, 800 eyes looking at it simultaneously. Awesome. So now we've inverted control. And we've also broken something. So if you run, if we do run all specs, which is a nice little VimR spec thing that you can grab. By the way, my .files are online. So if you're curious how I'm getting this cool test thing at the bottom and how I'm doing off in the editor, it's all on my .files. Go check it out. So I just ran our sort of integration spec that's tests through the controller, which means let's go to invitation controller, which I showed you a little earlier. Here we're still passing in this parameter directly. But we actually need to do. This is where the actual version of control is happening. OK. Rather than passing in a CSV file that we read, let's make a parser right here. And the parameters of that will be the CSV file. And we'll read that. And now we can change this to parser. And we can run our tests again. And they pass. That right there was the inversion of control. That's where we switched from, hey, invitations, you build your own dependencies to, hey, invitations, I'm going to build your dependency and give it to you directly. Is this better or worse? Well, like all things in programming, there's a trade-off. Don't ever let anyone tell you that there are hard and fast rules in programming. Anytime someone says something is always good, they're pretty much always wrong. Crap. I'm back myself into a corner. There may be. They're often wrong. Pros and cons. Well, cons are, some people find this a little bit more confusing. You end up with dependencies in a controller. You specify a ton of dependencies in the controller and then dependencies sort of flow down into your system. That kind of weirds people out. So that's one of the cons. It can sometimes be hard. You end up with these classes that are, this class right here takes a message and a parser. What are message and parser? Who knows? I'm just going to take your word for it that parser parses stuff and messages is some sort of message that is message-like. But you can't really see what's going on here. It's actually probably a little bit more obvious when there are class names. But this is maybe a little bit harder to read. Context might be a little bit harder to get. However, testing becomes more simple. You notice how when we switched to inversion of control, I deleted a bunch of crap stubbing from our tests. So the tests can get simplified, which is a big win there. This is also interesting. If you're into solid in general, you should check out solid as a set of principles. This makes it easier to follow something called open-close principle because it lets you, so basically on apps where I follow this, I will have commits adding functionality where it is purely green and there are no changes. It's just new code. And if I want to remove functionality, it's purely red. And that's basically what open-close principle gives to you. It's worth digging into that, playing with that a little bit. But one way of achieving that is using getting inversion of control with dependency injection. And we're towards the end. So I have a couple of closing questions, and then I'll let you head out. So before we go, I just have one, I have a jingle for you that I crafted, I wrote for you to remember the main principle of this talk, which is, if you want to follow SRP, don't manage your dependencies. Thank you.