The catalogue of best design patterns for OOP is a relatively well defined set of rules, and listing the dos and don’ts is a really simple task compared to actually following those guidelines, and implementing them. Particularly if what you get to work with is an existing spaghetti code project, left after a dev who just got fired because he couldn’t maintain it any more. It also is often the case of patches and changes done quickly under time pressure, which turn out to be more and more problematic over time. Full refractoring of such duct tape solutions usually takes time, and it is not always the option to get everything right at a single moment. What I notice in such cases is that while trying to improve the overall design, I often end up braking design rules – and it actually is a sign of positive changes.
The very basic example is ‘god classes’ – overall defined as classes which do too much – in some cases spread over thousands of lines, handling numerous tasks and acting as one big container of functions. Such classes are never a target, but if what you had before, was just a group of functions or maybe code executed line by line, then you are getting a great advance by splitting it into functions, and encapsulating it into at least some very basic container. This example is quite obvious and it often is the first step on the way to switch to OOP.
Calling ‘this’ is another example. A nice article here describes why self referencing a class is a problem. Yes – it’s not a desired pattern, but it also is the first step to replace copy pasted code and introduce any reusability. It is also a good choice when working with an overgrown structure, with nested loops and conditions. Breaking such a structure into several functions allows defining smaller scopes of local function variables. Defined function names also serve as some basic documentation making the development a lot easier to work with. Worrying about self referencing before code readability is resolved is premature in my opinion.
Another rule I often break without much worry is having too many constructor parameters. Usually this comes after getting to work with a god class having no constructor parameters at all. Since the class does a lot of work, it often means quickly ending up with a lot of services to do the work. I find having any inversion of control is a great step (even if it means 10 constructor parameters), over having none. This leads to god class shrinking in size in terms of line count. In the end it is making it a lot easier to recognize areas which can be separated and this will lead to deconstructing the overgrown class we started with.
I do not recommend breaking design patterns, when they can be preserved, but coding is often handling situations far from ideal and not planned. I find it is a process of compromise. Understanding which rules are worth sacrificing in a given scenario is important.
One of the larger projects I have worked on, started as a request to develop a simple one file script – no classes, no templates, no frameworks. Sometimes success only comes after we verify [with some surprise] that an idea works and is actually brilliant and should be given a lot more attention than initially intended. In such cases the design patterns we want to implement will change over time.
Sometimes I wonder how unique is this experience to freelance development, where specific development processes are not followed so rigorously. Can projects be really planed with the precision which allows to avoid constantly adapting to changing expectations and environment?
I find OOP a blessing which certainly helps to achieve a large amount of flexibility when requirements change, but maintaining an OOP project still means adapting to changing expectations of the application. By this I mean any functionality may grow in complexity in an unexpected way, and replacing an implementation of Foo or Bar will not be enough. Trying to stick to best design patterns too strongly may at some point lead to a situation where resolving existing design issues will be very hard. This isn’t really about breaking rules, but about a progress of giving priority to resolving the most serious problems and ignoring some minor code smells.