 Thank you. Good morning. It's so nice to see all of you. This is literally my favorite thing to do. It's to stand in front of a bunch of brilliant, talented, beautiful Rubius 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 bot 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 Emacs fans in the audience? Wow. Okay. I mean, I kind of like Emacs, 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. Here's what 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 let me, let's, let's, 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 happens. Let's do that again. I'll get rid of the fail. When they do work, they go, 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 was 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, too. Once you get the gist of that. Okay. So just a quick overview. This is a class that sends invitations. It takes a CSV of people we 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. Was that your response? Often code I wrote makes people say ouch. Yeah. What's ouch to you about it? Yeah. So the one feedback was what if my, 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 retrial jobs, I hope. Other thoughts on this code? Yeah. You are, you're, 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's, that is exactly what I was hoping you would say. So to me, so this is, 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 will look at this and say, okay, we take, we take in a CSV full of unparsed data, we parse it, and then we send, 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. Or I shouldn't say that 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, so we're going to do the extract class refactoring 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 refactoring? 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 my first step is I like to sort of write the code I wish I had, right? This is kind of a classic TDD trick, and we are going to be TD in 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 passed, you don't need to see anything, right? Like, 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 keystrokes 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, and you type in like our spec and then you tab complete a file name, and then you type a line number you 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 editor activity, it should be in the editor activity. So let's start writing what the 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. Parsing is 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 moving even smaller steps. 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, right? 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 passed. 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. So 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 re-run the test for invitations spec, for the invitations. So I run the invitations 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. Come on. 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, so 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 is a difference between move method when you move a public method and when you move a private method. You okay? Cool. So 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? Sandy says yes. Yes! It's nerve-racking. Sandy Mets is sitting right there. I know if I mess up, she's going to come yell at me. She's an aggressive person. You don't know that about her. Like, physically, like, she'll hurt you. You test a private method directly. Sandy Mets will seriously, like, have 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 in 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. It's actually deliver. That's embarrassing. Cool. So it sends emails to all the invi-ties. 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 active support. 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're 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 have received two method calls for invitation for each user in our CSV. So again, now that we're extracting a parser, this has kind of become 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, that tests our recipients method directly. So let's grab this. I use visual mode which is a total smell. You shouldn't do it but I did it because my screen's too small. Let's print this. Yeah, there we go. Juniel's here too. Juniel, Vim, master, extraordinaire. There's a little intimidating audience to write code in front of, using Vim and doing oh oh in front of Juniel and sanding mats, you know. All right. So let's change this around. We're not going to stop the mailer. That 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 store 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. Just kidding. We need to close the equals and whatnot. Good. Believe it or not, that's what we want. Okay. Wrong number argument. 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 wrong number 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 initializer. Absolutely. So this class needs to take a, oops, I have a snipper for this. It's my favorite thing. CSV. What? Hold on to him. It goes together. There we go. I will come back there. I will turn this talk around. Okay. We're going to rerun our test. And notice I just did something stupid, but I'm going to be harping on these fundamental little things, by the way. All I did was have, 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 have 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 want to comment this. Are you ready to test? And it blows up. Ah. Does anybody know why that blew up? That's not a great error. Can you figure it out? It's admittedly a little bit confusing. Just shout it out. Mm-hmm. Exactly. Yeah, we are, we're calling, we have, 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 be 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 code we wrote. 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. It's because of that. Great. And now we're green again. We're using a 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 test 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 like 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're getting a lot of trouble, we know we can rescue. I do that when I'm working by the way. Like whip commits, work in progress, WIP are underrated. They can save you. Okay. So let's look at our test 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. Or I think component test is maybe the better word, component test, meaning like 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 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. Oops. Let's grab these five lines and throw them here. And what do we need to do? We need to we want 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're going to will 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. It's 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 come, you know, 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. Okay. So we went to submit parser. Now I need to actually say, Hey, when the invitations class uses this parser, use this fake one. So I have a little 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 method, 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, 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's your bringer. This little, if you want to tap, this is just 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 this 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. Yep. Other, other benefits. Why might you stub out collaborators like this? Why am I going through all this work? Yeah. You can swap out CSV. Yep. We can swap out messages. So for instance, I have, it's, 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 when you, if you give the same string. But let's imagine this was something that used time or hit an 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 of handling like 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 to 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. We pass here like 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 an 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? 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 kind of my last resort things. What I typically find is when I pull stuff into a before, what I typically get is 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 this weird thing where I need to go 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 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 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 invert 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, this is 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. Oh, thank you. This is why it's great to pair with 400 people simultaneously. Because all errors are shallow when you have 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 it off in the editor, it's all on my .files, go check it out. So I just ran our sort of integration-y spec that's test 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. Okay, rather than passing in a CSV file, let 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, right? 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! A back-up-up into a corner. They're maybe, they're often wrong, right? 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 in a parser. What are message in parser? Who knows, right? I'm just gonna 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 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. Like 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. And this is also interesting. If you're into solid in general, which you should check out, solid, as a set of principles, this makes it easier to follow something called open-closed principle, because it lets you... So basically, on apps where I followed 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-closed 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 if I have a couple 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.