The following entry is written specifically for Google scavengers who are butting heads with the same problem I encountered earlier this week.
I've been on a huge automated build kick lately. Sure, it's cool to be able to generate a build automatically, but it's even cooler to do the build, run the unit tests, and generate all of the documentation. (What would be cooler still would be if it made omelettes at the very end. I'm not to that point yet.) Omelette making aside, I used CruiseControl.NET to integrate all of this, along with Nant for the build, NUnit for the tests, and NDoc for the documentation.
The problem I kept encountering is that I couldn't figure out how to make CruiseControl.NET and NDoc play nice. I had no problem building the app and running the unit tests from CruiseControl.NET, but I couldn't get it to call NDoc to generate our class library documentation. I searched and experimented for hours with absolutely no luck, and finally, I came to a solution that works. The secret is to call NDoc from Nant, NOT CruiseControl.NET. Nant interfaces very well with NDoc; take a look at its
If you're doing continuous integration in a .NET environment and you want to generate all of your NDoc documentation as part of this process, call it from Nant, not CruiseControl.NET.
I am a big fan of agility. That doesn't mean I appreciate the fluid movements of the puma and the billy goat, but that I appreciate the goals of agile, iterative approaches to development. I'm a fan, but not a slavish admirer; I think you need to understand iterations before you can apply them. To understand iterations, you only need to answer one question: How big is your appetite? Let's look at an analogy.
One day, noon rolls around and you discover you're hungry. You go into kitchen, ready the bread and bologna, and announce to the world, "It's sandwich makin' time!"After all of the cool stuff you've been reading on agile methods, you decide to apply some here. You tear off two square centimeters of bread and a square centimeter of bologna. With them, you construct a very, very tiny sandwich. You put it in your mouth and say, "Mmmmm, now that passes the unit test!" But that tiny sandwich isn't enough; you need several more. You repeat the process several dozen times until your stomach finally says, "Back off, Boudreaux." Then, at the end, you analyze what you've done. You got to tear off 45 little pieces of bologna, so you really, really perfected that part. No matter how good you got, though, it still took a while to tear off all of the little bologna chunks and put them between your little bread chunks.
The next day comes, and you decide to take a different bent towards the problem. Iteration is out the window; you fart in its general direction. Instead, you'll streamline and do it all in fell swoop. As you ready your materals, you have another great thought. You think, "Hey, I eat sandwiches every day. Since I'm making myself one today, why not make several more?" Emboldened, you set to work and a twenty minutes later, you've created 15 sandwiches.
The first one is great. The next day, you eat the second and you're still happy. After a week, however, you find yourself getting a little tired of the bologna. The bread is going stale and the meat is sporting a few fuzzy green spots. You want something else, but you feel obligated to eat your way through your bologna stockpile.
People do approach software this way, though. I've talked to people whose iterations consisted of literally a single feature. The difference between releases was so marginal, there was hardly a point in even releasing it; it was like making a sandwich a bite at a time. On the contrary, we've all been involved in projects where we did nothing but stockpile sandwiches in the hopes that someday, someone would want to eat them.
The point of an iteration is, I think, to satisfy without overwhelming. An iteration aims to please end-users without frustrating them. Applying that to the sandwich world, it means you make only as many sandwiches as you plan to eat for that meal. Not only that, but you make only the kinds of sandwiches you wish to eat; the menu isn't set until lunch time, in other words.
The scope of an iteration will always be nebulous; you can't whip out a formula or chart it in Excel. Ultimately, the only way is to go to the user and ask them the size of their appetite. Deliver no more and no less. Then, when they're ready for more, you deliver more. In that way, you define your iterations. If that sounds nutty, well, you can always go back to making your sandwiches one bite at a time.
Hey, remember last time when I was talking about how some objects implement IDisposable, but often you can't tell because there's no public Dispose method? And remember how I had no idea how that worked and I got really frustrated and tried not to cry in front of you? Well, yeah. Patrick did a little research and discovered this is actually because of something called explicit interface implementation.
To steal a line from that link, "a class that implements an interface can explicitly implement a member of that interface." So, when a StreamReader implements IDisposable, it must be explicitly implementing its Dispose method. By doing that, it can fulfill the contract with the interface. It sounds complicated, and it pretty much is. Here's a quick example.
//Explicit implementation of IDisposable.Dispose
void IDisposable.Dispose()
{
}
//IDisposable expects a Dispose method with
//no parameters, so this doesn't work.
public void Dispose(bool confirm)
{
if (confirm)
{
//release all resources in here.
}
}
}
Now, if you look at that code, there are a couple of salient points. First, it compiles. That's always good. Second, it implements IDisposable and does so without a public void Dispose() method. It does have a public void Dispose method, but it takes in a parameter, so it doesn't fulfill the contract with the interface. However, it works because it explicitly implements the IDisposable.Dispose() method. Sneaky, eh? For verification, wrap an ExplicitDisposable object in a using block and let the compiler roll.
All of this was news to me. I knew nothing about explicit interface implementation, and my head was beginning to combust from trying to comprehend this. Thankfully, Mr. Lioi pointed me to a very informative usenet thread that got me straightened out. I'm still not sure just how useful it is and whether it does anything but confuse developers, but it's out there. Oh, it's definitely out there, so it's good to learn.
I have in my right hand an interesting C# tidbit that I offer up for the benefit of the world. Did you know that some objects actually implement the IDisposable interface, but that it's impossible to tell from Visual Studio's Intellisense? You say, "Big deal. Shut your yapper, Powell, and get back to your Thundercats fan fiction!" Well, IDisposable is one of the most important interfaces to know about. If an object implements IDisposable, its creator is telling you that this is an object you must dispose. A lot of times, that's because the object is tying up a physical resource like a database connection or a file on the hard drive. If you don't dispose of those objects properly, you get resource conflicts.
Usually, you can tell through Intellisense if an object has a Dispose method. I always thought that if object.Dispose() isn't available, it solves that conundrum. However, there are lots of objects that DON't have a public Dispose method that DO implement IDisposable. Case in point, the StreamWriter and StreamReader objects (LINK TO MSDN DOCS). If you check their members, their Dispose() methods are actually protected. Intellisense is not enough in these cases.
If you're ever unsure about whether an object implements IDisposable, just wrap it in a using block like so:
Now, why is the Dispose method protected? I have no idea. I have it on my agenda tomorrow to ask Bill Gates and Anders Hejlsberg the next time we're playing horseshoes. For me, the important thing isn't necessarily the visibility of that Dispose() method, but the IDisposable interface. An easy way to check that is with a using block.
The only thing worse than drafting requirements is maintaining requirements. Just trust me on this one. At the very least, you stand some chance of convincing someone to do requirements by telling them, "Look, YOU get to decide what goes into the system. Isn't that exciting?!" Lots of folks will fall for that one (case in point: me). There's not such a good chance that you can convince someone to maintain the requirements by telling them, "Look, YOU get to sit in on a bunch of reviews and then integrate all of the changes into the existing documents in a timely manner. Isn't that exciting?" That's not exciting; it's pretty awful. However, if you fail to maintain the requirements, they're no longer requirements. Allow me to explain.
Like I was saying, I got tricked into doing requirements for a big application about nine months ago. It didn't take long before I regretted that decision something fierce. I realized it was important, though, so I did it. As soon as I finished the requirements, I exhaled deeply. I spit in the dirt, wiped my hands on my overalls, and declared, "My work here is done!" Then my boss said, "What about reviews?" And I said, "Didn't you hear me? My work here is done!" Of course, it wasn't; someone had to change the requirements based on everyone's feedback. Since I wrote it, why shouldn't it be me?
My argument, a very cunning one, was that the requirements were good enough. We had 90% of the system covered, and we could figure out that 10% at a later date. I had a good reason for this: I was tired of doing requirements. I wanted to do real work. I hadn't compiled anything in weeks, and I feared my code fu was leaving me.
I continued to think about it, and slowly I began to realize that if SOMEONE didn't maintain the requirements, they were no longer requirements. A requirement says that a certain element of functionality is required to be in the system; no ambiguity exists there. However, if the documents didn't get updated, we lost that certainty. No longer could we absolutely say that the feature was required to be in there. We could say that we were fairly certain that the feature should be included, but there'd be no authority. No longer were they requirements, they would devolve into Fairly Certain Should Be Includeds. Trust me, I was not happy to convince myself of that point.
I think this applies for all documentation; if it no longer reflects the project, it's no longer useful. If the requirements change, update the requirements. If the design changes, update the design doc. If the code changes, update the comments. Otherwise, the only point of the documentation is to give you a starting point, a general idea of what's going on. You can get that from a lot of places, so what's the point in some elaborate document? Why not just take notes on cocktail napkins if that's all you're using your documentation for? Or why not just barge into your coworkers' offices and ask for a one sentence explanation?
As much as I hate to admit it, documentation has a point. It exists to inform you, in a concise, easy-to-understand manner, about a project. If it doesn't do that, there's no point in having it. In order for documentation to fulfill that purpose, it must be maintained. All the scribbled cocktail napkins in the world can't get around that. Now, whether requirements maintenance has to be left to ME is another issue entirely.