BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Podcasts Michael Feathers: Looking Back at Working Effectively with Legacy Code

Michael Feathers: Looking Back at Working Effectively with Legacy Code

Several years ago, today's guest Michael Feathers published a book called Working Effectively with Legacy Code. This book introduced ways of wrangling large codebases. In the book, Feather's discussed leveraging unit tests to introduce--not only a validation of correctness but also-- documentation on a system's operation, ways to decouple/modularize monolithic code, and 24 different techniques to introduce change safely. Today on the podcast, Wes Reisz and Michael Feathers go back and review the book. The two spend some time reviewing key concepts from the book and then discuss how the techniques can be applied today. The two wrap with a discussion on what might change in a new version of the book.

Key Takeaways

  • A seam is a place in your code where you can separate things for testing without doing any extra work. It's an effective technique for finding a place to insert tests where maybe none existed before. 
  • Unit tests often are used to test for correctness. However, it's equally valid to use unit tests to understand and document actual behavior.
  • While there are 24 techniques discussed for use in different situations found in Working with Legacy Code for breaking down monolithic codebases. A handful of techniques seem to have the most application in the largest number of use cases. Extracting interfaces and parameterized constructors are two of the more important ones and have stood the test of time.

Transcript

Wes Reisz: [00:19]:

The next QCon Plus, which is an online version of the QCon you know and love, will be taking place over two weeks, between May 17 to 28 of 2021. QCon Plus focuses on emerging software trends and practices from the world's most innovative software shops. All 16 tracks are curated by domain experts to help you focus on the topics that matter the most in software today. Tracks include leading full-cycle engineering teams, modern data pipelines, and continuous delivery, workflows and platforms. You'll learn new ideas. You'll learn new insights from over 80 software practitioners in innovator and early adopter companies all across software. Spaced over two weeks, just a few hours a day, these expert level technical talks provide real-time interactive sessions, regular sessions, async learning, and additional workshops to help you validate your software roadmap. If you're a senior software engineer architect or team lead and want to take your technical learning and personal development to a whole new level this year, join us at QCon Plus. It's May 17 to 28th. You can visit QCon.plus for more info.

Wes Reisz: So with that, let's jump in.

Introductions [01:20]

Wes Reisz: A few years back, I was often asked for resources, usually books, that I'd recommend for new to intermediate developers in software programming courses that I taught at the University of Louisville. These were typically CIS students at maybe the 300 or 400 level. The books that I recommended often changed based on a number of things, but they usually included books like Kathy Sierra's Design Patterns, Dave Thomas and Andy Hunt's Pragmatic Programmer, The Mythical Man-Month, of course, by Frederick Brooks Jr. Other books like Clean Code and Clean Architecture often came up, all still great books, by the way, along with recent suggestions, like maybe Phoenix Project or Accelerate if you're answering that question today. In addition to those books, one of the ones that I often recommended, particularly with someone who wanted to know about how they can start adding new features to an existing code base that maybe they weren't the ones to write was called Working Effectively with Legacy Code. Today, on the podcast, we're talking to it's author, Michael Feathers:.

Wes Reisz: Hello, and welcome to InfoQ podcast. My name is Wes Reisz:. I'm one of the co-hosts for the show and a platform architect for VMware. As I mentioned, today's guest is Michael Feathers:. Michael has a new role. He is the chief architect for Globant, a position that he just started, I believe in January of this year. Prior to that, he was a consultant for many years and my go-to expert whenever I wanted to talk about or work with someone who could help me with an area of legacy code. He's one of the first people that I ever really heard talking about test-driven development and refactoring, besides Martin Fowler and this book Refactoring. He really, in this book, describes concrete approaches to tackling a really hard problem that we all have to deal with it at different points, and that's legacy code.

Wes Reisz: So today, on the podcast, we're going to talk about some things like seams or the idea of how to figure out where you can introduce tests into a place where maybe none existed before. We'll talk about this idea of how to attack maybe a big ball of mud and introduce new features. We'll talk about tactical approaches of sprouting and wrapping. We'll discuss even how communication structures--think Conway's Law--can actually reveal hotspots or problem areas in code that can be addressed using unit tests.

