Code Navigation, Design Patterns, and Too Many Functions
Published on 2010-10-12.
When going into extremes in using a specific programming style or a specific design pattern the result is always bad code - even if it looks nice.
In software engineering, a design pattern is a reusable solution to a commonly occurring problem in software design. A design pattern is not a finished design that can be transformed directly into code. It is a description or an idea for how to solve a problem that can be used in many different situations. Object-oriented design patterns typically show relationships and interactions between classes or objects, without specifying the final application classes or objects that are involved.
Sometimes you need to refactor your code and this gives you a great opportunity to consider wether you can benefit from implementing a specific design pattern. Refactoring code means that you change the source code of a program, making it better in some way, but without changing the external function of the program. Some of the advantages of refactoring include improved code readability and reduced complexity to improve the maintainability of the source code, as well as a more expressive internal architecture or object model to improve extensibility. Martin Fowler's book Refactoring: Improving the Design of Existing Code is the canonical reference on the issue.
Refactoring is usually motivated by noticing a code smell (something that indicates a deeper problem). For example the method at hand may be very long, or it may be a near duplicate of another nearby method. Once recognized, such problems can be addressed by refactoring the source code, or transforming it into a new form that behaves the same as before but that no longer "smells".
Some typical code smells are:
- Duplicated code: identical or very similar code exists in more than one location.
- Long code: a method, function, or procedure that has grown too large.
- Feature envy: a class that uses methods of another class excessively.
- Lazy class: a class that does too little.
- Contrived complexity: forced usage of overly complicated design patterns where simpler design would suffice.
Experienced programmers understand the problems with, for example, duplicated code. When you update the code in one place you have to remember to update the code everywhere. Using refactoring, and possibly (but not always) a well known design pattern, can help greatly to eliminate code duplication.
When we try to improve ourselves, and in this case more specifically our code, we sometimes get hung up in the philosophy of a particular pattern or idea and tend to forget to think practical. In other words: We go into the extreme.
As with everything in life we have to try to keep a nice balance.
We should experiment with different ways to solve problems and also gain experience by looking at how other more experienced people solve problems.
The other day I was looking trough the code of a couple of the popular PHP frameworks and it was clear that the programmers was following well known patterns in the design of their code. The code was very readable and it all looked very nice. However, there was one major problem in common with the systems that turned beauty into a beast.
The philosophy of the design patterns and refactoring principles where religious followed to such an extend that the systems actually became cluttered and messy.
One system contained so many class methods that every single bit of code execution was a single method! One method would call upon another method that again would call upon another method that once more would depend upon the return value of another method. All of these many methods where spread around the system in a lot of different files.
Trying to understand the different parts of such a system is like navigating a huge maze with many different paths that all interconnect. It's a nightmare.
Sure, I can reuse a method that performs some kind of calculation, but if I really only need that calculation performed one specific place in the code I might actually benefit from programmering that part in a simple procedural way.
It is beneficial to think about what programming paradigms that will make things more readable and easy to navigate, but it is even more important to consider efficiency.
If the code is well written and well structured it is sometimes more easy to navigate a single file containing one or even several thousand lines of procedural code, than it is to navigate even a maze of small nested class functions.
When code is working from within a method it is possible to test the function in isolation from the rest of the system, and that might be beneficial, but adding yet another method even though no code duplication exists and no return value is needed several places, might make the code more complicated to navigate.
One of the nice things about PHP (and several other programming languages) is that it supports multiple programming paradigms, which makes it possible to implement many different solutions in the same application.
We shouldn't get to caught up in the philosophy or idea behind a specific pattern or solution. Our main concern is to keep the code easy to navigate and as a result easy to maintain and easy to keep secure.
We must also remember that there exist such a thing as an anti-pattern. It is a design pattern that may be commonly used but is ineffective and/or counterproductive in practice.