Useful Patterns Every Developer Should Know

Patterns for facilitating a software developer’s daily programming

Itchimonji
CP Massive Programming
10 min readMay 3, 2021

--

Patterns for facilitating a software developer’s daily programming

In my opinion, any coding pattern is a vital template a developer can orientate towards when searching for a general and reusable solution for a commonly occurring problem. Having a default language for communicating software issues and solutions when we talk with other developers is also essential. It saves a lot of time if we only call the pattern by its name for our proposed solution instead of describing the whole problem and its fix.

Patterns are also good alternatives for sharing information flows, software architecture design (e.g., UML) with others and, besides, making our code base more testable.

This article includes patterns of three main categories: Refactoring Patterns, Design Patterns, and Working Effectively With Legacy Code Patterns.

Some main patterns

Refactoring Patterns

Refactoring Patterns are a big part of reusable and readable code. The more our application grows, the more we need to reorganize the code base to eliminate confusing, duplicated or unsuitable implementations. It is also a chance to improve the system’s architectural design.

Refactoring is one of my favorite software books written by Martin Fowler. It is worth reading to improve our coding skills.

Decompose Conditional

If-Statements are quickly writable conditions, and every developer implements at least one every day. Because it is so easy to extend them, the complexity can rise very quickly with every additional extension. The problem with long conditions is that they tell us what happens, not why.

The why, however, can be handled straightforwardly by extracting the condition with a function call named after the intention of the condition.

Let me show this in an example.

When we look at the condition in the example above, we may have a presentiment that this if-statement could be extended with upcoming requirements. To avoid combing the entire code base to extend these conditions, we could extract it into a function and name it after the cases’ intentions.

We could break down the condition into subparts if we have clear intentions.

After that, we can clarify our main condition.

At last, we have one place to find and change the condition and its context. Besides, the code block is shrinking, where the condition was written to increase the readability.

Consolidate Conditional Expression

Often we should sum a series of conditional checks into a single one. The advantage of this pattern is it improves readability, controls the result value type and extracts the condition for encapsulation.

In the example above, we could extract the conditions into a separate function call named according to its intention that returns a unified boolean value. After that, we check the result value, here of checkWeatherConditions(…), to return the original function type.

Introduce Special Case

Early in my career, the most commonly occurring condition in TypeScript and Angular was the null checking condition. Other developers have the same habit of making their code more stable. Such a condition is not bad, but when we check the same special value every time in different places in our code, you may have redundant code, and we may not be complying with the DRY principle.

Comb code for null checking conditions

A null checking condition is nothing more than a check for data availability.

In simple words, regarding the null checking condition, a Special Case is to create a special object for the cases when a value or an object can be null so that a default class with expected behavior will be returned.

We will love this pattern if we like object composition and our code is architected by many classes and interfaces.

We can find more about Special Cases and how to refactor Null Checking Conditions in my detailed article.

Design Patterns

Design Patterns are an important resource and base knowledge for every developer — they are very helpful for solving programmatic problems, help with consistent communication with other developers about system design, and are a significant introduction into object composition (besides inheritance) and dependency inversion.

Factory Method (Virtual Constructor)

Creational Patterns are indispensable when abstracting the instantiation process of objects. Viable techniques are object composition and dependency inversion. Of high importance are encapsulation of concrete classes and hiding the creational process. So, Factory Method is an excellent choice for learning and implementing these aspects. Because of its high flexibility and easy integration, it is a good starting point for Creational Patterns.

Object models could change daily in a long-term project or an Enterprise Application. So, we need maintainable source code. One characteristic of maintainable code is finding the right things in the right place. A Factory Method should be placed next to the model it creates. When the model changes, we could adapt the Factory Method as a corresponding part. And in this context, we should have only one place where we create concrete objects and not randomly in a source file because when the object’s constructor changes, we would have to comb the entire code base to find them. To avoid a messy search, we need to encapsulate our objects. Some developers opt for programming by coincidence and let the compiler work for them to point out all error places — what a mess, right?

Let me show an example — the starting point is an interface. I choose some kinds of trees.

