It’s probably over the top, although I respect the intent.
The quickest way to destroy “velocity” is by introducing dependencies between implementation details with no barriers in code.
The more constraints you have to satisfy when you make changes, the more effort it is to make the changes (provably); and thus, the more time it takes, destroying velocity.
That said, doing this as described is probably overly dramatic; I like the rust model: by default, parents can access child scopes, and children can access their immediate parent. Otherwise you have to explicitly “pub” a symbol even to use it in the same crate, which is the escape hatch for pragmatism over strictness.
It would be lovely if some other languages (js, python) had such a delightful module system, but, they don’t.
With something as fundamentally undisciplined (“flexible”) as js, you need to enforce the rules pragmatically with process and tools like code reviews and linking.
It is worth doing though; this is one of my favourite architectural topics because it’s so easy to totally destroy anyone who tries to argue the point. :)
Do you mean that if the constraints are visible when you change the code, it's better? But "hidden" constraints which will manifest only under specific conditions under runtime are bad.
A constraint is an invariant that must be true before and after the change.
It doesn't matter if they are 'hidden' or 'visible'; if you must maintain the existing behavior, then you have to prove that no constraints are violated by the change you make.
The effort you have to expend is proportional to the number of places where you have invariant that must be maintained.
However.
If you isolate constraints in groups (call them crates, modules, whatever you want), then you can create a region which has no constraints except at the boundary where you interact with a defined API.
Since the volume is always >= the surface, you can prove that having the boundary is strictly <= effort to maintain than not having it.
Concretely:
If you can prove (because it is impossible due to the compiler rules or whatever manual process) that you have no dependencies on the internal functions inside a particular namespace / module / etc. because the only way it is ever possible to call those functions is via a specific public API.
...then you are not constrained in changing / refactoring / removing / whatever you want.
The internal workings have no external dependencies, no callers.
It is therefore less effort to maintain that code; because any change does not require you to go hunting around for someone calling helpers.internal.finance.convertCurrency for it's view model in a completely different domain.
If you want to call that 'hidden' then sure.
I'd probably say 'different view', but if basically yes, the point is that of the entire set of constraints for the system, you can say, today, our work is in this area of the code and we have a much much smaller set of things we have to worry about while we make changes here.
It's not rocket science; you see people doing this all the time; 'It's just a few tests, it can't break the database'.
Correct. The tests are a separate package that strictly the database schema has no dependencies on.
Therefore it is quick and easy and safe to make changes to them.
...
If your project only has two domains; 'tests' and 'non-tests', you literally doing the same thing the OP is suggesting, just at a slightly reduced scale.
In bigger, more sophisticated projects, the number of mini-domains inside a single project is usually > 2; but for exactly the same reasons.
You'll find out that analogies to Euclidean geometry do not hold for software structure in general.
That's a nice heuristic though. Just be wary that if you follow it blindly, you will inevitable optimize things into a counterexample where everything breaks down while your proof still finds it's the optimal.
Static analysis is good, in moderation; dynamic validation is good, in moderation; accepting errors and dealing with them is goo, again, in moderation. All of those things turn bad if you do them out of dogma.
Care to point out your best example on when static code analysis ceases to be good?
I don't think there is possibly any copout to justify away the benefits of static code analysis. Either your code works by complying to the interface, or it doesn't and violates interfaces. The only thing static code analysis does is force you to acknowledge the real interfaces.
Hum... You mean you've never encountered complex interfaces dictated by their types?
I was about to make a Haskell joke, but it's actually Java that is most famous for this. There are lots of "enterprise Hello-Word" published on the internet if you want some done on purpose.
> Hum... You mean you've never encountered complex interfaces dictated by their types?
I asked you what you personally believe is your best example on when static code analysis ceases to be good. I fail to see how anyone's opinion or personal experiences determine your own personal opinion on a subject.
Can you provide any example on how static code analysis can be anything other than a good thing?
> There are lots of "enterprise Hello-Word" published on the internet if you want some done on purpose.
I'm sorry, what does this have to do with static code analysis?
Sure, maybe the metaphor doesn't make perfect sense.
...but, strictly, if you have a module with K public functions and P private functions the maximum possible number of unique external dependencies you have to track on the module is K.
If P > 0, then the 'api surface' of the module is smaller than the total set of functions.
If K = 0, the module has no external dependencies and you can do whatever you want.
I'm going to say with complete confidence that creating 'boxes' in your software where you minimize K creates maintainable software.
> The quickest way to destroy “velocity” is by introducing dependencies between implementation details with no barriers in code.
Nonsense.
The biggest velocity killer is having to deal with the technical debt left behind by those who mindlessly commit changes that turn your software into a big ball of mud.
Ask yourself this very simple question: why did your team felt the need to add these constraints? What classes of problems they were avoiding by preventing specific types of changes to the software architecture from being applied? What problems they experienced earlier that motivated them to ensure they wouldn't experience them again?
And why should your laziness to do things the right way take priority over avoiding making the same mistake?
I think you're conflating the language design stuff with the tooling (packaging, and calling that 'modules') ....to be fair, you're conflating but I'm fanboying python
in any case, the point being there's a distinction between an linker and a compiler but not in interpreted languages. so comparing rust's crates with JS, python (even java) is an instance of apples-to-oranges comparisons
Maintaining boundaries in your code is something you can do in any language, regardless of built-in support in the language or tooling.
Ironically, untyped languages like js and python are particularly prone to the problems from poor unstructured code, because you can't use static type checking to find broken dependencies at compile time.
Can you explain how types would help with code structure? From experience, even with typed TS nothing prevents developers from creating a big ball of mud
The quickest way to destroy “velocity” is by introducing dependencies between implementation details with no barriers in code.
The more constraints you have to satisfy when you make changes, the more effort it is to make the changes (provably); and thus, the more time it takes, destroying velocity.
That said, doing this as described is probably overly dramatic; I like the rust model: by default, parents can access child scopes, and children can access their immediate parent. Otherwise you have to explicitly “pub” a symbol even to use it in the same crate, which is the escape hatch for pragmatism over strictness.
It would be lovely if some other languages (js, python) had such a delightful module system, but, they don’t.
With something as fundamentally undisciplined (“flexible”) as js, you need to enforce the rules pragmatically with process and tools like code reviews and linking.
It is worth doing though; this is one of my favourite architectural topics because it’s so easy to totally destroy anyone who tries to argue the point. :)