08 October 2013

Solving the Legacy Code Problem

What is Legacy Code?

In his book, Working Effectively with Legacy Code, Michael Feathers defines legacy code very simply:  Code without tests.  He isn't trying to be inflammatory, or suggest that you are a bad person because you have written legacy code (perhaps yesterday). He's drawing a clear boundary between two types of code.  James Shore (co-author of Art of Agile) has an equivalent definition:
"Code we're afraid to change."
Simply put, if it has tests that will prevent us from breaking behavior while adding new behavior or altering design, we can proceed with confidence.  If not, we feel appropriately uneasy.

Of course, if you plan to never release a new version of your code, you won't need tests.  In my almost 40 years of programming, I've not seen that happen even once.  Code needs to change. Ergo, code needs tests.

Build Your Team's Safety Net

Your team may want to adopt this metaphor: Think of your whole suite of automated regression tests as a safety-net that the team builds and maintains to keep themselves from damaging all prior investment in behavior.

If it takes two hours to run them all, you'll run them once per day, and if they catch something, you know that someone on the team broke something within the last 24 hours.  If it takes one hour, you'll run them twice per day (once at lunch) and you've narrowed down the time by half.  That's probably better than 80% of the teams in the world, but it can be even better. 

I'll give you a real-world example: I worked on a life-critical application in 2002.  After two years of development, this product had a comprehensive suite of 17,000 tests.  They ran in less than 15 minutes.  That team often took on new developers, and we gave them this simple message:  "You break something, someone may die.  But you have this safety net.  You can make any change you believe is appropriate, but don't commit the change until you run the tests." In the time it took to walk to the cafe and buy a latte, I would know whether or not I was making a mistake that could cause someone to die.

We made changes up to a day before going live.

It can be that good.  Of course, it takes effort to "pay down" the legacy code debt (and a lot of mock/fake objects…another topic for another day.)  But the longer you wait, the worse the debt becomes.

Characterization Tests

The product mentioned above was developed from the ground up with unit tests written by a team who embraced unit-test-level Test-Driven Development (TDD).  Nice work if you can get it.  The rest of the world faces legacy code debt. 

You don't have to pay it all down before you proceed. In fact, you mustn't.  You have to be thoughtful about selecting high-risk areas:  An area of code that breaks frequently, or is changed frequently, should first be "covered" with "characterization" tests.

"Characterization test" is not defined by any particular type of tool. We often use the unit-testing framework, but we're not limited to it.

Like unit-tests, these tests must be deterministic, independent, and automated.  Unlike unit-tests, we want to "cover" the most amount of system behavior with the fewest number of tests and the least effort.  When you write these tests, you are not bug-hunting, but rather "characterizing" or locking down existing behavior, for better or worse.  It's tempting to fix production bugs as you go, but fixing a bug that's escaped into the wild could introduce another bug, or break a hidden "feature" that a customer has come to rely on.  It's fine to note or log the bug, but your characterization test should pass when the bug manifests itself.  Name the test with a bug description or ticket number, so the team can easily find it later.

