To Public Or Not To Public, That Is The Question...

time to read 6 min | 1144 words

Paul Stovel talks about defaulting to making your code public (accessible to developer).

There is nothing more infuriating than looking at the exact code you need in Reflector, yet not being able to call it yourself.

I said it before, many times, and I fully agree with him on that.

Scott Hansleman reponded:

When you're designing for Users, you do a usability study. When you're designing for Developers, you need do a a developability study.

Yes, and when you give me six months and real world use cases, then maybe you can get something out of it. You aren't hitting the need of those until you are really deep in complex scenarios. Scenarios that aren't going to be covered in any shorter time frame. It is not the scenarios that you thought of when you design the framework that worries me. It is the scenario of needing to integrate the way I work with a new framework and finding out that I need to modify the existing functionality just a little bit, and then finding the hood welded shut.

Brian theorized that they didn't need as much extensibility as they thought, and shipped a internally basically sealed version. When folks needed something marked virtual, they put it in a queue. The next internal version shipped with something like 7 methods in one class marked virtual - meeting the needs of all - when originally the developers thought they wanted over 50 points of extensibility.

Scott, this is for an internal application, used by several dozens of developers, building most the same kind of applications, right? What Paul is talking about, and I fully cuncur him, is that when you don't have a release cycle that is measure in days/weeks, and when your target audiance is far more diverse, you need all those extention points.

In the comments to Scott's post, Joshua Flanagan suggests:

public override unsupported void Foo(); // overriding a non-virtual method

Widget x = new Widget();
x.$InternalHelper(); // call a non-public method on an object

I have issues with the syntax, but I agree with the idea, for myself, I use the [ThereBeDragons] attribute.

A good point that was raised in this issue was that exposing everything limits extensibility and will hurt forward compatability. As much as I like those terms, they are not sacred. In the name of the Holy Backward Compatability, Microsoft refuses to fix this bug:

TypeDescriptor.GetConverter(typeof(int)).IsValid("abcd") == true

Is there anyone reading this that thinks that this is a valid approach?! In the name of forward compatability, this approach hurts the developers far more than it helps. In the commercial world, there is such a fuss about Supported vs. Not Supported anyway, so it is not an issue. The framework vendor should have the balls to say, "Breaking Changes" and deal with it.

Thinking that you can control software once it is outside the gate is silly. If I need this functionality, I will get it. Take a look at my SqlCommandSet, huge performance improvement that the ADO.Net team didn't see a reason to expose to the outside world. Of course that they didn't see a reason, no one tried to build a OR/M framework on 2.0 Alpha/Beta/Gamma until very late into the game, and they didn't encounter the batching performance implications until very late into the game.

Microsoft is very good in saying "This is unsupported scenario" (recent example, putting Atlas' assemblies on the GAC, rather than on the application base, which completely breaks continious integration scenarios) - they should learn to say "unsupported extensability" and move on, instead of slapping sealed and internal over everything and anything in sight. Strangely, on the Java world, where everything is virtual by default, people still do manage to issue a second version of their software. So I don't see a big issue there.

Note that I am not saying not to use internal, I use it myself, but usually for very different reasons. To hide complexity or remove things from direct reach. In 95% of the cases, if I have internal, I have a public way of safely overriding it (and if I don't, it is a bug, page me).

Let me give an example of a scenario that you wouldn't think about until late into development. I have a static IoC class that holds the reference to the IoC container that I am using. Part of the initialization process of my container recursively uses itself to load stuff. I need to replace the container dynamically, but then I hit a problem. I can't fully initalize the container because if I do, it will use the previous container and not its own, and I can't replace the global container because I have other threads that are running and might access the still-initalizing container and break. This is not something that you are going to run into until very late in the game, and if you can't fix this issue, then it is Game Over replace everything from scratch.

Until I run into this scenario, I would have never thought that I would need a seperate container (accessibly globaly) side-by-side with the global container. But I did, and if I had been limited to the external interface, there would have been hell to pay for it.

Make everything public, and only interalize stuff after a lot of careful thought and agonizing decision process. Other people will use your stuff, be polite to them.