[Part 1] - [Part 2] - [Part 3] - [Part 4]
Represent All Concepts
Let's continue our exploration of how the MicroObjects technical practices support the principle of representing all concepts that exist in the code as objects in the code.
In the last post we covered some benefits of applying the practices for this concept.
- Near Zero Bugs
- Fast Delivery
- Less Code
- Less Complexity
Let's continue to explore my thoughts on the benefits of applying these practices to be able to Represent All Concepts in the code.
Why Represent All Concepts (continued)
Composability
The mechanisms that give us less code and less complexity decouple objects in our code. Cohesion is represented as objects; and objects interact. Not just objects; but the contract the objects implement are what we interact with. This decouples our code.
It's a result of these practices that our code is very decoupled.
This decoupling means we can compose objects amazingly easily. This largely stems from a combination of 'No new inline' and 'Composition, Not Inheritance' practices.
Everything goes through a constructor. All objects we interact with are either injectable, or what generates them is injectable. We don't instantiate anything we use in the class logic itself.
Let that sink in a little - All objects we interact with start in the constructor. The logic of our class has no object instantiation. The logic of our class only interacts with existing objects.
All of our classes are entirely composed. If we need a new flavor of a contract, we don't have to change any internal code. Just what's instantiated.
When there's common behavior that just needs different objects to interact with, I start to create objects that I call "Knowledge Classes". These allows me to create a representation of the set of objects for concept they represent. I can use a common class with different collaborators to be the representations of the different concepts in the system.
Sometimes I'll implement these knowledge classes as inheritance classes. I have ways to avoid it; but it complicates the message I'm trying to send about composability
What these knowledge classes look like is very small and simple
public abstract class CommonBehavior{
protected CommonBehavior(IFoo foo, IBar bar)
//Rest of the details ignored
}
public sealed class SpecificKnowledgeClass : CommonBehavior{
public KnowledgeClass():base(new SpecificFoo(), new SpecificBar()){}
}
The implementation of SpecificKnowledgeClass
is complete. Nothing left of there. It just knows what objects are required for the Specific scenario and provides them to the CommonBehavior
.
It's why I call them "Knowledge Classes". They don't DO anything; but they KNOW something. This knowledge represents a concept that exists in our system; we give it a represention, as a class.
We compose our behaviors. We give those compositions representations.
Testability
When we structure our code so that everything is composed, that all objects interacted with are sourced through the constructor - We can test them with relative ease.
We use actual mocks. I like how Uncle Bob describes Mocks vs Fakes in The Little Mocker.
Our practices have set us up to have an easy time to test.
Wait - Test after!?!?
OK. OK. Do TDD. Get the behavior implemented. Apply these practices. The test you wrote becomes a high level test of the system. Do it that way. All the classes you create will continue to have 100% coverage because we're not adding anything new.
TDD works fantastically with these practices.
Back to the main thought - We're set up to test. It's easy. We have a constructor that takes ALL of the objects the class will interact with; we can mock those pretty quickly, easily, and get solid unit tests in place.
When we use TDD and these practices, things are always testable. We never have to refactor to make it testable... it just is. As we refactor, we can add new tests easily. Rarely will we need to change our tests; interface changes don't happen a lot after these practices have been applied.
Flexibility
Code changes.
Code that is flexible is code we can use in many situations.
Code that is flexible through complexity isn't maintainable and won't persist through changes. We can't have code that adds complexity. Complex code isn't maintainable code. We want maintainability. A function that's flexible because it has a high cyclomatic complexity is code that's inflexible.
We need flexibility through all of our code and systems.
The flexibility from these practices and representing all concepts is the flexibility to add new concepts quickly; and change concepts quickly. The flexibility doesn't come from creating a god class or monster method that enables the user to flag exactly what they want; it's through very small classes that ONLY do exactly what the consumer wants. Only what the concept needs.
Our system becomes flexible, but every representation itself is Rigid. There's no flexibility. It does what the concept represents and allows for nothing else. It's very interesting that a system composed, practically entirely, of inflexibile components gives us the flexiblity to move quickly. To change quickly. Building by representations creates classes that have a single responsibility. They represent one thing, one concept of our system. The flexibility is that they concepts are truely reusable. Just like reuse enables quick delivery, reuse enables high flexibility. We can plug in different concepts and get different behaviors. We have the flexibility to use existing concepts with new concepts for new behaviors.
Individual inflexibility forces us to have a system we can compose for extreme flexibility.
Parting thoughts
I like this. It's like all the pieces of the puzzle finally organized themselves enough that I saw the bigger picture.
I've said the concept before, frequently with a few teams. It's never been a bigger picture though. Always 'one of the practices... ish'.
I've had to hold to the practices because they are what made my code amazing, powerful, bug free.
Now; I think now I've realized a better concept. There are probably a lot of ways to get there; I expect some more effectively than others. What I have with the idea of "Represent All Concepts" is something applicable across all domains. All languages. All styles.
I've found the generalization of the practices. Different domains will acomplish it differently, but I expect it's the generalization I can take with me to develop great code anywhere I go.
If you have a representation for every concept that exists in your code, you will produce value for the customer faster with fewer defects.
That's my theory... hypothesis? - Whatever - That's what I'm expecting to see.