Why not fix the production defect? Because the point of creating this safety net is to give you the freedom to refactor. You may be refactoring so you can add new behavior more easily, or even so you can fix a bug more easily later, but refactoring and adding behavior are two distinct activities. Using TDD, they are two separate steps in the cycle.  (Aside:  Fixing a bug is effectively adding new behavior, because the system wasn't actually behaving the way we expected. You can use TDD for that.)

The unit-testing framework and developer IDE usually gives us the most flexibility, plus the ability to mock dependencies and use built-in refactorings for safety. But in order to lock down large swaths of behavior, teams should think creatively. I've worked with teams who compared whole HTML reports, JPEG images, or database tables; or who have rerouted standard input and output streams. The nature of the product and the size of the mess may dictate the best approach.

And don't aim for a duration target, e.g., "15 minute test runs." Teams sometimes respond to arbitrary targets by sabotaging their own future in order to make the numbers.  For example, deleting existing tests! Rather, aim for improvement by looking for the greatest delay in testing.  Weigh a "huge refactoring" of the persistence layer against using an in-memory database.  There is no in-memory version of your database software?  Use a solid-state drive. Developers are naturally creative problem-solvers, particularly when they collaborate.

Resistance is Futile

Code written without tests often resists testing. When you write unit-tests test-driven, they tend to be very tiny, compact, isolated, and simple (once you get the hang of it). It's actually easier and faster to write them with the code using TDD, even though you end up with more of them.  Interestingly, if you write your unit-tests after the code has been written, you are really writing characterization tests: They're harder to write, they're often a compromise that tests a number of behaviors, and they often give you the bad news that you made a mistake while coding. This is why most developers hate writing "unit-tests" (me, included). We were doing it backwards.

That may make writing characterization tests seem unbearably painful, but it's really not.  Once you collect a handful of simple, "surgical refactorings" for creating testable entry-points into your behaviors, the legacy code problem becomes a bit of an archeological expedition: Find the important behaviors, carefully expose them, then cover them with a protective tarp.  It can be rewarding all by itself. But the big payoff comes later, when it's time to change something.

01 October 2013

Yet Another Meeting?! How to Make the Daily Scrum (or Stand-Up) Meeting Work for Your Team


Let's cut to the chase:  No one likes meetings.  A status meeting every day is enough to drive you crazy.

The Stand-Up isn't meant to be a status meeting.  Status may get communicated, and extracted by the ScrumMaster (SM) if he or she is paying attention, but that's not the purpose of this meeting.

The Stand-Up is a daily tactical planning meeting: Of, by, and for the team.  For teams who need to collaborate (and I would suggest that there is no other kind, but that's a digression), this brief daily huddle creates visibility into what is needed by each team member today.

The Three Questions

You may know the three questions: (1) What did I do yesterday? (2) What am I doing today? (3) What are my impediments?

Each person takes turns answering these three.  Sounds like a status meeting, right?

Let's reword them:

1. What did I provide or learn yesterday that the team needs to know about?
2. What do I hope to accomplish today and whom do I need to collaborate with to get that done?
3. What's preventing me from doing my best professional work?

Sorry I'm Late, But Not Real Sorry

When people are consistently late to, or absent from, this meeting, it's because the meeting has lost its value.  Rather than creating some form of punishment/reward system to attract people, or simply canceling the meeting, we need to uncover the root problem, and take action to solve that.

Is the meeting at a bad time?  Try moving it. A lot of teams use 11:45am.  Usually folks are available then, and it's nearly guaranteed not to run long. Many follow-up discussions can happen during lunch.

Are people reporting task status to the SM?  Try holding a no-electronics huddle. (Update Jira before or after.) Can't remember what you did yesterday?  Take a minute or two prior to the huddle to write down what happened since the previous stand-up, and what you will be working on today.  Got nothing done, or your task is lingering in the "In Progress" state for more than a day? Think about what's impeding you, or with whom you'd like to collaborate to get that task completed. In other words, stop expecting yourself to shoulder all blame, or heroically accomplish each and every task, alone.

Someone shows up consistently a few minutes late?  I recommend starting the meeting on-time, every time.  If they miss something, they can ask someone to get them caught up afterwards. Besides, there's another stand-up tomorrow.  The habitually late person is responsible for finding his own strategy for on-time arrival.  (I was that person many decades ago, in high school, so my mother introduced me to black coffee. On-time, and my grades improved!  Your mileage may vary.)

Particularly with this team meeting, it's important that one individual cannot delay everyone else. This applies to your SM, PO, CEO, and POTUS. It's not their meeting. They are always welcome to schedule something afterwards (and perhaps buy the team lunch?)

This Won't Hurt a Bit

If the team feels that some part of their day-to-day work activities are uncomfortable or unnatural, ask them if they would like to invite an agile coach in to observe and make some recommendations.

A coach is not a manager: More like a "team doctor." Having someone intrude and observe may be uncomfortable, too, but the goal is the overall health of the team.

The daily stand-up contains much more than task progress: it often reveals the attitude of the whole team and hints at systemic dysfunctions. If your stand-ups are dull, too long, frustrating, mechanical, or contentious, then let the coach see all that. Good coaches don't blame individuals for a systemic symptom: The coach is there to identify challenges and help the team find an agreeable set of adjustments. We want to see the team create its own way of working that is professional, productive, supportive, exciting, and sane.