🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Giblets round 3 - redux

Published May 07, 2008
Advertisement
If you haven't already, take a moment to read the Brain giblets round 3 entry, including the comments.

The idea was brought up of using comments rather than actual function call stubs, and then exploiting IDE tools like global find or Visual Studio's Task List to locate and flesh out those comments at a later date.

This isn't a particularly bad idea, per se, but it misses the spirit of what I was trying to suggest. There are a few major shortcomings to this approach:

  • It doesn't integrate with test-driven development. Function stubs can be set up for TDD immediately. Moreover, you can adjust the tests of the calling code to expect (or not expect) the stubbed function to work. This provides close support for regression testing and other quality-control measures.

  • Commenting scales poorly - suppose we have 1 function that gets stubbed in, and 200 calls to that function. That's 200 replacements we have to edit into the code; if we'd used a function stub, we'd simply have to write the function.

  • Using comments still places the burden of updates on the client calling code, rather than the called code.



This last point is really the Big One, so I'll spend the rest of the entry digging into why it's such an important (but subtle) distinction.

First, to clarify: this goes back a bit to the scaling issue. Let's take a look again at our hypothetical function, which is called 200-or-so times. If we use comments, we have 200 locations that have to be updated. If we format our comments intelligently, we can automate the replacement process. However, that actually introduces more work than just stubbing in a fake call.

Here's some examples to illustrate what I mean:

// Commenting methodvoid DoFoo(){  Baz();  ALittleBitOfQuux();  // STUB: Xorph (20%)}// Now we just need a regular expression or something// to convert that call to a Xorph(0.2) call when we// get around to implementing the Xorph function.// Stub methodvoid DoFoo(){  Baz();  ALittleBitOfQuux();  Xorph(0.2);}void Xorph(float percentage){  // TODO: implement Xorph functionality}



Using the commenting method has some problems. First, there is nothing ensuring that all stubs are consistent. Using the stub method is superior because we can exploit the compiler to make sure everyone treats the interface the same.

Secondly, the commenting method introduces the overhead of the search/replace step. There's really no need to add this extra work, since we can just write the stub calls the first time and be done with it.

Lastly, since the comments are not using the compiler to enforce a consistent interface, we can't find potential problems in the interface until later. Suppose we comment-stub our 200 calls to Xorph, expecting to use a floating-point percentage value. Later, when we implement Xorph, we discover that some calls really should be an integer scalar from 0 to 1000.

If we had used the code stub method, we would have learned this sooner, and introduced an overload for Xorph to properly handle the two different cases. However, with the commenting method, we're screwed. We literally will have to go back through every case to make sure it uses the proper call (float or integral). Now we're worse off than when we started - because, as you hopefully recall, the original problem was that we wanted to avoid going back through all the Xorph client code in the future!


In closing, I'll revisit the Big Point from earlier: using stub code rather than comments shifts the burden of effort very significantly. Instead of each client call being a separate focus point, we divert all of the focus directly into the stubbed module itself. This concentrates the amount of effort and work we have to do into a single location. And, as I talked about earlier, the smaller chunks of information you have to keep in your mind at once, the better your code will be.

So hopefully that clears up why I specifically suggested what I did, rather than using comments. Thanks to everyone who dropped in for the discussion; it's always great to see feedback [smile]
Previous Entry Brain giblets round 3
Next Entry Now I am the master
0 likes 4 comments

Comments

remigius
Hey, they're great giblets to backfeed on [smile]

This post pretty much matches my point, but it might be worth mentioning that this practice really shines in larger scale projects and code interactions where comments are simply unwieldly.

I had the good fortune to be part of a project where we had a few teams working on seperate but interdependent parts of a system. We decided to stub out the 'interaction points' using (quite complex) interfaces for the classes the code would share across parts. These interfaces were then implemented by placeholder objects that would give back somewhat meaningful data for preliminary testing. This way each team could work on their own stuff, confident that everything would work together smoothly as parts were combined, while quickly spotting incomplete or impractical interfaces as the project progressed.
May 07, 2008 11:05 AM
Dragon88
I see your point, but I guess I still have to respectfully disagree. The comments I was putting forth were supposed to reflect intentions, rather than actual calls. So you wouldn't have the issue of floating point versus integer values. If you're passing such drastically different values, you obviously had different intentions.

But rather than dwell on that (I think it's pretty much beat to death at this point), it is worth reaffirming that the Big Point is very important. Some would view it as shortsighted to focus on the small rather than the big, but when you look at it from a consumer-worth point of view, your point makes a lot of sense. Having a sense of the Big Picture is a good thing, but if you spend too much time thinking about it you're likely to run into the bug of trying to design the perfect system (recently brought up in KHawk's journal), rather than actually working on the features that make the biggest difference to those who will be using your product at the end of the day.
May 07, 2008 08:34 PM
ApochPiQ
Quote: Original post by Dragon88
I see your point, but I guess I still have to respectfully disagree. The comments I was putting forth were supposed to reflect intentions, rather than actual calls. So you wouldn't have the issue of floating point versus integer values. If you're passing such drastically different values, you obviously had different intentions.

But rather than dwell on that (I think it's pretty much beat to death at this point), it is worth reaffirming that the Big Point is very important. Some would view it as shortsighted to focus on the small rather than the big, but when you look at it from a consumer-worth point of view, your point makes a lot of sense. Having a sense of the Big Picture is a good thing, but if you spend too much time thinking about it you're likely to run into the bug of trying to design the perfect system (recently brought up in KHawk's journal), rather than actually working on the features that make the biggest difference to those who will be using your product at the end of the day.


Well it is worth noting that I'm speaking from the perspective of a medium to large scale team project; in this case, it's virtually impossible to guarantee that everyone has the same intentions about what code should be doing - especially when the code isn't written yet.

It's very common to get bit by this issue in my experience; and specifying the interfaces as early as possible is a powerful defensive measure against differing expectations. Just describing intentions in prose Englise isn't enough, because every team member is almost guaranteed to interpret the documentation in a slightly different way.

However, I'd definitely agree that in a smaller scale personal project, it's advantageous to play things a little more fast and loose. Since there are fewer (or no) other people involved, intention comments can provide the advantage of avoiding the Perfect System trap (aka Second System Syndrome).


So bottom line I guess is that the stubbed-interface vs. intention-comments question really depends on the scale of the project and how many people need to be keeping track of things.
May 07, 2008 09:12 PM
RAZORUNREAL
It's a bit of a catch-22. On the one hand, you want the interface done as soon as possible so you can put in stubs like this. On the other hand, it's easier to design a good interface if you know how it's going to be used. It's also easier to implement the interface at the same time (or shortly after), while it's fresh.

So here's what I propose. The first few times it comes up, just leave a // TODO: Sound. After an unspecified number of those it's probably time to actually implement sound. So you go and do that, and fill in all your TODO's with sound code (note that they'll all be different, search replace won't help you here). It's important that you do that before things get too out of hand. These TODO's serve as the first practical tests, and you can continue on writing actual sound code when it comes up. If for whatever reason it's not practical to actually implement sound right away, then you settle for adding an interface and continue with stubs as suggested. But I don't think it's a good idea to interrupt what you're working on to write an interface the very first time sound comes up. By writing the interface early you run the risk of a bad design, and could end up changing all (or worse, half) your stubs anyway.

Of course, I'd be lying if I said I always do exactly that.
May 14, 2008 07:49 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement