ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button
Better, Faster, Lighter Java

Better, Faster, Lighter Programming in .NET and Java

by Justin Gehtland, coauthor of Better, Faster, Lighter Java
07/14/2004

In our new book, Better, Faster, Lighter Java, Bruce Tate and I lay out five basic principles for combating the "bloat" that has built up over time in modern Java programming. That bloat comes in the form of specifications (the J2EE spec, specifically EJBs), implementations (heavyweight containers), and standards (XML, web services). We chose to focus on Java in the book because Java is at a real crossroads right now: with the impending release of the J2EE 3.0 specification, juxtaposed with an explosion of lighter-weight open source frameworks such as Spring, Hibernate, Pico, Kodo, and so forth, the Java world is ripe for an in-depth look at the costs and benefits of the various approaches.

This doesn't mean that our principles aren't equally applicable to .NET. Even though .NET programmers aren't used to thinking in terms of heavy and light containers, enterprise development in .NET is ripe with assumptions about how things are done, and they almost always involve one of two services: COM+ (for EnterpriseServices) and IIS (for web deployment). These two services are, in truth, nothing more than containers provided by Windows. Developing applications that live in these environments is a complex task that grows more complex by the day, and isn't likely to become any less complex with the advent of Longhorn (with its Indigo messaging stack and integrated services for SOA).

In this article, I'll lay out the five principles from our book, and examine how they apply to programmers working on that other major managed platform, .NET. We'll see a lot of the same problems, but not always the same solutions.

Principle 1. Keep it Simple

The primary thrust of this principle is that complexity leads to disaster. Your application should be built around simple constructs and understandable layers, which combine to perform complex tasks. The code itself, however, should avoid complexity at every stage. This is much easier to say than to do, though, since many programmers are afraid of missing important pieces, or of oversimplifying.

You don't need to be afraid of doing something too simply if you embrace change in your application. The ability to fearlessly embrace change is based on good testing practices. If your code has thorough, automated, repeatable tests, then making changes to the code loses much of its anxiety. As changes are introduced, the tests will tell you whether or not you are breaking something important. Automated testing gives you a safety net, allowing you to examine simple solutions first, and to change them over time without fear.

That safety net is provided through a combination of tools, namely:

  • NUnit for unit testing
  • NAnt for automated build support
  • CruiseControl.net (or draco.net) for integration building and testing

I do a lot of speaking and teaching in front of .NET developers. I am constantly amazed at the response I get when I mention NUnit. It is invariably something like: "I've heard of that. How do you use it?" For whatever reason, unit testing has not penetrated the .NET development mindset. For my money, NUnit is the best thing to happen to Microsoft developers since Intellisense.

To create unit tests, you have to make a reference to the NUnit.framework assembly. Then, create classes that will hold your tests. Tests are nothing more than specially decorated methods on test classes that contain one or more assertions. For example, imagine you have a class that validates certain kinds of inputs into your system. Such a class might look something like this:

public class Validator
{
    public bool validateCustomerId(string custId)
    {
        // ... validate customer idea, return 
        // ... true or false
    }

    public bool validateSSN(string SSN)
    {
        // ... validate Social Security Number
        // ... return true or false
    }
}

You will want to know that you have programmed these methods correctly. To test your success, create tests that demonstrate both the ability to recognize valid inputs, and to reject invalid ones. Here is an NUnit test class that accomplishes the goal:

[TestFixture]
public class TestValidator
{
    Validator validator;
    private const string GOOD_CUST_ID = "123-456-12345";
    private const string BAD_CUST_ID = "xxx-xxx-xxxxx";
    private const string GOOD_SSN = "111-11-1111";
    private const string BAD_SSN = "11-111-1111";
    // could define as many good and bad consts as needed

    [SetUp]
    public void setUp() 
    {
        validator = new Validator();
    }

    [Test]
    public void testValidCustomerId() 
    {
        assertTrue(validator.validateCustomerId(GOOD_CUST_ID));
        assertFalse(validator.validateCustomerId(BAD_CUST_ID));
    }

    [Test]
    public void testValidSSN() 
    {
        assertTrue(validator.validateSSN(GOOD_SSN));
        assertFalse(validator.validateSSN(BAD_SSN));
    }
}

To run this test, you would point the NUnit test runner of your choice (the graphical WinForms version or the console version) at the compiled assembly where your tests are stored, and run the suite. NUnit will tell you whenever one of your assertions fails or an exception bubbles out. In the graphical viewer, you get a nice red bar to tell you that things aren't well, and a green bar for the "all's clear." If you use NUnit long enough, you develop a Pavlovian response to green and red; red makes your heart pound, green makes you feel all warm and fuzzy.

Related Reading

Better, Faster, Lighter Java
By Justin Gehtland

Creating good unit tests makes you write simple classes that perform their functions with a minimum of fuss. Unit tests act as the first clients for your code, and you quickly get the hang of writing just enough code to make your tests work. Most people who write unit tests often enough end up going all the way and writing their unit tests first; this technique is called Test Driven Development. It can be a tremendously powerful tool for simplicity, since it forces you to think through the public interfaces of your classes before you write them, giving you greater insight into how they will be used, and letting you cut out needless complexity before you even get started.

