The author talks about a piece of software as if it were a closed system, where disorder would inevitably increase. This is not the case, disorder in software can be decreased by doing work. However, the business requirements will inevitably become more complex over time, and there will come a point where the cost of adaptation of the software is higher than the cost of starting over with a design that better accomodates the current requirements. Inability to adapt is observed in ageing organisms also.
Lehman's 2nd (increasing complexity) law addresses this. He defines an E-type system as one which is embedded in the "real world" and thus both influences and is influenced by it, and then claims that:
"as an E-type system evolves, its complexity increases unless work is done to maintain or reduce it" [1]
There is a cost to everything-private, which is that downstream consumers of your software hate you because now they have to get PRs merged for every little thing that you didn't anticipate. Actually it probably makes the problem worse because we're just going to patch your shit and access the private fields anyway.
The functional programming and especially the data driven schools of thought tackle the same problem more elegantly.
> downstream consumers of your software hate you because now they have to get PRs merged for every little thing that you didn't anticipate
If that's the case then the build system or dependency management tool is too stupid. NPM is, for example, because you can't install most dependencies straight from git repositories. Most npm packages need to be built on the developers machine and only artefacts are uploaded to the npm registry. If you need to patch for example babel (which you depend on through five levels of intermediate dependencies), you're in for a hell.
Haskell, on the other hand, makes it very easy to fork & patch a dependency (even a transient one) and you can use that while upstream figures out how to merge a PR.
The ability to quickly use your PR for a system that was overly private is beneficial, but is still decidedly non-zero in terms of time and effort. The PR has to be written in the first place, for example. And that's before we get into whether upstream even accepts your patch without modifications, or even accepts it at all.
I'm always assuming that upstream will either accept the PR, or explain how to achieve my goal without requiring a change to upstream. Either way, such a pull request should be resolved fairly quickly and not linger around for too long.
Why does this not not scale when doing enterprise java? Is it the 'enterprise', or 'java', or the combination thereof?
Many reasons, cultural, not often on github or developed openly, huge variance in quality, maybe not so agile, huge code drops every 6 months with tons of churn so even tiny patches can add huge overhead to the rebase when you upgrade versions soooo you don't.
I agree, also little or no thought has been given to readonly fields in OOP.
I view objects as a natural outcome of mutability, so in that sense they are a code smell. I've found that using const liberally (and keeping methods free of side effects) significantly reduces the number of variables in each object. Which means that I can make more of those const variables public, which tends towards better reusability/composability.
Honest question, what would prompt you and so many others to assume that the laws of thermodynamics are only applicable to isolated systems? Do you honestly believe that we don’t have a model that is useful for realistic systems? Like thermodynamics is only some pipe dream that. Vet actually planned out because we do not only have isolated systems?
Because actually the laws of thermodynamics are only applicable to isolated systems.
"The second law of thermodynamics states that the total entropy of an isolated system can never decrease over time."
But usually you can build quasi-isolated systems and minimize the external interference so it is negligible (i.e. a car engine). This is hardly the case of software systems though.
> In the attempt to make this move, the first step will be to find all the places in the code-base that make use of this public property and fix those so that they don’t do this anymore. If the tooling around the project is amazing, there will be some static code analysis to use; but most will end up having to grep the entire code-base
Isn't "find all usages" a pretty basic feature in modern IDEs? If you're working in a single self-contained codebase this really isn't very hard to do.
IMO a lot of the teaching around best practices in OO programming doesnt differentiate enough between things that matter in authoring libraries versus self-contained proprietary code. When everyone is committing to the same repo certain types of encapsulation and abstraction aren't always as critical as they're made out to be.
> When everyone is committing to the same repo certain types of encapsulation and abstraction aren't always as critical as they're made out to be.
Until you want to refactor or reuse. We develop a lot of in-house tools for our rather esoteric systems and systems analysis. We start by writing specific solutions, then refactor as we find common elements across tools. Now we have several libraries that can be used by anyone to develop custom solutions to problems because we've factored out common elements into those libraries.
Treating encapsulation and abstraction like they're unimportant would've resulted in a lot of extra work over the years.
It's definitely important. My point is simply questioning whether it's really critical to always get it right from day one. Certain types of refactoring like public -> private really aren't always that hard in a closed system. I've done it many times with a few clicks in my IDE. Of course the difficulty depends on the nature of the change and the type of abstraction you need modify; but to me the difference between public and private is pretty low on the list of things that create big headaches. Especially in Java where unless you're using immutable objects everywhere, hiding something behind a getter that performs no additional logic is often useless.
> Isn't "find all usages" a pretty basic feature in modern IDEs? If you're working in a single self-contained codebase this really isn't very hard to do.
Not all "find all usages" are created equal. Here, the language is very significant. In languages where there is more than one way to use something, this becomes more complicated, with more corner cases to keep in mind. In languages where there is only one way to do something, this is less complicated.
> IMO a lot of the teaching around best practices in OO programming doesnt differentiate enough between things that matter in authoring libraries versus self-contained proprietary code. When everyone is committing to the same repo certain types of encapsulation and abstraction aren't always as critical as they're made out to be.
This isn't a matter of library/proprietary code. This is a matter of scale.
18 comments
[ 1.7 ms ] story [ 47.2 ms ] threadThis could be the difference, but I'm not a physicist.
"as an E-type system evolves, its complexity increases unless work is done to maintain or reduce it" [1]
https://en.wikipedia.org/wiki/Lehman%27s_laws_of_software_ev...
The functional programming and especially the data driven schools of thought tackle the same problem more elegantly.
If that's the case then the build system or dependency management tool is too stupid. NPM is, for example, because you can't install most dependencies straight from git repositories. Most npm packages need to be built on the developers machine and only artefacts are uploaded to the npm registry. If you need to patch for example babel (which you depend on through five levels of intermediate dependencies), you're in for a hell.
Haskell, on the other hand, makes it very easy to fork & patch a dependency (even a transient one) and you can use that while upstream figures out how to merge a PR.
Why does this not not scale when doing enterprise java? Is it the 'enterprise', or 'java', or the combination thereof?
I view objects as a natural outcome of mutability, so in that sense they are a code smell. I've found that using const liberally (and keeping methods free of side effects) significantly reduces the number of variables in each object. Which means that I can make more of those const variables public, which tends towards better reusability/composability.
https://en.wikipedia.org/wiki/Technical_debt
Isn't "find all usages" a pretty basic feature in modern IDEs? If you're working in a single self-contained codebase this really isn't very hard to do.
IMO a lot of the teaching around best practices in OO programming doesnt differentiate enough between things that matter in authoring libraries versus self-contained proprietary code. When everyone is committing to the same repo certain types of encapsulation and abstraction aren't always as critical as they're made out to be.
Until you want to refactor or reuse. We develop a lot of in-house tools for our rather esoteric systems and systems analysis. We start by writing specific solutions, then refactor as we find common elements across tools. Now we have several libraries that can be used by anyone to develop custom solutions to problems because we've factored out common elements into those libraries.
Treating encapsulation and abstraction like they're unimportant would've resulted in a lot of extra work over the years.
Not all "find all usages" are created equal. Here, the language is very significant. In languages where there is more than one way to use something, this becomes more complicated, with more corner cases to keep in mind. In languages where there is only one way to do something, this is less complicated.
> IMO a lot of the teaching around best practices in OO programming doesnt differentiate enough between things that matter in authoring libraries versus self-contained proprietary code. When everyone is committing to the same repo certain types of encapsulation and abstraction aren't always as critical as they're made out to be.
This isn't a matter of library/proprietary code. This is a matter of scale.