Better, Faster, Lighter Programming in .NET and Java
Better, Faster, Lighter Programming in .NET and Java
Principle 4. You Are What You Eat
Anyone who has heard me talk about enterprise development has heard me say, at least
once, that the tools you use aren't just tools, they're risks. Every time you use somebody's
framework, or IDE, or library, or interface, or what-have-you, you are essentially
investing in that team/person/company. If that tool suddenly goes away, or takes a sharp
right turn when you were hoping for a left, then your project might suffer as a result.
Additionally, when you ship applications to your customers, if something goes wrong
(even deep down in some third-party library you got from the Internet), your customer will
come back after you, not the third party. Your application is always your responsibility; if
you make use of a buggy framework, those bugs will reflect back on you.
In the Java world, there are a hundred ways to do anything. If you want an MVC
framework, there's Struts, WebWork, SpringMVC, and one implementation for every
J2EE vendor. For O/R mapping, there's Hibernate, JDO, Kodo, EJBs, etc. Heck, there
are even five or six viable IDEs. On the Microsoft side of the world, we have tended to
be less diverse. We use Visual Studio for development, ADO.NET for data access, and
ASP.NET for our web front end. There aren't nineteen different web form strategies
(Tapestry, Velocity, JavaServer Faces, etc.). However, that focus on a single vendor is
starting to change.
If you take a look at SourceForge, you will
notice that there are almost as many .NET projects as Java projects now. All of the
major tools on the Java side are being ported to .NET. This list includes:
Struts.NET for web MVC
NHibernate for O/R mapping
NUnit for unit testing
Pico.NET and Spring.NET for lightweight alternatives to EnterpriseServices/COM+
NAnt for build management
CruiseControl.NET and Draco.NET for integration builds
C# support in Eclipse and IDEA
And the list goes on. As the world of alternatives begins to open, Microsoft developers
will now have to face the decisions that Java programmers face all the time: which
implementation is right for this team, and this project? Before, you just used what was
available. Now, there are competing strategies at every turn.
Making the right decision about your tools always comes down to a cost/benefit analysis.
The cost side of the equation is some amalgam of money, research time, training,
performance, and sustainability ("Will this product be around for as long as my project?").
The benefits can take the form of simplified development and maintenance, better
performance or scalability, or simplification of the code. Tool choices should never be
based on either of the following propositions:
"Microsoft said I should."
"Joe thought this looked cool."
Microsoft may or may not be right about whether a given tool is the right fit, and Joe may
or may not be a good arbiter of what's hip. You should think carefully and make your
own decisions about whether a certain tool brings you the most benefit for the costs, and
implement accordingly.
Principle 5. Allow for Extension
It is an axiom of distributed programming that no matter how small your user base is
when you start, if you write a good enough application, eventually you will have to scale
up. Therefore, distributed applications must always be architected for scalability in the
face of future need. Likewise, any application written well enough will someday be put
to uses its authors never intended or imagined. Therefore, applications should be written
in such a way as to allow for extension.
In general, this means writing code that allows for the unknown. This sounds like an
impossible proposition, but in reality, the key factor is understanding where future users
are likely to want to change the way your application works, and making those areas as
loosely coupled to the rest of the application as possible.
Let's examine the notion of loose coupling first. Many developers think of loose
coupling as using interfaces. That is, instead of:
Document document = new Document();
having:
IDocument document = new Document();
The code is now bound to an abstract interface called IDocument instead of the concrete
Document class. However, that call to new Document() is still tightly coupled. Changing
it later is impossible without recompiling, which defeats the whole purpose of loose
coupling. Instead, what is needed is a factory method. This is usually a static method on
the class itself that can return new instances of a given type.
The beauty of factory methods is that they can return any concrete type that implements
IDocument, not just the original Document class, and they can look up the actual
implementation at runtime using reflection. The implementation of CreateInstance might
look like:
As long as the calling code has passed in an accessible assembly, and the type they asked
for implements the IDocument interface, then it doesn't matter what the concrete type is,
or even if it existed when the application was first written.
To allow future users to add or replace types this way, usually you will add a section to
your application's .config file that contains references to the appropriate assemblies.
When allowing your users to choose a type of document to interact with, you simply grab
the list of configured document types from the .config file and display them in some sort
of selection interface. Once the user makes a decision, your factory method can load the
assembly and type and return the appropriate implementation.
A special note about reflection: it gets a bum rap. Reflection is slower than direct
coupling, but usually measured at about two or three times slower. That means you shouldn't do
everything via reflection, but its judicious use for something like loading a dynamically
assigned class at runtime (but binding it statically to a known interface) is perfectly
reasonable, especially if you are talking about a modern distributed application, where
the two more important criteria for boosting performance are limiting round trips on the
network and minimizing hits to the database. If you can solve those problems, then
maybe you can think about how a little reflection affects performance. I find that
extensibility is usually a more important factor for me.
Conclusion
As you can see, there is plenty of needless complexity piling up all over the development
landscape, and one of the principal tasks of any programmer is recognizing the bloat for
what it is, and avoiding it where possible. .NET is no more immune to this problem than
Java is. For that matter, as my good friend Ted Neward points out in his blog, ".NET is Microsoft's solution to the bloat build-up in COM." If we know anything about the technology industry, it's that history repeats itself. Programmers need to take it upon themselves to limit the bloat, and prune the complexity that is keeping their applications from living the good life. I hope these five principles give you a starting point for
examining the choices and assumptions you have made about your projects, and give you
some ideas of ways to make your programming life more simple and fun again.
Justin Gehtland
is a programmer, author, mentor and instructor, focusing on
real-world software applications.
In June 2004, O'Reilly Media, Inc., released Better, Faster, Lighter Java.
Sample Chapters 3 and 10 are available free online.
You can also look at the Table of Contents, the Index, and the full description of the book.
For more information, or to order the book, click here.