The first “barrier to entry” for doing emergent design, an advanced developer practice for incrementally building software, is to understand object-oriented design and development.
In theory, the object-oriented model should be easy to understand but I don’t often see it well-understood in industry. I’ve reviewed millions of lines of code from companies across a range of industries and while most of them use good object-oriented languages like Java or C# or C++, what I found is that much of the code I see is simply procedural code wrapped in a class statement.
So what is the difference between procedural code and good object-oriented code? What makes software object-oriented?
The answer is a matter of perspective. In procedural software, we maintain a global perspective and think of our program as a series of instructions that the computer executes step-by-step. This makes understanding the program straightforward and reasoning about what the computer is doing also straightforward because this is how we represent programs to be executed by a computer. This is also the way we think consciously. We think procedurally. We think consciously as a stream of words that are processed sequentially.
However, this is not the way the world works. There is no global perspective, at least not one that I’m aware of. The world manifests through the interaction of entities. There isn’t one global perspective but rather many perspectives and it is through the interaction of these many perspectives that things happen in the world. This is how the real world works. And so, the object-oriented programming paradigm attempts to model this.
So, what is the object-oriented paradigm? I’ll give you a couple of ways to think about this. I’ll describe one way that I like to think about object-oriented programming and then in my next blog post, I’ll discuss a different way of thinking about object-oriented programming.
Let’s start with instantiation. Instantiation gets to the very core of what object-oriented programming is all about. It’s about separating out the responsibilities of creating an object versus using the object. This is actually a pretty familiar concept because it’s how biological systems work. We all went through embryogenesis before we were born. It’s also how many other things in the world work.
For example, the way fractals are drawn is through iterating simple mathematical formulas over and over again. One way to draw a fractal is through successive refinement where it starts in low resolution and then, through a series of iterations, gradually increases in resolution so that a high-resolution fractal emerges. Fractals, like many things in the world, are an emergent phenomenon.
In object-oriented programming, the responsibilities of a system are broken out into different objects. This helps us manage and scale large systems. In object-oriented systems, every object should have a single responsibility. We should be able to represent the entities responsible for the behaviors that we want to create and show their relationship to each other. Doing this helps us gain clarity in our design and it also helps it become more understandable to others.
When object-oriented programming is done well then systems are scalable and understandable because they’re clearly defined. When object-oriented systems are done poorly then systems tend to be tightly coupled and resistant to change. Object-oriented programming, like the very best medicine, can either cure you or kill you depending upon how you use it.
Note: This blog post is based on one of the “Seven Strategies…” sections in my book, Beyond Legacy Code: Nine Practices to Extend the Life (and Value) of Your Software.
Previous Post: « Why Practice Eight: Implement the Design Last
Next Post: Understand Design Patterns »