Let's say you get an opportunity to refactor and clean up a bunch of existing code. Hooray! It's a glorious chance to make your life better! Off you go, cleaning up bizarre statements, backwards conditionals, unnecessary dependencies, and so on. That stuff's easy. Then you get into the more fun stuff, where you start extracting methods, moving things around, and generally reorganising. Things go well for a while, then you realise that -- horror of horrors -- you've put some things together in a class where they really don't all belong. Infamy! Scandal! Horror! Clearly you've made a terrible mistake and should never be permitted near code again!
Hardly.
In reality, you'll rarely see a significant refactoring session go by without at least a couple things getting moved repeatedly. Personally, I've found that for any code that gets moved once, there's about a fifty percent chance that it'll get moved a second (or third) time in the same session.
There are a couple reasons for this. The initial cause is that if it were easy to see where all the code should live, you wouldn't have the mess in the first place, would you? Of course not. So don't be surprised when you try something out and find it doesn't work out quite as well as you'd hoped. For this reason more than any other, I always start refactoring sessions with basic code hygiene (fixing formatting, making bracket usage consistent, adding or removing blank lines as appropriate, etc), then check those changes in to source control before moving on to the chunkier work. That way, if I go down a completely wrong direction, I can reset to a point that at least is better than how I started.
The second reason is a little harder to spot until you've done a few significant refactoring sessions with really tangled legacy code. The thing about code, especially code that's being actively worked on the way it is when you're refactoring, is that its development is a very organic process. You do a little work here, which suggests a little work there, which suggests further work over in this other place, which ends up making the first bit of work irrelevant or even incorrect, so you fix that up again, which points you to some extra work that could be done in this other spot... etc, etc. The design is continually evolving as you work on the code, which means that your plans should always be evolving as well.
Does that mean that you've wasted effort by not simply moving code to where it belongs in the first place? That's certainly one way of looking at it, and on one level it's true. But it's never wasted effort to move to a better design. And without that first round of changes, you might never have gotten the code into a position where the updated design would have suggested itself. So the first round of work is really a trial run which leads you to the proper design -- you might consider it a prototype refactoring. If you move the code again, well, that second round was also just a prototype, right?
Another way to look at it is to take a metaphor from home renovation. Have you ever seen a supporting wall taken out, either partially (for a door or window, for example) or completely (for replacement)? The first step is to build new walls that can take the load from the support wall. Once the new walls are in place, the old wall can be changed, replaced, restructured, or whatever. When that work is done and the updated structure is complete, the temporary walls are removed again. Are they wasted effort? Of course not -- they provided the structure to allow the real work to happen without destroying anything. The fact that they're taken down at the end doesn't mean they weren't critical.
So yes, you'll probably start finding yourself moving the same things around a few times to try to find a good home for them. That's a normal and healthy part of the design/development process. As long as you don't spend all your time moving the same few items around between homes, simply enjoy the fact that you've found your way to such a refined design.
Sunday, May 17, 2009
Wednesday, February 18, 2009
Baby steps: Pick your battles
The most important thing about cleaning up old code is knowing which battles are worth fighting. Obviously, you should try to bring your code under test and clean up the design as much as you possibly can, but that doesn't mean that you should kill yourself doing it.
Recently, a friend of mine was given a task that seemed a perfect candidate for unit testing. It was a small, logical unit that seemed perfectly capable of living independently. He committed himself to writing unit tests for this work and factoring out some of the existing design into more readable classes. Then he took a close look at the source file. When he discovered that the class he had to work with included a page and a half of member variable declarations (often two or three to a line), he began to sweat. When he further discovered a half-dozen different global objects buried in the rest of the code, he was nearly ready to scream.
Knowing that I'm a big fan of continual code refinement and unit testing, he came to me for advice on dealing with the problem. I took a quick look, and in addition to his other problems, it turned out the class itself was actually a window, and the only way to get some of the required elements initialised was to actually open the window and display it to a user.
In the end, my advice to him was to move on to the next task. Sure, we could have found a way to break apart the class, handle the dependencies, get things pulled out of the damn window class, wire up some unit tests, and make things a little better. But it would have taken at least a week of pretty intense work, and in the meantime, there were a dozen other tasks waiting that could also be handled in that week, while cleaning up the code and adding unit tests.
If you have the choice between cleaning up one small area of the code, or spend the same time and effort cleaning up a half dozen, go with the option that gives you the biggest bang for your buck. Sure you're leaving an ugly section in your code, but it's not like you'll never come back to it. The cleaner the rest of the code is, the less time you'll waste in other areas and the more time you'll have to focus when you come back to it later. And if you're really lucky, some of the cleanup work elsewhere might help you pull some of the garbage out of your monster class, making life easier.
Recently, a friend of mine was given a task that seemed a perfect candidate for unit testing. It was a small, logical unit that seemed perfectly capable of living independently. He committed himself to writing unit tests for this work and factoring out some of the existing design into more readable classes. Then he took a close look at the source file. When he discovered that the class he had to work with included a page and a half of member variable declarations (often two or three to a line), he began to sweat. When he further discovered a half-dozen different global objects buried in the rest of the code, he was nearly ready to scream.
Knowing that I'm a big fan of continual code refinement and unit testing, he came to me for advice on dealing with the problem. I took a quick look, and in addition to his other problems, it turned out the class itself was actually a window, and the only way to get some of the required elements initialised was to actually open the window and display it to a user.
In the end, my advice to him was to move on to the next task. Sure, we could have found a way to break apart the class, handle the dependencies, get things pulled out of the damn window class, wire up some unit tests, and make things a little better. But it would have taken at least a week of pretty intense work, and in the meantime, there were a dozen other tasks waiting that could also be handled in that week, while cleaning up the code and adding unit tests.
If you have the choice between cleaning up one small area of the code, or spend the same time and effort cleaning up a half dozen, go with the option that gives you the biggest bang for your buck. Sure you're leaving an ugly section in your code, but it's not like you'll never come back to it. The cleaner the rest of the code is, the less time you'll waste in other areas and the more time you'll have to focus when you come back to it later. And if you're really lucky, some of the cleanup work elsewhere might help you pull some of the garbage out of your monster class, making life easier.
Saturday, December 13, 2008
Baby steps: Bad is the new good!
One of the things that people tend to find frustrating during major refactoring efforts (especially their first such effort) is that it seems to take damn near forever to get anything done. I've had friends and coworkers complain to me about it, to which my response is generally, "Maybe that's why writing good code to start with is so important..." The more tangled the code is, the smaller the steps you have to take in fixing problems. In particular, when you're untangling legacy code, you'll probably find that the only steps you can take without risking breaking everything are changes that seem trivial -- but it's still worth it, because if you take enough nearly-trivial steps, they still add up to a big leap forward.
I'm actually planning to write a series of articles on this theme, but today I'd like to start with a particularly gruesome example of a baby step that a coworker brought to me the other day.
The problem was that we had a static class that did database reads and updates. We're trying to add in some unit tests to cover basic regression scenarios (the code's in constant flux right now, and there's a steady stream of goofy bugs being introduced -- pretty much the exactly perfect time to have automated tests), but we couldn't because the database access made the tests far too slow and far too brittle.
Step 1 in solving the problem was switching to a Singleton, which was a pretty simple if tedious change. (Updating seven hundred usages takes a while, y'know, even when you can lean on the compiler to help you find everywhere that needs updating.) Step 2 was extracting the database read/update code into virtual methods. Step 3 was creating a testing-specific subclass that overrode only the database calls with do-nothing fake logic. Step 4 was getting the new subclass to override the Singleton on command.
Each of those steps was simple, easy, almost even trivial. But when it was done, we went from saying "We'd love to write unit tests but there's no way to work around the database access" to "Hey, we can write a few dozen unit tests to cover some of the basic scenarios." Which is pretty major progress, all things considered -- especially since the whole process uncovered a few bugs that no-one had thought to test for. And now we've got automated tests covering a few of the scenarios that seem most prone to having goofy bugs introduced, so our developers can take slightly bigger steps in their work with less worry of breaking everything.
(I'd like to point out here that I'm not recommending using Singletons -- it was just a convenient first step that was ever-so-slightly better than the original static class. There are plans underway to update some logic so the Singleton can change into a regular object.)
I'm actually planning to write a series of articles on this theme, but today I'd like to start with a particularly gruesome example of a baby step that a coworker brought to me the other day.
The problem was that we had a static class that did database reads and updates. We're trying to add in some unit tests to cover basic regression scenarios (the code's in constant flux right now, and there's a steady stream of goofy bugs being introduced -- pretty much the exactly perfect time to have automated tests), but we couldn't because the database access made the tests far too slow and far too brittle.
Step 1 in solving the problem was switching to a Singleton, which was a pretty simple if tedious change. (Updating seven hundred usages takes a while, y'know, even when you can lean on the compiler to help you find everywhere that needs updating.) Step 2 was extracting the database read/update code into virtual methods. Step 3 was creating a testing-specific subclass that overrode only the database calls with do-nothing fake logic. Step 4 was getting the new subclass to override the Singleton on command.
Each of those steps was simple, easy, almost even trivial. But when it was done, we went from saying "We'd love to write unit tests but there's no way to work around the database access" to "Hey, we can write a few dozen unit tests to cover some of the basic scenarios." Which is pretty major progress, all things considered -- especially since the whole process uncovered a few bugs that no-one had thought to test for. And now we've got automated tests covering a few of the scenarios that seem most prone to having goofy bugs introduced, so our developers can take slightly bigger steps in their work with less worry of breaking everything.
(I'd like to point out here that I'm not recommending using Singletons -- it was just a convenient first step that was ever-so-slightly better than the original static class. There are plans underway to update some logic so the Singleton can change into a regular object.)
Wednesday, December 3, 2008
Handy trick from the Agile community
First off: If you're an agile developer of some sort, you probably already know all this. Feel free to move on.
For the rest of you, here's a little trick that the Agile folks came up with that's just freaking handy. Ready? Here it is: Write tests for your bugs before you start fixing them.
I'm not a big fan of Test-Driven Development. I mean, yeah, interesting idea. Probably handy as anything in the right situation. But for me... No, sorry. Just doesn't suit how I work, most of the time. Especially since I almost always seem to be working in legacy code. (Cleaning it up. Just to be clear.)
However, I make a very vehement exception for bugs. Why? Well, unit tests let you verify behaviour, right? But one of the frustrating things about legacy code -- especially code so tangled that it earns the title of Gordian code -- is that you can never be entirely sure that your test is verifying what you think it's verifying.
I recently ran into a situation where I got lazy, broke my own rule, and coded up the fix, then wrote a unit test afterwards to verify the fix. In fact, I wrote a good half dozen unit tests to verify that the fix corrected a bunch of different variations on the original bug. I hit compile, ran the tests, and hooray, everything worked! There was much rejoicing.
I was about to check my code in when a little voice popped into my head. "Sure, the unit tests passed," it hissed in my ear. "But can you trust that? After all, you've never seen this code before yesterday. How sure are you that you understand the nuances? The subtleties? The hidden, insane side-effects?"
(The code I'm working on should probably be featured on The Daily WTF.)
I sat there for a minute, reassuring myself that all was well. After all, it was a pretty clear bug. Did I really need to let that voice of paranoia sidetrack me from all the other wonderful productive things I could be accomplishing? The fight went back and forth, doubt gnawing away at the pit of my stomach, until I finally gave in and undid my fix and ran the unit tests again with the broken code.
They all passed again. Even without the fix.
I ended up spending hours tracking down the real bug and writing a handful of new tests that actually verified that behaviour. In the process, I discovered that my original "fix" would have broken a few other things that I wasn't aware of, and that weren't covered by unit tests. (I added tests for those too. Which have already caught a potential new bug. So that's good.)
One isolated incident doesn't mean a whole lot, but over the years I've had a number of fixes that turned out to fix the wrong thing, or fixed it but in a less-than-ideal way, or just plain did nothing. Writing the test first is just basic defensive programming -- make sure you know what the problem really is before you try to solve it, and then make sure that your solution actually works.
For the rest of you, here's a little trick that the Agile folks came up with that's just freaking handy. Ready? Here it is: Write tests for your bugs before you start fixing them.
I'm not a big fan of Test-Driven Development. I mean, yeah, interesting idea. Probably handy as anything in the right situation. But for me... No, sorry. Just doesn't suit how I work, most of the time. Especially since I almost always seem to be working in legacy code. (Cleaning it up. Just to be clear.)
However, I make a very vehement exception for bugs. Why? Well, unit tests let you verify behaviour, right? But one of the frustrating things about legacy code -- especially code so tangled that it earns the title of Gordian code -- is that you can never be entirely sure that your test is verifying what you think it's verifying.
I recently ran into a situation where I got lazy, broke my own rule, and coded up the fix, then wrote a unit test afterwards to verify the fix. In fact, I wrote a good half dozen unit tests to verify that the fix corrected a bunch of different variations on the original bug. I hit compile, ran the tests, and hooray, everything worked! There was much rejoicing.
I was about to check my code in when a little voice popped into my head. "Sure, the unit tests passed," it hissed in my ear. "But can you trust that? After all, you've never seen this code before yesterday. How sure are you that you understand the nuances? The subtleties? The hidden, insane side-effects?"
(The code I'm working on should probably be featured on The Daily WTF.)
I sat there for a minute, reassuring myself that all was well. After all, it was a pretty clear bug. Did I really need to let that voice of paranoia sidetrack me from all the other wonderful productive things I could be accomplishing? The fight went back and forth, doubt gnawing away at the pit of my stomach, until I finally gave in and undid my fix and ran the unit tests again with the broken code.
They all passed again. Even without the fix.
I ended up spending hours tracking down the real bug and writing a handful of new tests that actually verified that behaviour. In the process, I discovered that my original "fix" would have broken a few other things that I wasn't aware of, and that weren't covered by unit tests. (I added tests for those too. Which have already caught a potential new bug. So that's good.)
One isolated incident doesn't mean a whole lot, but over the years I've had a number of fixes that turned out to fix the wrong thing, or fixed it but in a less-than-ideal way, or just plain did nothing. Writing the test first is just basic defensive programming -- make sure you know what the problem really is before you try to solve it, and then make sure that your solution actually works.
Monday, September 29, 2008
Don't forget to have fun
"Wow, you're lucky!" I've been told on several occasions, "You actually still like coding!" I have to admit, this always makes me scratch my head. If you're not enjoying what you're doing, why do it?
One trait I've seen among people who've fallen out of love with coding is a tendency to just focus on the day-to-day work. They rarely take the time to admire what they've built, or even to build something worth admiring in the first place.
If you've got a tangled mess of legacy code, you're going to be spending a while unraveling it. The temptation is probably to just hack bits of it into submission and call it a day. That's a mistake -- sure, you'll end up with reasonable, workable code that's cleaner and easier to use than the original legacy code, but you're missing an opportunity to step outside of the daily grind and exercise your skills as a master craftsman.
Master craftsmen in other fields spend a lot of time building fairly utilitarian items -- even the best master woodworkers spend a lot of time building fairly plain dressers, tables, chairs, and what have you. But every so often, they take the time to build something extraordinary, just to demonstrate their skills.
For example, I took a woodworking course a number of years ago from a master cabinetmaker. At one point, he brought in a photo album of some of his better work. Most of it was pretty great stuff (several commissions for embassies, for example) but there were a few pieces that were just wildly different. One that sticks out in my mind was a particularly beautifully-made coffee table where the legs were actually taken from mannequins. Not to my taste or even to his, but absolutely perfect for his clients and infinitely more memorable than the original, fairly boring design for the legs.
Were the mannequin legs better than ordinary legs? From any sort of practical viewpoint, no. But the maker had a ton of fun building the table and the clients absolutely loved it, so on any real scale, yes, the legs were better.
Now, don't take this example the wrong way. I'm not arguing in favour of doing weird and impractical things to your code. What I am saying is, if you see the opportunity to build something extraordinary, take it. And if a good opportunity doesn't land in your lap, then go out and find a way to make it happen. Take some time every once in a while to build something you can be truly proud of. Otherwise, all you're left with is the daily slog... and in no time at all, you'll be finding yourself telling some over-enthusiastic coworker, "Wow, you're lucky! You actually still enjoy coding!"
One trait I've seen among people who've fallen out of love with coding is a tendency to just focus on the day-to-day work. They rarely take the time to admire what they've built, or even to build something worth admiring in the first place.
If you've got a tangled mess of legacy code, you're going to be spending a while unraveling it. The temptation is probably to just hack bits of it into submission and call it a day. That's a mistake -- sure, you'll end up with reasonable, workable code that's cleaner and easier to use than the original legacy code, but you're missing an opportunity to step outside of the daily grind and exercise your skills as a master craftsman.
Master craftsmen in other fields spend a lot of time building fairly utilitarian items -- even the best master woodworkers spend a lot of time building fairly plain dressers, tables, chairs, and what have you. But every so often, they take the time to build something extraordinary, just to demonstrate their skills.
For example, I took a woodworking course a number of years ago from a master cabinetmaker. At one point, he brought in a photo album of some of his better work. Most of it was pretty great stuff (several commissions for embassies, for example) but there were a few pieces that were just wildly different. One that sticks out in my mind was a particularly beautifully-made coffee table where the legs were actually taken from mannequins. Not to my taste or even to his, but absolutely perfect for his clients and infinitely more memorable than the original, fairly boring design for the legs.
Were the mannequin legs better than ordinary legs? From any sort of practical viewpoint, no. But the maker had a ton of fun building the table and the clients absolutely loved it, so on any real scale, yes, the legs were better.
Now, don't take this example the wrong way. I'm not arguing in favour of doing weird and impractical things to your code. What I am saying is, if you see the opportunity to build something extraordinary, take it. And if a good opportunity doesn't land in your lap, then go out and find a way to make it happen. Take some time every once in a while to build something you can be truly proud of. Otherwise, all you're left with is the daily slog... and in no time at all, you'll be finding yourself telling some over-enthusiastic coworker, "Wow, you're lucky! You actually still enjoy coding!"
Saturday, August 30, 2008
How to be a better developer
Some developers are just plain better than others. There's plenty of evidence for the orders-of-magnitude difference between OK developers and great developers, along several different axes. Some programmers are incredibly fast, some produce incredible quality, and some are incredible at troubleshooting, and interestingly, skill in one has no relationship to skill in the other areas.
A recent article in Scientific American, The Expert Mind, talked about the learning process that chess players seem to go through. To drastically summarise, it turns out that people improve through effortful learning. In the case of chess players, that means not just playing a lot of games, but actively seeking to learn lessons from each game, analyse strategy, and seek improvement.
From personal experience, I can tell you that the same basic technique applies to improving your troubleshooting. Whenever you find yourself in a troubleshooting situation, try to be more aware of what you're doing and why, rather than simply running on autopilot, and try to actively learn from your successes and failures. Most developers learn a few basic troubleshooting techniques, learn how to apply them effectively, and then stop worrying about it. As long as they're able to eventually troubleshoot bugs, they feel there's no need to improve their bug-hunting skills.
On the other hand, I always try to learn from every troubleshooting excursion. If I find myself on a wild goose chase, I stop and think about why I went down a particular path, and try to adapt my approach to avoid that mistake in the future. If I try out a new technique that helps me narrow down the problem faster than usual, I'll also go back to that to try to figure out how I arrived at that technique and why it worked, and why it might not work in other situations. Once I've found a problem, I also try to learn from it, compare it with previous bugs I've found, and build my catalog of error-prone code constructs.
The result? I started out the same as other people -- in fact, in my first few years of university, I asked others for troubleshooting help more frequently than they asked me for help! -- but now, I'm probably the fastest troubleshooter I know. I can find most non-trivial bugs faster than the original developer, even if they developed that code recently and I've never seen it before.
The difference is that because of my constant attempts to learn from my experience, and from the experience of others, I've built up a huge mental catalog of code constructs that I recognise and can immediately evaluate based on risk. Where most people have to methodically follow through every step of the code, I've developed a number of heuristics that allow me to rapidly scan through code to find a likely trouble spot.
I don't actually have more experience than my coworkers in troubleshooting; after comparing notes, I've found that we tend to have the same frequency of troubleshooting sessions, and I typically spend a lot less time per session than my coworkers do. It's not innate talent, either, as my high-school and university programming friends will attest (some of the mistakes I needed help to find... oy vey). It's just that I've always enjoyed solving problems, so I've always put more effort into my troubleshooting sessions and tried to actively learn from them, so where other people quickly developed a decent skill level and then plateaued, I've been continuously learning and improving for fifteen years.
So if you're an OK troubleshooter but not great, can you get better? Yes! In fact, there's no reason you couldn't be far better than I am, all you need to do is practice. If possible, try doing a troubleshooting session or two with a coworker and see if they have some techniques you can learn from.
We know there's no innate difference between the OK troubleshooters and the great ones, just constant effortful learning over a period of years. It's not too much of a stretch to say that the same thing can probably be said for the code-quality axis of developer talent.
The team I used to work with was packed with people who were at the top end of the scale for productivity, and all average to good on the code quality scale. (It was a heck of a team.) One of the members of the team, let's call him Kyle (not his real name), was hired on during summer break from high school, stayed through university, and eventually hired on full time. His code quality rose quickly at first, eventually settling just on the positive side of average, and then stayed there for a few years. Then, after years of sitting at this plateau, he suddenly started improving dramatically. His code went from adequate to elegant in the space of a couple years. The difference was that during that time, he suddenly had to start sharing his code with other people for the first time, some of whom had significantly more experience than he did. As a result, he was knocked out of his familiar coding routines to begin with, and then started getting constant feedback on his design decisions. Whether he actively set out to improve or not I don't know, but given the situation, he could hardly have avoided the effortful learning process.
So, back to the title of the article: "How to be a better developer". The answer is simple: work at it. Constantly try to learn from your mistakes and your successes, and from the mistakes and successes of others. If possible, set up code reviews to get different perspectives, to help knock you out of ruts you may not have known you were in.
And if you're a good or merely average developer, and you want to be great, and you're stuck working on Gordian code? Well then, my friend, you've got a golden opportunity -- thousands and thousands of examples of not-quite-optimal decisions leading to unfortunate consequences down the road. If you're given the opportunity to try to fix the code, then you're downright required to actively consider the logic of the original choices and how they could have been better. It's a nearly unparalleled opportunity to learn a whole lot in a surprisingly short time. Make the most of it.
A recent article in Scientific American, The Expert Mind, talked about the learning process that chess players seem to go through. To drastically summarise, it turns out that people improve through effortful learning. In the case of chess players, that means not just playing a lot of games, but actively seeking to learn lessons from each game, analyse strategy, and seek improvement.
From personal experience, I can tell you that the same basic technique applies to improving your troubleshooting. Whenever you find yourself in a troubleshooting situation, try to be more aware of what you're doing and why, rather than simply running on autopilot, and try to actively learn from your successes and failures. Most developers learn a few basic troubleshooting techniques, learn how to apply them effectively, and then stop worrying about it. As long as they're able to eventually troubleshoot bugs, they feel there's no need to improve their bug-hunting skills.
On the other hand, I always try to learn from every troubleshooting excursion. If I find myself on a wild goose chase, I stop and think about why I went down a particular path, and try to adapt my approach to avoid that mistake in the future. If I try out a new technique that helps me narrow down the problem faster than usual, I'll also go back to that to try to figure out how I arrived at that technique and why it worked, and why it might not work in other situations. Once I've found a problem, I also try to learn from it, compare it with previous bugs I've found, and build my catalog of error-prone code constructs.
The result? I started out the same as other people -- in fact, in my first few years of university, I asked others for troubleshooting help more frequently than they asked me for help! -- but now, I'm probably the fastest troubleshooter I know. I can find most non-trivial bugs faster than the original developer, even if they developed that code recently and I've never seen it before.
The difference is that because of my constant attempts to learn from my experience, and from the experience of others, I've built up a huge mental catalog of code constructs that I recognise and can immediately evaluate based on risk. Where most people have to methodically follow through every step of the code, I've developed a number of heuristics that allow me to rapidly scan through code to find a likely trouble spot.
I don't actually have more experience than my coworkers in troubleshooting; after comparing notes, I've found that we tend to have the same frequency of troubleshooting sessions, and I typically spend a lot less time per session than my coworkers do. It's not innate talent, either, as my high-school and university programming friends will attest (some of the mistakes I needed help to find... oy vey). It's just that I've always enjoyed solving problems, so I've always put more effort into my troubleshooting sessions and tried to actively learn from them, so where other people quickly developed a decent skill level and then plateaued, I've been continuously learning and improving for fifteen years.
So if you're an OK troubleshooter but not great, can you get better? Yes! In fact, there's no reason you couldn't be far better than I am, all you need to do is practice. If possible, try doing a troubleshooting session or two with a coworker and see if they have some techniques you can learn from.
We know there's no innate difference between the OK troubleshooters and the great ones, just constant effortful learning over a period of years. It's not too much of a stretch to say that the same thing can probably be said for the code-quality axis of developer talent.
The team I used to work with was packed with people who were at the top end of the scale for productivity, and all average to good on the code quality scale. (It was a heck of a team.) One of the members of the team, let's call him Kyle (not his real name), was hired on during summer break from high school, stayed through university, and eventually hired on full time. His code quality rose quickly at first, eventually settling just on the positive side of average, and then stayed there for a few years. Then, after years of sitting at this plateau, he suddenly started improving dramatically. His code went from adequate to elegant in the space of a couple years. The difference was that during that time, he suddenly had to start sharing his code with other people for the first time, some of whom had significantly more experience than he did. As a result, he was knocked out of his familiar coding routines to begin with, and then started getting constant feedback on his design decisions. Whether he actively set out to improve or not I don't know, but given the situation, he could hardly have avoided the effortful learning process.
So, back to the title of the article: "How to be a better developer". The answer is simple: work at it. Constantly try to learn from your mistakes and your successes, and from the mistakes and successes of others. If possible, set up code reviews to get different perspectives, to help knock you out of ruts you may not have known you were in.
And if you're a good or merely average developer, and you want to be great, and you're stuck working on Gordian code? Well then, my friend, you've got a golden opportunity -- thousands and thousands of examples of not-quite-optimal decisions leading to unfortunate consequences down the road. If you're given the opportunity to try to fix the code, then you're downright required to actively consider the logic of the original choices and how they could have been better. It's a nearly unparalleled opportunity to learn a whole lot in a surprisingly short time. Make the most of it.
Friday, August 29, 2008
Design debt
Pretty well every developer in the industry has experience arguing with management (or at least watching others argue with management) over how necessary it really is to keep the software clean, clear, and well-designed, and management overruling any technical objections and demanding the quick-fix. Or worse, the developer might be asking for time to go back and properly redo old quick-fix patch jobs... The response to that is pretty clear.
As developers, we understand how important it is to maintain good code with a clear design, how taking the time to do things right will speed things up big-time later on, and make the software so much more stable and reliable, and yet no matter how hard we try, we never seem to be able to explain to management why this is the case.
Here's the bad news: The problem isn't with management. They aren't developers, they don't understand code, and quite honestly, they don't really care about elegance or robustness, unless it impacts the bottom line. Whatever the stereotypes, most managers and other suits are definitely not stupid people. They don't understand you because they can't -- they just don't speak the language. Talking to them in programmer-speak is about as useful as trying to speak Chinese to a German. Stop wasting your time.
The good news is that there's a way to explain to management why you want to spend all that time doing work that seems to them like a waste of time -- all you have to do is explain things in terms that managers and the rest of the suits can understand.
Design Debt is a term popularised by Joshua Kerievsky's Refactoring to Patterns, although he credits the concept to Ward Cunningham via Martin Fowler's Refactoring. The idea is to explain the cost of poor design choices in terms that business folks can understand -- money.
Whenever you make a sub-optimal choice in designing or developing an application, you rack up design debt. The less optimal the design decision, the bigger the design debt. Every time you need to work with code that's carrying some design debt, you pay a penalty, typically in terms of extra time required to understand the existing functionality and figure out how to modify it. The bigger the design debt, the harsher the penalty.
Just like real debt, design debt accumulates if you don't pay it off. Whether you've made one gargantuan mistake or a dozen smallish ones, you'll be carrying the same amount of design debt, which means you'll be paying the same penalties. Software rot is generally a gradual process, where hurried decision after hurried decision has built up over the course of years, none individually very large but adding up to a huge -- and compounding -- sum. That's why it's so hard to point to more than one or two major issues with legacy code, despite an overall impression of a weak, brittle code base.
Also like real debt, there's good design debt and bad design debt. Good design debt is debt you take on as an investment that earns you more back than you spend servicing it. For example, suppose a market opportunity comes up that could mean a significant increase in your product's revenue stream, but only if you're able to provide a solution within a very narrow time frame. You don't have time for the elegant solution, but you do have time for a dirty hack that basically works, even if it'll be a pain in the neck to work with later. Believe it or not, this kind of debt is a good thing -- with an increased revenue stream, you'll hopefully have enough money to hire an extra developer or two to help do the work.
Of course, like any other debt, design debt has to be paid off. A small amount of manageable debt is perfectly normal and healthy for any company, but if the debt just keeps growing and growing, eventually the only solution is to declare bankruptcy. In the case of design debt, that means throwing out the old code and rewriting it from scratch. And while rewriting things appears to be fashionable in the computer industry right now, the fact remains that it's an expensive, painful process, and you rarely end up with a better solution than what you started with.
So, the next time you find yourself trying to explain once again to some suit why you need to spend so much time rebuilding code that seems perfectly fine to them, just remember that these folks speak a different language than you do. Explain design debt to them, help them to understand what their decisions are really going to cost, and you'd be surprised how often even the most seemingly pointy-haired come around and start making much more intelligent decisions.
As developers, we understand how important it is to maintain good code with a clear design, how taking the time to do things right will speed things up big-time later on, and make the software so much more stable and reliable, and yet no matter how hard we try, we never seem to be able to explain to management why this is the case.
Here's the bad news: The problem isn't with management. They aren't developers, they don't understand code, and quite honestly, they don't really care about elegance or robustness, unless it impacts the bottom line. Whatever the stereotypes, most managers and other suits are definitely not stupid people. They don't understand you because they can't -- they just don't speak the language. Talking to them in programmer-speak is about as useful as trying to speak Chinese to a German. Stop wasting your time.
The good news is that there's a way to explain to management why you want to spend all that time doing work that seems to them like a waste of time -- all you have to do is explain things in terms that managers and the rest of the suits can understand.
Design Debt is a term popularised by Joshua Kerievsky's Refactoring to Patterns, although he credits the concept to Ward Cunningham via Martin Fowler's Refactoring. The idea is to explain the cost of poor design choices in terms that business folks can understand -- money.
Whenever you make a sub-optimal choice in designing or developing an application, you rack up design debt. The less optimal the design decision, the bigger the design debt. Every time you need to work with code that's carrying some design debt, you pay a penalty, typically in terms of extra time required to understand the existing functionality and figure out how to modify it. The bigger the design debt, the harsher the penalty.
Just like real debt, design debt accumulates if you don't pay it off. Whether you've made one gargantuan mistake or a dozen smallish ones, you'll be carrying the same amount of design debt, which means you'll be paying the same penalties. Software rot is generally a gradual process, where hurried decision after hurried decision has built up over the course of years, none individually very large but adding up to a huge -- and compounding -- sum. That's why it's so hard to point to more than one or two major issues with legacy code, despite an overall impression of a weak, brittle code base.
Also like real debt, there's good design debt and bad design debt. Good design debt is debt you take on as an investment that earns you more back than you spend servicing it. For example, suppose a market opportunity comes up that could mean a significant increase in your product's revenue stream, but only if you're able to provide a solution within a very narrow time frame. You don't have time for the elegant solution, but you do have time for a dirty hack that basically works, even if it'll be a pain in the neck to work with later. Believe it or not, this kind of debt is a good thing -- with an increased revenue stream, you'll hopefully have enough money to hire an extra developer or two to help do the work.
Of course, like any other debt, design debt has to be paid off. A small amount of manageable debt is perfectly normal and healthy for any company, but if the debt just keeps growing and growing, eventually the only solution is to declare bankruptcy. In the case of design debt, that means throwing out the old code and rewriting it from scratch. And while rewriting things appears to be fashionable in the computer industry right now, the fact remains that it's an expensive, painful process, and you rarely end up with a better solution than what you started with.
So, the next time you find yourself trying to explain once again to some suit why you need to spend so much time rebuilding code that seems perfectly fine to them, just remember that these folks speak a different language than you do. Explain design debt to them, help them to understand what their decisions are really going to cost, and you'd be surprised how often even the most seemingly pointy-haired come around and start making much more intelligent decisions.
Subscribe to:
Posts (Atom)