Recently in Programming Category

In the course of a recent quick JavaScript side-project — post forthcoming — I needed a simple cache. Then I needed another cache within the cached objects. Then I needed another cache. It was at that point that I realized I should probably factor things out into a reusable component, and built JSLRU.

Of course then I refactored things dramatically and it's barely used at all. But if you need a JavaScript least-recently-used cache, check it out.

Generic Accumulators in C#, Redux

|

It turns out one of the most-trafficked pages on this site is my discussion of generic accumulators in C#. It occurs to me that it could use a bit of an update, as some newer features like lambdas and the predefined Func<> family simplifies things quite a bit:

class Program
{
    public static Func<T, T> MakeAccumulator<T>(T start, Func<T, T, T> addFunction)
    {
        return inc => start = addFunction(start, inc);
    }

    static void Main(string[] args)
    {
        var intAccumulator = MakeAccumulator(0, (i, j) => i + j);

        Debug.Assert(0 == intAccumulator(0));
        Debug.Assert(1 == intAccumulator(1));
        Debug.Assert(11 == intAccumulator(10));
        Debug.Assert(55 == intAccumulator(44));

        var floatAccumulator = MakeAccumulator(0.0, (i, j) => i + j);

        Debug.Assert(0 == floatAccumulator(0.0));
        Debug.Assert(0.1 == floatAccumulator(0.1));
        Debug.Assert(1.1 == floatAccumulator(1.0));
        Debug.Assert(5.5 == floatAccumulator(4.4));

        var stringAccumulator = MakeAccumulator("", (i, j) => i + j);

        Debug.Assert("" == stringAccumulator(""));
        Debug.Assert("ZYZ" == stringAccumulator("ZYZ"));
        Debug.Assert("ZYZZY" == stringAccumulator("ZY"));
        Debug.Assert("ZYZZYVA" == stringAccumulator("VA"));

        Console.WriteLine("Success!");
        Console.ReadLine();
    }
}

So there's that. Still not terribly useful, but I do like shortening code.

Should've Bought an Xbox

|

I bought a Playstation 3 a few years back (when I got my HDTV, as I recall) primarily to use as a blu-ray player (at the time it was actually cheaper than a standalone player). As Sony doubtless expected, it entered my home as a shiny black Trojan horse, and I've bought a handful of games since then.