Wes Reisz: If you've ever had to work on an older codebase or just weren't sure where to start if you've ever been afraid to touch a section of code because maybe it's brittle or you're just worried about unintended consequences that may come about it. If you're just decomposing a monolith, working effectively with legacy code gives you some strategy, it gives you some techniques for being able to address these problems.

Wes Reisz: Michael Feathers is one of those people who helped push the idea that code without tests is a smell you should pay attention to. As always, thank you for joining us on your jogs, walks and commutes. Michael, welcome to the podcast.

Michael Feathers: Thank you very much.

Wes Reisz: In January, you joined Globant as the chief architect. What is the work that you're doing there?

Michael Feathers: I can't speak too much about everything I'm doing there, but a lot of it is really kind of like the typical architect role is doing internal consulting and basically helping teams out that have questions, or basically you spend enough time in the industry and you basically build up almost like a portfolio in a sense of approaches to problem solving. So I go around and help people with those things. So generally, that's the kind of work that I'm involved with.

What's the origin story for Working Effectively with Legacy Code? [04:26]

Wes Reisz: So I guess one of the very first times I ever ran across test-driven development was actually a conversation with Chad Fowler in the early 2000s. But right after that, I remember running across your book, Working Effectively with Legacy Code. That's probably around the 2005-06 timeframe. I think the book was 2004. It was really one of the first times, I guess, that I saw a really concrete strategy of saying, "Hey, you've got this legacy codebase. How do you go about doing things to isolate/abstract and still be able to introduce code and really the idea of test-driven development and how you can make sure that things are modularized?" So I guess what made you come up with the book? What was the origin story for the book?

Michael Feathers: Yeah, it's a really rather interesting one. At that time, basically, I was spending a lot of time hanging out with people that were in the early Extreme Programming (XP) movement prior to the Agile even getting a name. I basically learned through them how to do test-driven development. I was basically doing it at a company I worked at. And then later, I left and became a consultant and I was helping teams transition into Agile process. The thing that was fascinating about this is that I kept running into this problem that basically people want to refactor, but they really couldn't because of the lack of tests around the area of code that they're working on. So, that was problematic. I worked with them to go and develop techniques to go and do these things, different clients.

Michael Feathers: And then a little bit later, there was this odd thing was going on and that Kent Beck had written a couple of books about Extreme Programming and everybody felt it was time to go and have a book on what we call test-first programming at that time. Kent, I think, basically said in some relatively private forum that you just didn't want to do it at the time. So, me and another guy named Frank independently started working on our own versions of a book about this, but I didn't really feel comfortable with it because I definitely hadn't been doing it as long as Kent have. So, I realized as well that this thing of going and getting tests in place around existing code was a problem that everybody was confronting.

Michael Feathers: I decided to go and write the TDD book in the mornings and the legacy code book in the afternoons and just work in parallel until one of them won. It turned out essentially the legacy code one was... It felt like ground that hadn't been covered by anybody else before. Also, it just felt like it had this nice moral arc to it, I guess, or kind of like it's more of a people arc in the sense that it's a very technical subject, but really it's about going and making the work experience much better for people that are just dealing with really horrible code situations. So that's what had won and that's how it came about.

You discuss seams as a method of being able to introduce tests into a codebase that is missing them. What are seams? [06:45]

Wes Reisz: Yeah, I liked that, kind of the developer experience for people working with legacy codebases. For those of the people that are listening that were recommended the book and have heard about it, but maybe don't remember some of the salient points, I thought we'd go through some of the ones, at least that I specifically remember. One was big about tests and you just talk to that a bit, but add tests before you make changes, the idea of getting feedback as being one principal idea there. Another one was about seams and that to identify the seams of a program, so that way you can be able to tease out dependencies and be able to test things. What did you mean by seams and what does that mean for developers?

Michael Feathers: I'll give a definition of it. It's a place in your code where you can go and separate things for testing without doing any extra work. Okay. So a good example of a seam is an interface in Java, for instance. It's kind of like once you have an interface in place, it's easy for you to go and implement the interface on another object and then mock out some collaborator you have and some object-oriented piece of code that you have. What it recognizes is that there are many places in programs where you have this ability to actually replace one thing with another without actually changing the code in the place where that seam exists. In my mind's eye, I always saw it as being the seam of a shirt, for instance, where the sleeve goes and meets the body of your shirt. It's kind of like, this is a thing that we see in clothing, right? You can see it when you look at how pipes fit together in the real world as well. But these things exist within software as well, right?

