On Good Programming Practices

April 22, 2026 (1mo ago)

This one will be more like a dump of good stuff that you can apply on your day-to-day, compare or even take some of it and apply something of yours on top of it.

Nothing new under the sun though, mostly well-known practices and concepts that I've been reading, seeing and applying with good ROI.

Patterns

Most used: strategy pattern, builder pattern, observer pattern, DI pattern. According to The Primeagen, 95% of software development is the first two and then you add a little bit of last two, more often DI. Source

Basically you prototype something, simple, and then you see where it could and/or went wrong. Interface most things for external interaction and create factories, then do strategy pattern for implementing those interfaces and factories in different classes and objects respectively. All loosely-coupled.

Program to abstractions, i.e to interfaces, which are high-level control layers for certain entities. This layer is not supposed to do anything but delegate the work to the implementation layer, AKA, the platform. Note that we’re not talking about interfaces or abstract classes for a programming language. When talking about applications the abstraction can be represented by a GUI (on desktop, browser) and the implementation could be the underlying OS code or an external API, which the GUI layer calls in response to user interactions.

All these patterns help us to separate the code, make it more readable and extendable. Also, in this process, we incur in less errors for changing large classes, i.e breaking existing working code, having merge conflicts, etc.

Creational Patterns

Factory

Allows us to create different objects from a super class using a sub classes, e.x: Logistics => RoadLogistics, SeaLogistics.

It comes handy when having to work with different but adjacent abstractions (objects) and we want to have the creation logic in one place. It can also serve to return existing objects in store (memory, database, cache).

Could implement builder pattern instead of factory pattern too, depending on the use case: e.x a highly composable object (let client choose what to add), which also is more loosely-coupled: It helps to avoid the rigid coupling and potential side effects that can arise from complex inheritance trees, which often leads to more brittle and difficult-to-manage systems as they scale. 

Abstract Factory

Allows us to create related objects without knowing their exact class, e.x UI components (buttons, modals, etc) corresponding to a certain OS.

Given a certain configuration we choose this or that factory, which uses the methods of the abstract factory, which in turn return abstract object interfaces (Button, Modal, etc) and that’s all the client code sees. Each concrete factory creates its concrete objects. Then we can easily extend this with more factories without breaking client code.

This pattern can be implemented as a Singleton, as also can the configuration object. Use it when a factory is doing more than a single thing (SRP), or you could just create another standalone factory.

Builder

Allows us to create highly composable objects and avoid the abuse of sub classes and inheritance resulting in simple hierarchies and loose coupling. It’s often used when multiple representations of an object are needed by client code, e.x a house, a hamburger, a car. Also to avoid “telescopic constructors” and ugly client code function calls to builder classes. On the other hand, it can increment the code’s complexity by adding builder interface, director classes and concrete product builder classes.

Usually one evolves from simpler patterns such as factory and abstract factory to this one. In the building process one can omit build steps and even create composite trees by calling recursively specific builder methods. The director class lets us take a specific builder and execute a sequence of predefined steps (usually for common products), although the result type to return it’s defined by concrete builders as we can have different types that not match a unique interface.

It can be complex but once we have it adding more products/variants of the same object it’s pretty easy, following the Open/Closed principle for extending what it’s already working without breaking client code. This pattern can be easily implemented with the Bridge pattern by having to separate hierarchies, abstraction and implementation, being independent from each other. This saves us from having to implement several sub classes by splitting large classes.

Singleton

Can be a good thing, but most of the time you don’t need it. Use cases can be when you need your app/system to behave in certain ways/have a certain role/have certain features, and you mix it up with the strategy pattern. For example, it could work with a database configuration instance: it’s always the same, unique and global; on the other hand it could not work with shopping carts as there can be several of them (for each user) and they need to be isolated. Bottomline it lets you create an object with a unique instance that is globally accessible, but not editable (constructor method is private).

Structural Patterns

Adapter

