Ruby on (Guard)Rails
The guardrails I love in the Ruby ecosystem and why you should use and love them too.Presented at Haggis Ruby in 2024.
Show transcript
- 0:00 Hello, thanks everyone for having me.
- 0:22 So if you've seen the schedule, you've seen this is the name of the talk.
- 0:26 But because I'm an idiot and I listen to people I respect, one of my co-workers who's here
- 0:33 today sort of nerd sniped me into just completely changing the talk.
- 0:38 So if you like this talk, I did it before, so you can go to this link and watch a video,
- 0:46 preferably not while I'm doing the talk right now, or at least wear headphones or something.
- 0:52 I've tried to make it so that the bits where you laugh will be exactly the same in each
- 0:56 talk.
- 0:57 So if you wait, we'll start at the same time, then everything should go a little bit better.
- 1:01 This is the actual talk I'm going to do.
- 1:02 I'm going to talk about what I call Ruby on guardrails, which is the way we've been building
- 1:09 stuff at my new gig at Workbrew, and generally the kind of guardrails that exist already in
- 1:16 the Ruby ecosystem.
- 1:17 How we've been leading into them super hard, why I think that's a good idea, why you might
- 1:21 want to try at least doing some of that too, and why you should love guardrails.
- 1:27 So firstly, I want to hear about you.
- 1:29 So can you, there's going to be a bit of hand raising during this talk, I apologise in advance.
- 1:36 If you write Rails stuff mainly for work, can you put your hand in the air please?
- 1:40 Okay, lots of people.
- 1:43 If you write Ruby stuff for work that is not Rails, can you put your hand in the air?
- 1:47 Okay, a few people.
- 1:49 If you write Ruby in your free time, put your hand in the air.
- 1:53 If you haven't already put your hand in the air, put your hand in the air.
- 1:56 Anyone who put their hand in the air in the last batch, just shout out why are you here?
- 2:02 What are you doing?
- 2:03 Like, I'm actually genuinely interested.
- 2:05 Someone shout something out.
- 2:07 We had lunch together, so you can shout something out.
- 2:10 There we go, getting back into it.
- 2:13 Great reason.
- 2:14 Thank you very much, sir.
- 2:15 Right, so, me, I got a lovely introduction from James already, but I'm going to introduce
- 2:19 myself as well, because why not?
- 2:22 I've been spending too much time around Americans, so, you know, you need to big yourself up.
- 2:26 You need to sell yourself, all that type of stuff.
- 2:29 So, I'm an engineer based here at Edinburgh, and I'm going to talk about a few Ruby projects
- 2:33 I've worked on to begin with, and then I'll talk about what I've sort of learned and the
- 2:37 guardrails through them through that.
- 2:39 So, Homebrew, I've worked on since 2009.
- 2:43 Those of you who might not have used it or heard of it already, it's a macOS primarily,
- 2:48 but nowadays Linux as well, package manager for open source software.
- 2:51 I started working on it about five months in.
- 2:53 A few of the previous talks have kind of resonated a little bit with me today.
- 2:56 Like, it started, I guess, as Ollie said, as my kind of creative side project, and it started
- 3:02 off being low-risk, low-pressure, labor of love.
- 3:05 It's still a labor of love, but, you know, it's not really the other things.
- 3:09 And similar to how Ayers said he found kind of Bridgetown.
- 3:12 Like, in my case, the story there was very similar in that I was sort of trying to hack
- 3:18 something around Mac ports to work essentially in the way that I later learned that Homebrew worked.
- 3:23 So then I was like, okay, I'm going to abandon my own crappy solution and go and get involved
- 3:26 with this thing instead.
- 3:28 And I guess, for me, again, there's been encouragement already today to get involved with open source.
- 3:33 Also, I would heavily recommend it.
- 3:35 The way I got into it, which I still think is the best way to get into it, is the expression
- 3:39 we used to use back in the day of, like, scratching your own itch.
- 3:41 Find a problem that you already have and try and solve that problem.
- 3:44 Don't go out trying to solve other people's problems for them because you feel good about
- 3:48 doing that or because you want a green contribution graph or something on your CV.
- 3:52 Like, you're not going to be motivated unless it's actually solving a problem for you.
- 3:54 So sometime after Homebrew, I got my first job where I was actually paid money to write
- 4:01 Ruby, and I worked at this company Old Trails for a couple of years.
- 4:03 I was employee number eight, so it was a nice little small startup environment.
- 4:06 The Rails app was probably only a year and a half, two years old at this point.
- 4:10 And then I went on fairly quickly from there to work at GitHub, where I was for 10 years.
- 4:15 I left as a principal engineer last year, and I worked on their kind of huge Ruby on Rails
- 4:19 application and, yeah, learned a lot of stuff about Ruby at scale.
- 4:24 And yes, Ruby does scale and how to do things well and sometimes how to not do things so well as well.
- 4:31 And then nowadays, I left GitHub last year, started a company with some ex-GitHub people,
- 4:36 which we're calling Workbrew, which is basically, well, I'll tell you about that in a minute.
- 4:41 But basically, we're building a Ruby on Rails application from scratch as our kind of like
- 4:46 cloud part of what we're building.
- 4:48 It looks a little bit like this.
- 4:49 It's basically a Rails application for kind of businesses to be able to view
- 4:53 all the packages installed across all the devices in their fleet.
- 4:56 If you're already running like an MDM solution, Jamf, Kanji, whatever, it integrates nicely with those
- 5:02 and basically just provides kind of Workbrew support and integration for those tools where
- 5:05 they're already lacking.
- 5:07 And there's a pricing slide because capitalism or something.
- 5:09 If you're interested in learning more about this, send me an email.
- 5:13 I will happily tell you more.
- 5:15 Don't worry.
- 5:16 I won't do it again.
- 5:17 Right.
- 5:18 So let's start with some background on what I was saying before.
- 5:20 So no, that's sorry.
- 5:22 That's not the right one.
- 5:23 Homebrew doesn't.
- 5:25 So Homebrew doesn't actually support this version anymore.
- 5:28 So we can't do this.
- 5:29 Right.
- 5:30 So why am I even here?
- 5:32 Like, why am I doing this talk?
- 5:34 So I think we have like a fundamental tension in Ruby land, right?
- 5:39 We -- I would assume it's common knowledge for basically everyone in the room at this point
- 5:43 that, like, Ruby is pretty great.
- 5:45 Like, Mac's already spelled out a bunch of reasons today why we all love Ruby.
- 5:49 I imagine we share some or all of those ourselves.
- 5:52 And I think part of the thing that, for me, makes Ruby really good is because it's --
- 5:59 such a kind of -- once you get familiar with it at least, you can move really, really fast.
- 6:02 Developer productivity is really high.
- 6:04 Developer happiness is really, really high.
- 6:05 I'll talk more about that later.
- 6:06 But it's also really good at, like, breaking things, right?
- 6:10 I guess there's -- I don't imagine this was what Zuck was referring to back in the day.
- 6:15 I guess particularly because it's a PHP shop.
- 6:17 But what I mean is, as I say, this is very much coming from a, like, you know, we're all Ruby people here.
- 6:22 It's like with my brothers or when I see my kids together.
- 6:27 You know, I can talk shit about Ruby because I love Ruby, right?
- 6:30 We're not going to let the node people say it's rubbish.
- 6:32 But we can -- in this closed room, we can say these things, right?
- 6:37 How many of you have seen errors like this, right?
- 6:40 If you've seen an error like this less than about a thousand times a year,
- 6:44 then you're a considerably better programmer than me.
- 6:48 I'm sure we've all lost non-trivial amounts of our lives being, like, oh, turns out, like,
- 6:53 I didn't check for nil in this particular place.
- 6:56 And that happened -- that's, like, the thing, right?
- 6:58 Like, it's -- for all that I love about Ruby, this is the most consistently annoying thing.
- 7:03 And if there's one takeaway from this talk, it is, okay, I'm going to point you to ways
- 7:08 that you might be able to see fewer or maybe even none of these in future.
- 7:14 So if you've worked with a compiled language, like, you know, Go or Rust or whatever,
- 7:18 the compilers are pretty good at, like, spotting these types of issues and giving you a decent,
- 7:24 like, heads up.
- 7:25 It's a compile time thing.
- 7:26 These types of problems don't make it as often into production.
- 7:29 But, like, the Ruby interpreters, you know, it's good at what it does.
- 7:32 But you can say, hey, Ruby, read this file.
- 7:35 Make sure everything looks okay.
- 7:37 The syntax is all right.
- 7:38 And then until you're actually running that code, you have no idea whether it works.
- 7:42 So I guess the common kind of maybe Ruby ecosystem solution to this is, like, cool,
- 7:49 we'll write lots of tests, right?
- 7:50 And I think that's good.
- 7:52 I think let's write tests.
- 7:54 Please, please write some tests.
- 7:56 But I think there's more than that that we can learn on.
- 7:59 And maybe even a way to write fewer tests.
- 8:02 So I'm going to basically tell you what, you know, we're doing pretty good.
- 8:06 We're, like, nine minutes in on this talk.
- 8:09 And I'm going to tell you the solution to the problem, right?
- 8:11 Are you ready?
- 8:12 Oh, no.
- 8:13 It's not actually as simple as that.
- 8:15 So obviously, like, anything in engineering, right?
- 8:19 Anyone who tells you that, like, there's an easy solution, you just need to do X.
- 8:22 And if you do X, it will solve all your problems is either, like, a noob or trying to sell you
- 8:28 something or, I don't know, smarter than me.
- 8:32 But what I'm going to talk about is what do I want to optimize for?
- 8:36 Like, why am I going in the direction I'm going on?
- 8:39 Why do I care about Ruby on guardrails, as I call it?
- 8:42 So for me, in descending order of priority, the most important thing, perhaps controversially,
- 8:48 is developer happiness, right?
- 8:50 Again, this is partly comes out maybe my working at developer tools companies, right?
- 8:57 And even more so in open source, because in open source land, like, I can't make anyone do anything,
- 9:02 Like, I can stop people from doing things.
- 9:04 I can, like, make aggressive request changes on a pull request and be like, thou shall not pass,
- 9:10 or whatever.
- 9:11 But if I can't say to someone, hey, like, I would really like you to spend the next month working on
- 9:16 this refactoring that's really overdue.
- 9:18 So ultimately, people need to be relatively happy in open source land, or they're not going to do stuff
- 9:25 at all. And in work land, again, well, I mean, certainly if I was giving this talk two or three
- 9:30 years ago, but even, even somewhat now, if people aren't happy, they will leave and be able to get
- 9:35 trivially a better job elsewhere. I'm sure everyone in here is like a super amazing engineer. So I'm
- 9:39 sure none of you would have any problems with that. Right. Number two, customer/user happiness.
- 9:45 Again, perhaps controversially, like, this is below developer happiness. Again, for me, this is why
- 9:51 I think tools like Ruby are important, is, you know, if I still think that's the best way to build
- 9:57 customer/user happiness, but you're optimizing first for making the developers happy and productive
- 10:01 in what they're doing. Thirdly, again, maybe slightly controversial, velocity/quality balance.
- 10:07 I'm sure we've all worked with some engineers on either end of the spectrum, people who move really,
- 10:13 really fast just vomiting out page after page of code that sort of works but then is impossible to
- 10:21 maintain and fix and debug and whatever. And then on the other end, actually probably equally as
- 10:27 problematic but not quite as shamed by our community, I guess, is the people who will fixate on making
- 10:33 sure everything and every PR is exactly perfect and if there's one piece of white space that's not quite
- 10:38 right, then we're going to block this PR that would deliver customer value today for another month just
- 10:42 while you would fix the white space. Yeah, not good either. We need to find that some sort of happy
- 10:46 middle ground. And the last thing for me is, I'll talk more about this a little bit later on, I'll just
- 10:51 leave it as it is for now, but this idea of robot pedantry, human empathy. Okay, so let's drill into
- 10:57 these a little bit. Like, what do I actually mean when I say these various things? So developer happiness,
- 11:03 said before, keep developers happy or they will quit and then you'll have to do it by yourself. That doesn't sound
- 11:08 good. For me, that is why I use Ruby and why I think others should use and like using Ruby because it's
- 11:17 pretty much the only language and ecosystem that feels really, really aggressively optimized for developer
- 11:23 happiness. And I feel like as a community, we lean into that pretty hard as well. And I think there's a reason why so
- 11:30 many people in this room and in the Ruby wider ecosystem have stuck with Ruby even when it's not
- 11:36 cool anymore, right? But I think there's still more we can do and I'll get to that later. And then, okay,
- 11:42 customer, user happiness. Okay, customers generally want software that works. And what I mean by software
- 11:51 that works is it's going to have some bugs. Ideally, as many bugs as possible are caught in test environments,
- 11:57 development environments, whatever, before they get into production and the bugs have to be experienced
- 12:02 by customers. But if they do get into production, then customers generally want a low MTTR, which has
- 12:10 helpfully two different meanings, of which I'm going to pick one, mean time to repair. So basically,
- 12:14 the time from them discovering that bug to that bug being fixed and the fix being rolled into production,
- 12:22 if you can get that time really nice and small, then your customers are going to be much happier. There's
- 12:27 we're in the stage of a business where some of our customers are so elated about having a fix
- 12:34 out 20 minutes after reported, like, it almost makes me want to add more bugs. They seem happier
- 12:40 with the bug being fixed that quickly than they would have done if the bug never happened.
- 12:43 Similarly, okay, velocity quality balance. Like, this is really hard. Like, this is arguably
- 12:51 one of the hardest problems in all of software engineering is how to get this right. And you're
- 12:55 probably never going to get this quite right. And it's going to be an eternal balancing act. And that's
- 12:59 fine. But to me, that's basically, if you're going to ship fast, you're going to ship with some bugs
- 13:05 sometimes, right? If you try and eliminate all the bugs, and ship fast with no bugs, you're not going to
- 13:12 ship fast at all, you're just going to ship really slowly. And if you just entirely prioritize velocity,
- 13:17 you're just going to ship a lot of bugs, and people are not going to be very happy with that at all.
- 13:22 And also, that's when when you work in this way, even in a relatively short time scale, that's when
- 13:28 your tech that ends up getting ramped up and up and up. Robot pedantry, human empathy, what do I mean by
- 13:34 that? Well, I did like, I've got another kind of blog post and stuff like that about this. So I'm not
- 13:39 going to go into this in too much level, too much detail if you're interested. But the TLD offer that
- 13:44 for me is essentially, I am just like addicted to automation, anything that can be automated away,
- 13:51 should in my mind be automated away. The reason why I call it human empathy in that is because at the
- 13:57 time, there was a bit of stuff kind of going on GitHub, where people would be like, hey, like,
- 14:01 we can also automate welcoming people to our project and telling them good job, and kissing our
- 14:06 loved ones goodbye, because before we go on a trip, and parents go, okay, like, there's a level at which
- 14:11 it goes too far. But like, generally, people are very tolerant of letting robots, I guess, when I said it
- 14:20 back there, I mean, you know, stupid CI jobs, rather than AI agents, or whatever. But basically,
- 14:26 people are way more tolerant of Rubicop or a CI build going, you need to fix your style here,
- 14:33 here, here, here, here. If you got a co-worker in the same team who expressed exactly the same intent,
- 14:38 they would not be very well liked, right? But it's okay, we don't have to like the robots,
- 14:44 they're coming for us. So why like them? So essentially, one of the practices I've tried to
- 14:50 adopt, particularly on Homebrew, but on most kind of projects I've worked on, is if you see the same
- 14:55 review comments coming up again, and again, and again, on multiple PRs, if you see the same classes
- 14:59 of bugs coming up again, and again, and again, then figuring out how can we automate catching this,
- 15:05 so it's not a human who's having to do this every time. My goal in my projects, which periodically bites
- 15:12 me, is to essentially be such that, say, like a dependency update, if CI is green, I can just merge. I
- 15:18 don't need to read the release notes, I don't need to read the diff, CI is green, I have confidence
- 15:22 the app is working, and 99.99% of the time, that is true, and the rest of the time I have a bad day.
- 15:28 Okay, again, let's get actually more specific about what I'm talking about. I've talked about some
- 15:35 principles we should be adopting, I've talked about what we're optimizing for, but let's go into the
- 15:40 nitty-gritty of what I actually recommend. Let's name some actual tools. Right, so I'm going to clump
- 15:48 these into a few groups. They're maybe a little bit tenuous to buckets, I apologize, but this is a talk,
- 15:53 so what can you do? I'm defining linters as anything that helps you catch issues in either local
- 16:01 development or automated test environments. They are probably not running in any form in production,
- 16:07 and they are basically good at screaming at you about things that may be a problem or may not be
- 16:12 a problem, so that humans don't have to do the same thing. So number one for me is, you know, our
- 16:19 everybody's friend and everyone's enemy, Rubicop. I try and lean really, really, really hard into
- 16:25 Rubicop, personally. I've seen in Homebrew, as our adoption has gone up of Rubicop, it has really,
- 16:31 really helped us, particularly there where we are accepting lots of external contributions on having a
- 16:35 consistent style across the entire code base. My take is enable essentially everything you can in a tool
- 16:41 like Rubicop and then disable what you don't want, and that's fine. I also have seen various degrees of
- 16:49 trust in things like Rubicop's ability to auto-correct things. I personally, I guess having done it at
- 16:54 Homebrew and GitHub, have a very, very high degree of trust in Rubicop's safe auto-corrections,
- 17:00 and just feel free to run that en masse over a million lines of source code, and in my experience,
- 17:07 for like the enabled by default Rubicop cops, you will not see bugs from doing that. It is,
- 17:12 when they say safe, they mean safe. I'm sure people in the room have different experiences and
- 17:17 will disagree, but I'm making myself a sacrificial lamb for this one. Right, number two,
- 17:24 on my list is erblint, essentially Rubicop and a few other bits and pieces that run on your ERB.
- 17:29 Gets your views looking nice, gets your views looking somewhat more consistent with the rest
- 17:36 of the code in your code base, and also catches a few other bits and pieces there as well.
- 17:40 Better HTML, that's more of like a runtime thing, which is kind of seeing and making sure that your
- 17:46 HTML is like roughly consistent and well formatted and stuff like that. Again, this is kind of more of a
- 17:52 development time check when you're editing views and stuff like that. Better HTML will go and basically
- 17:57 throw up big errors for stuff that just looks like that your browser would probably handle just fine,
- 18:02 but we can get some nicer HTML, so let's do that. I have no idea how to say this word,
- 18:07 but I'm going to call it Prozopite. Who knows what an N+1 is?
- 18:12 Folks in the room, yeah. Who has had to deal with someone joining the team who didn't know what N+1 is?
- 18:19 And then, yeah. So anyone who doesn't know what N+1 is, that is essentially the class example would be
- 18:25 if you do something in a Rails view, you have maybe a dot each, and you're looping over something,
- 18:31 then an N+1 would be when each run through that each, you're ending up doing a separate database
- 18:37 query. So you're querying the same table quite possibly again and again and again. So say you
- 18:42 might display a page where you should list 10 users, you're going, hey, user table, I want this. Hey,
- 18:47 user table, I want this. Hey, user table, I want this. Rather than doing a single query where you
- 18:50 query everything in advance and then pass it through to the view. Prozopite, it's a bit fiddly. It
- 18:55 doesn't work all of the time, but it's pretty good at catching a lot of these things. Again, if you
- 18:59 enable in development and test, then you can get a nice situation where you stop yourself from being
- 19:05 bitten by N+1s in production and you catch more of them in development instead. This is a slightly more
- 19:12 weird abstract one that almost certainly none of you care about except maybe CTO types, but it's one of
- 19:19 those tools where if you use it sooner rather than later, you'll probably be glad. It came out of
- 19:24 GitHub. It's called licensed. It's basically a sort of linter for what licenses of your dependencies
- 19:33 you have in your code base. So you can plug it into like Go, NPM, Ruby gems, and basically just be like,
- 19:40 okay, what licenses are all these things on there? You may not care about open source licensing. Few people do.
- 19:47 And if you do, I feel sympathy for you as a kindred spirit. But it's one of those things where it's
- 19:53 very easy to not care until it bites you in the ass. I don't know if anyone heard about the Winamp
- 19:58 stuff in the last couple of weeks, where yeah, essentially, there was a bunch of stuff where
- 20:03 people had just not been following the licenses. And, you know, they've taken the repo off GitHub,
- 20:08 and there was some drama and whatever. But I mean, people can legitimately end up in court and being
- 20:13 sued for this stuff. And it probably won't affect Winamp because it's more or less a defunct company
- 20:18 or product or whatever at this point. But like, this is real things that destroys companies, right?
- 20:22 So and it's really not that hard to just have a list of licenses that are standardized and approved and
- 20:28 stuff like that. So I would recommend getting on this train when you can. It's also if you're someone who
- 20:35 is in a startup, and you have VCs or other maybe very security focused companies who need to do an
- 20:43 audit of you, it's really nice to be able to have the information they need like instantly rather than
- 20:48 being like, yes, I will go through my gem file and individually check the license of all of the
- 20:53 software in there. Right, action lint, again, not super exciting thing. But if you're using who's using
- 21:02 GitHub Actions nowadays, yeah, lots of people. Basically, just make sure your GitHub Action
- 21:09 workflows are more likely to work before you push the commits than not. And also, avoid those cases
- 21:16 which you've probably all had where you push the commit and you've managed to fuck the workflow file
- 21:22 in GitHub Actions sufficiently such that it won't just run any of your CI anymore at all. No errors,
- 21:27 it would just be like bad yaml. So yeah, let's not have that. Let's run action lint. And then ES lint,
- 21:34 because inevitably, as much as we don't want to, we're probably all going to have to write some
- 21:38 JavaScript at some point. And finally, I mean, as this list of seven things already probably indicates,
- 21:44 I'm basically addicted to linters. So if you see anything here, they're like, Mike, there's another
- 21:49 linter you should really try. And I mean, my coworkers will probably say, please don't send that to me. But
- 21:55 please send that to me. I really, anything I can lint, I want to lint. If you just wanted to take my
- 22:03 opinion for it and just like dump this shit straight in your gem file, it would be something like this.
- 22:07 Similarly, when you're configuring Rubicop, remember, if you put stuff in your gem file,
- 22:12 you need to remember to actually require it in here. Otherwise, Rubicop might not do it.
- 22:15 You also probably want to remember, this is another kind of common gotcha, is set your target Ruby
- 22:21 version. Because if you're using a nice newer version of Ruby, then you can get additional things which
- 22:25 nudge you to use or not use depending on your preference. Newer Ruby syntax features. If you're
- 22:32 really wild like me, then new cops and enabled by default just means as soon as Rubicop adds any cop,
- 22:39 no matter how weird or broken it is, you'll get it straight away. But then I like that because I
- 22:46 prefer to then be like, okay, I can individually disable those cops and whatever. And then remember,
- 22:51 like, rather than disabling a cop globally, if you want to, you can just say, hey, turns out these
- 22:57 migration files are auto-generated, so I actually don't really care about the layout of them. Thanks
- 23:03 very much. Similarly, a pattern I would encourage you to do if you're really into linters like me,
- 23:08 and I apologize in advance for the at least two people in here who will continue to get me making
- 23:14 these comments on every code review until I die. If you disable the linter, please consider adding a
- 23:20 comment above the linter explaining why you're disabling the linter. Now, this might seem really,
- 23:24 really annoying, but it is actually useful to do this stuff. Because often in cases like this, this is
- 23:30 a particularly good example because it actually states a thing that in future may not be true. So we want
- 23:37 to actually explain why I've done that rather than just be like, oh, I actually don't care. And also,
- 23:42 this is a linter that I imagine may have literally triggered some of you in the room, because it's
- 23:47 essentially any time you do anything that is going to skip the model validations, you have to explicitly
- 23:52 disable the linter and add a comment. So in our code base, there's a few of these comments, and I can
- 23:57 suspect some of you right now are being like, this seems very silly and pointless. Why are you doing this?
- 24:03 But bear with me, I will come to that. Right, on to tests. So I'm defining tests as a little bit more loosely
- 24:13 than we might otherwise do. I'm basically going to call it anything that requires the developer to
- 24:17 actually write additional code that is not run in production to catch problems. And also, again, maybe
- 24:23 slightly controversially, you actually want as few of these as you can to maximally exercise your code base.
- 24:30 So we're using RSpec on Workbrew, mainly because we're using on Homebrew. I actually don't care
- 24:36 very much about whether you use RSpec or Minitest. We were talking about this over dinner last night.
- 24:40 I feel like if you do want to write RSpec, consider, again, RuboCops for RSpec that will make your RSpec
- 24:46 actually look like nice RSpec and not just Minitest in another name.
- 24:52 SimpleCov, I don't know whether you do code coverage stuff. Shout out to GitHub, who didn't,
- 24:59 and it was very annoying. But it's generally, even if you're not aiming for a particular number or
- 25:06 guarding on code coverage or whatever, it's generally useful to be able to run a given test or a given
- 25:13 test suite and say, "Which lines did this actually run?" So you have an idea of what's being tested or not.
- 25:19 Playwright, I'll come back to this in a minute. But basically, if you're using Selenium to drive your
- 25:26 Rails system tests and you're like, "Why is this so flaky all the time?" It's because of Selenium. If you
- 25:32 switch to Playwright, everything gets way less flaky almost immediately. I have a citation for that in a
- 25:38 minute. VCR, again, I imagine most of you use that perhaps already. If you don't, VLCR lets you,
- 25:45 essentially the first time you run a test that hits a remote endpoint, it lets you store the response
- 25:51 from that endpoint. And then that will be replayed like a VCR cassette every time you run that test.
- 25:58 So it's a kind of nice middle ground between like just mocking out the request and having tests that
- 26:03 actually hit remote endpoints. Because it means you can run them offline, it means your output is stable,
- 26:07 but then you do actually have the way to basically check, "Okay, this is actually what the production
- 26:13 data looks like." And then you could re-record the cassette in VCR's terminology in future to get
- 26:19 better production data and then update it and whatever. Similarly, parallel tests. We've all got
- 26:25 lots of cores on our laptops now. Let's make them work. Unfortunately, you don't get-- they remove the
- 26:31 data. It's actually a feature when you've got Apple Silicon Macs in that it no longer heats up your room
- 26:35 quite as effectively and makes that noise that we all loved with our Intel Macs. But it's still useful
- 26:42 for running things much faster, in my experience. And like I set this up for our codebase the other day,
- 26:47 it took me like, I don't know, a couple of hours. And it's more than halved the speed of our test
- 26:53 suite. So consider doing that. CodeCov, it's got a free tier. It's like a paid product and stuff.
- 27:01 I'll show you an example in a minute of like the one thing I love with CodeCov. But basically,
- 27:04 code coverage tool as a service plugs into SimpleCov and bear with me and I'll show you the cool thing.
- 27:11 And then finally, GitHub Actions. I mentioned that already. But essentially, you're probably
- 27:14 going to want to run those tests somewhere. But in general, as I said before, like, I want to just
- 27:19 automate everything. So I push a bunch of stuff into GitHub Actions that maybe has no business
- 27:24 being there, but I like it. On Playwright versus Selenium, if you are not convinced by me just stating
- 27:31 something as a fact, go see my friend Justin Sorles' post on this that explains kind of how he
- 27:38 transitioned this over and why. And I think you will also have a good time.
- 27:42 On CodeCov, this is essentially like the single nicest feature I think that you get from CodeCov and
- 27:50 really the only thing I care about in the entire product, which is if you have a code base that has
- 27:54 more than 0% coverage and less than 100%, you can have on individual PRs, it will comment being like,
- 28:01 hey, this particular line was not hit by any of the tests in your test suite. And this is really useful,
- 28:07 not because every single time you see this box, you have to write a new test, because particularly
- 28:12 with stuff like homebrew, that's not always completely practical, but because it at least lets
- 28:19 you be more scared, I guess, when you're reviewing the code. When I am reviewing a PR and I see a lot
- 28:25 more of these than usual, then I'm like, okay, I need to basically do the job that the test suite might
- 28:29 otherwise be doing. As an example of stuff that I excessively automate, I have a GitHub Action that
- 28:37 basically just essentially on every PR pretty much will run Rubocop and try and autocorrect all the
- 28:43 offenses and then push. Same with ERBLint, same with Sorbet, which I'll come to later. And then you can get
- 28:52 nightly jobs as well. They do nice little things like this where, again, if there's a new bundler version,
- 28:56 it would just open a PR and then a human like my colleague Carlo and I can review this and then
- 29:02 we can just merge it. And it saves a human from having to go and manually create this PR and check
- 29:06 it out and merge it and whatever. Right. So I'm going to speed through the last little bits because
- 29:14 I'm running a little bit low on time. This is our test suite in Workbrew. I was telling you, it's nice
- 29:20 to see 10 different processes. And this is the vaguely interest bit. I don't show this really as a flex,
- 29:25 but because I guess on test coverage, I would vaguely encourage you, if you're anywhere close to 100%
- 29:32 test coverage to get to 100% test coverage and then make it mandatory because some magical things
- 29:38 happen when you have it, which is like you don't have dead code anymore because once you have dead
- 29:43 code, it becomes a blocking thing for your tests and you end up deleting the dead code unless you have
- 29:49 silly tests that extract dead code and you can delete them as well. And it can encourage you to lean into
- 29:53 your type system a little bit more as well, which I'll talk about shortly. If you want to just copy my
- 29:57 gem file again, that's what's there. Right. Monitoring. I imagine we all have some sort of
- 30:04 monitoring tools. I'm really not going to dwell on this because I don't really care. But essentially,
- 30:08 you want some sort of error performance monitoring tool of your choice. My choice is Sentry. You want
- 30:13 some sort of logging tool of your choice. My tool is LogTail. You want some sort of alerting,
- 30:18 monitoring, uncalled tool of your choice. My tool is BetterStack. If you do that with these gems,
- 30:24 there you go. You can get woken up in the night about your crappy code.
- 30:27 So finally, types. In Ruby, this basically means pick which of the type systems you want to use.
- 30:35 I have picked and it is Sorbet. I have used Sorbet at GitHub and Homebrew and Workbrew and it works
- 30:41 great in all the cases I've used it. And some people hate it, but they hate it less over time.
- 30:45 This is an example of what it might look like if I was using Sorbet. This is very much like a happy path
- 30:52 sort of Sorbet usage where I can say okay, I'm creating this view component here. I'm taking in a
- 30:58 user. I know that it's a user because I've told you it's a user. I know that I'm going to give back a
- 31:02 user and here I've got this user here. The thing that's notable about this, which I want you to pay
- 31:07 attention to, is at no point do I ever check if they passed a no user in there or a nil in there,
- 31:14 because this is actually being verified both at runtime and at check time by Sorbet. And therefore,
- 31:21 that no class thing we saw earlier on, essentially, if you lean in hard enough, if you use Sorbet in
- 31:26 strict mode of your entire code base, you too can never see no class method not available in production
- 31:33 again. And this is the type of thing you might want to add to your gem file. So, finishing up,
- 31:39 I expect this is the part where I guess we can get some nice ad hominems in there. You can say,
- 31:44 well, Mike, it's very easy for you to say you're working on this Greenfield project. I saw you've
- 31:49 got like three tests. Your code base obviously has like six lines of code at this point. Like,
- 31:54 yeah, okay. Some of us are dealing with real problems, okay? Yeah, okay, yeah. It is easier for
- 32:00 me than it is for you. But then again, you know, I do stuff on Homebrew. Homebrew's code base has been
- 32:04 around for like 15 years. Like GitHub, when I worked there, their code base was around for,
- 32:08 you know, I guess, more than 15, coming up to sort of 20 years.
- 32:14 Like, perfect is the enemy of good. You can always make things better. In Homebrew and in GitHub,
- 32:20 many of the tools I mentioned, certainly tools like Sorbet and Rubicop, were adopted incrementally
- 32:26 over time. And in the case of Sorbet, went from zero to non-zero. And that made the code quality better.
- 32:33 There is no app too big in the world to adopt these tools. You can always get to a better place from
- 32:40 where you are today. Finally, I guess another common objection perhaps to my approach with this stuff
- 32:46 is like, Mike, this sounds really oppressive and overwhelming. Like, you've got just like all of
- 32:51 these robots shouting at you all the time. And you've enabled all these things. And you're trying to get
- 32:54 100% test coverage. And doesn't this just suck all the joy out of it? No, because you too can cheat.
- 33:00 So what I do is I have all of these like guardrails I built for myself to try and make me do good code.
- 33:07 And I will liberally just do things where I rewrite code to have 100% line coverage by put it like,
- 33:15 essentially putting a branch on one line instead of two, right? And is that somewhat stupid? Yes.
- 33:21 Is that maybe a really weird and bad attitude? Yes. But it still gets to a better place, I think,
- 33:28 than if you don't set these guardrails at all. Again, perfect is the enemy of good. You're still going
- 33:33 to end up with dramatically better code from trying to have more guardrails. And it will make you and
- 33:39 your team and your customers slash users happier. The key to success is knowing when to break your own
- 33:45 rules. Let's just not tell the robots that. If you have any questions, then we're not doing it now. But
- 33:50 you can send me an email or find me on the internet. And if you want to read a blog post of this talk,
- 33:55 it went out about an hour ago. And you can see it at this URL. Thank you very much.