Michael Feathers: So essentially, the place where you can actually go and pass parameters for a constructor, for instance, that's a seam as well. A place where you don't have a seam, for instance, is if you have, say, a call to some global resource, like a database, that's just embedded inside of a piece of code, and there's no way to parameterize. There's no way to go and actually replaced the reference that you're going and calling into the database throughout. So, that's a place where the seam is absent. And then the question becomes, can you put a seam in that place and where are you going to actually go and mock out the things that are getting in your way of testing?

Michael Feathers: The strange thing about this though is that essentially it's like... I came very close to going and actually leaving this out because I thought it was almost like a private way of understanding code that I happened across. But like all metaphors, you don't really know where the metaphor is going to connect with people or not. So I'm kind of glad I left it in because it seems like one of the concepts from the book that people keep coming back and talking about it over and over again. And so, I'm very happy we provide a mental frame for that.

What are characterization tests? [08:57]

Wes Reisz: Yeah, it does because it gives you that idea, particularly when you're working with this code base that already exists, how do you begin to decompose the monolith to be able to isolate areas? Where do you find these places to be able to inject this stuff? So the idea of a seam, particularly with that shirt metaphor, I think, really helps people get that idea in their head. There's a blog post you wrote a few years back that starts with a statement that says, "I think the most confusing thing about testing is the word testing." That post is all about the idea of characterization tests. It's this idea that we write tests often not necessary for the sake of correctness but to really understand what the code does. This is another concept that's big in the book. Can you expand a bit on the idea of characterization tests and their role?

Michael Feathers: I think more recently, I've heard some people would call them pinning tests, but it really depends on who you talk to. I think with a lot of these things, there's different nomenclature that's floated around. But within the book, I define a characterization test as essentially a test that you write to understand the behavior of the system. Okay. It's strange about this because I don't really see those as being different tests than, say, the tests you would develop using test-driven development, for instance, but rather it's almost like a different aspect of the test that you write. What do I mean by this? Well, it's kind of like when you have a set of tests for a particular area of code, if you change that code and those tests fail, you've received information about the change that you just attempted to go and make. It was really a bad change relative to the behavior of the code that is defined by the task.

Michael Feathers: So characterization testing is really this process of going and writing tests to understand what the code does. And then essentially, it's kind of like the tests aren't really defining correct behavior, they're really defining actual behavior for the codebase. That's what I mean when I say that essentially it's a different aspect in a way. We have a choice when we look at the test we've written for something of seeing them as the correct behavior. We're seeing them as basically documentation of the behavior that exists currently. Once you've made that mental switch into going and seeing them as documentation of the current behavior, you can look at your tests and go and say, "Well, gee, this behavior right here, that's what I really want for this codebase." And then you recognize you have more work to do. You don't really take those tests as being a definition of correctness, but rather definition of actuality. I think that mental shift is something which is really powerful for people.

Wes Reisz: I'm curious, how does that distinguish between just integration tests and different tests like that? Because the integration test is defining behavior, at least behavior in how different parts of the system work together. That's something that's happening. So how do you draw the, I guess, distinguishment between characterization tests with an integration test? Is it just the interaction of components?

Michael Feathers: I think it's really just the way that you see them in a way, right? And it's just that sometimes people tend to look at testing as being a way of going and determining whether something is correct or not, determining whether it's right or not, particularly if you have a different team that's going and defining a test suite that you have to go in and basically match behavior against. With characterization testing, we're basically going and saying that these tests are documentation of the current system. And then we can use to go forward and decide, "Well, what do we want to do now?" Okay. This is where the system is telling us that it does right now. Is that good behavior or bad behavior?

Michael Feathers: I think the thing about this mental shift that helps us a bit sometimes is that it makes this work easier to go and to write tests for legacy code. Because quite often, when I confront people and say, "Hey, you should write more tests," they say, "Oh, okay. Let me go and do that." I can watch them and see them trying to figure out in their minds what the correct answer should be for, say, a particular function call or something like that. When you're writing characterization tests, you can just go and write a test and say, "Well, I'm going to call this function with these arguments, and I'm going to go and say that the return results should be zero. And just putting a zero there because I don't know what the actual return result is going to be." But then you get the actual return result and then you take that and you make that the expected value. Once you do that, you basically have a test which documents the current behavior.