It’s just strategy pattern. You choose to use something else (interface) to fit your use case, e.x you have data in XML format but using a library for analytics that takes in data in JSON format, so you implement an adapter to transform the data format. Use it when you want to use an existing (service) class but its interface it’s not compatible with the rest of your code (client classes), or when you want to reuse several existing sub classes that lack some common functionality that can’t be added to the super class. In this cases the service class are usually 3rd party, legacy or has lots of dependencies. Note that sometimes it’s easier to just change the service class to match the other classes.

  • Object Adapter: client code and service code through the implementation of an adapter interface: client -> adapter (wraps object to match the service) -> service. This is easily extensible by creating new adapters while not breaking the client code.

  • Class Adapter: the adapter inherits interfaces from both objects at the same time. This is only possible in languages that support multiple inheritance such as C++. Here the object is not wrapped and the adaptation happens within the overridden methods

Bridge

Lets us develop two or more related classes independently, i.e, we won’t end up growing exponentially our hierarchies through inheritance. This pattern favors composition over inheritance. For example, if we have Shape and then sub classes Circle and Square, and then we add color classes, we will end up having RedCircle, RedSquare, BlueCircle, BlueSquare and so on growing exponentially.

So this pattern helps us with that by developing “abstractions” and “implementations”, each by their own side (classes), splitting large-would-be classes into separate hierarchies/dimensions. This works by linking a reference object in the abstraction to the implementation’s interface, in this case a color, avoiding inheritance and a consequential large hierarchy. This way we can have red and blue circles and squares, with shapes and colors each by their own side.

Use it when you want to divide and organize a monolithic class that has several variants of some functionality, when you need to extend a class in several orthogonal, independent dimensions, when you need to be able to switch implementations at runtime. It lets us create cross-platform apps and the client code works with high-level abstractions and is not exposed to platform details. But have in mind that you could make more complicated an already highly cohesive class.

You can combine this pattern with Builder: the director class plays the role of the abstraction while different builders act as implementations. This pattern it’s also designed upfront usually.

Facade

Not really a pattern, more like just good programming; e.x: a http request to a server that returns a json, you just see the response but lots of things got done to deliver it. A method that does a cool thing, a system, an app. It’s more like an abstract concept for coding better. Defines a new, simpler interface for an existing object.

Behavioral Patterns

Strategy

A behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class and make their objects interchangeable. Basically it lets you take a big class that does something specific in a lot of different ways and extract all of these algorithms into separate classes called strategies.

The original class must have a field for storing a reference to one of the strategies, through which delegates the work to a linked strategy object instead of executing it on its own. But note that this class is not responsible for choosing an appropriate strategy, instead, the client passes the desired strategy to it. In fact this class does not know much about strategies, it works with them all through the same generic interface, which only exposes a single method for triggering the algorithm encapsulated within the selected strategy. This way you can easily add new strategies or modify existing ones without changing an entire class.

It allows picking and choosing components for your architecture and DI pattern enables it; it’s making the thing into an interface: program below the interface, program at an interface level. This pattern defines a family of interchangeable algorithms (strategies’ methods) and allows selecting one at runtime (specific methods implementations), eliminating conditional logic blocks.

Strategy Interface/Abstract Class: Defines a common contract (method signatures) that all concrete strategies must follow. TypeScript interfaces are ideal for this.

Concrete Strategies: Classes that implement the strategy interface, each providing a unique implementation of the algorithm or behavior. These are often injectable services in NestJS.

Use it when:

  • you have a big class that has a lot of conditional logic, that does the same thing in different forms
  • you want to switch between algorithms/flows at runtime
  • when you have several similar classes that only differ in the way the execute some (the same) behavior
  • to isolate business logic of a class from the implementation details of strategies that may not be as important in the context of that logic

Do not use if you only have a few algorithms that rarely change, since it overcomplicates the code with new classes and interfaces.

This pattern is very similar to Bridge, State and to some degree to Adapter since all of them are based on composition, which is delegating work to another object.

State

Can be considered as an extension of this pattern; both are based on composition: they change the behavior of the context by delegating some work to helper objects. Strategy makes these objects completely independent and unaware of each other. However, State does not restrict dependencies between concrete states, letting them alter the state of the context at will.

Observer

Lets us “monitor” the state of a given object, AKA publisher, and let other objects, AKA subscribers, know about it. This is common for event-driven architectures and this pattern is the core of event-sourcing (tracing sequences of immutable events/state changes that composes the complete history of the system). This way we can know where, how and when objects modified their state, making it useful for highly-audited data (finance, transactions, inventory, etc).

