Pretty often in software engineering, people end up in situations where there are several pieces of functionality in which the integration between those pieces become harder to reason about. One example is when the developer is reading a piece of functionality that is dependent on a specific scope, and that scope is shared amongst several parts of the system.

There are several principles that indicate how components should behave to be maintainable (SOLID, DRY, Atomic Commits, etc.). It all works, despite the programming paradigm that is being used.

However, there is one that is used quite often, but seems to stay (ironically) restricted to certain communities and languages as standard best practices instead of being defined as a general principle that can be applied almost anywhere:

Restrict the scope of what you do. Increase the scope as the need arises.

This means that, if there is a given component that doesn't need knowledge of an outer scope, one should always start writing by default with a restricted scope, then expose to other scopes as necessary, even if the defaults of the language or framework are different.

If the functionality is not exposed, then it's less likely to be used for a purpose different from the one it was intended to. Also, there is no need to speculate about the conditions in which the functionality might be used in another scope.

Below are some examples of best practices that exist inside specific communities:

Java

In Java (and many other similar languages), there are access modifiers that change the visibility of the members in a class. When starting to write a class or the members of it, there is no need to know if that functionality should be exposed or not. By default, the developer should always use the private modifier, which represents the maximum access restriction in Java. In this case, if the need arises, one can increase the visibility scope for that class or method, so that it can be used outside its initial strict scope.

A code example showing the use of the "private" modifier in Java

Another example is the final modifier. Even though it doesn't care about the visibility of the member, it restricts the binding. If used in a class, it prevents to be subclassed. If used in a variable, it prevents the reference to be changed, ensuring the binding is consistent, even in a multi-thread environment. Because of that, one should always use the final modifier by default in all declarations.

A code example showing the use of the "final" modifier in Java

One could argue that classes should be open for extension but closed for modification, so it might not make sense creating everything with a reduced scope by default in Java. If one wants to make the class extensible, though, then it might be reasonable to increase the strictness a little bit. However, this is a smell that encourages the author to consider using other forms of class extension instead of inheritance (like composition or prototypal inheritance using the prototype pattern).

JavaScript

JavaScript is not (as from May 2016) as robust as Java for handling visibility access. It requires a lot of Duck Typing and inferences. Still, it does have some features that could leverage the Strictness Principle, and they are very well documented in the community.

For example, it's impossible to create in pre-ES2015 something that is block scoped, therefore it’s necessary to use a closure so that everything inside will not be accessible outside the scope. This capability gave birth to a pattern called Revealing Module, which uses an IIFE (Immediately-Invoked Function Expression) as the engine for its execution. In this case, one should restrict the variables by default unless they are required to be exposed into the parent scope.

A code example showing the Revealing Module pattern in JavaScript

In JavaScript, ES2015 and beyond, it's possible to use the const modifier which, besides being block scoped (like let ), it also prevents the variable binding to be changed. Similar to Java's final modifier.

Again, it's recommended to use const by default due to its strict properties, unless there is an additional need for rebinding (like the punch variable shown below).

A code example showing the use of the "const" modifier in JavaScript

Angular

This principle doesn't apply only to languages, but also to frameworks.

Angular has something called isolated scope for directives. By default, all directives will share the scope of the parent, but that can cause unintended consequences because the directive can use by mistake something that is declared in the parent scope and break when moved to somewhere else. If the isolated scope is used by default instead, then the directive will create a new scope that will only be accessible through the attributes that should be passed explicitly from wherever the directive is in the HTML template.

The best practice here is: Use isolated scope for directives by default unless there is a good reason not do to so. Be careful, though, using by default doesn't mean using it blindly.

An HTML example showing a pagination component containing a single "page" component

A code example showing the pagination component controller using an isolated scope to pass some information

As you probably realized, this principle doesn't limit itself to technical issues. It can also be applied in other contexts where there is a definition of scope:

The scope of a project regarding the proposal of new features, which should be restricted by default unless there is a good reason to tackle problems outside that scope.

Discussions that should be restricted to a specific topic unless there is a need to increase the scope of the conversation.

Never start something with low strictness by default, even if your tool, language, framework or context allows that. Always try to be as strict as possible in order to prevent unintended side effects and keep the focus on what is important.

Leverage the strictness. But don’t be blinded by it.