Bug Tracking 101: Where TFS Blows It

As a developer, I love Microsoft Team Foundation Server. As a manager, I hate it.

Why? Bug tracking.

"But TFS does bug tracking," you say. Sure it does. So let me try to explain why Microsoft's brand of bug tracking just isn't going to get the job done. I'll start with my personal opinions about bug tracking.

Bug tracking is one of the basic life-support functions in software development. I'd rank it second after Source Code Control as far as critical support functions. To put it bluntly, there's just no reason not to have a bug tracking system these days. There are hundreds of very good systems out there, and you can get most of them free in a box of breakfast cereal.

It's pretty common, in my experience, for people to argue against logging bugs. There's a perception that logging a bug is a big, hairy deal, or maybe they want to keep the count low in their "real" bug database so their numbers look good. Bull-pucky, I say. Log everything. If a bug occurs and it doesn't get recorded, it never happened.

Sure, you're going to get some bad bugs. You're going to get some duplicates. You're going to get some bugs you can't reproduce.

So what?

If you log a bug and have to close it because it's a bad bug, you spend some time doing so. Bug if you don't log a bug, you can never reverse that decision. In fairness, you'll usually want to have *something* in a report that's actionable, but I really prefer to set the bar low so I sweep more bugs into the net. I can far more easily wipe out the bad than I can make up for lack of data if there's something going on that I'm not seeing.

You may have inferred by this point that I prefer to let most, if not all users and testers enter bugs. Right on. Again, this is to help gather information as close to the source as possible. And yes, again, this increases the chance for junk data. And yes, yet again, the alternative is no data. If you make it easy for lots of people to give you feedback, some of them will give you feedback. If you make it difficult, damned few will give you any at all. You're in the dark -- probably with a false sense of confidence about the state of your system. "Look - no bugs logged in the last week! We're perfect!" Sure, you are.

Ok, so that's how I feel about bugs. Feel free to argue amongst yourself, or even with me. 😉

Back to TFS. Like I said, they have a bug tracker, so what's the problem? Not a thing, if you happen to be a developer with TFS already running as part of your IDE. If you're not a developer, here's what you've got to do to use TFS:

  • Buy a license. Yes, if you want to log a bug, you need a full TFS client license. Just to log a bug. JUST TO LOG A BUG.
  • Since you have to buy a license, you're probably going to need to justify that license to somebody (not that I've run into that brick wall recently, or anything....). Quick review: you're going to be asked to make a business case for purchasing a client license for someone so they can LOG A BUG!! Let me know how that goes for you, ok?
  • Now you need to get Team Explorer installed on the workstation of the person who's supposed to log the bug. Oh, no -- you can't just browse to a web page to do this (despite the fact that TFS makes heavy use of SharePoint). Is any of this making sense to you? Astute readers will observe at this point that there are add-on software components available to web-enable TFS. I'll observe right back at you that the very fact that this is an add-on tells you something about whether Microsoft gets how this software should be used.
  • Now, your user is ready to log a bug. After they open Team Explorer. On the machine where it's installed. Oh, they're at a different machine now? Maybe they're testing on a Mac? Nuts. Well, write everything down and log it when you get back to your desk. Poof! Your bug just disappeared, because 99 out of 100 users are just going to say, "why bother?" And they'd be right, of course.

Come on, Microsoft -- think this through and get it fixed. Now. Please.

In the mean time, use this.

Translators are a pain! Use Reflection to make them easier.

I'm working with a SOA architecture that's forcing me to bend, fold, spindle, and mutilate objects as they move across tiers. This is awkward, but it's a common side-effect of splitting applications across tiers. One of the implications of this design is that objects have to be translated, or copied value by value across tiers.

This work is pretty straightforward, but it's tedious, and worse, it doesn't add any value to the software -- it's just plumbing. Boy, I thought, wouldn't it be nice to be able to hook two equivalent objects up and move values from one to the other automatically.

Enter reflection. Now, before I show the goodies, you'll want to be careful where you use this. Reflection isn't as efficient as writing early-bound code, but when used sparingly, it can haul a lot of groceries with very little code. If nothing else, use this as a quick primer on reflection. It's not as hard as many people think, and it's a great technique to have in your toolbox.

Here's the code. If I were to enhance it, I might have it raise an event when it comes across a property it can't match, so a consumer could handle custom maps in-line. I think it would also be great to use this as the basis for a Visual Studio addin to manufacture early-bound code for translation - sort of like the translator wizard in the Web Services Factory, but a little more automatic. Have fun with it...

