C N Z 2 0 1 5 I S Y O U R C O D E TO O S O L I D ? https://www.flickr.com/photos/pie4dan/4567311801 Hello! I gave this talk in July 2015 at WDCNZ in Wellington, New Zealand.
H I E F C O N S U LTA N T D E V M Y N D U N I T E D S TAT E S C H I E F O F BA L D E A G L E S A N D F R E E D O M http://world-map1.org/map1520769_0_0.htm http://www.coolholidaygraphics.com/flagday/glittergraphics/flagdayglitter2.gif https://www.devmynd.com/images/team/sarah-mei-cutout.png Iām Sarah Mei. This is my dorky work picture, and Iām the Chief Consultant at DevMynd. Iām from the United States, speciļ¬cally San Francisco. The US, as you know, is the chief of bald eagles and freedom. I copied that directly from the state department websiteā¦
R K O N N O R M A L C O D E BA S E S https://www.flickr.com/photos/pmarkham/5361277692 Iām usually working with teams managing large codebases that have become unwieldy and hard to change. This is a common problem, right now, among developers of all persuasions. In addition to the Ruby and JavaScript folks, Iāve heard it from folks who do .NET, python, CSS, Java, and PHP. This isnāt a new problem in the industry. IBM was having this problem in the 70s. Itās interesting that itās again at forefront of our collective consciousness right now, though.
is easier to write than it used to be. There was the explosion of dynamic languages in the past 10 years, but also new frameworks in compiled languages, and the addition of usable functional languages. All of this has made it easier than ever to generate code in volume. You can achieve a large, unwieldy codebase faster than ever before! Perhaps even when youāre still a small company. You donāt need to be the size of IBM, anymore, to have their codebase problems.
teams failed at planning. Many teams need, at ļ¬rst, the type of very fast iteration that something like Rails brings. Companies that planned ahead for a large codebase, and put structures in place, couldnāt iterate as quickly. Some of them donāt exist anymore as a result. For a business, a messy codebase that works is better than a well-structured one that doesnāt. So we should all be so lucky as to have these problems.
need to make working in these codebases suck less, because this type of thing is what the vast majority of developers work on. We need to make working on this code easy and fun again. Some people attempt that maneuver this with microservices. That rant is another talk entirely.
this. Carving services out of a monolith and carving objects out of a large class are the same skill. If you havenāt been doing good object design in your main codebase, I guarantee youāre not going to design a good set of services. The skill is much easier to practice in a single codebase, where, if youāre wrong, you just have adjust object boundaries. If you have to adjust service boundaries when youāre wrong, the learning is much more expensive.
better at object-oriented design. Itās perhaps not as shiny as microservices, but from a cost perspective, itās more responsible. Not coincidentally, weāve been seeing a resurgence of interest in object-oriented design in communities that werenāt that interested a few years ago - particularly the dynamic language communities. There are now lots of books, and blog posts, and conference talks (including this one) about software design.
E X P E RT AT E V E RY T H I N G S E R I O U S LY, G E T I T A M A Z I N G B O O K http://www.sandimetz.com/ http://www.poodr.com/ This is the best of the modern takes on object design. Sandi Metz wrote this fantastic book on understanding objects. I highly recommend you read it, even if youāre not a Ruby developer.
E X P E RT AT E V E RY T H I N G S E R I O U S LY, G E T I T A M A Z I N G B O O K http://www.sandimetz.com/ http://www.poodr.com/ Now, I love this book, and all the blog posts, and all the conference talks. But what I do in my client work doesnāt really look anything like in there. The book uses example code being written from scratch to show you how to put the right boundaries around your objects. At ļ¬rst, going into large, monolithic, messy codebases, where no one heeded Sandiās advice, and moving things around didnāt seem like āobject-oriented designā to me. For a long time I called it ārefactoring.ā It turns out, though, that those are not separate ideas.
D I N G H OW TO A R R A N G E C O D E https://www.flickr.com/photos/matley0/3616669592 Before we go any further, I want to deļ¬ne some terms. Letās start with software design. Iām not talking about architecture, or systems, just within a single codebase - software design is really nothing more (or less) than deciding how code is arranged.
D I N G H OW TO A R R A N G E C O D E https://www.flickr.com/photos/matley0/3616669592 Many people think that software design is something completely separate from programming. But in reality, when youāre programming, even if youāre not consciously making any decisions, youāre still doing design. Every time you put a function in this object and not that one, youāre doing design. You can try to do it ahead of time, and some people do, but the vast majority of software design done by developers in our industry is inline. Just like programming, youāll be bad at it at ļ¬rst, but just like programming, you get better at software design the more you practice.
N T E D D E S I G N G R O U P I N G R E L AT E D F U N C T I O N A L I T Y I N O B J E C T S https://www.flickr.com/photos/mwanasimba/2901201955/ Now letās be more speciļ¬c and deļ¬ne object-oriented design. OOD is deciding how code will be arranged, grouping related functionality in objects. This does not sound anything like the wikipedia deļ¬nition of OOD. Itās pretty abstract. Perhaps it will help us to talk about what object-oriented design is NOT.
E AT U R E A WAY O F T H I N K I N G O B J E C T- O R I E N T E D D E S I G N I S https://www.flickr.com/photos/[email protected]/15635620289/ 1. OOD is not a language feature. Itās a way of thinking. You can write object-oriented code in CSS. You can write it in C, or JavaScript, or Java, or C#, or Ruby. Languages with explicit syntax support for objects are what youāll hear people call āobject-oriented languages.ā That just means ālanguages in which it is more convenient to make objectsā, but thatās too long for a wikipedia page title, so⦠Object-oriented design is a way of thinking about code arrangement. In some languages itās easier to express than others, but itās possible anywhere. Buy me a beer and ask me about object design in Haskell some time.
E AT U R E A WAY O F T H I N K I N G O B J E C T- O R I E N T E D D E S I G N I S A D E S T I N AT I O N A M E A N S TO A N E N D https://www.flickr.com/photos/[email protected]/15635620289/ 2. Object-oriented code is not a destination. Itās a means to an end. You donāt write object-oriented code for its own sake, or because itās somehow morally or professionally ābetter.ā No particular way of arranging code is inherently better than any other way. Object-oriented design is a means that we use, mindfully, to move us toward some goal. And for most of us, that goal is ease of change.
what they want. They imagine one thing but they change their mind when they actually see it in action. Or the business shifts focus. Or a key person is replaced. Or itās Tuesday - the only constant in software development is that the end goal shifts as we build it. And it wouldnāt do us any good to wait, because the act of building is what causes it to shift. In theory, object-oriented design makes it easier to respond to shifting requirements.
tell you that. But Iāll bet you most people in this room have been on a project where the code was parceled out into objects, and that made it harder to respond to shifting requirements, harder to change, rather than easier. To ļ¬gure out how that happens, we have to take a step back and consider our goals when weāre designing software.
L OW C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E There are two useful axes to consider when weāre looking at diļ¬erent ways to design software. On the bottom, we have the cost of understanding the code (low or high). This is how hard is it to ļ¬gure out whatās going on. On the side, we have cost of changing the code (low or high). Every choice about how you arrange code - every choice about software design - goes in one of these quadrants.
L OW C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Letās start with writing long procedures. Someone does a GET on /calendar to see their calendar for the month, and a procedure is executed - a list of instructions. It determines the date range, fetches events within that range from the database, draws the right shaped grid, places the dates on them and returns that page to the user.
L OW Procedures C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Itās pretty easy to understand what happens in a procedure. Cost of understanding is low. Everything that happens is right there in an ordered list. The tradeoļ¬, in a project with lots of long procedures, is that the cost of change is high. So procedures belong in the upper left. The biggest devil there is duplication, which forces you to change multiple places in the code to make a single logical change. Youāll know you have this problem if you end up touching every ļ¬le in the project to make a change that seems like it should have been simple.
L OW Procedures C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Some projects, having been burned by the high cost of change with procedures, go completely in the other direction. The system is made of innumerable tiny objects that each donāt do much. When you do that GET on /calendar, a RouteReceiver picks up the callā¦
RouteResolver that looks at the URL and ļ¬gures out that it needs a CalendarRouteResolver, which it gets from a CalendarRouteResolverFactory, and the CalendarRouteResolver looks at what youāre requesting and instantiates a GetCalendarIndex object, which sends your params to a CalendarParamManagerā¦and so on. The sequence of events isnāt written down anywhere in the code. You just have to trace it through to ļ¬gure it out. Little pieces of functionality are spread across many classes.
L OW Procedures Set of Small Objects C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E A system like this is harder to understand than a procedure. The cost of understanding is high. A list of instructions will always be easier to understand than a set of objects. However, once you understand the system, the cost of change is low. Assuming youāve got the right abstractions (a notion weāll deal with in a moment), itās relatively easy to take, for example, one params handler out and start using another. So a set of small objects goes down in the lower right.
L OW Procedures ā ā” Set of Small Objects C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Now letās talk about this quadrant here. Danger zone! Code with a high cost of understanding AND a high cost of change is the worst of both worlds. There are two types of codebases here, both of which are distressingly common.
L OW Procedures Set of Small Objects C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Really big objects The ļ¬rst is a codebase made up of really big objects. Perhaps the framework dictated an initial set of classes, and all behavior just sort of accreted onto them. The huge objects always seem to be the ones that are core to the application. They change with almost every commit, and changes go wrong easily, because all that functionality in one place means unintentional interference is almost a given.
L OW Procedures Set of Small Objects C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Really big objects The wrong set of small objects The other type of codebase is small objects gone wrong, which is what happens when you try to break down a big class but donāt get the object boundaries quite right. Then itās both hard to understand, because itās objects, and hard to change, because for one logical change you still have to make changes in lots of diļ¬erent places. The wrong set of small objects is the worst-case scenario. Really big objects are bad, but not that bad.
Procedures Set of Small Objects L OW C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Really big objects The wrong set of small objects So there are two questions here. The ļ¬rst: how can we get to the lower left? Can we? Is that the perfect solution that cannot exist? The second: how do we move our big lumbering codebases out of the upper right? It doesnāt really matter what direction we move; anywhere will be an improvement. But usually, when a codebase is this size, reducing the cost of change is worth increasing the cost of comprehension. So most people want to move down into sets of small objects.
Procedures Set of Small Objects L OW C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E Really big objects The wrong set of small objects This is where they start reading about things like SOLID and design patterns, hoping they can ļ¬gure out how to make the move. Letās talk about patterns brieļ¬y before we dive into SOLID.
W E M A I L User Letās say you have a User class, and when a new user is created, meaning someone has signed up for your service, it automatically sends them an email to get them to conļ¬rm their account. Normally this is ļ¬ne. If your user class is small and your object graph is uncomplicated, then itās ļ¬ne to leave this here. But a lot of times, the user gets to be one of the biggest classes in the system, and it can be annoying to have it send email every time you make one. You have to ļ¬nd ways to turn it oļ¬ when youāre creating a user in your tests, and every test requires you to create a user - so to make it easier to turn oļ¬, you want to separate user creation from email sending.
You create a new class UserObserver, and you move the email functionality over there. UserObserver gets a creepy set of googly eyes so it can observe the User class.
! E M A I L N E W Now when a new user is created, the UserObserver notices, and sends email. Thatās cool, right? Youāve reduced the size of your User class and made it easier to turn oļ¬ email sending. Youāve made the code easier to change. But youāve also made it harder to understand. You used to only have to look one place to see everything that happened when a user was created. It all happened in the User class. Now you have two places to look. Because itās in a separate class, other people may not know it exists, let alone that they have to turn it oļ¬, and then be unpleasantly surprised when the users they create get emails.
L OW E A S I E R TO C H A N G E H A R D E R TO U N D E R S TA N D C O S T O F U N D E R S TA N D I N G C O S T O F C H A N G E If youāre starting out with a codebase in the upper right quadrant and you apply the observer pattern as we just talked about, you move it down here. (Thatās the confounded face emoji, in case you were wondering.) Youāve made it easier to change but harder to understand. Applying a pattern improves changeability but worsens understandability.
then start looking for opportunities to apply them. They assume that making less-structured code into patterns is always a good idea. They donāt realize that everything has a cost. And determining whether itās worth it is the hard part. At what point does lower cost of change outweigh higher cost of understanding? Thereās no single answer. Itāll be diļ¬erent in diļ¬erent parts of your code, and it will be diļ¬erent at diļ¬erent times in the same part of the code.
awesome! Who doesnāt want āsolidā code? Or to be a āsolidā programmer? There are many object-oriented principles. Academia has been studying object orientation for decades. However, academics tend not to deal in volumes of code, so most of the principles are highly academic. In the 90s, Robert Martin took the ļ¬ve principles that seemed the most relevant to working software developers, and assembled this acronym. Letās talk about what each letter means.
U T I L I T Y S O L I D Weāll be ļ¬lling in this chart as we go. We have three columns: 1. The name of the principle 2. A summary of what it means (not what it says) 3. A measure of how useful it will be in our everyday developer life. The ļ¬rst letter of SOLID is S!
O N S I B I L I T Y P R I N C I P L E S O L I D https://www.flickr.com/photos/shenamt/8582808329/ S is for the single responsibility principle. It says that a class should have one responsibility, or to put it another way, one reason to change. This was ļ¬rst articulated by Rebecca Wirfs-Brock in the 80s. Itās a fancy way of saying that smaller things are easier to understand, and harder to mess up, than larger ones.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O L I D As far as utility goes, itās sort in the middle. The diļ¬culty hinges on the deļ¬nition of āresponsibility.ā If youāve got a class that ļ¬nds users, persists users, validates users, allows access to its related objects, and contains business logic related to users, you could plausibly say itās got one responsibility: it manages the user.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O L I D You could equally plausibly say that all of those things are separate responsibilities that all belong in diļ¬erent classes. The principle doesnāt give you guidance because there is no universal right answer. Sometimes it makes sense to put all that together; other times it doesnāt. And that shifts over time, even for the same codebase. The answer to every question in software development is āit depends.ā
D P R I N C I P L E S O L I D https://www.flickr.com/photos/[email protected]/4063800774/ O is for the open/closed principle. This is usually stated as āa class should be open to extension but closed to modiļ¬cation.ā Bertram Meyer came up with this in the 80s. Itās a fancy way of saying that editing existing code is more diļ¬cult and more error-prone than just adding new code, so: arrange your codebase such that we can add new functionality just by writing new code.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O O P E N / C L O S E D D O N ā T E D I T C O D E ; A D D N E W C O D E L O W L I D As far as utility goesā¦it sounds great, doesnāt it? But itās hard to conceive of how it could ever happen in a codebase of signiļ¬cant size. Itās not super practical day-to-day.
T U T I O N P R I N C I P L E S O L I D https://www.flickr.com/photos/vpickering/14416940341/ L is for the Liskov substitution principle. This is perhaps the most academic of the SOLID principles. It is a precise, mathematical statement. Here it is:
of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T. Let theta(X) be a property provable about objects X of type T. Then theta(Y) should be true for objects Y of type S where S is a subtype of T. ā¦okā¦
T U T I O N P R I N C I P L E S O L I D https://www.flickr.com/photos/vpickering/14416940341/ Itās a fancy way of saying: anywhere you can use an instance of a class Foo, you should be able to use an instance of class Bar that subclasses Foo. And nothing should go wrong. The Liskov Substitution Principle was formulated by Barbara Liskov in 1987, when she was in her late 40s.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O O P E N / C L O S E D D O N ā T E D I T C O D E ; A D D N E W C O D E L O W L L I S K O V S U B S T I T U T I O N I N H E R I TA N C E . I T ā S A T H I N G . L O W I D The Liskov substitution principle basically says āinheritanceā¦itās a thing.ā It seems pretty obvious to us at this point, but when it was ļ¬rst articulated, it was not at all so obvious. The LSP was such a good idea that, since it was introduced, we have baked it into our languages. Itās now part of the air we breathe, and we donāt even notice itās there. Itās had a huge impact on the way we write software, which, I would guess, is why Martin chose to include it in SOLID, despite its academic nature. However, while it is fundamental, itās not really a great source of practical help day-to-day. Utility is low.
G R E G AT I O N P R I N C I P L E S O L I D https://www.flickr.com/photos/[email protected]/8477516123/i I stands for the interface segregation principle, which says that classes should only have to depend on the part of an interface they actually need. Itās a fancy way of saying ādonāt let changes to unrelated functionality in a class aļ¬ect other things that use it.ā
E I N T E R FA C E S ? Musician AlbumCreator recordSong editSong mixSong sellMerch playSetList Gig Letās say you have a class Musician that has ļ¬ve methods on it: recordAlbum, editSong, mixSong, playSetList, and sellMerch. It has two classes that consume it: AlbumCreator, and Gig. They use diļ¬erent sets of methods and are not related to each other.
E I N T E R FA C E S ? Musician AlbumCreator recordSong editSong mixSong sellMerch playSetList Gig driveVan Then you add another method - driveVan - that is only used by Gig. Youād expect at this point that Musician and Gig would have to be recompiled, but it turns out all consumers must be recompiled. Even AlbumCreator, which didnāt change at all. With just two consumers - who cares? But what if Musician had hundreds of consumers that had to be recompiled every time you make any change? You can see how itās a huge pain. It makes re-running a test, for one thing, a very long process.
E I N T E R FA C E S ? StudioActions AlbumCreator recordSong editSong mixSong sellMerch playSetList Gig driveVan VenueActions Interface segregation says you should break Musician into two diļ¬erent classes - StudioActions and VenueActions. That way, when a new method is added to VenueActions, AlbumCreator wonāt need recompiling.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O O P E N / C L O S E D D O N ā T E D I T C O D E ; A D D N E W C O D E L O W L L I S K O V S U B S T I T U T I O N I N H E R I TA N C E . I T ā S A T H I N G . L O W I I N T E R FA C E S E G R E G AT I O N M A K E T H E A P I S M A L L H I G H D Anyway - it makes more practical sense in compiled languages, so many of us who primarily use dynamic languages dismiss it as unrelated. However, it does have a really really useful core idea: if diļ¬erent consumers of a class use non-overlapping sets of methods, thatās a sign that that class has multiple responsibilities (see S). So I put its utility high, relative to the other principles weāve looked at here.
I N V E R S I O N P R I N C I P L E S O L I D https://www.flickr.com/photos/hansmaulwurf/13702053625/ And ļ¬nally, D, which stands for the dependency inversion principle. Not dependency injection, which is one particular implementation of this idea. Dependency inversion says ādepend on abstractions rather than concretions.ā This is a fancy way of saying: if you want to make a new thing, a new instance of a class, inside another class, think about creating it outside and just passing it in to the constructor, instead. This has the eļ¬ect of moving all your choices towards the edges of your system.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O O P E N / C L O S E D D O N ā T E D I T C O D E ; A D D N E W C O D E L O W L L I S K O V S U B S T I T U T I O N I N H E R I TA N C E . I T ā S A T H I N G . L O W I I N T E R FA C E S E G R E G AT I O N M A K E T H E A P I S M A L L H I G H D D E P E N D E N C Y I N V E R S I O N M O V E C H O I C E S T O E D G E S O F S Y S T E M H I G H There are lots of ādependency injectionā frameworks. Angular, .NET, Java. They make it easier to test classes. Given that most of them have essentially become gigantic global state, though, itās sometimes a bit diļ¬cult to tie implementations back to the principle. But dependency inversion does seem to be, theoretically, something a lot of people see. Utility high.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O O P E N / C L O S E D D O N ā T E D I T C O D E ; A D D N E W C O D E L O W L L I S K O V S U B S T I T U T I O N I N H E R I TA N C E . I T ā S A T H I N G . L O W I I N T E R FA C E S E G R E G AT I O N M A K E T H E A P I S M A L L H I G H D D E P E N D E N C Y I N V E R S I O N M O V E C H O I C E S T O E D G E S O F S Y S T E M H I G H Now that weāve ļ¬lled it all in, letās look at this chart for a moment. SOLID has principles of vastly varying degrees of utility, or concreteness. Utility, or immediately applicability to code youāre writing today, is one end of a spectrum. The opposite end is abstraction, which describes a general rule that sounds like a good idea, but is hard to connect to code youāre looking at in your editor.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O O P E N / C L O S E D D O N ā T E D I T C O D E ; A D D N E W C O D E L O W L L I S K O V S U B S T I T U T I O N I N H E R I TA N C E . I T ā S A T H I N G . L O W I I N T E R FA C E S E G R E G AT I O N M A K E T H E A P I S M A L L H I G H D D E P E N D E N C Y I N V E R S I O N M O V E C H O I C E S T O E D G E S O F S Y S T E M H I G H O and L are the most abstract, I and D are the most concrete, and S sits in the middle. Weāve got at least three diļ¬erent levels of abstraction here. None of them, even I or D, seems like itās actually useful day-to-day for refactoring code. Itās nice to say make the api on an object small, but ⦠that ship has sailed. We need things that are more concrete to guide our everyday programming. But itās not clear how to ļ¬nd them.
we talked about earlier, are more concrete, but still donāt help us with when. We need something else - our last missing puzzle piece - to help us actually move our code forward. Hereās an idea that may be useful.
T I C S https://www.flickr.com/photos/teegardin/6150427712/ āStrategyā and ātacticsā are military concepts. I didnāt grow up in a military family, so for most of my life, I thought of these two words as essentially synonymous. But theyāre not. A strategy is a high-level objective that will move us closer to some goal. A tactic is something you do on the ground to achieve that strategy. Hereās an example of how these are diļ¬erent.
OA L : P R O M O U N TA I N E E R S T R AT E G Y: C L I M B T H I S M T N TA C T I C S : T H E R O U T E U P https://www.flickr.com/photos/frenchy/222016256/ Your goal: be a pro mountaineer. Your strategy: climb this mountain that few people have climbed before. Youāre starting from the bottom (lower left) and need to make it to the summit (middle top). There are no trails - you need to ļ¬gure out your own path up. The strategy is where you want to be at the end of the day, and your tactics are how you get there. Tactics includes the planned route, and contingency plans, and are guided by (and may be changed by) the strategy as you walk up the mountain.
OA L : P R O M O U N TA I N E E R S T R AT E G Y: C L I M B T H I S M T N TA C T I C S : T H E R O U T E U P 1 2 3 https://www.flickr.com/photos/frenchy/222016256/ Possible routes include: 1) walking along the treeline and then going up the right-hand ridge. 2) climbing this rockface, sliding along the little shelf there, and then along the left-hand ridge to the top. 3) just going for it, straight up the face. They all involve diļ¬erent tactics - walking vs. climbing vs. rappelling, etc. The actual route you take will depend on the weather, your skills, your gear, and many other things.
N TA I N E E R S T R AT E G Y: C L I M B T H I S M T N TA C T I C S : T H E R O U T E U P \ O / https://www.flickr.com/photos/frenchy/222016256/ Once you choose a route, itās still probably not whatās actually going to happen. If you get to the base of the ridge and discover that an avalanche has made it too perilous to go up that way, you change tactics, because your strategy of climbing the mountain is no longer in line with your original tactics. Part of your tactics includes determining when to change tactics.
N TA I N E E R S T R AT E G Y: C L I M B T H I S M T N https://www.flickr.com/photos/frenchy/222016256/ What would happen if all you had was a strategy? All you know is āok, thereās that mountain, I need to get to the top of it.ā If you set out to achieve this strategy without working out any of little steps you can take to get thereā¦itās not likely to work out for you very well. You may get to the top accidentally, but itās more likely that youāll try a few fruitless paths, ļ¬nd yourself in a valley you canāt get out of, and then have to signal to the park rangers to airlift you out. You need to have tactics in mind, or you probably wonāt achieve your strategy.
A B L E S O F T WA R E S T R AT E G Y: TA C T I C S : O O D E S I G N S O L I D https://www.flickr.com/photos/sunfox/5084875405/ Our actual goal is changeable software, rather being a pro mountaineer. Changeable software is the promise of objects. One way to think about strategies & tactics for changeable software is that our strategy is object oriented design. Our tactics would then be things like SOLID & patterns. This is how object-oriented design is taught, particularly in academia, and it is how most developers look at it, whether or not they could articulate it.
A B L E S O F T WA R E S T R AT E G Y: TA C T I C S : O O D E S I G N S O L I D https://www.flickr.com/photos/sunfox/5084875405/ Thereās a problem, though, with this picture of the world. āObject-oriented designā isnāt actually a strategy. Remember, a strategy describes what we want our world (in this case our codebase) to look like at the end of the day. āObject-oriented designā doesnāt describe that. On the other hand, SOLID is reasonably good at describing what our codebase should look like when itās āļ¬nished.ā Hmm.
U T I L I T Y S S I N G L E R E S P O N S I B I L I T Y O N E R E S P O N S I B I L I T Y P E R C L A S S M E D O O P E N / C L O S E D D O N ā T E D I T C O D E ; A D D N E W C O D E L O W L L I S K O V S U B S T I T U T I O N I N H E R I TA N C E . I T ā S A T H I N G . L O W I I N T E R FA C E S E G R E G AT I O N M A K E T H E A P I S M A L L H I G H D D E P E N D E N C Y I N V E R S I O N M O V E C H O I C E S T O E D G E S O F S Y S T E M H I G H Single responsibility principle, for example, would make a good strategy. It describes a state of our codebase in which there is one responsibility (at the right granularity!) per class. If we had that, our codebase would be more changeable. Open/closed describes this codebase utopia where you never have to edit code to add features. In fact all these principles are, at some level, descriptions of when you know your code is āright.ā āIf your code were like this, it would be more changeable.ā
A B L E S O F T WA R E S T R AT E G Y: TA C T I C S : S O L I D P R I N C I P L E S https://www.flickr.com/photos/sunfox/5084875405/ SOLID is a great set of strategies. Our problem is that weāve been using them like tactics. For example, you canāt apply single responsibility principle directly to a thousand-line class. A class like that has a muddy set of abstractions spread across multiple methods each that are hard to distinguish. When you squint at the class and envision how youād break it up, youāll most likely be wrong. The abstractions are, by deļ¬nition, hard to see, because if they werenāt, youād have done something about it already.
A B L E S O F T WA R E S T R AT E G Y: TA C T I C S : S O L I D P R I N C I P L E S https://www.flickr.com/photos/sunfox/5084875405/ Trying to just eyeball a large class and see what the right abstractions are is like trying to head towards the summit without planning a route ahead of time. But we do this a lot! Iāve been on many teams where a class got too monstrous, so theyād take a week oļ¬ from doing feature work. Theyād spend that week eyeballing the class and trying to refactor it into the right abstractions. Teams do these a lot. I call this a āstop-the- worldā refactoring.
A B L E S O F T WA R E S T R AT E G Y: TA C T I C S : S O L I D P R I N C I P L E S https://www.flickr.com/photos/sunfox/5084875405/ Whatās wrong with a stop-the-world refactoring? Refactoring is part of being a good software engineer, right? Taking time to clean up & make code better is important. Right?
interesting things happen when you stop the world to refactor: 1. The product team is unhappy. Youāre taking a whole week āoļ¬,ā from their perspective, meaning youāre not working on anything they can see. 2. Because youāre time-limited, you feel pressure at the end of the week to break that class up somehow, even if youāre not entirely sure yet where the right boundaries are. #2 is exactly how you end up with one of these.
A K E A S TO P - T H E - WO R L D R E FA C TO R I N G WO R K I S TO N OT D O I T . http://www.imdb.com/title/tt0086567/ The only way to make a stop-the-world refactoring work is to not do it. Itās like global thermonuclear war. The only winning move is not to play. When we apply strategies as though they were tactics, we end up with these ham-handed, mostly unplanned, unpopular changes that, more often than not, leave us in a worse place than before.
refactorings, weāre doing this. And itās frustrating. Ever wonder why good developers who started out in an OO language are turning to functional programming? Certainly novelty is part of it, but thereās also a strong undercurrent of fundamental criticism of OO. They say it just doesnāt work. And thatās because thereās a summit there, and they can see it, and it seems like they should be able to get to it. But they canāt.
gets us up there in explicit, small steps. We need things we can do every day, as weāre doing feature work, to inline the refactoring time and allow the right objects emerge from the mess over time. Right now we donāt have very many of tactics. One thing we do have, though, is lots of rules that tell us what not to do.
Demeter that tells us that `post.comments` is ok, but more method calls chained on the end of it gets a big olā NOPE. When we make long chains of method calls on diļ¬erent objects, weāre introducing coupling we donāt want. So, ok, donāt do that.
when something might be wrong with our code. We have are very few ādoās. Most of our rules at this level are ādonātās. We need ādoās. We need a set of tactics. Letās make one! Iāve made my own acronym. :D
which is cool. But when Iām trying to go up it, what I really need is stability. I want it to, for example, not be a volcano, not avalanche out from under my feet, not drop boulders on me, etc. I need it to be solid, sure. But at a more immediate level, when Iām making my way on the ground, I need stability. The same is true in my codebase. I want to know that if I make a change in one part of the code, it wonāt cause an avalanche on the other side of the mountain.
Y O U R C O D E https://www.flickr.com/photos/pedrosimoes7/169983321/ S: smell your code. Study code smells and the other ādonātās, so that you can start identifying things you could ļ¬x. Martin Fowlerās Refactoring book is great for this. Iāve worked with teams who would pick one code smell for a lunch & learn each week, and discuss and look for examples in their code. You want to start noticing & naming the problems in the code you look at, even though you wonāt be ļ¬xing them all yet.
M S F I R S T S TA B L E https://www.flickr.com/photos/ian-arlett/13706787684/ T: tiny problems ļ¬rst. In messy code, there will be lots of smells. Theyāll overlap and intertwine and interfere with each other. It will be hard to see what should be made with all this mess. The best way to get started is to pick a really small problem that you know how to solve in a very concrete way, and just ļ¬x that. For example, rename a variable whose usage has diverged from its name.
P R O B L E M S F I R S T Pick the smallest problem to ļ¬x even though you see enough of the bigger problems to start guessing their answers. Your goal is to see the large problems better, by clearing away the small problems obscuring them. The more information you gather about these larger problems, the more likely your eventual abstractions will be right. Proper abstractions are worth waiting for. Let them emerge from the code you have by clearing away the easy cruft.
T S S TA B L E https://www.flickr.com/photos/horiavarlan/4273968004/ A: augment your tests. You will almost certainly have to do this to be able to refactor large classes. You need integration tests at one level higher than the class youāre working on, and you need these in place before you start doing any of this. For example, in a server-side MVC application, if you want to refactor a big controller method, youāll need view-level integration tests in place that exercise it. If you want to refactor a big model, youāll need controller-level integration tests.
E N T T E S T S In general: test behavior, not implementation. Thatās why you go one level up. You need tests that describe the behavior you want to keep. This may mean getting rid of some lower-down unit tests, and/or writing a new set of a tests at a level you havenāt had them before.
https://www.flickr.com/photos/mjonasson/19228720295/ B: back up when itās useful. When the code has an abstraction in it that is no longer serving you well, sometimes the most useful thing to do is to ārewindā the code into a more procedural state, put all the duplication back, and start again. It is much, much harder to move from a procedure to the right set of objects, than from the wrong set to the right set. Donāt get caught by the sunk cost fallacy. Donāt forge ahead with a set of objects that donāt even ļ¬t now, let alone the future.
T B E T T E R T H A N Y O U F O U N D I T https://www.flickr.com/photos/spierisf/6989462184/ L: Leave it better than you found it. During any one expedition into the code to add a feature or ļ¬x a big, you wonāt be able to ļ¬x all the problems you see. (Canāt hug every cat! [Hello memes from 2008.]) Sometimes the only thing you have time to do alongside your stated goal is rename a method so it describes its behavior better.
I T B E T T E R T H A N Y O U F O U N D I T That action seems so small, doesnāt it? Our instinct is to save a bunch of those up and do them at once. Donāt give into that stop-the-world temptation, though. Fix one thing, the smallest thing, while youāre working on a story that concerns the code. I was a girl scout, and one of our rules when we were out camping was that we always left a camp site in better shape than when we arrived. Over time this made a better experience for everyone, including ourselves.
I T B E T T E R T H A N Y O U F O U N D I T When you change that one method name, youāre removing a little piece of cognitive dissonance. The next person to come through this code will be able to understand it a little more easily. Maybe the big abstraction will suddenly be obvious. Or maybe theyāll just ļ¬x the next smallest thing. The key insight is that little things add up over time.
R E A S O N S S TA B L E https://www.flickr.com/photos/an_untrained_eye/5012331537 E: expect good reasons. Assume past developers had good reasons to write the code they did. Some code looks so horrible that we think, āwhat idiot wrote this? Why would anyone ever do it this way?ā And then you run git blame and ļ¬nd out it was you, six months ago. Or it was one of the devs you really admire.
C T G O O D R E A S O N S There are forces at work on the code beyond the developerās experience and technical skill. Deadlines, relationships with other groups like product, QA, and operations, company ļ¬nancial situationā¦all these and more leave their ļ¬ngerprints on the codebase. Start thinking about the social pressures that aļ¬ect your codebase, and many things will make a lot more sense.
C O D E T T I N Y P R O B L E M S F I R S T A A U G M E N T T E S T S B B A C K U P L L E AV E I T B E T T E R E E X P E C T G O O D R E A S O N S These are little things you can do to inline-refactor large classes as youāre making progress on features. You donāt have to stop the world. You can rebuild trust with your product team, and as a bonus, youāre much more likely to end up with the code you want. Itās a cycle. Keep ļ¬xing the small problems, and the solutions to the large ones will become obvious.
C O D E T T I N Y P R O B L E M S F I R S T A A U G M E N T T E S T S B B A C K U P L L E AV E I T B E T T E R E E X P E C T G O O D R E A S O N S You might ask, donāt you still have to stop the world sometimes? After all, once youāve cleaned up enough of the small problems so that the solution to the larger problem becomes obvious, you do have to still actually solve the larger problem.
about this. āFor each desired change, make the change easy (warning: this may be hard), and then make the easy change.ā When you remove small problems, the big problems become obvious, AND they become much easier. You amortize the cost spent ļ¬xing the big problems and as a result you end up with higher-quality solution. And when you see it, will look, in retrospect, so completely #@$ %ing obvious.
may be hard.ā The STABLE cycle wonāt always prevent you from extracting the wrong objects, or trying a ļ¬x that doesnāt work. Youāll still have go down some paths that lead to dead ends. But in this process, thatās expected. Back up & try another. Thatās why āback upā is one of the essential steps in this process.