Thank you all that participated in my lecture at KTH October 26, 2011. I had a lot of fun, and we had some good discussions. For you who were not there, it was about object-oriented programming and how to write good code. We used some example code to discuss OO principles and testing. Here’s the presentation (pdf)!
What is it that companies look for when they hire programmers? Surprisingly, they probably want something else than what’s in the job ad.
Job advertisements often focus on technologies (network programming, windows programming, XML, you name it) or expert skills in a specific language. As a consequence, most CVs focus on the same thing. This is only natural, since people want to get hired. With otherwise equally strong candidates, the technology skills listed in the CV might affect who’s hired. But highlighting technology skills in a job ad is probably misguided, since I think these skills are not what companies really need. Let me explain what I mean.
In order to get a software system to do what we want, we need the above-mentioned technology knowledge. So, it’s definitively required, don’t get me wrong. But for the project to be successful over time, technologies are worth nothing unless the structure of the code is good enough for maintenance. (Somebody said that your code is in maintenance mode after the first line of code has been checked in. There is some truth in that.)
Also, acquiring a new piece of technology is not rocket science. Give it a couple of months, and you will have good enough knowledge. You will not become an expert, but there is no need for everyone to be an expert. To me, the skill to properly structure software is an essential one. Nevertheless, it is a rare one. Could it be that it is a skill hard to acquire? My point is this: avoid hiring people for knowledge that can be taught, without making sure he/she has what is essential. More specifically, what is that?
Here it is: I value people that write code that are testable, modular, readable and extensible. Without testability, I dare not touch your code. So I would have to throw it all away and start over, risk breaking it while making it testable or just dive in with my eyes closed. Nether is acceptable. With good modularity, the worst case scenario is that modules with unreadable code can be replaced or wrapped with tests. Modularity will also help readability and understanding. If you can understand the interfaces, understanding the internals become less important. Once we get down to internals of a module, readability becomes crucial. Only if I can read it (= understand it), can I maintain and extend it. Extensibility allows your software to meet customer’s needs and survive over time.
In object-orientation, there are design principles that will help you write testable, modular, readable and extensible software (see e.g. these three posts). If someone showed up with a good sense of how to write well-structured software using these design principles, he would certainly be a good hire. And conversely, if someone wasn’t convincing enough on his skills in writing structured software, he would be hard to hire. Regardless of his skills in other areas.
Design patterns help you write good software. How can you master all 23+ of them? You are probably better of focusing on the underlying truth: the design principles.
The book “Design Patterns: Elements of Reusable Object-Oriented Software” is a classic and a must-read (see post). When I first came across the book, I read about all those 23 fantastic patterns. They were all different and applicable in different situations. As a computer scientist, I hate special cases, so I wanted to see some kind underlying truth. At the time, I did not see a pattern (ironic, huh? :).
The book “Implementing Lean Software Development” (see post) makes the following definitions:
- “Principles are underlying truths that don’t change over time […]”
- “[…] practices are the application of principles to a particular situation”
If the design patterns are 23 different applications of some underlying truth, this will make them object-oriented software development practices. Then what are the principles of object-oriented software development?
Here are my favorites among the design principles:
- Decoupling: Strive for loosely coupled designs between objects that interact.
- Open/Closed Principle: Classes should be open for extension, but closed for modification (= classes should be easy to extend without needing to change them).
- Abstraction: Depend on abstractions, do not depend on concrete classes.
- Single Responsibility: A class should have only one reason to change.
- DRY (Don’t Repeat Yourself): Store information in a single place, do not scatter it throughout your code.
To exemplify, let’s look at design patterns that support some of the above principles. Two of the patterns that are closely related to Decoupling is Observer and Factory Method. Observer lets a class notify other classes without knowing about them. Factory Method lets one class create objects without knowing which type they are. Not knowing about each other typically favors a loosely coupled design.
Two of the patterns that support the Open/Closed Principle is Strategy and Template Method. Strategy lets you vary the behavior of an object as if the type was not fixed at compile-time. You determine the behavior by supplying a strategy object e.g. during construction. Template Method will let you implement parts of an algorithm. Typically, you subclass the algorithm and implement specialization in the subclass. Both let you change the behavior without changing the code.
If you adhere to the object-oriented principles above, design patterns come up naturally. I think this is also the best way to learn how to apply design patterns. Instead of actively looking for places where your design patterns could fit, use the principles and the patterns will follow.
Some of the design principles are hard to match with any of the design patterns. For example, Abstraction is typically something expressed in the domain language of the problem you’re trying to solve. Thus, it helps you to write code that relates better to your domain. The same goes for Single Responsibility, since the reason for change is domain specific. If you look at all design principles, it is clear that they are themselves special cases that can be divided into groups of related responsibility. It makes you wonder what is the underlying truth of the design principles?
How do we write good object-oriented programs? Despite the name, it is not by focusing on objects.
Object-oriented (OO) programming presents several concepts to the programmer not present in a procedural language. Given the name “object-orientation”, objects is clearly a central concept. Objects are the basic building blocks of our program and they carry out all the important work of our program. This should be contrasted with procedural programs, where functions are global and data is passed as arguments.
From time to time, you come across OO programs that has a procedural flavor to them. There are few, if any, interfaces and classes are primarily used to organize related functions with data. Admittedly, this is a slight administrative improvement over procedural programming with structs and global functions. The problem is, OO programs like this have the same problems as procedural programs: coupling.
Martin Fowler illustrates the concept very well in his article Design Principles and
Design Patterns (Figure 2-17 on page 13). Assume you have a main function. Main calls three other functions, which in turn calls two other functions. Main now depends on all its descendants! High-level logic should not depend on low-level implementation.
The same applies to the “procedural style OO program” described above. Main might use three objects and call a function in each. Those functions might call two functions on some other classes and so forth. Main will depend on all classes and functions. Before you know it, main depends on your whole system.
The point is, even though objects do all the important work, they do not take us very far when trying to write good, decoupled software. Instead, have a look at Figure 2-18 in the article. Assume that main calls functions on three different interfaces. Main would not know or care which class implements each interface. Main depends only on the interfaces.
Thus, when writing object-oriented software, think primarily about abstractions and interfaces. After that, you can think about objects.