Michael Feathers: Now, the test result you get may be surprising to you, and then that's good because you've learned something, but it takes all this guesswork and mental modeling out of the process. It's just only a matter of going and saying, "Let me try this. Let me try this. Let me try this. Let me try this," and getting those answers, saying that these are the tests that basically go and show the behavior that the system currently asks.

There are two techniques that you use to introduce change in the book called sprout and wrap. What are these techniques? [13:01]

Wes Reisz: There are a couple of techniques in there that stuck in my mind, sprout and wrap in particular. They've stuck in my mind as just a way to be able to take, I say monolithic, but a monolithic class, I guess, a large class that I'm trying to decompose into something smaller, into more modular. This technique has been something that's always stuck in my mind as a way to attack that kind of problem. So, what is sprout, what is wrap when we talk about dealing with legacy code?

Michael Feathers: It's really kind of like answering the question, "What do I do when things are really bad and I don't have much time in order to going to make an effective change to a piece of software that I have?" I often say to people, "Look, you should probably be running more tests." Everybody knows that. But the thing is that when people try to go and write tests for big ugly piece of code, it can be a lot of work breaking dependencies just to basically make the code tractable for testing. Sprouting is really all about the thing of going and saying, "Well, rather than modifying this existing piece of code, can I just write the new functionality in a new classroom method and then delegate to it from the existing monolith in a way, right?"

Michael Feathers: That's pretty much what it is. It's going in saying, "Look, we should probably be making smaller pieces when we do up software anyway. So why don't we try to go and frame the new code that we're adding as new classes and new methods?" Once we do this, we can develop those things in the test harness and then delegate two of them from the existing code. It doesn't work in all scenarios because quite often, you're developing things where you have a lot of communication back and forth. But if you're able to go and do this where you have the work in a new piece of code and you're able to delegate to it, don't get the control flow to the new item, then you have the ability to go and just almost have like little pieces of greenfield inside of a large brownfield code base.

Wes Reisz: That's a great point right there. We rarely get the luxury of working on a completely clean code base. Even if we do, give it a couple of weeks and it'll probably feel like a legacy codebase anyway. So, what I really like about the idea of using the seam metaphor to identify where you can inject testable code, what I really like about the idea of sprouting is that it almost gives you a safety line so that you can introduce features into a place that may be really brittle or even a big ball of mud.

Michael Feathers: Yeah. And that's kind of what I thought as well. The way the book was written is really basically going in and writing for somebody who's doing the work day to day and who probably doesn't have very much test coverage in their code base. The framing really is that we don't want tests just to have tests. We want tests to support our work. We want to be able to go and understand when we make a change, what's actually going to happen, right? So, a lot of it is really very pragmatic in a way. It's going and saying, "Look, identify the places where you want to make a change in your codebase."

Michael Feathers: And then basically try to go and move backwards up the call chain a bit, figure out where a good location would be to go and write some tests that will cover the area of behavior that you're basically about to change or refactor or add to. When you write those tests, then you're in a situation where you actually have almost like embedded sensors in your process in the sense that if you make something which is counter to your expectations as defined by those tests, then you're going to get a test failure and you understand what's actually happened.

Why did you focus almost exclusively on unit testing and not look at other types of testing? [16:02]

Wes Reisz: Yeah, I get it. It gives you a really almost scientific method way of attacking and introducing change to a code base. I really liked it. I'm curious though, why focus specifically on unit test? What I mean is there's other types of testing, integration tests that we can use to be able to test this. Why the focus so much on unit tests in particular?

Michael Feathers: For the most part is because I see them as being there to support the work. It's great to go and have higher-level tests. But I think for the most part, the thing that always comes to mind for me is like, imagine being at the top of a well and dropping a pebble and trying to get that pebble to go and land on a particular shelf inside the well. It's very hard to do, right? And it's very much the same thing that happens when we're dealing with very high level tests. We're trying to always move around with the arguments that we're passing in order to go in and actually hit the particular branch that we care about for the change we're about to go and make, and that distance to it gets in our way. So you can have higher-level tests, but they tend to generally be smoke tests because the combinatorial explosion of number of paths you can have from a particular endpoint often precludes you going and getting very decent coverage from the higher levels of an application.

