Published by Marco on 7. Apr 2016 22:13:31
------------------------------------------------------------------------

"Unwritten code requires no maintenance and introduces no cognitive load."

As I was working on another part of Quino the other day, I noticed that the
oft-discussed registration and configuration methods [1] were a bit clunkier
than I'd have liked. To whit, the methods that I tended to use together for
configuration had different return types and didn't allow me to freely mix calls
fluently.

[The difference between Register and Use]

The return type for Register methods is IServiceRegistrationHandler and the
return type for Use methods is IApplication (a descendant), The Register*
methods come from the IOC interfaces, while the application builds on top of
this infrastructure with higher-level Use* configuration methods.

This forces developers to write code in the following way to create and
configure an application.


public IApplication CreateApplication()
{
  var result =
    new Application()
    .UseStandard()
    .UseOtherComponent();

  result.
    .RegisterSingle()
    .Register();

  return result;
}

That doesn't look too bad, though, does it? It doesn't seem like it would cramp
anyone's style too much, right? Aren't we being a bit nitpicky here?

That's exactly why Quino 2.0 was released with this API. However, here we are,
months later, and I've written a lot more configuration code and it's really
starting to chafe that I have to declare a local variable and sort my method
invocations.

So I think it's worth addressing. Anything that disturbs me as the writer of the
framework -- that gets in my way or makes me write more code than I'd like -- is
going to disturb the users of the framework as well.

Whether they're aware of it or not.

[Developers are the Users of a Framework]

In the best of worlds, users will complain about your crappy API and make you
change it. In the world we're in, though, they will cheerfully and
unquestioningly copy/paste the hell out of whatever examples of usage they find
and cement your crappy API into their products forever.

Do not underestimate how quickly calls to your inconvenient API will
proliferate. In my experience, programmers really tend to just add a workaround
for whatever annoys them instead of asking you to fix the problem at its root.
This is a shame. I'd rather they just complained vociferously that the API is
crap rather than using it and making me support it side-by-side with a better
version for usually feels like an eternity.

Maybe it's because I very often have control over framework code that I will
just not deal with bad patterns or repetitive code. Also I've become very
accustomed to having a wall of tests at my beck and call when I bound off on
another initially risky but in-the-end rewarding refactoring.

If you're not used to this level of control, then you just deal with awkward
APIs or you build a workaround as a band-aid for the symptom rather than going
after the root cause.

[Better Sooner than Later]

So while the code above doesn't trigger warning bells for most, once I'd written
it a dozen times, my fingers were already itching to add [Obsolete] on
something.

I am well-aware that this is not a simple or cost-free endeavor. However, I
happen to know that there aren't that many users of this API yet, so the damage
can be controlled.

If I wait, then replacing this API with something better later will take a bunch
of versions, obsolete warnings, documentation and re-training until the old API
is finally eradicated. It's much better to use your own APIs -- if you can --
before releasing them into the wild.

Another more subtle reason why the API above poses a problem is that it's more
difficult to discover, to learn. The difference in return types will feel
arbitrary to product developers. Code-completion is less helpful than it could
be.

It would be much nicer if we could offer an API that helped users discover it at
their own pace instead of making them step back and learn new concepts. Ideally,
developers of Quino-based applications shouldn't have to know the subtle
difference between the IOC and the application.

[A Better Way]

Something like the example below would be nice.


return
  new Application()
  .UseStandard()
  .RegisterSingle()
  .UseOtherComponent()
  .Register();

Right? Not a gigantic change, but if you can imagine how a user would write that
code, it's probably a lot easier and more fluid than writing the first example.
In the second example, they would just keep asking code-completion for the next
configuration method and it would just be there.

