Published by Marco on 25. Sep 2015 21:47:36
Updated by Marco on 25. Sep 2015 21:50:57
------------------------------------------------------------------------

[image]In this article, I'm going to continue the discussion started in "Part I"
, where we laid some
groundwork about the state machine that is the startup/execution/shutdown
feature of Quino. As we discussed, this part of the API still suffers from 
"several places where generic TApplication parameters [are] cluttering the API".
In this article, we'll take a closer look at different design approaches to this
concrete example -- and see how we decided whether to use generic type
parameters. 

[Consistency through Patterns and API]

Any decision you take with a non-trivial API is going to involve several
stakeholders and aspects. It's often not easy to decide which path is best for
your stakeholders and your product.

"For any API you design, consider how others are likely to extend it -- and
whether your pattern is likely to deteriorate from neglect."

For any API you design, consider how others are likely to extend it -- and
whether your pattern is likely to deteriorate from neglect. Even a very clever
solution has to be balanced with simplicity and elegance if it is to have a hope
in hell of being used and standing the test of time.

In Quino 2.0, the focus has been on ruthlessly eradicating properties on the
IApplication interface as well as getting rid of the descendant interfaces,
ICoreApplication and IMetaApplication. Because Quino now uses a pattern of
placing sub-objects in the IOC associated with an IApplication, there is far
less need for a generic TApplication parameter in the rest of the framework. See
"Encodo’s configuration library for Quino: part I"
 for more information and
examples.

This focus raised an API-design question: if we no longer want descendant
interfaces, should we eliminate parameters generic in that interface? Or should
we continue to support generic parameters for applications so that the caller
will always get back the type of application that was passed in?

Before getting too far into the weeds [1], let's look at a few concrete examples
to illustrate the issue.

[Do Fluent APIs require generic return-parameters?]

As discussed in "Encodo’s configuration library for Quino: part III"
 in detail, Quino
applications are configured with the "Use*" pattern, where the caller includes
functionality in an application by calling methods like UseRemoteServer() or
UseCommandLine(). The latest version of this API pattern in Quino recommends
returning the application that was passed in to allow chaining and fluent
configuration.

For example, the following code chains the aforementioned methods together
without creating a local variable or other clutter.


return new CodeGeneratorApplication().UseRemoteServer().UseCommandLine();

What should the return type of such standard configuration operations be? Taking
a method above as an example, it could be defined as follows:


public static IApplication UseCommandLine(this IApplication application,
string[] args) { ... }

This seems like it would work fine, but the original type of the application
that was passed in is lost, which is not exactly in keeping with the fluent
style. In order to maintain the type, we could define the method as follows:


public static TApplication UseCommandLine(this TApplication
application, string[] args)
  where TApplication : IApplication
{ ... }

This style is not as succinct but has the advantage that the caller loses no
type information. On the other hand, it's more work to define methods in this
way and there is a strong likelihood that many such methods will simply be
written in the style in the first example.

"Generics definitely offer advantages, but it remains to be seen how much those
advantages are worth."

Why would other coders do that? Because it's easier to write code without
generics, and because the stronger result type is not needed in 99% of the
cases. If every configuration method expects and returns an IApplication, then
the stronger type will never come into play. If the compiler isn't going to
complain, you can expect a higher rate of entropy in your API right out of the
gate.

One way the more-derived type would come in handy is if the caller wanted to
define the application-creation method with their own type as a result, as shown
below:


private static CodeGeneratorApplication CreateApplication()
{
  return new CodeGeneratorApplication().UseRemoteServer().UseCommandLine();
}

If the library methods expect and return IApplication values, the result of
UseCommandLine() will be IApplication and requires a cast to be used as defined
above. If the library methods are defined generic in TApplication, then
everything works as written above.

This is definitely an advantage, in that the user gets the exact type back that
they created. Generics definitely offer advantages, but it remains to be seen
how much those advantages are worth. [2]

[Another example: The IApplicationManager]

Before we examine the pros and cons further, let's look at another example.

In Quino 1.x, applications were created directly by the client program and
passed into the framework. In Quino 2.x, the IApplicationManager is responsible
for creating and executing applications. A caller passes in two functions: one
to create an application and another to execute an application. 

A standard application startup looks like this:

new ApplicationManager().Run(CreateApplication, RunApplication); [3]

"Generic types can trigger an avalanche of generic parameters(tm) throughout
your code."

The question is: what should the types of the two function parameters be? Does
CreateApplication return an IApplication or a caller-specific derived type? What
is the type of the application parameter passed to RunApplication? Also
IApplication? Or the more derived type returned by CreateApplication?

As with the previous example, if the IApplicationManager is to return a derived
type, then it must be generic in TApplication and both function parameters will
be generically typed as well. These generic types will trigger an avalanche of
generic parameters(tm) throughout the other extension methods, interfaces and
classes involved in initializing and executing applications.

