Some key nuggets of knowledge toward writing good code are either not taught in school or are mentioned as esoteric theoretical knowledge and most people don’t realize how practical and important they are for competent, everyday software development work. Building software from components, assigning responsibilities to components, deliberately deciding how state is stored, coupling, cohesion, and cyclomatic complexity are essential elements of good software practice.
This the third article in the series on Starting in a Software Development Career.
When you write code you are solving problems: small ones, big ones, one problem after another. Problems you need to solve tend to be too big to solve with just a few lines of code. You have to take most problems apart, and identify smaller problems that make up the big problem (small ones that you can solve). These smaller problems are the components that you identify. To be successful at writing software that solves big problems, you have to be good at writing software components that solve a part of the big problem. Building software from components is the first critical skill to master for a software developer.
As you are taking things apart, identify which part of your code will do what. In other words, assign responsibilities to parts of your code (#1). A good component has clear responsibilities and collaborators to get its job done. Of course, as you are taking the problem apart, you must keep in mind that you will have to reassemble the parts in order to make the system work.
Each part of the software has to be responsible for doing some work, and you have to decide what that work is. Each line of code, each function has to be responsible for something, otherwise why would you include that line of code in the system? Assigning responsibilities to parts are possibly the most important decisions that you make during the design of the software system.
But why would you want to assign responsibilities to software parts? The short answer is to cope with complexity. This allows you manage the complexity of not having to think about how the part is implemented. As you are assembling the parts, what matters is the part’s interface and its responsibilities.
A subset of important decisions as part of the responsibility assignment are deciding which part of the system should hold on to state. State is a term that collectively refers to any data that the software holds. This state can be short-lived or long lived. Sprinkling the state across a large swath of the system will make it difficult to reason about the behavior of the system.
A good strategy is to make a large portion of the system stateless, sometimes referred to as purely functional. Make sure that this part of the system doesn’t have any side effects, in other words each function only operates on its input parameters and returns its results for others to use.
After graduating from college, like many others, you probably thought that most of what you were taught will not be all that useful during your “real” work. Interestingly enough—if you are a computer science graduate who develops software—most of the things you learned will come in handy at some point in your career, provided you know when and how to apply them. Here are three esoteric sounding things that are present every working day, even if you don’t recognize them: coupling, its close relative, cohesion, and their friend, cyclomatic complexity.
As you are deciding on the responsibilities and where to store state, you need to pay attention to coupling, cohesion, and cyclomatic complexity. Coupling is a connection or dependency between two software parts and it’s a measure of that relationship. Coupling exists, because it must, in every software system. Without coupling, or in other words, without being connected to or dependent on one-another, one function could not call another function.
A certain amount of coupling is necessary for a system to do its job. The problem appears when in a system there is too much coupling. For most cases it is best to have as little coupling as you can get away with. As systems evolve, the amount of coupling can become troubling. When there is too much of coupling it becomes difficult to change the system without creating undesirable changes to parts of the system that you didn’t intend to disturb.
While coupling looks at the relationship between parts of the software system, cohesion looks at what’s happening inside a software part. Specifically, cohesion describes how strongly the responsibilities of a software part (be it a function, class, component, module, subsystem, or even system) are related to one another, and how consistent those responsibilities are.
A software part is called cohesive if the software part’s responsibilities are focused around a single purpose and form a meaningful unit. Why is cohesion important? If we keep related things together, then we can reduce the churn caused by changes in the system. If you ensure that the software parts you write are cohesive, meaning that the related responsibilities are kept together, then as a result they don’t have to talk to too many components, thus they will have lower coupling.
So far we looked at a software part and said that it’s cohesive if its responsibilities are related to each other, and it has low coupling if it doesn’t have to talk to a lot of other parts to get its job done. Next let’s see how well it gets the work done. The formal definition of cyclomatic complexity is that it measures the number of linearly independent paths through a function or procedure.
Empirically you can get a feel for cyclomatic complexity by looking at the function. When you see complex conditional series of if .. else if .. else if, if .. and you get the feeling that this looks pretty hairy, then cyclomatic complexity is probably out of whack. There are tools that measure the cyclomatic complexity number (CCN) of each function in your code. If the CCN is greater than 10 for a function, then there is a good chance that it’s difficult to comprehend that function and you should consider rewriting it.
By paying close attention to the assignment of responsibilities to components, state, coupling, cohesion, and cyclomatic complexity as you are writing software, you can make your code much easier to design, write, and read. Keep in mind that you are the primary reader of your code, since you have to go over it many times before its complete. The easier you make it to comprehend, the more likely that you will complete it faster and with fewer defects.
- Larman, Craig. Applying UML and Patterns, An Introduction to Object-Oriented Analysis and Design and Iterative Development. Prentice Hall PTR. Upper Saddle River, NJ. 2005.