Unencapsulated code is code that exposes its implementation or references how it does something. This can happen in subtle ways, so we have to be careful to create strong contracts through our method signatures.
Poorly encapsulated code is very difficult to work with. It can quickly sprout new bugs at the slightest change. Lack of encapsulation is one of the most difficult code qualities to deal with because it widens the field of what could possibly go wrong.
People call unencapsulated code “spaghetti code,” but I find it far from appetizing. Code that isn’t well-encapsulated isn’t well-defined, and that’s because the programmer who wrote it didn’t take the time to think about creating a service that could be extendible in the future.
We see tendrils of unencapsulated systems reach everywhere. This creates systems that are highly coupled and very difficult to test at a unit level. Perhaps the worst form of unencapsulated code is a public static global variable. These variables can be changed at any time by anyone, which means they’re unreliable. We can’t write automated tests for them. All sorts of subtle interactions can happen between unencapsulated code and the rest of the system, so both testing and reproducing bugs that happen in the field can be difficult.
Sometimes people think that just because the implementation is exposed doesn’t mean people will abuse that information. In my experience, this is not true. When you expose implementation, people will try to take advantage of it to improve optimization (or whatever). But generally, exposing implementation details is a bad idea.
A lack of encapsulation is the number one cause of the “ripple effect” in software, where one change causes several problems in seemingly unrelated pieces of code. This kind of phantom coupling exists because we make subtle assumptions about how we build software that sometimes turn out not to be true.
Think about creating services that hide their implementation details as much as possible, that accept what the client wants to give us, and returns what the clients want to receive. Think about creating strong contracts for services that remove ambiguity and give informative error messages. In addition to making your data private, have good reasons to offer public getters and setters and keep data scoped to the behavior that needs it.
Languages like Java and C# have four access modifiers: public, private, protected, and package, which are used when no other modifiers are stated. Scope all data declarations and behaviors as tightly as possible. If only the class needs that item, make it private. If it is to be shared among subclasses, then make it protected. If it is needed by other classes in the same package, then leave off the access modifier and the system will assume package protection. You should only make an item public if these other access modifiers are too restrictive.
When code is unencapsulated, business rules are spread out and when the business rules change, code has to be modified in several places, which drives up the cost of change.
I like my objects to be modest. I get embarrassed for them when they expose too much of their implementation. Only expose what you need through your method interfaces and hide everything else.
Unencapsulated code can be a real mess and really hard to work with. Deal with it by extracting classes and methods through refactoring. This can be a lot of work, but it’s worth it.