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?

BT builds Etch-a-Sketch UMPC for Dilbert’s boss

Just about everyone has seen the famous Dilbert strip where Dilbert reminds his boss to turn his laptop over and shake it to reboot. As the PHB is walking away, Dilbert asks his co-workers if they think he'll ever figure out that they gave him an etch-a-sketch.

Well, I just about fell out of my chair when I saw this one, but Engadget is reporting that BT has finally built the famous etch-a-sketch computer. I have no idea how they could resist the urge to release the first picture without a Adams' famous strip on the screen!

Bugs should not be logged?

Here's a fun one. I came across an article explaining why bugs should not be tracked via a post on Joel on Software. As I read the article, I oscillated between outrage and amusement. Could this guy really be serious?

I considered the possibility that Mr. Thorup just wasn't experienced in software development, and then I thought that perhaps he was being intentionally outlandish in order to make a point. I'm leaning toward the latter.

No matter. Let's take his idea at face value and examine it.

To summarize the article, Lars suggests that the ability to log a bug breeds a casual attitude toward these bugs. Log it and forget it. Out of sight, out of mind. Better to deal with the little beastie right now, or if now isn't doable, then at least keep the bug somewhere close-at-hand. A post-it on your monitor. A bug report in a database, says Lars, adds no value to your software. It's unnecessary overhead, and it not only can be eliminated, it should be eliminated.

Fair enough, and all things considered, a pretty healthy attitude towards software development. After all, I've long been a proponent of using only as much process as is needed for the task at hand. So why am I not beaming with excitement at the prospect of ditching my bug database?

Simple. Two reasons: workflow and usability.

Workflow should be pretty easy to understand. It's really difficult for one guy with a fist-full of post-it notes to scale a bug tracking process up to deal with more than a small workgroup. Throw in a remote tester / user / developer / manager, and you're cooked. Simply put, the larger your project, the more people are going to be involved in the process. There are guaranteed to be cases where the person who's reporting the bug isn't the person who needs to fix it. In fact, the person who's reporting the bug may not even know who's supposed to fix it. This implies workflow, and that isn't satisfied well with post-its.

Of course, the post-it that falls off the wall of shame and behind the desk will kill you, too.

But what does usability have to do with tracking bugs? Besides the really clever segue from my last post, the way you handle bug reports has a very real usability factor for your users and testers, and your developers, too. Again, for small shops and small projects, I can see the value of hollering, "got another one!", and then slapping another yellow square on the wall.

Lars suggests that the optimum approach is to fix the bug as soon as it's seen. Great -- as long as the bug I see is my own, and and long as the fix can happen quickly without tearing me off of whatever I was working on when I found the bug. If I find a bug in an area of the code I'm not familiar with, or if the fix for the bug is going to turn into a non-trivial refactoring effort, I really have to question whether you want your developers dropping everything to play exterminator.

How about when users or testers find bugs? Every developer fights a constant battle to get "into the zone" -- it takes time to get your head into a design or coding task, and interruptions cause a loss of productivity entirely out of proportion to the time apparently taken by the interruption. This is the premise behind point #8 in Joel Spolsky's 12 Steps to Better Code -- be sure that developers have quiet working conditions. If you have testers and users running up to developers every time they think they've found a bug, the developers quite literally won't get anything done.

Say "yes" to frequent interaction, but say "no" to real-time interruptions.

This, by the way, is the same reason that computer architectures make use of asynchronous channels like message queues. It is far more scalable and efficient to let multiple subsystems operate with a little buffer between them, lest all of the component parts get hung up waiting on one another.

A bug database can also be more user-friendly to someone reporting a bug. Consider what would happen if you made every tester and user run a gauntlet before you accepted a bug report. Is this bug already logged? Is it reproducible? On our system, too? Is this bug *really* important enough to work right now? Don't you think we should walk around to see the other stakeholders to see if they agree that we should make this change right now?

Never mind.

Your goal when tracking bugs is not to collect the smallest number of bugs. In fact, if your bug reporting mechanism is working well, you'll probably get a ton of feature requests and product suggestions via bug reports. Don't flog these users for improper use of the bug system. Thank them for their feedback. It's far easier to categorize bug reports into feature requests than it is to guess what your users are thinking.

Make bug reporting as easy as possible for testers and users. The closer the testers are to your team, the more you want to work with them to educate them on what makes a good bug report, because that will save everyone a ton of time, but for the beta testers, non-technical testers, or users, just take whatever they'll give you. The alternative, in many cases, is to just not learn about their experiences, and you can't recover that lost data.

To log or not to log? No contest -- you won't catch me without a bug database any more than you'll catch me without source code control. It's just not going to happen.

You call this usability? ‘Cause I don’t!

So, how useless is this?? If you’ve got an email without any formatting in it, you don't need print preview very badly, do you? I mean, how hard is it, exactly, to figure out what it’s going to look like when it’s printed? When your email is formatted, on the other hand, you're up a creek. Nice.

I'm pretty sure that Lewis Black would be able to come up with something far more colorful to say about the bozo's who punted on this feature, but it might not be printable for polite company. Still, this is a pretty lame cop out.

This is a classic case of a user interface usability problem. This is what most people think of when they refer to usability. Interface design and implementation is the most easily-seen, and often the most vividly-felt form of usability we encounter in software development. Plenty of books have been written about this sort of usability. Cooper's About Face is one of my favorites.

The inspiration for this post was a classic UI usability foible, but this isn't the only kind of usability you should be aware of. As software developers, we encounter usability in a number of areas beyond the traditional "pretty picture" stuff (apologies to Cooper).

Consider the dynamics of that Outlook dialog, and ask yourself why it's so immediately identifiable as a usability problem. It's a disruption. It was supposed to work, and then suddenly, it didn't. I got angry at Outlook. That's how I know it's a usability problem.

I'm going to explore some other aspects of usability in software development in some coming articles, and I think this well-timed error message is a great leaping-off point.

Where do you find usability in unconventional places -- in development or otherwise?

Turning off a Unit Test does not fix it.

Late this afternoon (is there any other time for things to go wrong?) I found myself hunting down a bug. I found it, and when I did, I had one of those, "how in the world could this have gone unnoticed" moments.

Specifically, how in the world could this have gotten by our unit tests?

I found that, too. It turns out that there used to be a unit test that would have caught precisely this error, and someone had commented it out. Most readers at this point are either asking, "what's a unit test", or exclaiming, "Duh!". For the rest of you, here's a free scrap of wisdon to tuck away:

If a unit test fails, commenting it out does not count as "fixing" it.

This makes roughly as much sense as a miner watching his canary drop dead, and fixing the problem by chucking the canary corpse in a hole. After all, if he doesn't see the bird anymore, then there's no problem, right?

Come on -- get real.