Sunday, February 13, 2011

How Big should a Class be?

It's a common question - how big should the class be? Should I break it into smaller classes? Am I making things overly complex by splitting it out.

I will say that I am not sure I have ever seen source code where the classes were consistently too small.

Let us take a trivial example:

Say we have sensor, and it holds it's readings in a list. (I know there should be timestamps etc, but we are limited by how much I'm prepared to type, and by how much you will read)

class T5000sensor : ISensor {
   List<double> readings;
   string sensorReference;
   // various other data...

And we know from experience that the readings often contain bad data. So we want to "clean" the readings. Lets say we want to remove the highest and lowest 3 readings.

So we can add a function void cleanReadings() which does this.

Of course, it's possible that we may end up with a number of different types of sensors. And perhaps for some sensors, we have different "clean" functions.

Now, cleanReadings has become a virtual function, and each sensor has it's own implementation.

The problem with this is that we no longer have flexibility. I cannot decide to swap the "clean" functions between the sensors. My T5000 Sensors always discard the top and bottom 3 readings, and my  R1300 Sensor, will always ignore any readings more than 20% away from the running average of the last 10.

Ultimately our software needs a model of the real world, and when that model diverges from the real world then is makes things complex. All models of course are abstractions, and contain only the detail we consider important. But this loss of detail is normal and required. The model being different from the real world is the problem.

When we look back, we see our "model" of the real world includes a sensor, and the sensor has readings. So far so good. But the "clean" function is not part of the sensor. It is external to the sensor. So much so, that we have just seen, we may want to interchange "cleaners". In our model of the world, we have made "clean" part of the sensor. The world can do things that our model cannot.

We can fix this simply by extracting the cleaner from the sensor. We could move the cleaners into separate static functions which we could pass down to the classes. We could create a cleaner class which will take a set of readings and clean them for us. We proably want to avoid our cleaner knowing about anything other than ICollection<double>. It certainly should not know about ISensor if we hope to use the functionality to clean our Output Data for example.

Ultimately, the functionality did not belong in implementations of ISensor, so we move it out. That leads us to the correct question. It's impossible to ask how long a class should be, it's far more specific and easier to answer the question, should this functionality be in the class.

One big hint that you might need to move functionality out to another class is that you can group together subsets of the classes functions and properties which interact closely within the subset, but do not interact with the remainder of the class.

Another hint is that the class has grown so large, that the network monitoring team know when you are editing it from the network drive !!

No comments:

Post a Comment