Hitchhiker's Guide to Software Architecture and Everything Else - by Michael Stal

Saturday, January 15, 2005

IT and Religion

Today, I'd like to discuss a more philosophical and controversial issue. Let me start with some background information. At home I am using a Sony Digital Camcorder, a Panasonic VCR, a Canon digital camera, a Philips TV, a Pioneer DVD system, and a Fujitsu-Siemens Activy Media Center. "So what?" you will think, "this is nothing special". And, of course, you are absolutely right. I chose in each area the device I heard would be great for a specific task. We might call this a best-of-breed approach. Most will agree that this is fine as long as the devices meet my requirements. Sure, it is not only the devices as stand alone gadgets that matters but also the interaction. For example, some VCRs and TVs have a special link interface to directly synchronize their channel settings. Great, but I don't care. On the other hand, it would be inacceptable if I could not play videos in the VCR and display them on my TV screen. Audio/Video interoperability is definitely a must. You still don't understand what I am talking about? So let me first give you some additional details: I am using Linux and Windows, and I am working with Java and .NET. I am editor-in-chief of a German Java magazine called "JavaSPEKTRUM" and at the same time I am appointed Microsoft MVP (Most Valuable Professional). Can you see the problem now? Some people really ask me how I could sell my soul to Microsoft, while others are wondering why anyone would ever use Java instead of Microsoft technologies. And that is the lesson learned: In the area of computer/software technology things are considered in almost religious dimensions. There may be similar discussions in some other areas too (e.g., IPod versus other MP3 players, BMW versus Mercedes Benz, Stallone versus Schwarzenegger, ...), but IT people seem to be much more involved in those stupid religious battles. I don't like these useless discussions as they are only a waste of time. But often it is hard to remain uninvolved. To be honest, I am working for Siemens which is one of the biggest companies in the world (440.000 employees). In our products you'll probably find all kinds of operating systems, programming languages, platforms and these products are developed using various engineering processes and different configuration management systems, etc. Do you see the point? For the consumer it does not matter what IT technologies we use. Every thing is fine when we can satisfy all requirements. Technology does matter, but architecture does even more. I am taking a rather pragmatic approach: Use all technologies that help to meet your requirements. First collect your requirements and use cases. Then build a baseline architecture. In the third step decide which technologies and tools to use for implementation. I have been involved in projects where the technology decisions came first. Interestingly enough, most of these projects failed either partially or completely. Sometimes, it would be really helpful to compare this technology-first approach with other areas. Want to cut a piece of wood into two halfs? Of course, you should use a screwdriver! Want to open your PC? Why not use a hammer for this problem? No person that is not completely insane would ever consider to solve the aforementioned problems with the stupid solutions I proposed. It is obvious that we should use tools and technologies only for what they were built for. Why should software development be so different? I love Java and .NET, and I stll love C++and CORBA because those technologies are extremely cool, each of them for a specific range of problem contexts. And what if there is a problem where we could use two or even all of them? That is even cooler because we got a choice.

Sunday, January 09, 2005

Simulate Closures in C+ 2.0

For all C# developers and Java developers who are interested in leveraging generic data types I've implemented an example that illustrates the implementation of block/closure-like features in the aforementioned languages. Those of you familiar with blocks in languages such as Ruby or Groovy will certainly know what I am talking about.
The example implements a generic datatype Collector.

The C# code is as follows (Java would be very similar):

namespace CollectOperation {
public class Collector {
public delegate T CollectDelegate(T temp, T parm);
public static object DoCollect
(ICollection coll, CollectDelegate cdel, T initVal)
{
T res = initVal;
foreach (T elem in coll) res = cdel(res, elem);
return res;
}
}
}

The main method in the class is the static method DoCollect which expects a collection of values, a method reference (probably an interface when using Java), and an initial value. The method iterates over all collection elements, and calls the delegate for each of them. The delegate method (the method to which the delegate refers to) should support two arguments, the first representing a kind of aggregation value and the second the actual element the iterator points to. Both are of type T (the parameter type). The delegate method returns a new object of type T which is then used as the new aggregation value in the next call to the delegator.

Confused by the theory? To show you the idea consider that you are using a collection of integers. You are going to use the Collector to sum up all values in the collection starting with zero.

In this case just invoke Collector.DoCollect(valuelist , addDel, 0)
where the delegate method is public int Add(int res, int parm) { return res + parm; }

Want to concatenate all string in a string collection starting with a specific prefix? Then you the Collector parametrized with string and pass a mathod that concatenates strings.

Got the idea? Basically, you are passing a method as a block to the Collector method DoCollect.

As you'll hopefully agree, generic datatypes offer you some opportunities to implement really sophisticated features.

Unit Testing

Unit testing is an extremely important activity within any software development. In contrast to system testing and other test disciplines, Unit Testing is a task of the developer. For each functionality you write code, you must prove its conformance with your expectations using unit tests. Unit tests are themselves pieces of code with the sole purpose of proving and exercising the correctness of particular code unter test. Thus, unit tests should also be developed with quality aspects in mind. Basically, a developer should consider unit tests to be integral constituents of the overall code delivery. Whenever any part of the code is changed, e.g. by refactoring, all existing tests should still run successfully. Thus, unit testing provides you with a kind of safety net. Many developers develop the code first and then write the unit tests for verification. In a Test First approach the philosophy is more "design and refactor a little, implement a little, test a little". In this case the test is written first, even before the first line of production code is available.

