00:00:12.920
About that time, welcome everyone! It is good to see a couple of friends out here. Thanks for attending this talk.
00:00:20.510
This talk is called "Bursting at the Seams." I'm sure you've all seen Tommy Boy. This is the one scene I think about whenever I hear that expression:
"the fat guy in a little coat." As a kid, I would attempt to reproduce that scene, but it’s actually really hard to do!
00:00:27.289
So they must have pulled a couple of threads out or something. Anyway, this talk is about seams: what they are, why we care about them, and how to work with them.
00:00:35.159
That’s really the purpose of the talk. Also, that's a Friesian horse by the way. I recently had a shaman tell me I needed more magic in my life.
00:00:40.469
They said that I needed to have my spirit guide be a part of my daily life, and they gave me some spirit animals. I don’t know, we'll see what happens.
00:00:51.440
We’re going to look at some code, talk about seams, what they are, why we care about them, and then we’ll take a practical application of what we've learned and apply it to the code we just looked at.
00:00:56.549
My name is David McDonald. That’s another spirit animal jumping over me, like the cow jumped over the moon. But that’s Norka. I like orcas because they’re matriarchal.
00:01:03.030
I’m a software engineer at Weedmaps, and I’d like to thank Weedmaps for sponsoring me and allowing me to come out here and speak to you today.
00:01:09.870
With that, let’s dive into things. A quick note on the format: I generally like live coding talks.
00:01:15.990
I know that they can be treacherous and very difficult. Every time I’ve tried to do one, I bumble it.
00:01:23.580
So, I’m not going to be doing live coding today. Instead, this will be a talk where I have some screen grabs.
00:01:29.080
I’ll talk to you about the screen grabs, and I will compare them to previous screen grabs that we just looked at.
00:01:36.330
Sorry in advance if this is at a pace that is too slow for you or if it’s too quick; I’m trying to find a middle ground so that everyone feels like they can follow along.
00:01:46.269
But remember, patience is a virtue and a requirement for senior developers. With that, let’s jump in.
00:01:55.380
Here we have a long-looking method called "make requests." It takes four keyword arguments, two of which are required. It looks like a lot, but it’s really just instantiating a couple of objects and then executing those, building up some arguments for them.
00:02:06.400
When we enter the method, we create an instance of a signer, and we will learn what that is shortly. The signer instance takes a method and an endpoint argument that we just passed into "make requests."
00:02:12.359
As you can see, we call that instance, and we get back another object: something called a signed header. We end up using that to build up the arguments for yet another object that we’ll retrieve from the build requests.
00:02:24.010
Next, we create an argument hash composed of the same method and endpoint args, and we also give them a name and a body if one was passed in.
00:02:36.120
This is code you might have written yourself or seen before. It can be a bit hard to look at. Ultimately, we just issue a request. As you can see, we’re rescuing a REST client exception.
00:02:46.269
This is a REST client object here that we’re building at the end. So we finally issued that request, parsed out the response, and did something with that response.
00:02:55.380
In this case, we are storing data locally in the database. This gives a bit more context to what we’re working with.
00:03:03.030
You can see that the large method we just went over is pretty much the bulk of this class. We’re going to call it the request class.
00:03:10.900
There’s another spirit animal, a legato, that was given to me very recently. I chose this example because it’s not very complex; it's fairly readable.
00:03:16.680
However, it's really easy to nitpick stylistically. Any one of us in the room could probably pick out little things: 'I don’t like that you named it this way.'
00:03:23.580
Or, 'I don’t like that all the arguments are structured like this.' Some might say, 'I don’t like that you have instance variables that you’re just directly calling aside from this method.'
00:03:30.920
However, we could do that, or we could talk about what’s really wrong with this method, and that’s kind of the heart of this talk. Beyond talking about seams today, that’s what I really want everyone in the room to take away.
00:03:44.300
So, let’s just think to ourselves here, or shout things out, or whatever: what is wrong with this method? Some of you might be saying a lot; others might say, 'I don’t know, not really a whole lot.' Who even wrote this terrible thing?
00:03:55.640
Ah yes, I did write this method. I’ve changed some things up here to obscure the source, but I wrote this a few years ago.
00:04:02.270
I was looking back over some source code, and that was the inspiration for this discussion. You could have listed a few things like maybe some SOLID principles.
00:04:10.900
Or what Sandy would call them: true principles. But the number one problem I see with this code is that it’s just hard to change.
00:04:20.920
There are a lot of places in the code base that seem to be tied to this one method. It feels pretty crucial to the health of the application.
00:04:27.570
This speaks to what is probably the real point here: software is meant to be changed. Otherwise, we’d probably be implementing this in hardware.
00:04:36.120
If you’re a software engineer, it’s literally your job to change software. So the need to accommodate changes in our code is one of the main reasons we write object-oriented code.
00:04:44.070
Code that is hard to test is often harder to change; it obviously depends on what the changes are.
00:04:51.130
But if your code is really rigid or brittle, that can just cause a lot of messes and a lot of time wasted.
00:04:58.650
But I don’t want to digress. What I wanted to focus on is how this method, which seems really crucial to our application, will respond to a change in requirements.
00:05:09.380
So, let’s imagine a new requirement comes in. It’s going to feel a little contrived here, but our users have recently been categorized into specific types of users.
00:05:16.700
Each user has been given one of several levels, let’s say gold, silver, or bronze. Depending on which group or tier they are in, we have to sign each of these requests with a different algorithm.
00:05:23.890
This is a real scenario that exists out there in the world, but for our cases, we can pretend there’s a time-locked service that we can only access during certain times of the day.
00:05:31.480
You have to have certain user permissions to do so, and you have to sign those requests each time; otherwise they won’t be accepted.
00:05:39.990
So we’re really worried about security here. For now, we’re only going to have two types of users: gold and silver. But we know more will be forthcoming.
00:05:48.200
So this is obviously going to require some changes to our code. We need to take a look back at our method.
00:05:55.330
We know that we need to add a new signer, or maybe that signer needs to apply a different algorithm depending on the user.
00:06:05.690
So, we need some way to toggle which signing algorithm we are going to use, and we also need to apply some logic to do that.
00:06:12.660
But what does our signer even look like? Do we need to know? Do we need to worry about that? Let’s take a quick look at it.
00:06:23.030
Not really, it’s not doing much special. There’s some magic where we’re applying an algorithm, a hashing algorithm.
00:06:31.180
Do we really care? In fact, another developer on our team implemented that, and they are going to implement the future ones and the ones after that.
00:06:39.510
So, we just know it’s there and what way it’s hand to say. Another contributor in the balloon implemented this entity called a channel, which delegates these calls off to a user.
00:06:47.690
It asks, "Are you a gold user?" or "Are you a silver user?" So, we have all of this stuff ready. It’s now up to us to implement these new signer classes.
00:06:56.570
Let’s also note that we have these two instance methods here on the channel for a consumer ID and a private key.
00:07:04.220
I might have noticed that we use those here in our make request method. That’s where those are coming from.
00:07:12.890
Let’s quickly implement a naive approach to the problem. We need to create a new signer class or extend our current one.
00:07:20.630
So for now, let’s keep it really simple: we’re just going to wrap our algorithm and copy-paste for two different signers—one for gold and one for silver.
00:07:29.960
Okay, so we basically cloned that whole class. We wrote some unit tests, those unit tests are passing, and now we’re back to our make request method.
00:07:37.920
We can conditionally call one or the other. We already have a reference to the channel, so in our request class, we can go through and ask the channel.
00:07:44.630
Like, hey, is your user a gold user? Is your user a silver user? We can also just look up the consumer keys since those are instance methods on the channel.
00:07:55.270
This avoids the need to pass around raw instance variables, which was bothering me, so we can do that.
00:08:01.620
We can also pass the channel down into the request method and look up those consumer keys directly.
00:08:08.690
Our signer classes both have the same signatures: they take the same arguments. So, we just build that up, check the channel level, and we’ve implemented our new requirement.
00:08:15.930
Here we simply ask the channel if you are gold; if you are, call the gold signer; otherwise, call the other one. Pretty simple.
00:08:22.860
Is that really all we need to do? Have we covered our bases here? We run our tests, and those are passing.
00:08:30.450
We’ve been diligent and thorough with our QA. We’re ready to go, so we just ship this thing, right?
00:08:37.210
What more could we do? You might be thinking, "Okay, didn’t you say there’s going to be more signers in the future?" Yes, I did say that.
00:08:44.020
But those aren’t here yet. We’ve only got two, so should we worry about that now?
00:08:49.990
As soon as we need to add another version of our signing algorithm, then we’ll be forced to make a change here.
00:08:58.320
But is that really that big of a deal? We could turn this ternary operator here into a longer if-else statement or a case statement.
00:09:04.900
That means we will have to come in here and change this every time. We don’t know how many different signers are going to come through.
00:09:12.480
Wouldn’t it be nice if we could just affect change in this method without having to change the code ever?
00:09:21.310
So, let’s pause there on that question. I’ll freely admit that I’ve never had an original thought in my life.
00:09:27.810
I’m very dubious of anyone that claims that they have. I owe a lot to those who have come before me.
00:09:35.770
One of those people is Michael Feathers, specifically in his great book "Working Effectively with Legacy Code."
00:09:41.680
The whole premise for this talk comes from this book, chapter four specifically. Please read it if you haven’t; if you have, read it again.
00:09:50.220
Every one of us in this room has probably worked within a legacy codebase. He defines that as a codebase that’s not under test.
00:09:58.200
That means everything under test; we’re probably all going to write code that is not under test at some point in the future.
00:10:05.700
I know that I work on a couple of apps at Weedmaps that do have 100% code coverage and a couple of others that don’t.
00:10:13.260
But it’s very relevant for today, so I highly recommend it. It’s from this book that we were first introduced to this concept called seams.
00:10:19.530
So, what are seams? We go back to that question we just discussed: wouldn’t it be nice if we could affect change in our request method from the outside?
00:10:29.040
Without having to change the code itself? This is really where seams come into play.
00:10:35.720
A seam is a place that allows you to modify the behavior of whatever you’re dealing with without modifying the code.
00:10:44.280
That sounds pretty abstract. Without looking at something, especially if you've never been introduced to this, you might think about mocks or something like that.
00:10:52.530
If you’re a Rubyist, you might be thinking is this like dependency injection or what is this?
00:10:59.360
Let’s get to a concrete example here with our code. The bigger question now is why do we care about this?
00:11:06.750
Michael Feathers, in the book "Working Effectively with Legacy Code," focuses on the idea of testability and getting something under test.
00:11:15.130
Having done that for years, he discusses the emergence of seams. There are many benefits to these seams that we can take advantage of on a daily basis.
00:11:23.120
Once you start seeing your code, you begin noticing these seams, specifically what he calls object seams.
00:11:30.310
Refactorings and decoupling kind of appear to you, and it comes naturally.
00:11:36.760
Thinking about seams is all about thinking about dependencies. It’s about finding and creating an incision point where we can loosen our dependencies.
00:11:43.830
This makes code easier to write tests for, which is a huge benefit. It also makes it easier to change later.
00:11:50.310
As I said, our software is always changing. It’s our job to change software, so let’s make it easier for ourselves and for those after us.
00:11:57.020
For our future selves, those who are going to touch our code later, read our code later, and write tests for our code later.
00:12:05.350
To write effective tests, you need to break these dependencies. Thinking in terms of these seams will help you first of all identify dependencies.
00:12:13.410
If working with dependencies is a new concept to you, this is a great place to start.
00:12:22.500
The analogy I like is that of seams in clothing. Here we have the seam on a baseball. If anyone’s ever pitched or anything like that, you know how important seam placement is with your fingers.
00:12:29.760
For me, the analogy of clothing works well: a seam is a place where two parts are stitched together.
00:12:37.020
The pieces on each side touch each other right at the seam. In software, if you identify a seam, you’ve identified a place where there’s a well-defined interface.
00:12:45.300
This allows you to leverage something like dependency injection, meaning you can take one of these dependencies and stick it in there.
00:12:52.060
You don’t have to declare it directly in your code. I like that seams help us talk about a lot of the SOLID principles, many object-oriented principles.
00:12:58.270
This gives us a nice way to discuss these concepts without having to talk about them directly. Maybe you don’t have a common ground with someone to discuss a topic like open/closed.
00:13:05.140
This is an easy way to get the ball rolling. Code complexity, extensibility, and testability have been hot topics in the industry for years.
00:13:12.300
Seams can help a novice get up to speed with object-oriented programming and help them think in helpful and beneficial terms for the whole team.
00:13:19.890
Hopefully, these insights can make your codebases more flexible and resilient to change.
00:13:26.230
Let’s take a quick look back at our implementation and ask this question: can I create a seam?
00:13:34.560
This is something that modifies the behavior without changing the code. Can that help us here? We don’t have a seam here right now.
00:13:42.680
Thinking emojis come into play: could we have a seam here? It looks like we could have done something right there.
00:13:51.920
That seems to be the hot spot in this method. But you know what? We’ve made our change; we deployed it. So that’s game over, talk over, thanks for coming!
00:13:58.650
Now the time is now. This is the talk. A new requirement comes in, and now we have a bronze level.
00:14:05.500
This is a perfect time. We know what to do; we know that seams one spot where we put that ternary operator or if statement.
00:14:12.270
We need to go in there, create a seam, and that will facilitate the addition of the next signer that’s surely coming down the line.
00:14:19.350
So let’s try to make use of that. We kind of have to get our bearings here so let’s take a glimpse at the overall class.
00:14:27.040
We have a new signer we want to slip in, but we just aren’t set up for that yet. To do this right, we need to know the bigger picture of the overall application.
00:14:35.150
How these signers are used, how this request class is used, right? What are we dealing with? We have these signers that take the same argument list.
00:14:42.950
Looks like they do. We basically want to figure out how to pull those things out and let something else decide which one we need to give to our method.
00:14:51.080
At that point, our method isn’t the one changing the behavior; it’s whatever something else decided.
00:14:58.040
We can create a signer method that wraps up this logic and gives us back the thing that we want. So we go ahead and do that.
00:15:07.040
Near the bottom, we have our signer which is going to forward along our method and endpoint arguments that we received from before.
00:15:15.460
If you look at the signer implementation above, it’s really just copy-paste from before. We’ve added signer C, our bronze signer.
00:15:23.020
Bing-bang-boom, we’re done. Our tests are passing, but what does this really give us? What have we gained here?
00:15:30.730
Is our code more extensible? Is it easier to test? Is it easier to read? Is it open-closed?
00:15:36.650
What happens next time we have to make a change and we have to add a fourth signer? Well, we just basically pushed the change off from our make request method into our new signer method.
00:15:43.220
I’d argue that it’s not really that great, but things start to emerge once you’ve made a change like this.
00:15:50.740
Okay, so this doesn’t fix everything, but this is the start of something. What’s the big deal with seams?
00:15:59.740
If we just created a seam, we don’t have to change the make request method to change its behavior now.
00:16:07.410
Believe it or not, this class is a lot more adaptable and easier to refactor. Once we create a seam like this, other improvements to the design tend to reveal themselves.
00:16:14.200
Other places that know about this code show up as points that can now be refactored, making them a little bit more resilient.
00:16:21.540
Seams, in addition to Michael Feathers’ insights about making our codebases easier to test, are like refactoring gateways.
00:16:30.220
You’ve opened up a whole new world to gain more breathing room with your dependencies and move them around.
00:16:39.300
Does this class really want to be responsible for deciding which signer is to be applied to every request?
00:16:46.100
Or is that the responsibility of someone else? How far should we take this?
00:16:52.640
The seam we created has provided us with the flexibility to explore this, and paired with our tests, we can make changes in this class without fear.
00:17:01.470
So let’s walk down the road I took to illustrate the point a bit and some of the benefits that you can gain.
00:17:09.140
This is ultimately up to you; it’s about how far you need to change it.
00:17:14.970
Do you expect more signers that will come down the line? Is it really worth your effort?
00:17:23.890
I argue that it almost always is, and hopefully, I can illustrate that point.
00:17:30.080
I pushed this one dependency out of my make request class and up a level, putting it in the request itself.
00:17:38.890
Ideally, I’d like to pass a signer instance into my request class. I don’t want to worry about toggling things in the request class.
00:17:47.130
I want this request class to build a request, make a request, and parse a request—that’s all I want it to do.
00:17:54.110
Other logic should be handled by someone else, so let’s outline what we want.
00:18:01.610
To make this work, we need to zoom out a little bit and rethink things. Let’s take a look at the request class and how it’s currently used.
00:18:08.950
At the bottom, on the left, we have a refresh stats worker that I made up. It takes an ID of the channel.
00:18:17.570
It looks at that channel, creates an instance of the request class, and passes in the channel so we can get the consumer ID and private key.
00:18:24.850
We can then call methods like refresh stats on that request instance.
00:18:32.340
The refresh stats method will forward along dynamic pieces of information we need, like what kind of HTTP method it is and what the actual endpoint is.
00:18:39.540
Then we return to our familiar make request method. That’s what we’re dealing with here.
00:18:46.360
We can actually pull this off. The channel already knows about the levels: gold, silver, and bronze. It already knows about our credentials.
00:18:54.690
We were only really using those to pass them along to the signer. Therefore, if we want to avoid toggling which signer is used in the request class, we can let the channel pick the signer.
00:19:05.640
If we examine the changes in a signer, we need to assess which ones are dynamic and which are static.
00:19:12.979
We saw that refresh stats passes in a couple of dynamic pieces, but the consumer ID and the private key are static.
00:19:20.290
So let’s quickly rework our signer. We want them to accept a consumer ID and private key.
00:19:27.440
We’ll retrieve an instance of a signer and whenever we need to call a sign request, we can just pass in the dynamic elements.
00:19:34.330
There’s another Friesian horse for you all. You wouldn’t even need to keep injecting your channel into the request.
00:19:41.250
This was only done so we could forward those details along to the signer. Now we’ll handle everything inside of the channel.
00:19:48.500
I realize this may be moving along a little quickly; you may or may not be losing track of where we are, but that’s okay.
00:19:53.040
I really just wanted to push this out there and show you how you can start thinking about seams.
00:19:59.440
Because I started to create one seam, there wasn’t one to begin with; we created a seam and these dependencies kind of naturally drifted apart.
00:20:06.760
Then we thought, 'Okay, I can move this out, make this class a little cleaner, and have this class take on certain responsibilities.'
00:20:13.270
Certain design flaws began revealing themselves without me even having to think about it.
00:20:20.560
You have to stop where you feel best; there’s a trade-off that we always have to make when software is changing.
00:20:26.909
I took it even further and thought, whose responsibility is it to determine which signing algorithm to use?
00:20:34.920
We accomplished our job already, but I thought, a signer is just applying certain algorithms.
00:20:43.530
This is a perfect instance for polymorphism. We can have a signer responsible for the credentials and apply a different algorithm based on the user's channel.
00:20:51.250
You can see how I ended up here. You could keep going, and that’s really the point: I have now been granted the space to do this refactoring.
00:20:58.640
Make things more readable and maintain less code overall, which is not always a win, but to me, it generally is.
00:21:05.360
In Ruby, you can do a lot of fun stuff with string interpolation and constantizing things that can get you into trouble.
00:21:11.590
So, I try to avoid that, though sometimes it’s a useful tool to reach for. Ultimately, I just separated all the algorithms.
00:21:20.410
This is just a point you can go as far with as you want.
00:21:27.620
I would like to end on this note: Michael Feathers focuses on how to get codebases that are not under test, under test.
00:21:34.850
From that work, the concept of seams emerges. It’s the ability to change the behavior of a method or object without changing the code.
00:21:44.390
This idea underscores how powerful this tool of seams can be. He focuses primarily on testing, and as you’ve noticed, I mentioned tests.
00:21:51.150
But I haven’t shown you any tests; we haven’t written any tests because that isn't the point here.
00:21:58.060
The goal here is that I’ve found that as I begin to see my codebase in terms of seams, refactoring naturally reveals itself.
00:22:06.850
Decoupling happens almost instinctively. When I say, "It would be nice if I could change the behavior of this class or this method," I’m automatically thinking of seams.
00:22:13.370
Hopefully, that’s a benefit you’ll start to notice for yourselves too. It’s a topic that’s not often discussed, but it’s a very useful one.
00:22:20.620
My goal today was to reintroduce that concept and help you identify places in your code.
00:22:29.070
To make that crucial incision, that first spot where your codebase can burst at the seams, facilitating future refactorings.
00:22:36.350
Inevitably, those requirements will come in. With that, that’s my Twitter handle there if you want to follow me.
00:22:42.930
Thank you! I can take a couple of questions.
00:22:55.900
I mentioned SOLID principles and also true principles. I attributed those true principles to Sandy Metz, who just spoke earlier.
00:23:03.480
I believe she mentioned them in her "Practical Object-Oriented Design with Ruby" book. I got a thumbs up in the back there; thank you!
00:23:11.820
But she’s written a lot of books, all of which I have read and I recommend.
00:23:17.660
It’s basically a pared-down version, specific to Ruby, of the SOLID principles.
00:23:24.410
So, there are four instead of five. Yeah, no... but I could goggle it.
00:23:31.540
I could google it.