Including, fatefully one from the Playstation Network (link goes to a Flash-only site, because Sony's just in full-on pissing-me-off mode at the moment).

If you're reading this there's roughly a 99% probability that you already know that the PSN has been 'out' for nearly a week, and the latest news from Sony is that it ain't comin' back anytime soon, and oh, by the way, some hackers probably have your credit card numbers.

I know this the same way you do, through the magical collaborative power of the Internet. Not, say, from an email sent to me by Sony warning me that my credit card number had been stolen. Strike one, Sony. You assholes.

But as annoying — and probably actionable — as their failure to proactively warn their customers may be, that's not what really pisses me off.

No, what's really bugging me is that I can't use Netflix on my PS3.

Why? Because Netflix on the PS3 — despite being tied to my totally independent non-Sony Netflix account — requires a PSN login to function. For no reason at all except that Sony are a bunch of goddamned morons.

Thank God for the Apple TV.

Potemkin Unit Testing

|

After the Russian Empire annexed the Crimean peninsula in 1783, Tsarina Catherine II (motto: “I'm greeeeat!”) took a tour of the area, guided by her trusted advisor and alleged-favored-non-equine-paramour Gregory Potempkin (or if you're not into the whole Anglicization thing, Grigory Potyomkin... or if you're really not into Anglicization, Григо́рий Алекса́ндрович Потёмкин).

As the story goes, Catherine's inspection took the form of a leisurely cruise down the Dniper River. Potemkin, being aware that the Crimea was kind of a crummy area and fearful that the Tsarina would mislike the realization that her new territory kinda sucked, ordered the hasty construction of numerous fake village façades along the river bank. The illusion held; Catherine went home thinking the Crimea was a bustling hub of non-squalid not-mud, and our modern world gained a vivid new idiom: 'Potempkin Villages'.

Of course all the actual evidence is that the story is bunk — Potemkin may have instructed the peasants to clean up a bit and maybe paint some fences, but the idea that the whole area was an elaborate set was an extreme exaggeration spread by his political rivals. But it's one of those stories that's just too good to not tell, isn't it? It's 'truthy'.

Changing direction entirely: unit testing is the new hotness in software development. The basic idea is that you decompose your software into small, manageable units and create dedicated automated tests that verify the functionality of those units. And it is, in general, a really good idea.

The problem that crops up with unit tests is that some things are just really hard to test, particularly when your code interacts with something outside your control — such as a human user, a piece of hardware, or a third-party web service. In those cases you need to get tricky; you create new layers of abstraction that separate the interaction from the main work of your module, and then you create 'mock' version of those abstraction layers and substitute in a 'simulated user' for a real user, or a 'simulated serial port' for your hardware, or a 'simulated web service' for the third-party service.

In some cases that approach may make sense. For some pieces of hardware the range of possible states is limited; a button is up or down, and it's never both. If a user is given an extremely limited set of buttons to click, you can reasonably simulate every possible interaction model. But for anything more complex, you're kind of creating a Potemkin unit test. So you've extracted out an ITwitterApiProxy that your TwitterService talks to, and you can plug in your MockTwitterApiProxy and write a whole host of tests for your TwitterService. Every failure mode you can think of can be simulated by the MockTwitterApiProxy and you've coded the TwitterService to deal with all of them reasonably — and hurray, the unit tests mean you're safe!

Now you go to production, with the RealTwitterApiProxy, and stuff goes wrong. Twitter's failing in ways you never anticipated! So you add more switches and dials to your MockTwitterApiProxy, and add more code to your TwitterService to deal with them, and write tests to exercise them... and hurray, the unit tests mean you're safe! ...at least until Twitter fails in another new way you haven't seen.

Compare that to how you would have handled things without unit tests: You code up TwitterService, handle the error cases you anticipate, go to production and discover some more error cases, update TwitterService to handle them... lather, rinse, repeat. It's essentially the same workflow.

So what does the extra effort buy you? What's the benefit of building your API abstraction and unit tests?

Well the obvious answer is that it buys you protection against regression. You have an explicit test in place for handling error type A, so if in the course of implementing support for type G errors you accidentally break type A, the unit test will fail and you'll find out. There's that, certainly. Without unit tests it's basically down to trust: I trust myself (or my fellow developers) not to break this part of the code while working on that part of the code.

But on the other hand, your test for handling error type A isn't really testing your handling of error type A, it's testing your handling of your mock simulation of error type A. How can you be sure that in the course of adding code to simulate error G to your mock you didn't accidentally break the code that simulates error type A? Isn't that the same sort of "trust yourself" approach that the non-unit-testing approach is left to? Should you add unit tests for your mocks to ensure that they're properly simulating errors?

Ultimately a lot of this is down to personal judgement, like most of the interesting questions in software development. And I'm certainly not going to argue that unit tests aren't a good idea a lot of the time. But I do believe that there are certain situations where unit tests create a lot of extra code to maintain without really adding much in the way of protection, and that committing to building such Potemkin unit tests is a foolish consistency of precisely the sort that will prove a poor substitute for Norman Osborn.

Damned Whippersnappers

|

Remember being a teenager*?

Teenagerititude is the state of believing that everyone else in the world is phenomenally stupid — that the solution to every problem is blatantly obvious, and that everyone would be much better off if they'd just shut up and do as you say.

The interesting thing about being a teenager is that you don't actually outgrow it.

At some point a light goes on in your mind, the scales drop from your eyes, the metaphor similes upon you, whatever, and you realize you've been a teenager and you stop. You congratulate yourself on being so adult and on owning up to your past bad acts and you move on with your life.

And then a few years later you realize that you were mistaken, that you were actually still a very-slightly-more-evolved form of teenager despite that revelation, and the scales drop from your lights and you eye what you metaphor and you move on with your life.

Until it happens again.

Eventually you reach a degree of meta-awareness — you recognize that Socrates kinda had a point about the whole 'knowing you know nothing' schtick. That's when you ascend to a higher plane of existence! Then you help Teal'c and MacGuyver out a few times, and eventually you return to the show with your tail between your legs because it turns out your landlord won't accept 'art' in lieu of money. But hey, higher power. You got that going for you.

Until, damn it, it happens again.

Meta ain't enough, nor is meta-meta. Maybe there's some omega-meta state where you stop realizing that you're an idiot, and you get to draw a Batman logo on Anthro's cave wall just to reassure the idiots who didn't figure out the pattern based on Kal and Hal and whatnot. But I can't be sure of that. I think any form of personal growth boils down to suddenly recognizing what a jackass you've been and thus becoming an exciting new form of jackass.

...

So what's the moral of the story? The moral is that I'm stupid, you're stupid, he's stupid, she's stupid, and the primary differentiating point between us is our awareness of our own incompetence. So when something looks dumb to you, remember that it either is dumb, or it's smart in a way you haven't thought yet deciphered — and prudence dictates that you assume the latter until you've assembled reasonable support for the former. And even if something turns out to be legitimately dumb, don't draw conclusions from that, because oftentimes there's more to the story. It's not uncommon to find clever implementations of a piss-poor design; that may be a sign of abstractional schizophrenia, or it may be a sign of the blind leading the brilliant.


*If you currently are a teenager, shut up and get the hell off my lawn. Damned kids.

Ask each of your developers who the three best developers in the company are. Only ask the developers — managers can't answer because they're in no good position to know. Average out the votes and produce a consensus 'top three' list. Hold on to it, and repeat the survey every six month.

If over a year has passed and two of those three people are still on the list, your company is probably screwed. You might not actually have realized it yet, but that doesn't make it any less true. Your management or business metrics may actually be positive, but if your development team isn't improving, the technology underlying everything is stagnating and sooner or later the smell will reach your customers.

Unless you already employ the best programers in the world, of course.

And if you really and truly believe that you employ the best programmers in the world, then unless your company name rhymes with 'Foogle', 'Schnapple', or 'Sicromoft', you're probably really screwed.

Normal function calls are easy to write; you call DoSomething( ), it executes and returns, and you continue on your merry way.

Asynchronous function calls seem, at first blush, only a little more difficult — instead of calling DoSomething() and executing your follow-up code after it returns, you pass in a callback: BeginDoSomething( Action callback ).

So, problem solved, let's go home. Unless you need a return value, that is. But even then it seems simple; to turn a synchronous method like int CalculateSomething( ) into an asynchronous method, you just pass it a delegate that takes a parameter: void BeginCalculateSomething( Action<int> callback ).

So is that it? Nope. Because all of that is wrong.

Even though the original DoSomething( ) method had no return type, it still had a return path — it could throw an exception. Let's imagine that BeginDoSomething looks something like this:


public void BeginDoSomething(Action callback)
{
    PrepareForLongRunningOperation( );
    
    ThreadPool.QueueUserWorkItem(
        delegate
            {
                LongRunningOperation();

                callback();
            });
}

A handy way to think about this sort of thing is to figure out where a thrown exception would emerge.

If something goes wrong within the call to PrepareForLongRunningOperation, that happens in the same context as the calling code — any exceptions will throw up to the calling code and come out of its call to BeginDoSomething. The same applies to the call to ThreadPool.QueueUserWorkItem — no problem there.

But what if LongRunningOperation throws?

LongRunningOperation would throw up into whatever internal part of the ThreadPool implementation actually launched it. That exception can't come out of ThreadPool.QueueUserWorkItem, because by the time the asynchronous anonymous delegate is running ThreadPool.QueueUserWorkItem has already returned. And since the exception can't come out of ThreadPool.QueueUserWorkItem, it also can't come out of BeginDoSomething — which means there's no way for the calling code to get the exception.

There are two main approaches to this problem — error handlers and completion calls.

Error Handlers

Instead of passing your begin method one callback, pass two: a callback to be invoked if everything goes to plan, and an exception-accepting callback to which errors will be passed.


public void BeginDoSomething(Action callback, Action<Exception> errorHandler)
{
    PrepareForLongRunningOperation( );
    
    ThreadPool.QueueUserWorkItem(
        delegate
            {
                try
                {
                    LongRunningOperation();
                    callback();
                }
                catch(Exception ex)
                {
                    errorHandler(ex);
                }
            });
}

// Sample usage:

BeginDoSomething(
    delegate
    {
		// Do something now that we're done
    },
    delegate(Exception ex)
    {
		// Do something with the error
    });

There are strengths and weaknesses to this approach. The biggest strength of this model is that it forces the calling code to think about error handling — the prompt for it is right there in the method signature. Error handling tends to fall through the cracks in any sort of code, but it's especially easy to overlook in an asynchronous context (It's also a lot more dangerous in an asynchronous context, because often dropping a callback invocation will cause a process to spin forever, sucking down resources and accomplishing nothing).

Separating the success case from the failure case may be either a strength or a weakness, depending on the particular task. Sometimes it makes your code much cleaner, but it often happens that your success and error handler need to share context and implementation, which can make for some very ugly code.

Completion Calls

Instead of just invoking a parameter-less Action callback or a single-parameter Action<TReturn> callback, your code calls a single-parameter callback and passes it an Action or Func<TReturn> that the callback in turn invokes.


public void BeginDoSomething(Action<Action> callback)
{
    PrepareForLongRunningOperation( );
    
    ThreadPool.QueueUserWorkItem(
        delegate
            {
                try
                {
                    LongRunningOperation();
                    callback(delegate { });
                }
                catch(Exception ex)
                {
                    callback(delegate { throw ex; });
                }
            });
}

// Sample Usage:

BeginDoSomething(
    delegate(Action complete)
    {
        try
        {
            complete();
        }
        catch (Exception ex)
        {
            // Do something with the error
            return;
        }
        // Do something now that we're done
	})

At first blush, this seems like a much clumsier solution; you're essentially trusting the calling code to call your completion method. That's true, at least in this case.

Where completion calls really shine are for asynchronous calls returning values; instead of calling their callback and handing in an Action, you call their callback and give it a Func<TReturn>, which they then must invoke to get their result. That gives you an opportunity to throw exceptions that they can't cleverly bypass:


public void BeginCalculateSomething(Action<Func<int>> callback)
{
    PrepareForLongRunningCalculation();

    ThreadPool.QueueUserWorkItem(
        delegate
        {
            try
            {
                int result = LongRunningCalculation();
                callback(() => result);
            }
            catch (Exception ex)
            {
                callback(delegate { throw ex; });
            }
        });
}

// Sample Usage:

BeginCalculateSomething(
    delegate(Func<int> complete)
    {
        int result;
        try
        {
            result = complete();
        }
        catch (Exception ex)
        {
            // Do something with the error
            return;
        }
        // Do something with the content of result;
	})

Personally I prefer completion calls, mainly because the the pattern works so well for return values. In practice, any time you need this sort of a pattern it's because you care about return values; if you need to ensure that X happens after Y, it's generally because X depends on the result of Y. If X doesn't depend on Y, that's often a sign that you're being too linear in your analysis and that the tasks should be happening in parallel.

Microsoft seems to have collectively reached the same conclusion; IHttpAsyncHandler, the asynchronous methods off of SqlCommand, and the asynchronous forms of WebRequest all use the completion call approach.

Functional Programming

|
public void BeginGetSingle(TIdentityCriteria identityCriteria, CompletionCallback<TItem> callback)
{
    TFilterCriteria filterCriteria =
        CriteriaUtilities.UpgradeCriteria<TIdentityCriteria, TFilterCriteria>(identityCriteria);

    RestClient.BeginGet<TItem>(
        CriteriaUtilities.CriteriaToUrl(
            filterCriteria,
            m_Map,
            m_ServiceUrlBase,
            r => typeof (TItem).FullName.Equals(r.OutputPayloadClass) && r.AllowedVerbs.Contains(Verb.Get)),
        completionFunction => callback(() => completionFunction().Payload));        
}

My function takes a function and when done it calls that function, passing a function that the calling function calls for the result.

Now in fact my function calls another function taking a function accepting a function to call for its result, and to that function it passes a function which when called calls the first function passing a new function that when called calls the function that was passed to the function that my function passed to the other function, thereby returning the result of that function to the function that called my function.

And they say you can't write Lisp code in C#.

Xclan Night

|

Still a work in progress, but pretty cool looking already in my rarely-humble opinion. The trick is to dim the red and green components while leaving the blue intact.

Xclan screenshot 1
Xclan screenshot 2

I'm trying to decide the best way to show text bubbles. Everything's pretty scrupulously iPhone-friendly this time around (that's actually what got me started on Xclan again, figuring out how to work with UDP CFSockets in a run loop), but as I understand it uploading new text textures every few frames will bog an iPhone down pretty quickly. I'm thinking of switching my NSOpenGLView to a CAOpenGLLayer and putting a bunch of CATextLayers on top for the bubbles.

There's actually an NSTextField in a layer in the game window; on Leopard it floats above the NSOpenGLView, but in Snow Leopard (where the screen shots were taken) it's invisible. I haven't pursued it because I was planning on replacing the OGL view with a layer anyway, but it's a strange difference.

Dear Reader,

Your code sucks.

It's okay though, so does everyone else's code, including mine. But a big part of improving is recognizing what you're doing wrong, and a big part of that is having random bald guys berate you. So in the spirit of improving the world, here are five thoughts on writing code that sucks less. Heavily C#-centric because that's what I've been reading lately, but I think most of it's meaningful in any environment modulo some terminological adjustments.

1. Dumb Comments Are Dumb

A comment that doesn't say anything useful is worse than worthless; it's actively harmful.

If you have a property named Marklar with a comment telling me that it 'gets or sets the Marklar', you haven't told me anything of value, and reading your useless comment has cost me precious seconds of my life that I'll never get back. You've also probably raised my blood pressure a bit, but I suppose that's more on me than on you.

Now if a Marklar is an odd or confusing type of thing, and you can't reasonably expect everyone to know what it is or why it's there, by all means explain it. Similarly, if this property has unusual behavior in some respect — perhaps it's lazily loaded — mention that in a comment. But don't just put a bloody comment on every property because some never-been-employed professor once told you that that's the right thing to do. It isn't. If someone can't tell you that the EyeColor property lets you get or set the color of the receiver's eyes, they have no business writing code in the first place.

You should only put comments where they're needed, and generally if a comment is legitimately needed it's a sign that you're writing bad code. Don't name the property Age and then add a comment explaining that it's in seconds; name it AgeInSeconds and skip the comment entirely. That saves you typing, saves me reading, and has the further benefit of being completely impossible for future programmers to miss unless they're severely mentally impaired (in which case, see above, they have no business writing code in the first place).

Similarly, if you feel compelled to add a comment explaining that the next five lines of code are stochastically approximating the chronosynclastic infundibulumation of the yada, yada... that's your sign that those five lines should be extracted into a method with an appropriate name like 'AproximateTechnobabble'. That makes it easier for me to read the original method because I can temporarily ignore the hard stuff, while also making it easier for me to understand the hard stuff later on if I need to (since it's now isolated with clear, labeled inputs).

Finally, dumb comments are dumb not just in the code, but everywhere. If your checkin note is "I changed the twelfth character of line 37 from an 'e' to a 'z'", you have so completely and cleanly missed the point of checkin notes that I can't even scrape together a proper disparaging simile. Checkin notes are there so I can get a high-level understanding of what you've checked in, without having to open the bloody file. "Ran a match along a patch of bark" is a lousy checkin note when what you actually did was "burned down the forest". Tell me about the forest, I couldn't care less about the trees.

2. Every Time You Type '#region' I Die A Little Inside

Regions suck. If I were king I'd ban 'em outright, so sincere and total is my dislike. They're a trick you play on yourself to let you pretend that your bad code is good, and they tell the world that you're a good enough coder to know a three-thousand-line file stinks, but that you're either not good enough to fix it or you're just plain too lazy. In either situation the solution is to step up, not to add a region and sweep part of the bad code under the rug.

A special note on regions within methods: wrapping a region around your twelve constructors to hide the excess complexity is a venal sin. Putting a region inside your method to hide 3,000 of its 4,000 lines is a mortal sin.

Some people might ask why extracting methods (like AproximateTechnobabble above) is good but collapsing regions within a method is evil. The simple answer is encapsulation; a region isn't a new scope, so it has access to all the fields and variables of the method it's within. That greatly increases the difficulty of understanding the code, because now I need to actively read through and figure out how the code inside the region interacts with the code outside the region. An extracted method doesn't have the problem, because I can look at exactly what parameters are passed in and exactly what comes out, without having to trace through and worry about side effects. Regions superficially seem to simplify the complexity of your code; extracted methods actually do.

3. Static Methods Are Your Friends, And You Should Want More Of Them

Static methods are pure functions, in the mathematical sense; they're black boxes that take some input and return some output. Remember where I said methods are better than regions because they simplify complexity? The same logic recommends static methods over non-static methods. Static methods help you make sure you're not weaving a tangled web of side effects and dependencies with each method call.

Imagine coming upon a call to myComplexMarklar.CalculateInfundibulumation(). Now, if you don't know what infundibulumation is (incidentally, the state of being or the act of becoming funnel-like or akin to a funnel), and have no idea how you would calculate it (no idea), that method doesn't tell you a whole lot. Now imagine stepping through that rather lengthy method and figuring out how it works. You are potentially entering a world of pain.

Now imagine a static method, Marklar.CalculateInfundibulumation(float topRadius, float bottomRadius, float height), and further imagine that the non-static method works by calling the static method and passing along the three appropriate values. It has instantly become a whole lot easier for me to understand what infundibulumation is, because now I know that no matter how gnarly the math is it's just a function of those three values. Now not only can I better understand that particular method, but a lot of the meaning of the Marklar class in general has become more clear.

In an object-oriented language, every instance method is getting more parameters than you've listed in the declaration line; it's also getting access to all of the instance's member fields. Imagine seeing that written out — would you deliberately write a method that takes a hundred parameters but only uses two of them? That might be a good sign that it's time for some judiciously-applied static methods.

Static methods have other benefits. For one, they're a lot more reusable; if the static CalculateInfundibulumation( tR, bR, h ) method is general, you might find you need it in another class. Depending on your situation, you could either make it public and call it right out of Marklar, move it to a new common base class, or move it to a utility class. Each of those solutions would be far more complicated if it were non-static. I've been in situations where I needed a complex non-static method from a pre-existing class that I couldn't reasonably modify, and ended up constructing an instance with a bunch of bogus data, setting the handful of fields that the calculation actually depends on, and calling the instance method on that. That's hideous — ugliness wrapped in brittleness wrapped in unmaintainable complexity. Don't make me do that again.

Static methods also lend themselves incredibly well to a form of optimization known as memoization. The root of memoization is basically the realization that the next time you run the same calculation, you'll reach the same result (yeah, I know, 'duh', right?). The tie-in to static methods is that static methods let you know precisely what the inputs are to the calculation you're running, while instance methods don't. Memoizing a static method is 'easy' (in the sense of being a solved problem), while memoizing instance methods can be a buggy pain in the butt.

3b. Subverting Static Methods Is Bad And People Who Do It Should Be Forced To Write Pascal

Sometimes you'll see a static class, full of static fields and static variables and static methods. That's not what I'm talking about when I say static is good. That's not good, it's the other thing. Bad. It's a crappy-ass degenerate-global-variable-singleton. Don't do that. Smart people hate singletons.


4. Properties Are Properties, Methods Are Methods

A property is intended to represent an attribute of an object, while a method models an action that the object may take or that may be taken upon the object. These are different concepts, and you need to think about which is appropriate for a particular case. If you have a property with a verb in the name, you're almost certainly thinking about things the wrong way.

Some people will tell you that properties should just be get/set wrappers around your fields. Others will say it's okay to do some field validation. Others go hog wild. It's hard to draw bright lines because the simple fact is that it's sometimes a judgement call. A good heuristic is to think like someone using your class. The default assumption is that properties are simple get/set wrappers around a private field, and everything you do to violate that assumption nudges the scale towards building a method.

Let's say your class has a ServerConnection property that returns some sort of communications channel to a remote server. As a caller, my mindset is that this connection already exists, and the property is giving me access to it. But if only a subset of the calling code ever needs this connection, it might make sense to lazily connect when the property is first accessed rather than connecting in the constructor and keeping around a connection that may never actually be needed. But that's changing the semantics of the property a little bit, because the mindset of the calling code is still 'this already exists, the property is just handing it to me', and the calling code may not expect this property to take a long time to execute. Maybe now there should be a progress bar, or the call should happen in another thread.

Like I said, judgement call. In situations like that one, asking ten developers will get you fifteen different opinions. There isn't a 'right' answer, it's just a matter of getting it right enough. All anyone can ask is that you think.

But in other cases, it's totally cut and dried. I've seen code with a LastSaveDate property that saves to the database and returns DateTime.Now. If you're not physically ill at the thought of that, you need to figure why and get sick fast.

5. Public Methods Are Contracts, Which Should Not Be Entered Lightly

When you make a method public, you're inviting the world to use it and you're promising that it will continue to do exactly what it does now. Forever.

Let's say you're creating a CarFactory class that creates new Car objects. CarFactory.CreateCar( ) was starting to get a little long, so you started extracting out helper methods: CreateTransmission( ), CreateBody( ), CreateWheel( ), etc. That's good, keep doing it.

Now let's say you decided that since CreateCar( ) was public that you might as well make CreateTransmission( ) et al public too. That's no-so-much with the good. Unless you know right now that someone else needs to create a transmission without creating a full car, leave that method private.

To the calling code, a CarFactory and a Car are black boxes. The calling code doesn't need to know that a car contains a transmission, and next year when you change CarFactory and Car to build fully-electric vehicles, which don't need a transmission, you won't have to worry about supporting that public method that a bunch of your coworkers have just spent twelve months linking against.

There's a more general lesson here: Share the hot dog, not the making thereof.

Which, come to think of it, is a good moral on which to end any story.

Pages