SIGN UP MEMBER LOGIN:    
ARTICLE

Coding Better: Programming From the Outside In. Part I

Posted by Matthew Cochran Articles | Current Affairs January 25, 2009
Modeling an API for a domain is a difficult task. The trick is to get the correct level of encapsulation/abstraction while making the API easy to understand and consume. If we can model our domains at the same level that we understand them linguistically then we are 90% of the way there. This article discusses a programming methodology by which we can build a concise interface that will provide a consistent level of abstraction and is easy to code against.
Reader Level:
Download Files:
 

Intro:

In this article we'll look at a technique for building an API from the outside in.  I'll just be going over defining the surface areas (the outer part) of the API and will not build out the implementation (the inner part) which is something that would be done later. 

Modeling a domain using this process has some significant benefits most of which are derived from the fact that we are coding from the outside-in rather than coding inside-out.  Just like when we wear our cloths inside out and see some of the seams and how the piece was constructed, code constructed from the inside-out usually has poor encapsulation/abstraction and some of the plumbing (or seams) leak into the consumer's layer.  Using this technique we'll be focusing on building the surface area of our classes first and later worry about the implementation.  This technique really helps code bloat because we will only be coding what we need and not what we think we might need at some point in the future.  We also have all the benefits of a TDD (Test Driven Development) coding process.

The resulting surface area of our library should be relatively simple and other programmers consuming our classes should find it very intuitive because it should make sense linguistically.  Sometimes this means some additional complexity will end up inside our library in order to present a simpler surface are.   Code written against our API will end up being much more readable than if we were to code the guts first.  We can take advantage of intellesense to provide developers consuming our library with a logical "next step" when they are coding.  All of this will be accomplished while we use a TDD approach so we end up with a nice suite of tests for our library.  Even if we don't exclusively use this approach for your development some of the techniques are useful in certain places.

Defining Our Domain:

This is the most difficult part of the process and often I find myself needing a couple tries to get it right.  The idea is to write down how we would think of a domain using common language and try to convert it to code using method and properties.  The trick is figuring out exactly what we are modeling and how it would be consumed in a manner that correctly abstracts the complexity of the domain.   This is the most significant change from the inside-out approach to coding where a junior programmer would start with a general idea of the functionality required and just can't wait to start putting code on the page.  By investing more time thinking about the surface are of the functionality we want to expose before we start implementation we end up with a much better product in the end and have less wasted work due to surface are changes later.

Let's say we have to model a marathon and its participants. Using the outside-in approach we'll always define how we want our code to read first, before we write a single line of code.  We would begin by writing some statements about how the domain should be consumed with the goal of making the API as close to spoken or written language as possible.  The following is our first sketch of how we would like our code to read. Because this code is readable by non-technical people, we would potentially go through many discussions with domain experts to make sure we have the structure correct.  There are many details hidden in the nuances of language that we often miss in code and being able to discuss code with a domain expert in this way before we set anything in stone will make our library much more accurate and complete.

Racer Joe, Frank, Fred;

Race race1 = Joe.Races(Frank);

race1. IsJoinedBy(Fred);

race1. IsJoinedBy(Bill);

 

This first overly simplified sketch of the API would probably go though a few iterations of change and become much more complex before really being solidified.  For the purposes of this article you can get the general idea of what we're shooting for so we'll leave it as is. In a real project we would probably go through a few iterations by ourselves or with a domain expert on this sketch alone to really flesh out the domain before moving forward.  Let's say we have gone through this process and are now ready for building an object model that behaves as close to our sketch as possible.

Building the Surface Area:

First we'll build the Racer class and some basic functionality.  We figure that we'll need a way to distinguish our racers so we'll make the first slight alteration to our sketch by adding a "Name" property.  On a real project it would be a good idea to keep our sketch up-to-date and modify it every time we make a change like this in order to ensure everything is consistent and all the changes make sense together with our sketch.  For this article we'll wing it for the sake of brevity and to give ourselves some elbow room.

public class Racer
{
    public Racer(string name)
    {
        m_Name = name;
    }
 
    private readonly string
        m_Name;
 
    public string Name
    {
        get { return m_Name; }
    }  
}

Next, we'll build a unit test in a separate test project to pin down our functionality. 

[TestMethod()]
public void RacerConstructorTest()
{
    string name = "Joe";
    Racer target = new Racer(name);
    Assert.AreEqual(name, target.Name);
}

I know we are not strictly following the TDD methodology, but we get the same benefits in the end as long as we are patient enough to ensure we have a test before we move on.  For those of you who this just rubs the wrong way… not to worry: after this we'll be better about following TDD a bit more strictly.