It’s useful when we want to let some classes know about the state of another class and maybe perform some kind of operation. For this usually we create a “publisher” class and have it communicate with “subscriber” classes through a common Subscriber interface which usually has an “update” abstract method. This way we can add more (concrete) subscribers and they will all work if using the common interface. This pattern is similar to Command, Mediator.

This usually goes hand in hand with CQRS architecture pattern where command (writes) are segregated from queries (reads), this we can easily scale up one or the other, depending on our app’s needs. It also allows us to emit a “write” event when we have writes so our reads database gets updated with the new data (there specific databases for writes and reads).

Tip: with event-driven we can count how much writes and reads we have on our app and make a (scale) decision based on that data.

Decorator

Lets us add new behaviors to objects, it enhances them without changing their interface.

Proxy

Allows us to perform certain actions before or after client code has any contact with a given object.

Iterator

You can traverse different collections (data structures) in different fitting ways. Also you hide the collection itself from the client.

Tips

  • A class should do one thing, just like functions, no god-methods or god-classes. Here come interfaces and strategies.

Code Reuse

According to Erich Gamma, three levels of reusability:

  • low-level: classes, interfaces, containers, iterators
  • mid-level: patterns
  • high-level: frameworks

Patterns are the sweet spot because they are solutions to common problems but adaptable and extensible enough to implement in almost any language or context. Classes are too small and would not adapt to many scenarios. On the other hand, frameworks are a high-investment endeavor.

Extensibility

Programs will change. So if we see some parts of the program that could grow over time, it’s better to build with this in mind and program to interfaces and isolate what’s likely to change. Have classes that do one thing so the code is searchable and readable, also if a class grows too big divide it into sub classes, same for interfaces.

Basic Design Principles

  • encapsulate what varies: if something is likely to change over time, isolate it, could be method-wise, class-wise. Encapsulation: binding state and methods together (classes), only object itself has access to internal state
  • program to an interface, not to an implementation: this way you have loosely-coupled components and weaker dependencies between objects. Tip: look for what is used in two or more objects/classes, then build an interface.
  • favor composition over inheritance: this gives us more flexibility and extensibility, and more options for clients during runtime, also we don’t have to implement all the superclass’ methods (possibly we don’t need all)
  • implement polymorphism when you can, static (compile-time) or dynamic (runtime) binding as you see fit. This have a lot to do with interfaces and abstractions as an object can behave differently depending the scenario (methods)

SOLID

SRP (Single Responsibility Principle)

A class/method should do one thing

Open/Closed Classes

Open for extension, closed for modifications: extend a functionality, fix a bug. but don’t break working code for existing clients

Liskov Substitution Principle

In most statically typed languages these are built-in.

  • a sub class methods and their parameters should work with all existing clients of the superclass. Tip: sub class parameters should be the same or more generic than those of the superclass. This is specially important for libraries/apis because you don’t know what object a user /client will pass as argument
  • a sub class return type should be an exact match to the super class or a sub type. E.x: if a client expects a Cat object, the sub class should return a) Cat object or b) BengalCat (sub type, still a Cat so it’s OK)
  • a sub class should not strengthen pre-conditions as it could break previously working objects of the super class
  • a sub class should not weaken post-conditions as it could lead to unknown and unexpected behavior for an existing client invariants of a super class must be preserved
  • a sub class should not change values of private fields of the super class

Interface Segregation

Interfaces should be brief, concrete, not bloated with many methods. Unlike classes, multiple interfaces can be implemented by a class. This is specially true if some class implementing an interface just have no use for many of its methods, this is a clear sign that you should segregate/divide into more than one interface and/or modify the class hierarchy.

Dependency Inversion Principle

High-level classes should not depend on low-level classes, they should depend on abstractions (interfaces) and low-level classes should depend on them too. Early on in a project low-level classes are usually developed first, so then high-level classes tend to depend on them. E.x: database related high-level classes implement low-level class MySqlDB, which can be limiting in the future; instead you should implement a Database interface which MySqlDB and, for example, MongoDB, can extend. This way low-level classes depend on high-level abstractions/interfaces

DRY

The key insight is that DRY should be applied thoughtfully, not religiously. Sometimes duplication is actually cheaper than the wrong abstraction (WET). It's often easier to extract common patterns later when you have 2-4 similar implementations than to guess what the abstraction should look like upfront.

More good stuff that you can check