Sins of .NET API Developers

March 25th, 2008

There are several annoying design flaws I often stumble into in .NET APIs.
I haven’t seen Design Guidelines on this matter, so I think I’ll point to 3 of these myself.

  1. The Generic Sin

    Do provide a non-generic overloads to generic utility methods.
    Ayende already wrote about it, so it does not make sense to repeat the reasons.
    Fortunately, this is a very rare issue.

    Violating framework: Castle.MicroKernel (DefaultKernel.ResolveServices<T>).

  2. The Sin of Shallow Digging

    Do support non-public members when performing type/member discovery.
    Do not ever use Assembly.GetExportedTypes unless this logic can easily be overriden.

    For example, I like to make my Domain Service interfaces public, but keep implementations internal.
    This means that if I use InternalsVisibleTo, I can unit-test implementations, but the clients must use interfaces.
    But if I try to define a common generic test base class, and use it like XTest : TestBase<X>, then if X is internal XTest should also be.
    Now neither MbUnit or TestDriven.Net see my test, regardless of TestFixture attribute.

    So if you use attributes to discover members automatically, the only reason to discard a member should be the absense of an attribute.
    Access modifiers should not be considered, if the member does have an attribute.

    Violating frameworks: MbUnit, Castle.Facilities.BatchRegistration.

  3. The Sin of Tivoization

    For each public interface you create, think how user can inject his own implementation.
    (If this interface is directly consumed by API public methods, you can skip this step).

    For example, let’s look at an interface MbUnit.Core.IFixtureFactory.
    It is public, which seems good enough for people who hate internals.
    But it is not possible to provide your implementation without wrapping the whole test-running engine.

    It’s a pity. The framework I am developing right now would really benefit from it.

    Violating frameworks: MbUnit.

  • Ayende Rahien

    Try IKernel.ResolveAll(), it has non generic version

  • Andrey Shchekin

    Thanks, it solves the problem at hand.

  • Jeff Brown

    Gallio and MbUnit v3 have already handily resolved the IFixtureFactory issue you mentioned. The system is much more open to extension now. Components can even be replaced via the IoC if you like.

    This is something I have *thought* very carefully about. Trust me. I don’t doubt we could do even better. Let me know if you have any ideas. (MbUnit v2 is a different story.)

    As for internal test fixtures, this is something we have considered and can very easily change. It’s not entirely clear its a good idea. For example, we might encounter problems in partial trust execution environments.

    However, you do make a good case regarding the interaction with [InternalsVisibleTo] and the implied constraints on the visibility of any derived types. Previous requests for this feature have largely been from people wanting to mix test fixtures with production code.

    In any case I’ll go ahead and enable internal fixtures in the next update. Not really worth fussing over.

    In the future, if there’s anything else you want a tool to do, please just ask for it. It might already be there or it might be something that has just yet to be done pending sufficient demand.

  • Andrey Shchekin

    Thank you, Jeff. I know that I could have asked you directly instead of blogging it, but I wanted to stress the importance of solving these things by default.

    As for Gallio, I’ll enjoy working with it when it’s out, but it is not here yet. So, since I wanted to extend a stable version, I haven’t looked deep into Gallio.

  • Jeff Brown

    The best way to ensure that problems are solved is to get involved.

    I’m pretty smart and I spend a fair amount of time thinking about these issues upfront so that they are “solved by default.” But I will miss stuff or I will postpone it due to conflicting priorities.

    So I count on the community of other smart people like yourself to point out deficiencies that affect them so that they can be directly addressed.

    Also keep in mind that I only found this post accidentally. So you’re lucky I even noticed it at all. The issue tracker and mailing lists are much better ways to ensure that your feedback gets noticed and taken into consideration.

  • Andrey Titov

    I’ve found another example similar to your #2.
    Rhino mock can’t create partial mock on non pulic types. So I can’t test my BC classes using partial mocking because they are all internal for BL and are accessible outside only with public facade.

  • Andrey Shchekin

    Jeff, I am sorry for having bashed MbUnit, but for me this post was about common deficiencies, and MbUnit was only an example, since I use it every day.

    Andrey, have you tried [assembly: InternalsVisibleTo(RhinoMocks.StrongName)]?

  • Pingback: Comparing .NET DI (IoC) Frameworks, Part 1 » Blog Archive » I Think It’s Interesting()