In order to make unit testing acceptable and feasible special frameworks are available that parse through your test code using reflection and then automatically execute the tests which are implemented by methods each exercising a specific aspect of your production code. The order of activities in all test methods is pretty the same:


  • Preparing the required environment for the code under test, e.g., resources and objects
  • Executing the test method
  • Verifying that what the test methods retrieve from the production code is what they expect
  • Cleaning up
If you are interested in such a framework just refer to http://www.xprogramming.com where you'll find a lot of details on existing xUnit Frameworks for most programming languages even exotic ones.

Let me show you this using a C# example where a factorial is calculated instead of providing you with a theory-first approach.

The first thing I would expect from a factorial method would be that it does not accept negative arguments.

using NUnit.Framework;
[TestFixture]
public class UnitTestFactorial
{
[Test, ExpectedException(typeof(ArgumentException))]
public void TestNegative()
{
MathOperations.Factorial(-1);
}
}


In this first test method we specify that we expect an exception to be thrown when we pass a negative argument. In a Test-First approach: the question is how can we implement our factorial class in such a way that this expectation is met. However, the code should be as simple as necessary to pass the test.

Here is the result:

public class MathOperations {
public static int Factorial(int arg) {
if (arg < 0) throw new ArgumentException();
return 0;
}
}

This implementation is passing the first test. But wait, there is more we expect:


[Test]
public void Test0And1() {
Assert.AreEqual(1, MathOperations.Factorial(0));
Assert.AreEqual(1, MathOperations.Factorial(1));
}

We expect that fac(0) == fac(1) == 1. If we run the test again with our simple implementation, it will fail. We need to fefactor our code to let the test succeed.


public class MathOperations {
public static int Factorial(int arg) {
if (arg < 0) throw new ArgumentException();
return 1;
}
}

Now, both tests succeed. But we are not finished yet.
How about the following test?


[Test]
public void TestMoreThanOne() {
Assert.AreEqual(2, MathOperations.Factorial(2));
Assert.AreEqual(6 * MathOperations.Factorial(5),
MathOperations.Factorial(6));
Assert.AreEqual(10 * MathOperations.Factorial(9),
MathOperations.Factorial(10));
}

This test will fail again. So we have to refactor our code to succeed:


public class MathOperations {
public static int Factorial(int arg) {
if (arg < 0) throw new ArgumentException();
if ((arg == 0) (arg == 1))
return 1;
else
return arg * Factorial(arg - 1);
}
}

And finally we are finished. The example is oversimplified but it definitely lets you understand the idea.

As you recognize Unit Testing is an important tool for each programmer. You'll appreciate it as soon as anyone changes the production code and one of your tests that fails will show you that the code changes had additional side effects you weren't aware of. Integrate the idea into a configuration management or automatic build tool and you'll get a taste of its potential. Unit testing may cost a little time and a little of additional efforts but in the end you will save more than you invest.

Sunday, January 02, 2005

Ruby

It is a long time since my last post. I will try to write more regularly this year. Today, I'd like to talk about a programming language which I am really excited about. As I prepared an introductory language tutorial in the last weeks for the OOP 2005 conference I had to intensify my learning and knowledge. The language is Ruby. It was developed 1993 by a japanese guy and originally intended to be a better Perl than Perl. Nonetheless it denotes a pure OO language (whatever that means :-). There are a lot of benefits using Ruby: the language itself, the libraries, the community. What I like most is its combination of different things, OO concepts from languages such as Java, features from Smalltalk and Lisp, and elements taken from scripting languages.

Let me just show you an example:

class RubyExample
def testMethod
if block_given?
yield("Here is Michael")
end
end
end

v = RubyExample.new
v.testMethod { \arg\ print arg }

The class above just includes one method. You might pass a block to the method which will then be called within the method's body. In the block the argument passed by the method will be printed to the console. Th example itself is somewhat boring. However, the feature it presents is very powerful. Blocks might be used to implement iterators, threads, or closures, to name just a few examples.

Another fascinating aspect of Ruby is its usage of Mix-Ins instead of multiple inheritance.
Suppose, you implemented the following module:

module M
def info
"42" # btw, the last statement in a method also is the return value
end
end

In your class you might include the module

class C
include M
end

If you now instantiate C you'll be able to use the included method:

c = C.new
c.info ==> "42"

Methods will be shared by the class including the module. All instance variables defined within the module will have separate counterparts in the including class. Basically, a class behaves like a singleton class.

Ruby offers some interesting built-in types such as ararys, maps, ranges, regular expressions, symbols. Especially regular espressions are essential for the scripting folks.

The libraries available for Ruby allow you to implement GUIs (using Tk), access Win32, build distributed applications, access databases, implement unit tests, process XML, build Web sites, and uncountable other sorts of applications.

Of course, Ruby is free to use although there are also commercial products out there.

If you are now curious about Ruby just go to my home page and download the tutorial slides.