[Attempt #1: Use a Self-referencing Generic Parameter]

In order to do this, I'd already created an issue in our tracker to parameterize
the IServiceRegistrationHandler type in order to be able to pass back the proper
return type from registration methods.

I'll show below what I mean, but I took a crack at it recently because I'd just
watched the very interesting video "Fun with Generics" by Benjamin Hodgson
, which starts off with a technique identical to
the one I'd planned to use -- and that I'd already used successfully for the
IQueryCondition interface. [2]

Let's redefine the IServiceRegistrationHandler interface as shown below,


public interface IServiceRegistrationHandler
{
  TSelf Register()
      where TService : class
      where TImplementation : class, TService;

  // ...
}

Can you see how we pass the type we'd like to return as a generic type
parameter? Then the descendants would be defined as,


public interface IApplication : IServiceRegistrationHandler
{
}

In the video, Hodgson notes that the technique has a name in formal notation,
"F-bounded quantification" but that a snappier name comes from the C++ world,
"curiously recurring template pattern". I've often called it a self-referencing
generic parameter, which seems to be a popular search term as well.

This is only the first step, though. The remaining work is to update all usages
of the formerly non-parameterized interface IServiceRegistrationHandler. This
means that a lot of extension methods like the one below


public static IServiceRegistrationHandler RegisterCoreServices(
  [NotNull] this IServiceRegistrationHandler handler)
{
}

will now look like this:


public static TSelf RegisterCoreServices(
[NotNull] this IServiceRegistrationHandler handler)
  where TSelf : IServiceRegistrationHandler
{
}

This makes defining such methods more complex (again). [3] in my attempt at
implementing this, Visual Studio indicated 170 errors remaining after I'd
already updated a couple of extension methods. 

[Attempt #2: Simple Extension Methods]

Instead of continuing down this path, we might just want to follow the pattern
we established in a few other places, by defining both a Register method, which
uses the IServiceRegistrationHandler, and a Use method, which uses the
IApplication

Here's an example of the corresponding "Use" method:


public static IApplication UseCoreServices(
  [NotNull] this IApplication application)
{
  if (application == null) { throw new ArgumentNullException("application"); }

  application
    .RegisterCoreServices()
    .RegisterSingle(application.GetServices())
    .RegisterSingle(application);

  return application;
}

Though the technique involves a bit more boilerplate, it's easy to write and
understand (and reason about) these methods. As mentioned in the initial
sentence of this article, the cognitive load is lower than the technique with
generic parameters.

The only place where it would be nice to have an IApplication return type is
from the Register* methods defined on the IServiceRegistrationHandler itself.

We already decided that self-referential generic constraints would be too messy.
Instead, we could define some extension methods that return the correct type. We
can't name the method the same as the one that already exists on the interface
[4], though, so let's prepend the word Use, as shown below:


IApplication UseRegister(
  [NotNull] this IApplication application)
      where TService : class
      where TImplementation : class, TService;
{
  if (application == null) { throw new ArgumentNullException("application"); }

  application.Register();

  return application;
}

That's actually pretty consistent with the other configuration methods. Let's
take it for a spin and see how it feels. Now that we have an alternative way of
registering types fluently without "downgrading" the result type from
IApplication to IServiceRegistrationHandler, we can rewrite the example from
above as:


return
  new Application()
  .UseStandard()
  .UseRegisterSingle()
  .UseOtherComponent()
  .UseRegister();

Instead of increasing cognitive load by trying to push the C# type system to
places it's not ready to go (yet), we use tiny methods to tweak the API and make
it easier for users of our framework to write code correctly. [5]

--------------------------------------------------------------------------------


[1] See Encodo’s configuration library for Quino "Part 1"
    , "Part 2"
     and "Part 3"
     as well as API
    Design: Running and Application "Part 1"
     and "Part 2"
     and, finally,
    "Starting up an application, in detail"
    .


[1] The video goes into quite a bit of depth on using generics to extend the
    type system in the direction of dependent types. Spoiler alert: he doesn't
    make it because the C# type system can't be abused in this way, but the
    journey is informative.


[1] As detailed in the links in the first footnote, I'd just gotten rid of this
    kind of generic constraint in the configuration calls because it was so ugly
    and offered little benefit.


[1] If you define an extension method for a descendant type that has the same
    name as a method of an ancestor interface, the method-resolution algorithm
    for C# will never use it. Why? Because the directly defined method matches
    the name and all the types and is a "stronger" match than an extension
    method.
  
  Perhaps an example is in order:
  
   interface IA 
   {
     IA RegisterSingle();
   }

   interface IB : IA { }

   static class BExtensions
   {
     static IB RegisterSingle(this IB b) { return b; }

     static IB UseStuff(this IB b) { return b; }
   }
  
  Let's try to call the method from BExtensions:
  
   public void Configure(IB b)
   {
     b.RegisterSingle().UseStuff();
   }
  
  The call to UseStuff cannot be resolved because the return type of the matched
  RegisterSingle method is the IA of the interface method not the IB of the
  extension method. There is a solution, but you're not going to like it (I know
  I don't).
   
   public void Configure(IB b)
   {
     BExtensions.RegisterSingle(b).UseStuff();
   }
   
   You have to specify the extension-method class's name explicitly, which
   engenders awkward fluent chaining -- you'll have to nest these calls if you
   have more than one -- but the desired method-resolution was obtained.
   
   But at what cost? "The horror...the horror."
   


[1] The final example does not run against Quino 2.2, but will work in an
    upcoming version of Quino, probably 2.3 or 2.4.