Michael Feathers: I tend to see the lower level testing as being more of a programmer's tool and something that you basically do in order to support the work that you're doing day to day. Over time, you tend to go and have better and better coverage in the areas that you're actually changing. In fact, I think this is one thing I want to go and really emphasize a bit. People often talk in terms of like the 80-20 rule in many areas of life, but one place where the 80-20 rule tends to manifest itself in software development is there tend to be hotspots and codebases, areas where you get lots and lots of change. So if you start just writing tests to support your work as you're doing your work day to day, over time you're going to start to cover the areas, which tend to have the highest amount of interaction, and it can make a horribly large monolithic piece of software tractable over time, even though you may not get much test coverage over everything. You'll certainly have it over the areas that really matter.

Wes Reisz: When I did that introduction, I talked about some of the books that I grouped your book into, Pragmatic Programmer being one. I can remember Dave Thomas talking many times about the best way to understand code is to document it with unit tests to document it by actually writing tests against working code so that you can truly understand the way it works. Yeah. So the introduction of unit tests also serves the document you have coded. It provides an area to help understand the behavior of how it worked characterization tests like you were talking about before. It helps you understand why this code does what it does.

Michael Feathers: If you are not used to working this way, you're getting this mindset where they're kind of thinking, like I said earlier, playing computer in their heads, trying to understand what the code is doing. And that's way less direct than actually being able to go in just to ask the question and find out what's going on. Many people are used to doing this in a debugger. Debuggers have some advantages in the sense that you don't need to isolate things well enough to go and start to exercise them. But the thing is, debugger sessions go away also, right?

How do these techniques hold up in a cloud-native world today? [18:51]

Wes Reisz: This is a repeatable test that's encoding tacit knowledge into something that's executable that can then be done as a suite. What amazes me, and I just thought we'd talk a little bit about it is like, I mean, this book was written 16-17 years ago, but I personally find it still completely valid with work that I do today. I've just ran into situations lately where unit tests weren't present in a particular project that I was working on. My first step was to say, "Okay. We need to introduce these unit tests so that way we can know what the current state is before we start introducing new features and new change." I don't know that your book in particular was in my mind when I was thinking that, but it's definitely influenced my behavior on how I approach problems. It doesn't matter whether I'm working on Java or I'm working in this serverless environment. I've just found it really applicable to the work that I'm doing. What have people told you about cloud-native today and still applying patterns for your book into it?

Michael Feathers: Basically, what I'm hearing from people is it's still very useful. I think testing isn't going to go away. I think that it's sad in a way that it almost feels like every new generation has to learn that by going and getting themselves in a bit of trouble. Short little story about this, I remember talking to somebody from University of Illinois back in the early 2000s. So yeah, about 20 years ago. This was when a lot of this stuff is really kind of new. He was basically teaching and he basically was trying to go and get the students to basically do TDD on their take-home projects. He basically got them back and realized that they wrote the test afterwards. So he could just tell by looking at it because he'd had enough experience doing it.

Michael Feathers: We mused about this a little bit and felt that maybe to go and see the value of doing some of these things, you have to paint yourself in a corner a couple of times, and then you're tired of the pain. And then you're like, "Okay. I can do something now that basically goes and forestalls that pain or just prevents it from happening at all." So I think there is that thing. We have a generational tension within the industry that go and cause some of these practices not to spread as far as they should.

Wes Reisz: I can literally remember having the same battles, trying to have the same conversation, try to get students to write tests before they actually write their logic. Mine was more around the mid-2000s, not so much the early 2000s. Even for something as simple as a hello world, I was trying to get them to understand the behavior and break it down to be able to encapsulate that very first thing what they're really trying to test for, wrap that into a test so that it's always executable and provable.

Michael Feathers: So when they reached back and they remember that when they start discovering the path they're going down isn't going to help anyone. It's very funny with this too, because you bring that up and it's like I think that one thing is that I feel like I've rediscovered over the years is that testing tells you a lot about modularity in your systems. And so, the thing of like saying, "Oh, I want to print hello world. How does that actually work?" It's like there's this thing of going and actually trying to go and get the computation away from the endpoints and not have those things glued together.