Once you have unit tests in place, you will want tools that make sure they get run a lot. On your development machine, that tool is usually NAnt (an open source, XML-based build management solution), though make works just as well if you are comfortable with its syntax and you like writing shell scripts instead of XML. NAnt allows you to automate the entire build/test/deploy cycle for your local code efforts.

On top of NAnt and NUnit, other tools worth examining are CruiseControl.NET and/or Draco.NET. These automated integration tools live on a central testing box and monitor your source code repository. As individual developers check in code (which has unit test code accompanying it), the service will check out the latest version of the code, build it, and run the unit tests. This will overcome the "but it works on my machine" problem, as developers will receive near-instantaneous feedback about whether their commits "broke the build." Often, code will pass its unit tests in isolation, but when combined with the rest of the application, will suffer mysterious failures. These automated integration services can demonstrate these errors almost immediately, and the shorter the feedback loop between committing bad code and finding out you did so, the more likely it will be that you can fix it with a minimum of effort.

Between these three tools, you can begin to code using the simplest architectures and abstractions possible, with the confidence that you can refactor to solve the problems that present themselves. That's the key to all good development; only solve the problems you actually have, and leave the needless complexity on the cutting room floor.

Principle 2. Do One Thing, and Do It Well

This second principle is all about understanding the problem you are trying to solve. Whether we talk about the business problem that a given application is meant to address, or the technical problem that a given method, class, or package is addressing, the key to doing it right is to clearly identify the actual problem.

The larger problem is not unique to any development platform. Customers come to you asking for software that solves their problems. More often than not, they have spent a lot of time carefully thinking through the requirements for the software. Unfortunately, far too often, those requirements include a lot of assumptions about what they need. The first job of any good development team is to wade through those assumptions and find out what is driving the customer to your door. Simplify the requirements down to the set of statements that clearly defines the real problem, and you can code with the assurance that you are tackling the right issues.

The smaller problem is one of architecture and design. Much of the code that exists today is entirely un-maintainable, and one of the major reasons for that is over-coupling. We write classes that do a million things, we ship assemblies with a hundred classes in them, we write methods with a thousand lines of code. It is almost impossible, given code written this way, to find the exact place where changes need to be introduced or where bugs are manifesting. Instead of dumping all of that functionality into a single location, it makes much more sense to have a clearly defined separation of concerns, but creating simple classes packaged into targeted layers.

One common example of too much coupling is classic ASP. The problem was that an ASP file contained both layout (static HTML) and logic (script). The business code was sprinkled throughout the page. Making simple changes to the presentation structure or business logic meant editing this mish-mash of code, and hoping that you didn't unintentionally affect the other side of the equation.

Luckily for ASP.NET developers, we were given a very clean way to separate those two concerns using code-behind. With code-behind, we can still create HTML template (.aspx) files, but instead of mixing the code into the file itself, we can have the .aspx page inherit from a class, which is written entirely in the .NET language of your choice. Changes to the presentation layer happen in the .aspx file, and changes to the logic happen in the base class. This is a very good example of our principle in action; write code that solves a single problem. The .aspx pages solve the needs of the UI, and the code-behind files solve the needs of populating those pages with data.

For example, you may continue to write your ASP.NET pages like you have always written ASP pages:

<%@page language="C#"%>

<html>
<script runat="server">
    public string getUserName() 
    {
	   // fetch user's full name and return it
    }
</script>
Welcome, <% = getUserName() %>.
</html>

To simplify and decouple this page, we'll create a code-behind file. The code-behind file contains a class that inherits System.Web.UI.Page:

public class WelcomePage : Page
{
    public string getUserName()
    {
        // fetch user's full name and return it
    }
}

The .aspx file then looks like this:

<%@page language="C#" inherits="WelcomePage"%>

<html>
Welcome, <% = getUserName() %>.
</html>

To complete the transformation, we can use a WebForm control in the .aspx page to display the value, and populate it in the code-behind file at page load, thus eliminating all code from the presentation file.

public class WelcomePage : Page
{
    protected Label lblUserName;

    public string getUserName()
    {
        // fetch user's full name and return it
    }

    protected override void OnLoad(object src, EventArgs e)
    {
        lblUserName.Text = getUserName();
    }
}

and the .aspx file:

<%@page language="C#" inherits="WelcomePage"%>
<html>
Welcome, <asp:label id="lblUserName" runat="server"/>.
</html>

You should repeat this same process whenever you are writing methods and classes or assembling packages. Separate out orthogonal concerns into their own private spaces. This makes it much easier to find the code you are looking for, whether that be to change or add to it, or to track down a bug. In the example above, if there is something wrong with how the page is displayed to the user, I know to go to the .aspx file, and it is easy to see the intent of the page. Likewise, if the data is coming out garbled, I look at WelcomePage.cs, which is uncluttered by presentation and layout.

Pages: 1, 2, 3

Next Pagearrow