public class Translator
    {
        /// <summary>
        /// Use reflection to copy as many properties from the "from" object
        /// to the "to" object as possible, matching properties by name.
        /// </summary>
        /// <param name="objFrom">Copy from object</param>
        /// <param name="objTo">Copy to object</param>
        public static void Translate(object objFrom, object objTo)
        {
            Type tFrom = objFrom.GetType();
            Type tTo = objTo.GetType();

            //Get the properties of our type
            PropertyInfo[] fromProps = tFrom.GetProperties();

            foreach (PropertyInfo pInfo in fromProps)
            {
                try
                {
                    object[] fromValue = new object[1];
                    fromValue[0] = tFrom.InvokeMember(pInfo.Name, BindingFlags.GetProperty, null, objFrom, null);
                    tTo.InvokeMember(pInfo.Name, BindingFlags.SetProperty, null, objTo, fromValue);
                }
                catch (Exception ex) { }
            }
        }
    }

Mouse Balls

My current client is insistent that my team use their equipment while we're working on their software. I can't say I really agree with this, but I can understand the spirit of the request. More difficult to understand, however, is why they'd persist in this direction despite the fact that the hardware isn't up to the job. Processor speed is fair, but there's not enough RAM, and I think we'd have to have a personal memo from the governor to get a second monitor.

But the thing that's driving me absolutely batty is my mouse. Yes, it's an old-style mechanical mouse, complete with the rubber marble that turns the little rollers inside. The moving parts pick up junk and start to fail, and no amount of cleaning makes it quite right again. Every day, I end up taking the thing apart a couple times, to very little effect, and every day, I silently boil every time I move my mouse and the cursor just sits where it was, blinking in silent mockery of me and my defective hardware.

Yes, I've tried to obtain a new mouse. Paperwork has been obtained, filled out, and signed. As of my last trip up that mountain, I hadn't yet achieved enough signatures to move this $12 transaction forward. Yes, the time I've spent on paperwork and signatures exceeds (easily) the cost of a new mouse. Yes, I could probably go out and buy a damned mouse myself and bring it in. I very well may end up doing that the next time I order from Newegg or Amazon.

In the mean time, I have to marvel at this brilliant example of process gone astray...while I clean my mouse.

A Special Kind of Crazy

The franken-systems we often find ourselves immersed in are a product of a hundred bad decisions, compounded like interest until they yield a wealth of dysfunction. Such is the case in this example.

First, some background. I'm working on a system to do some event scheduling. At the beginning of the project, my team learned that we'd also be implementing a web service for a name and address system, since we needed some of that data, and the client kind of wanted a web service for that other system anyway. We could take as much time to build it as we needed, up to two weeks. A couple other guys on the team built this web service under the direction of a designer. This train of thought contains several bad decisions, for those keeping score at home.

Fast-forward to today. I'm working on updating address information in the scheduling system. What should have been a pretty simple effort had proven ridiculously difficult. The root of the current set of problems is the design of the aforementioned web service. Specifically, I'm beating my head on the wall over the state and county components of the address.

These are not, in fact, textual representations of the state and county, respectively. They're objects. Now, don't get me wrong - I'm a big fan of objects, and I can appreciate the elegance of a wide and deep hierarchy. It's a great way to explore a problem domain you may not be familiar with. Invoices have headers and detail lines, and a detail line has an item and a quantity and a description, right?

But is that really what you need for a state?

Let me run through some of the places where the choice of an object for the state has caused problems.

  • ASP.Net DataGrid data binding. It turns out that the DataGrid doesn't handle complex objects well, so it just couldn't cope with State as a whole object within an Address. There are multiple workarounds for this, of course, including replacing the grid, I'd expect. The direction I went on this was to craft a UIAddress object that flattened an Address the way I needed it. This resulted in extra code - not just to define the object, but to translate to and from the new, different address forms, but it worked for the UI.
  • Updates. It turns out that when you update the state value for an address, not only do you need to reach down in to the object (address.State.StateAbbreviation) -- no, that's not quite enough. Try to update that, and you'll get an error -- a key violation error -- because you haven't set the StateID to a valid value. That's right. You can't set an address to a different state unless you happen to know the database key of that state. Upon further examination, this made a bit more sense to me. After all, if we happen to grant statehood to Puerto Rico or maybe annex Canada or something, and we choose to abbreviate Puerto Rico as
    "TX" or "AB", well, then, we'd just have a mess on our hands, wouldn't we? So you can't just send in the state abbreviation -- that won't work at all. Sigh.
  • New objects. You can't just create a new Address() now. If you do that, you won't have initialized the corresponding State and County objects. Again, not the end of the world, but it's just a little bit more code you have to write whenever you use these objects.

The moral of this story is clear - consider the users of your APIs before they show up at the gates with pitchforks and torches.

When Tools let us Down