Wes Reisz: Right. Exactly.

Michael Feathers: And that's really the place where you might want to have a seam, for instance, as a place where you're actually computing a string or doing some kind of computation and then having some degree of separation between that and how you're going to get back and response, for instance, or how are you going get it on the screen or how it's going to go into the database. It's funny because I think what happens is that unless you're thinking about how this code could be used and just say more than one context, you're not going to see the full story of modularity. So yeah, there is this thing of being able to go and think what does this look like from the outside, and I think that going in being very diligent about testing helps with that. The key thing that I noticed with this as well though, is that the reason why code is so difficult to get tests around quite often is because it wasn't designed with testing in mind, right? So you really don't have any of these modularity affordances in the code and then you're just in trouble.

Wes Reisz: Well, that's one of the things what I like about sprout and wrap, because you can insert a piece of code without actually necessarily modifying the original structure, but actually wrap it, break it out, test it separately and just really giving the result back into it. That was always a technique that I tried to use whenever I could.

Michael Feathers: I think there's this message I try to get people all the time, is that when you have trouble testing something, look for the design problem because it's there to see where things could be different.

Are the issues in legacy code always in code? I mean, are some of them really indicative of people problems? [23:02]

Wes Reisz: It's so interesting you say that. So look for the design problem because it's there. It's not always a technical problem, even in code, even in legacy code. Sometimes it's a people, it's an organization. I mean, is that a thing? Do you see that? I feel like you see that in code.

Michael Feathers: Definitely, in fact, I think if I were to rewrite the book, I would probably go ahead and add a lot more about that particular thing. One of the things that's fascinating is noticing that the way that we work together has such a strong impact on code. I think it's giving a very subtle read to Conway's Law that was first articulated by Mel Conway decades ago, that essentially the structure of your code tends to mirror the structure of the teams producing it. But I'd say it's not just the structure, it's the quality as well. If you have people that aren't really communicating, that's going to show up in the codebase. It just does. It's very strange about this. I think one of the things I keep coming back to with this as well is that I think sometimes we are not as direct as we need to be when we're working with other people in terms of saying, "Maybe there's a better way to do this."

Michael Feathers: My sense of this is just being a bit of an introvert myself and getting into this industry when you didn't have to code quite as socially basically, I was basically almost my own worst critic. I was able to go and do very well doing that, but it's like, how do you actually have the conversation with your teammate when you see a different way of doing things that might be better? If those conversations are safe, then things are going to get better overall. If they aren't, then it's going to be this thing of kind of like being a bit of pain. Now, when you talk to anybody about it, thinking, "Am I being wrong for going into suggesting this better way of doing things in saying that we can improve this?" that's really a bad situation many teams get themselves into.

Wes Reisz: It's interesting. So you're talking about Conway's law. And so, I also see the reverse, right? The reverse Conway maneuver. So, you can actually improve the structure of your code by improving the communication patterns of your team as well. It's not just mirroring it, but actually forcefully shaping the architecture and improve the codebase by improving the communications with your team effectively with what you're saying.

Michael Feathers: I think one thing that just really landed home for me just recently, it's kind of like talk about how late in your career, you can actually learn lots of deep and profound things. It's like the reason why Conway's Law works is because it really is one system. It's kind of like the people and the code really are... You can't do something with one thing without going impacting the other. In our mental framing, we, in our mind, separate objects because of our scale and our perception. I can see this glass as being different from that fork, right? But there's things you can do the fork that going to affect the glass if they're close enough to each other, right? Humans are a constantly touching code, and the code is constantly going and provoking reactions from us. And so, it's probably more realistic to go and say that we are one thing together in a way, right? When you start, you going to adopt that mental frame, you start to really, really think in a very systems-oriented manner and it really helps.

If you were going to rewrite the book today, what would you add? [25:37]

Wes Reisz: Yeah. Absolutely. Systems thinking, right? So you mentioned just briefly if you were going to rewrite the book, you'd add some more things about the people, the culture, that kind of thing. Anything else? If there was going to be the long-lost hidden chapter that was just going to be released now in 2021, what might we find in it?

