Internal for a reason

One of the things about working on an open source project is that everybody can go and hunt through the code and see all the private, protected and internal things. I occasionally get requests to make certain types or methods public, because the requester can see a way to use them to achieve some piece of functionality that they are otherwise unable to implement. Sometimes these requests have an undertone of “why are you hiding this stuff in the first place?” So I thought I’d take a moment to explain the reason why we have these visibility modifiers at all, why I use them the way I do, and also to highlight a couple of times where I was over-protective (or over-internalist) and did actually make the change.

You can’t depend on me

When a type is internal, or a method isn’t public, it means your code can’t take a dependency on it. Think about how much we try to avoid firm dependencies just within a single codebase with a single programmer maintaining it. We do this to avoid the code becoming brittle and hard to maintain; the fewer things that directly depend on something being the way it is, the fewer places we need to make changes when we change the thing they depend on. This is just good practice.

For a public API, it becomes even more important. Every publically accessible member in an API gets an arbitrary number of dependencies the moment you publish the library. So you hide everything, just as a matter of course, and only make things public if you really have to.

As an example, in Simple.Data, I have a type called ExpressionHelper, which turns an IDictionary<string. object> into a SimpleExpression composed of straight-forward equality comparisons. I use it to generate the criteria for all the FindBy/FindAllBy methods. I got a change request asking me to make this type public, and I rejected it, because I think there might be a better approach so I want to reserve the right to completely replace ExpressionHelper and remove it from the code-base entirely. I’m not even sure there are any direct tests against ExpressionHelper; it’s probably covered through the higher-level behaviour tests, and that’s the way it should be.

Now, if you want to copy ExpressionHelper out of my code-base and include it in your own source, well, that’s fine, and more power to you. That’s one of the beauties of Open Source. But if I’ve marked something as private or internal, it means I don’t think it’s a required part of the API, and I want to reserve the right to change it, or remove it, as and when the need arises.

An example of when I changed my mind

There is a public method on the Database class called “GetAdapter”. Until a few versions ago, this method was internal. Then Bobby Johnson (@NotMyself) started having some issues with his SQLite provider when using SQLite’s in-memory mode. He needed to be able to get to the underlying ADO connection to do initial setup for tests. My initial reaction was doubt, because my immediate take on what he wanted to do was

“Get the IDbConnection from the Database object”

which would be bad, not least because the Database object doesn’t necessarily have an IDbConnection; it might be using the Mongo adapter, or the Azure Table Service adapter (I’m working on it) or any of the adapters I hope people will write in the future.

But what Bobby actually wanted to do was

“Get the Adapter from the Database, and if it’s an AdoAdapter, get the IDbConnection from that.”

which is fine, and it’s why we have the as keyword in C#. And it wouldn’t just apply to AdoAdapter, there might be other adapters with arbitrary public methods which could be used in this way. This was a case of me being over-private, and I made the change, and I think the API is much better for it.

Tonight’s movie

will be Falling Down. An unemployed defense worker frustrated with the various flaws he sees in society, begins to psychotically and violently lash out against them.

That is all.

Share on facebook
Share on google
Share on twitter
Share on linkedin


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.