µObjects: Never see the concrete
Maintainability by never using concrete implementations.
Our goal as software developers is to produce code that can be easily maintained. This is frequently a challenge; as writing good code is hard.
µObjects is a way of Object Oriented Programming that I've found gives us this. There are a number of ways that µObjects improve the maintainability of code; but how do we create the objects to get these wins? There's a number of guidelines in place for creating the µObjects that give you these. Let's dive into always using interfaces and never using static.
Always use interface
Always program to an interface. I've read this for a long time. I've never found the value in doing it - because I was programming wrong.
I'll call procedural programming wrong for Object Oriented Programming. It's wrong. It defeats so many of the best practices; and always program to an interface is one of them.
When we instantiate an object in the method we're using it; an interface is all but worthless. Yes; for passing to another method; but it also defeats one of the key aspects of always programming to an interface - never have public methods that aren't defined in an interface. If you call a method it /must/ be from an interface.
The biggest road block I had originally was the 1:1 nature of interfaces to classes. It was bloat and a waste of time; and I was wrong. For the style I did before; sure - for good code, nope.
When working with microObjects; if you have a 1:1 interface, that's ok. It's actually pretty expected.
A MASSIVE caveat to this is that if you see similar definitions floating about; see if they are duplicate. Using the "correct" definition of duplication - Changes for the same reason.
If the interfaces would be updated for the same reason then make them one. If they just have the same signature; then not the same and do not combine them.
I accept that 1:1 is hard to stomach, but when writing tests, it's 100% required to create the fakes. Fakes are another topic; but use fakes, not mocks.
microObjects encourage a lot of refactoring. Creation, deletion, and renaming objects should be a frequent occurrence while developing. By using an interface you are decoupling your refactor from the consumers. Those classes won't have to change because something else changed. The limited impact from changes due to the interface decoupling allows cleaner code commits and, more importantly, less fragile tests.
Think of the tests and always use an interface.
Don't use static classes or methods
There's a lot of talk about not using static methods or classes, and I could never find a way out of using them. Two big reasons; I also didn't know how to stop using utility classes and the system and libraries are LITTERED with static methods. This makes it hard to be disciplined about not creating static methods yourself. I developed code like this for nearly two decades. When I finally got it all clicking in my head; static methods are evil. They are the first part piece refactored out when I'm breaking legacy code into microObjects. It's basically a wrapper for the static method; but it has no dependencies and I can fake it's behavior. Otherwise I'd have to construct data to be acted on and produce the result I want. What if I can't? What then? As a microObject you'll make a fake and produce the behavior you want.
Don't create static classes or methods in your code. If an object has no state, it has no reason to exist. Static objects have no state. As an object, they have no behavior.
Statics are global values and functions. Global things are bad practice. It tightly couples you to an implementation that who knows who else is accessing; or what side effects could be.
It's a shame that the system and libraries do so much as static methods. It does have one fantastic side effect, wrapping static usages in a class sets up the code to avoid Asymmetric Marriage. It prevents software from becoming firmware. Or, as I've seen multiple projects do - have hardware pieces. When you're unable to upgrade because of poorly written code; it's not firm; it's hard.
There's no clean place to correct specific system/library behavior when it's used everywhere. Isolate dependency. Use a library in a single location; that has an interface. Make system calls from one object. Any tweaks or system.changes are then in a single place.
A single place to change for a single reason.