Next, we need some concrete classes that implement the chosen interface.

Last but not least, we need to implement the abstract Creator. In the example below, the Factory Method is createObject(). This is an abstract method that subclasses have to override themselves. An AppleTreeCreator returns an AppleTree object. We could extend the abstract base class with more logic that needs the return value of the Factory Method.

Another simple way to encapsulate creational processes in TypeScript is to use literals, as in the following example. There is no abstraction (except for the interface), so maintenance is more complex in larger projects. For some small applications and to avoid overhead, we could start by encapsulating the creational process and add abstraction later with incoming requirements.

Factory Method is a good starting point to maintain the source code for long-term projects regarding creational processes. Also, it trains the feel for encapsulating objects and methods.

Template Method

Behavioral Patterns deal with algorithms and the assignment of responsibilities to objects, describe mutual communication patterns, and measure complex programming sequences. The Template Method is one of few Design Patterns that use inheritance to define the structure of an algorithm in a superclass but allows its subclasses to override specific steps of the algorithm without changing the structure.

I wrote a comprehensive article about that.

Here is a short example of using a Template Method. We can call the generateReport() method in the superclass because it forces every subclass to override methods with the keyword abstract.

A subclass can look like this.

Clients can use the implementation with the following code.

Template Method is beneficial in mastering inheritance in a maintainable way. Michael C. Feathers describes in Working Effectively With Legacy Code that one of the best ways is to use inheritance only with abstract superclasses to maintain source code for a longer time.

Working Effectively With Legacy Code Patterns

Working Effectively With Legacy Code is a wonderful book by Michael C. Feathers, and I highly recommend it when we start to improve the comprehension, readability, and maintenance of our implementations to avoid creating legacy code.

Preserve Signature

“Refactoring is particularly error-prone.” (Michael C. Feathers [Working Effectively With Legacy Code])

To avoid errors through invasive editing, we should write tests. In many systems, we need to refactor first to make the code testable enough for more refactoring.

When we are breaking dependencies for tests, we need to be very careful and should not end up making foolish mistakes. One best practice of extracting methods, or its body is Preserve Signaturecut/copy and paste parameters of old methods to the new method (method signatures).

In this example, a service is not the right location to export data — an object could be a better place. So, we need to extract the method or its body to the new model.

Tests still pass, and we can refactor the design more deeply with the new model.

For some, this pattern is obvious, but in stressful situations, it is excellent to recall this handling, to go tiny refactoring steps, to shrink the error rate.

Break Out Method Object

“A method object is a way of turning a complicated method into an object on its own. The great advantage of this is that it allows we to put values in fields instead of passing them around in parameters. The usual way of using a method object is to create it, fire it up, and then let it die once its duty is done.” (Martin Fowler [Patterns of Enterprise Archtitecture Patterns])

Extract monster method to new class

Shortly explained, a Break Out Method Object is to extract a monster method to a new class for refactoring the behavior to improve comprehension. The new class does not use any instance variables or methods from the original class — it is fully encapsulated. Old parameters can be used as attributes now.

Encapsulating the method into a new class increases readability and maintainability for the affected class.

Simultaneously, we are in a better situation to refactor the monster method and write better tests for this because of the encapsulation.

Conclusion

In this first part of Useful Patterns, we learned Refactoring Patterns for maintaining and reorganizing our source code sustainably. Using them eliminates confusing, duplicated or unsuitable implementations.

Another section of this article focused on Design Patterns that provide foundational knowledge about template solutions for programmatical problems and facilitate consistent communication with other developers about system design.

Finally, Working Effectively With Legacy Code Patterns helps us to refactor old code in tiny steps and makes it more testable for upcoming maintenance.

Thanks for reading! Follow me on Medium, Twitter, or Instagram, or subscribe here on Medium to read more about DevOps, Agile & Development Principles, Angular, and other useful stuff. Happy Coding! :)

Learn More

Resources

--

--

Itchimonji
CP Massive Programming

Freelancer | Site Reliability Engineer (DevOps) / Kubernetes (CKAD) | Full Stack Software Engineer | https://patrick-eichler.com/links