We now will define our next entity, the "Race" class with no implementation.

public class Race
{
}

And we'll write a test for construction of Race using our Racer class.  Take note that there is currently no "Racer.Races()" method.  Using this technique we will first be writing code in the way we want to consume it and then write the implementation.  This is similar to TDD where we write the test first but taken to the next level.  We will write the consuming code first, before the method stubs even exist.

[TestMethod()]
public void RaceConstructorTest()
{
    Racer joe = new Racer("Joe");
    Racer frank = new Racer("Frank");
 
    Race race1 = joe.Races(frank);
 
    Assert.IsNotNull(race1);
}

Now we'll take advantage of the IDE (In this case, Visual Studio 08) in order to stub out the method we want.  "Races()" is not implemented, but using Studio08 we can right click on the unimplemented method and generate a method stub.  There are other tools available as well to help with this style of development.  The one I currently use is from DevExpress and is a great addition to VisualStudio.  Because everyone might not have this product, we'll just go with the VisualStudio functionality for this article.  We right click on the method that has not been defined and we see a "Generate Method Stub" in the menu and select it.

GenerateRacesMethod.gif

And now the IDE writes the method for us in our Racer class with the NotImplementedException in place.

public class Racer
{
    public Racer(string name)
    {
        m_Name = name;
    }
 
    private readonly string
        m_Name;
 
    public string Name
    {
        get { return m_Name; }
    }
 
    public Race Races(Racer frank)
    {
        throw new NotImplementedException();
    }
}

We can now use the IDE again to generate a test against our new method in order to define how it should behave:

GenerateRacesUnitTest.gif

And put in the code we expect.  At this point we may need to add to the surface are of our classes in order to test them.  We'll try and write new methods that are more like natural language so that they make sense in the context of the domain.  Again, if this were a real project we should update our original code sketch to include this new method. 

In the domain of a marathon, it would not make sense to say: a race "contains" a racer.  So we'll write a method so our code will read as: a race "includes" a racer. Just like last time, we'll first write the method in the way we would like it consumed and then stub it out using the IDE.

[TestMethod()]
public void RacesTest()
{
    Racer joe = new Racer("Joe");
    Racer frank = new Racer("Frank");
    Race race1 = joe.Races(frank);
 
    Assert.IsTrue(race1.Includes(joe));
    Assert.IsTrue(race1.Includes(frank));
}

We'll right click to generate method stub.

GenerateIncludesMethod.gif

And now we have our first method on our Race class.

public class Race
{
    public object Includes(Racer frank)
    {
        throw new NotImplementedException();
    }
}

We need to make a few modifications.

public class Race
{
    public Boolean Includes(Racer frank)
    {
        throw new NotImplementedException();
    }
}

Then we can generate a unit test

GenerateIncludesUnitTest.gif

We'll wait to implement our tests because we'll handle the "IsJoinedBy()" method by first writing the test for our object model first.  It is usually a good idea to get as much of the surface are built out as possible before implementing the code because the method stubs will most likely change how the classes are composed.  This will help to avoid the situation where we have to throw out some implementation because it does not work with a new method signature.

[TestMethod()]
public void StructuralTest()
{
    Race race1 = new Race();
 
    Racer joe = new Racer("Joe");
    Racer frank = new Racer("Frank");
 
    race1.IsJoinedBy(joe);
    race1.IsJoinedBy(frank);
 
    Assert.IsTrue(race1.Includes(frank));
    Assert.IsTrue(race1.Includes(joe));                  
}

Now we'll generate "IsJoinedBy()" method stub.

GenerateIsJoinedByMethod.gif

 

And we get the following code:

public Object IsJoinedBy(Racer joe)
{
    throw new NotImplementedException();
}

This needs to be modified slightly to be exactly what we want.

public Race IsJoinedBy(Racer joe)
{
    throw new NotImplementedException();
}

We generate the test:

GenerateIsJoinedByUnitTest.gif

And now we have a solution that builds and a suite of failing unit tests that we can start working on:

Conclusion:

If we felt the surface are of our classes were pretty much complete according to our sketch, we could start implementing some of our classes to start getting our unit tests to pass or pass it along to a junior developer. Another option is to continue building out our tests and hammering out the surface area of the domain.  I like having as much of the surface are in place as possible because the surface area can affect the guts of the class and I want to keep rewriting implementation code to a minimum.  The process of picking where each linguistic element should reside is one of the tricky parts of building a solid API and this can be figured out before writing code ensuring no time is wasted coding implementations that have to be scrapped later. 

In my next article I'll show you how to use the Fluent Interface pattern to make more sophisticated linguistic code that is even easier to read and code against.