That sounds horrible. This sounds like a pretty easy decision. Why are we even
considering the alternative? Well, because it can be very advantageous if the
application can declare RunApplication with a strictly typed signature, as shown
below.

private static void RunApplication(CodeGeneratorApplication application) { ... }

Neat, right? I've got my very own type back.

[Where Generics Goes off the Rails]

However, if the IApplicationManager is to call this function, then the signature
of CreateAndStartUp() and Run() have to be generic, as shown below.


TApplication CreateAndStartUp(
  Func createApplication
)
 where TApplication : IApplication;

IApplicationExecutionTranscript Run(
  Func createApplication,
  Action run
)
  where TApplication : IApplication;

These are quite messy -- and kinda scary -- signatures. [4] if these core
methods are already so complex, any other methods involved in startup and
execution would have to be equally complex -- including helper methods created
by calling applications. [5]

The advantage here is that the caller will always get back the type of
application that was created. The compiler guarantees it. The caller is not
obliged to cast an IApplication back up to the original type. The disadvantage
is that all of the library code is infected by a generic 
parameter with its attendant IApplication generic constraint. [6]

[Don't add Support for Conflicting Patterns]

The title of this section seems pretty self-explanatory, but we as designers
must remain vigilant against the siren call of what seems like a really elegant
and strictly typed solution.

"But aren't properties on an application exactly what we just worked so hard to
eliminate?"

The generics above establish a pattern that must be adhered to by subsequent
extenders and implementors. And to what end? So that a caller can attach
properties to an application and access those in a statically typed manner, i.e.
without  casting?

But aren't properties on an application exactly what we just worked so hard to
eliminate? Isn't the recommended pattern to create a "settings" object and add
it to the IOC instead? That is, as of Quino 2.0, you get an IApplication and
obtain the desired settings from its IOC. Technically, the cast is still taking
place in the IOC somewhere, but that seems somehow less bad than a direct cast.

If the framework recommends that users don't add properties to an application --
and ruthlessly eliminated all standard properties and descendants -- then why
would the framework turn around and add support -- at considerable cost in
maintenance and readability and extendibility -- for callers that expect a
certain type of application?

[Wrapping up]

Let's take a look at the non-generic implementation and see what we lose or
gain. The final version of the IApplicationManager API is shown below, which
properly balances the concerns of all stakeholders and hopefully will stand the
test of time (or at least last until the next major revision).


IApplication CreateAndStartUp(
  Func createApplication
);

IApplicationExecutionTranscript Run(
  Func createApplication,
  Action run
);

These are the hard questions of API design: ensuring consistency, enforcing
intent and balancing simplicity and cleanliness of code with expressiveness.

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


[1] A predilection of mine, I'll admit, especially when writing about a topic
    about which I've thought quite a lot. In those cases, the instinct to just
    skip "the object" and move on to the esoteric details that stand in the way
    of an elegant, perfect solution, is very, very strong.


[1] This more-realized typing was so attractive that we used it in many places
    in Quino without properly weighing the consequences. This article is the
    result of reconsidering that decision.


[1] This call looks the same for all UI (console, Winform, WPF, etc.), all
    services (e.g. ASP.NET, Windows-services, etc.) as well as for automated
    tests. This fact isn't germane to the discussion above, but it's pretty neat
    in its own right. All an application has to do is define two methods with
    the right signatures and call the appropriate Run() method for the desired
    type of application. Almost all of the startup code is shared and the
    pattern is the same everywhere.


[1] Yes, the C# compiler will allow you to elide generics for most method calls
    (so long as the compiler can determine the types of the parameters without
    it). However, generics cannot be removed from constructor calls. These must
    always specify all generic parameters, which makes for messier-looking,
    lengthy code in the caller e.g. when creating the ApplicationManager were it
    to have been defined with generic parameters. Yet another thing to consider
    when choosing how to define you API.


[1] As already mentioned elsewhere (but it bears repeating): callers can, of
    course, eschew the generic types and use IApplication everywhere -- and most
    probably will, because the advantage offered by making everything generic is
    vanishingly small.. If your API looks this scary, entropy will eat it alive
    before the end of the week, to say nothing of its surviving to the next
    major version.


[1] A more subtle issue that arises is if you do end up -- even accidentally --
    mixing generic and non-generic calls (i.e. using IApplication as the
    extended parameter in some cases and TApplication in others). This issue is
    in how the application object is registered in the IOC. During development,
    when the framework was still using generics everywhere (or almost
    everywhere), some parts of the code were retrieving a reference to the
    application using the most-derived type whereas the application had been
    registered in the container as a singleton using IApplication. The call to
    retrieve the most derived type returned a new instance of the application
    rather than the pre-registered singleton, which was a subtle and difficult
    bug to track down.