This afternoon, I was building a new class. I had a handful of data items I knew I needed, so I made fields for them, and then began constructing properties to encapsulate the fields. This is a well-known best practice, of course, because it lets you protect your internal representation of this data, and to therefore insulate consumers of your class from any changes in this underlying data.

Needless to say, setting up a property get...set block is slightly more time-consuming than just creating a field and being done with it. Along comes the Visual Studio C# refactoring tools to help encourage the proper behavior. Just right-click on your field, choose "Refactor...." --> "Encapsulate Field", and you can create the getter and setter in one swift action.

Except that this dialog takes longer to load (at least on this client's system) than it would take to type out the damned block to begin with. Thanks for the gesture, though.

In this case, the problem seems to be that the tool is solving a larger problem than I've got right now. It's able to seek out existing code that's already referencing this field and repair the effects of any name changes you make.

Ok, that's cool. But I don't need that right now. This is a brand-new class -- there are no references to these fields yet. But someone might need this, and I concede the fact that if I found myself needing functionality like this, I'd scream just as loudly if it was taken away from me.

So is there a better solution? I think so -- two, in fact, in this case, or maybe even three.

The first improvement I'd consider is to not spend time hooking up the "search all references" stuff unless I said I needed it.

In my case, though, there's an even better solution. The fields I was trying to encapsulate were already marked "private", which means that they can't be referenced outside the current class. That should simplify matters considerably.

Oh, and the third improvement, for extra credit, would be to multi-thread this dialog so I can run the simple functionality even if the advanced stuff is still loading.

Usability is everywhere.

George S. Patton on Refactoring

"The time to take counsel of your fears is before you make an important battle decision. That's the time to listen to every fear you can imagine! When you have collected all the facts and fears and made your decision, turn off all your fears and go ahead."

-- George Patton

I know, I know -- what in the world did Patton know about refactoring?

Nothing. But he knew a hell of a lot about getting things done. He knew that there is a time for planning and a time for acting, and he knew that if you prepared when it was time to prepare, you should feel confident in your actions because you've done your homework ahead of time. Had he lived to witness Agile development, he would have had the following advice about refactoring:

Preparation

Patton knew that no battle was ever won without proper preparation. For him, that meant training, discipline, and strategic planning. We could learn a few things from him.

  • Training. It's important to know the technology you're working with. This shouldn't be a news flash, but you won't be effective unless you are fluent in the development language and architecture in use. Please also be comfortable with development best practices such as those from McConnell.
  • Version Control. This had better be another no-brainer. In fact, if you don't have your code in an SCC system, give up now and go into sales. You're not going to make it in software development.
  • Unit tests. Make sure you have them, and make sure they're really exercising the system. It's sometimes difficult to hit 100% code coverage, but if you're not at 80% or better, you're not trying very hard, and you're badly exposed to regression bugs. You'll deserve whatever sad fate awaits you.
  • Objective. Patton knew the value of planning. In this case, you need to clearly understand what you're trying to accomplish with your refactoring. Maybe there's a pattern that isn't being followed consistently. Maybe there's functionality that should be extracted into an object or component. Whatever - just make sure you know exactly what you're trying to get done. When you start to work, you'll find it easy to be distracted - there's always something else to fix or improve. While Patton also understood the value of tactical flexibility, losing sight of your objective is the best way to ensure that you'll fail.

Refactoring

Now you're ready to work, so crank up the chain saw. Now is not the time to be timid - you've planned for this, so make it happen. While you're in there,

  • Don't leave dead code -- commented dead code is useless. It's available in SCC if you need it. You don't buy a book and expect to see all the edits. Outtakes can be funny, but not in the middle of the movie. You want your live code to be living -- not a monument to your past sins. You already decided that your old code needed to be refactored, so don't chicken out now. An agile methodology thrives on having frequent builds that are as near to production-ready as possible. You wouldn't want to ship with dead code in your project, so get rid of it.
  • Don't half-refactor. You're probably going to want to try to reach some interim stable points where you can build and run some unit tests, but beyond that, try not to stop until you're done. When you are refactoring, this is your chance to apply a consistent thought process, design, and coding style across the project. Future developers will thank you if you manage not to leave a Frankenstein-style mishmash of source code.
  • Keep notes. If you find additional areas that need work, make a note somewhere where you'll follow up.
  • Test. You need to see clean unit tests before you're done. Remember, commenting the test out doesn't count. If you can, run through some UI-level or System testing, too.

As always, scale the process to match the task - small refactoring jobs move through planning and refactoring in seconds, but large efforts can take considerable time. Remember, though, that source code control is never optional, and if you skip unit tests, you're really asking for trouble.

What other refactoring tips do you have?