Until next time,
Happy Coding

I also uploaded a video demonstrating this approach if you are interested.

Login to add your contents and source code to this article
share this article :
post comment
 

Dear Mr. Cochran, I would need your great help regarding the requirement mentioned as below - Requirements- I need to know when to use Interface Based Programming using .NET Framework 2.0 and if you could send me a sample would be great. Note : Interface Based Programming with 3 tier Architecture along with MVC concept. Awaiting your reply Regards, Lakshmikanth Matta

Posted by Lakshmikanth Matta Feb 10, 2009

Thanks for your comments.  I agree, the primary point is the code must be simple. 

I think the trick is finding the balance between the two styles of coding and finding the “sweet spot” for whatever you are working on.  For example, I wrote an article a while ago about Neural Networks and the modeling was done for purposes of example.  While the code works, in a real world project it would have been better to have modeled a bit more coarsely not only for performance gains, but also to take advantage of the New Jersey style and make the implementation simpler by taking advantage of matrix calculations (as another reader has commented). 

Finding the right “level” at which to model a domain only comes with experience.  If we focused on building an extremely granular object model focused on using this (and only this) approach it would be disastrous.  We would have object bloat and the complexity would be unmanageable. In the same way, having one monolithic method with all the functionality for a domain would also be disastrous because the code would be brittle and inflexible.  For this reason, I always am concerned when I hear absolutes in statements like “never do this” or “always do this” because it shows closed-mindedness and inflexibility when more often than not the best thing to do will depend on the situation.

We have to always use our brains. Thinking it through prudently and finding the best balance for the situation is the most important thing.  Usually I find that this takes more than one person looking at the code together and there is usually more than one “right” way to do something.  Statements like “it would be a good idea to do XYZ because ABC” end up being very productive because they invite discourse.  I’m always pleasantly surprised by just how much I learn by just having these kinds of discussions with another developer, especially when I can get someone to seriously play devil’s advocate when they look at my code.  It seems like the more I learn the more I realize how much I don’t know.  But that’s one of the things I love about programming.

Anyways, as I step off my soap-box,  I think the answer to the question….

Q:“The whole point is that code must be "simple", but is it the interface or the implementation that must be simple?”

Is..

A:“Yes”.

The trick is how to get there.

Posted by Matthew Cochran Jan 26, 2009

Coding from the outside in seems to be an instance of MIT-style: "It is more important for the interface to be simple". I agree that this is a "good" style - don't code it, if it ain't needed. The competing style is New Jersey style: "It is more important for the implementation to be simpler than the interface". This has the benefit that the API can be used for many other purposes than it was intented. Not meaning to start a war of religions, but: Examples of New Jersey style seems to be Java API and Linux. Examples of MIT style seems to be .Net library and Windows. The whole point is that code must be "simple", but is it the interface or the implementation that must be simple? That is the question. see also: http://en.wikipedia.org/wiki/Worse_is_Better

Posted by hv Jan 26, 2009

It always amazes me how much code is written with the straight implementation in mind. Since most programmers don't want to do visual design up front, this is a great approach to coding a scaffolding API around the later implemented code. All of my current projects use the approach you mentioned. (Writing an interface API, coding unit tests against the interface. Mocking services to allow the interfaces to work, and implementing LAST.) This approach inevitably makes for a much better implementation because it forces you to think about the entire project and what is required in all of your interfaces first. It also forces you to go through the thinking process of how the interface will meet the business requirements without writing a line of code.

Posted by Mike Gold Jan 26, 2009
Become a Sponsor
PREMIUM SPONSORS
  • Finally – a virtual platform that delivers next-generation Windows Server 2008 Hyper-V virtualization technology from a managed hosting partner you can truly depend on. Visit www.maximumasp.com/max for a FREE 30 day trial. Hurry offer ends soon. Climb aboard the MaxV platform and take advantage of High Availability, Intelligent Monitoring, Recurrent Backups, and Scalability – with no hassle or hidden fees. As a managed hosting partner focused solely on Microsoft technologies since 2000, MaximumASP is uniquely qualified to provide the superior support that our business is built on. Unparalleled expertise with Microsoft technologies lead to working directly with Microsoft as first to offer IIS 7 and SQL 2008 betas in a hosted environment; partnering in the Go Live Program for Hyper-V; and product co-launches built on WS 2008 with Hyper-V technology.
    The leading .NET charting control now features PDF, Flash and Silverlight export, visualization of large datasets and more. Deliver true charting functionality to your BI, Scorecard, Presentation or Scientific apps. Download evaluation now.
Become a Sponsor