Michael Feathers: Oh yeah. Well, I think I'd started going in and talking a bit more about functional programming and just where we are with that and the ways that some of those things can become a little bit problematic. But in retrospect, it seems like the book itself, so many of the issues really come down to mutable state and rampant conditionality. And so, for its time, it did well, and I think those same issues still occur even in functional environments these days because it's never completely pure, for instance. Yeah. More on dealing with external configuration. That's a thing that's come up over and over again, working with people and more strategies around breaking up abstraction. So I would definitely go and deal with the thing of service modularity and where you want your services to be and how you go through the process of going and actually separating services when you find that advantageous for the work that you're doing.

Wes Reisz: Speaking of that hidden loss chapter, as I was doing some research for this, I saw another book, Brutal Refactoring. Is there another book out there in the offering?

Michael Feathers: It feels like a whack-a-mole type thing because that is not a real book. I gave a workshop on that in Chicago. I forgot how many years ago, but somebody has put that up as a page on Amazon and I need to go and figure out where to go and get that taken down. But having said that though, it's like I am asked periodically about a second edition for Working Effectively with Legacy Code. There are things like an add to it. I just haven't really gotten around to going and slicing down the time and figuring out just how to go in and really approach doing that. But yeah, I think there will be more in that vein.

If you were going to remove something from the book, what would it be? [27:14]

Wes Reisz: I'm curious, so you're talking about things that you would add, but at some point, you'd have to remove something. What would you remove from the book?

Michael Feathers: It's a very good question. I think the thing that is really startling to me is that there's 24 different dependency breaking techniques at the end of the book. I think I could cut them down to maybe five or six ones now that have maximum bang for the buck. It would feel very weird to go and remove the other ones because they're useful in niche circumstances. But I think that there's the guidance I can offer about coalescing upon just a handful of techniques that going to get you out of most of the trouble that you find yourself in.

Wes Reisz: So, we're running short on time. We probably don't have time to go through all five of those, but give people some homework. If they want to go back to your book, what five techniques would you recommend as the best way to break the dependencies?

Michael Feathers: I think the main one really is parameterized constructor. Okay. And I think the parameterized method is another one, where people don't really think about all that often. Introducing interfaces and places where they don't exist, extract interface is a basic one of those. Maybe not as many as five or six. The thing is it's kind of like, I haven't really done the work to go and actually figure out which ones off of that main branch of things tend to provide the most value, but I find myself over and over again, just basically going and choosing to go on to add more and more constructive parameters and going to mock things out when I can.

Michael Feathers: The reason why is because the constructor that you use the testing constructor ends up being decent documentation for the badness of the extraction. It's like if you have an abstraction that has 10 arguments, for instance, because you have to go and mock out all these different things, it just basically means that there's some real work that has to go and happen in that context. So it's decent documentation for that. So I can tend to lean on that one much, much more heavily than other techniques.

What's next for you? [28:48]

Wes Reisz: Well, Michael, what's next for you? Anything coming up that we should know about?

Michael Feathers: Well, there's the thing I've been working on for a while. It's another book called Unconditional Code, but it's still in progress. Okay. And that's really about removing conditionality from code in order to make it better. It's a different take than, say, in functional programming, a lot of it really is about removing immutability, but Unconditional Code is about looking at the concept of conditionality and trying to go and basically deal with it in a multilevel approach across development and trying to make things better by reducing decisions. I don't have a projected end date for that, but it's something I've been working on and off aside from my other work.

Wes Reisz: I'll look forward to seeing it. Well, Michael, thank you for teaching me about TDD. Thank you for guiding me on the journey over the years. I look forward to a time when we can get together in person and talk more about refactoring code and exploring unconditionality in code.

Michael Feathers: Yeah, sounds good.

Wes Reisz: All right. Michael, thank you so much.

Michael Feathers: See you soon.

More about our podcasts

You can keep up-to-date with the podcasts via our RSS Feed, and they are available via SoundCloud, Apple Podcasts, Spotify, Overcast and YouTube. From this page you also have access to our recorded show notes. They all have clickable links that will take you directly to that part of the audio.

Previous podcasts

Rate this Article

Adoption
Style

BT