This article is just highlighting the tip of the iceberg from the original book. Please read the book for better detail and examples given by the author.
Coupling is the enemy of change because it links together things that must change in parallel.
Decoupled Code is Easier To Change
Here are some symptoms of coupling:
- Wacky dependencies between unrelated modules or libraries
- "Simple" change to one module break stuff elsewhere in the system
- Developers are afraid to change code because they aren't sure what might be affected
- Meetings where everyone has to attend because no one is sure who will be affected by a change
One way to look at this is to think about responsibilities. The fix for that is to apply something we call:
Tell, don't Ask
The principle says that you shouldn't make decisions based on the internal state of an object and then update the object.
The book also recommends us to not chain method calls, unless the things you're chaining are really unlikely to change. But in practice, anything in your application should be considered likely to change.
Another coupling issue is also due to globalization, as globally accessible data is an insidious source of coupling between application components. When you make code reusable, you give it clean interfaces, decoupling it from the rest of your code.
If you have a singleton with a bunch of exported instance variables, it's still just global data, with a longer name. You can still make a singleton by hiding all the data behind methods. For example, instead of
Config.log_level, it could be
2. Juggling the Real World
We'll start off with the concept of an event.
Whatever the source, if we write applications that respond to events, and adjust what they do based on those events, those applications will work better in the real world.
These are the recommended four strategies:
a. Finite State Machine (FSM)
A state machine is basically just a specification of how to handle events. The neat thing about FSM is that we can express them purely as data.
State machines are underused by developers, and we'd like to encourage you to look for opportunities to apply them.
b. The Observer Pattern
In the observer pattern, we have a source of events, called the observable and a list of clients, the observers. who are interested in those events.
The observer/observable pattern has been used for decades, and it has served us well. But the observer pattern has a problem: because each of the observers has to register with the observable, it introduces coupling. This is solved by the next strategy, Publish/Subscribe
c. Publish/Subscribe (Pubsub)
Pubsub generalizes the observer pattern, at the same time solving the problems of coupling and performance.
The downside is that it can be hard to see what is going on in a system that uses pubsub heavily since you can't look at a publisher and immediately see which subscribers are involved with a particular message.
d. Reactive Programming, Streams, and Events
It's clear that events can also be used to trigger reactions in code, but it isn't necessarily easy to plumb them in. That's where streams come in.
Event streams unify synchronous and asynchronous processing behind a common, convenient API.
3. Transforming Programming
We need to get back to thinking of programs as being something that transforms inputs into outputs.
Programming is about code, but Programs are about Data
Sometimes the easiest way to find the transformations is to start with the requirement and determine its inputs and outputs.
If you're familiar with |> operator which sometimes called a forward pipe or just a pipe, then using it means that you're automatically thinking in terms of transforming data.
If your background is OOP, then your reflexes demand that you hide data, encapsulating it inside objects. This introduces a lot of coupling, and it's a big reason that OO systems can be hard to change.
Don't Hoard State; Pass It Around
To manage Error Handling during the state transformations, there are two basic ways of writing the code: you can handle checking for errors inside your transformations or outside them.
Thinking of code as a series of (nested) transformations can be a liberating approach to programming.
4. Inheritance Tax
If you use inheritance, stop! It probably isn't what you want to do.
Inheritance is coupling. Not only the child class coupled to the parent, the parent's parent, and so on, but the code that uses the child is also coupled to all the ancestors.
Here are the suggestions that can help you to never need to use inheritance again:
a. Interfaces and Protocols
Using interfaces create no code since it simply says that any class that implements the interface must implement the methods defined, but doesn't dictate how the code should be written.
Interfaces and protocols give us polymorphism without inheritance.
By using delegation, we can split the class methods to not rely on the parent class API. We can either write the method directly in the class or take a step further to create another class that delegates the method that is related to the class.
c. Mixins, Traits, Categories, Protocol Extensions
The basic idea of mixins is simple: we want to be able to extend classes and objects with new functionality without using inheritance.
The implementation of mixins can vary between languages. The important thing is the capability that all these implementations have: merging functionality between existing things and new things.
When code relies on values that may change after the application has gone live, keep those values external to the app.
Parameterize Your App using External Configuration
Some things you will probably want to put in configuration data:
- Credentials for external services
- Port, IP address, machine, cluster names
- Environment specific validation parameters
- License keys
To access the configuration, the book suggests wrapping the configuration information behind a (thin) API. This decouples your code from the details of the representation of the configuration.
The benefits of storing configuration behind a service API:
- Multiple applications can share configuration information
- Configuration changes can be made globally
- Configuration data can be maintained via a specialized UI
- Configuration data becomes dynamic
That last point is critical as we move toward highly available applications. The idea that we should have to stop and restart an application to change a single parameter is hopelessly out of touch with modern realities. When configuration values change, there's no need to rebuild the code.
What's your experience to keep your code easy to change, and configurable? Please share your journey and I'll be happy to respond. Thank you!
Oldest comments (0)