DEV Community

Cover image for Why Closed Software Is Better Than Open
Fagner Brack
Fagner Brack

Posted on • Edited on • Originally published at fagnerbrack.com

1

Why Closed Software Is Better Than Open

Restrict the scope of your software by default. Increase the scope as the need arises.


When you build software, you may end up in situations where the integration between several components becomes significantly hard to understand. Say you're reasoning about a component that is dependent on a set of variables or functions, and you share those variables or functions between other elements in the same system.

Several principles help you create software that humans can understand (SOLID, DRY, Atomic Commits, etc.). Those principles are practical regardless of the programming paradigm or language you use. However, there is one principle many communities follow, and you can apply it anywhere regardless of the paradigm or programming language:

Restrict the scope by default. Increase the scope as the need arises.

If there is a component — be it a variable, function, class — that doesn't need knowledge of an outer scope, you should start writing it with a restricted scope first. Later, you expose to other scopes as necessary.

If you don't expose the component, then it's unlikely a developer will try to "reuse" for a purpose different than the one you intended. Also, you won't have to think about the requirements that don't exist.

Think of the "Principle of least privilege" applied to the process of software development and design.

Java

In Java, there are access modifiers you can use to change the visibility of the members in a class. When you start to write a class or its members, you shouldn't spend time thinking if that functionality should be exposed or not. By default, use the private modifier, which represents the maximum access restriction. If there is a legitimate need later, you increase the visibility scope for that class or member and use it outside its strict initial scope.

class Main {
// This method is not
// final because it is
// the main entry point
public static void main(String[] args) {
Fighter john = new Fighter();
Fighter johnny = new Fighter();
john.punch(johnny);
// These attributes are
// hidden. We don't need
// them now:
// john.skills;
// johnny.skills;
}
}
// Most here is
// private by default
// It should only be
// changed as necessary
class Fighter {
private SkillSet skills = new SkillSet();
// This is public
// because it's the
// only thing we
// want to expose
public Damage punch(Fighter target) {
System.out.println("Punched");
return new Damage();
}
private class Damage {
// Intentionally ignored
}
private class SkillSet {
// Intentionally ignored
}
}

Another example is the final modifier. Although it's not concerned with visibility, it restricts the binding. If you use it in a class, it prevents subclassing. Likewise, if you use it in a variable, it prevents the reference to change, ensuring the binding remains consistent, even in a multi-threaded environment.

By default, use the final modifier in all declarations. If there is a legitimate need later, you increase the visibility scope for that class or member and use it outside its strict initial scope.

final class Main {
// This method is not
// final because it is
// the main entry point
public static void main(String[] args) {
Fighter john = new Fighter();
Fighter johnny = new Fighter();
john.punch(johnny);
}
}
// Most here is
// final by default
// It should only be
// changed as necessary
final class Fighter {
private final SkillSet skills = new SkillSet();
public final Damage punch(Fighter target) {
System.out.println("Punched");
return new Damage();
}
private final class Damage {
// Intentionally ignored
}
private final class SkillSet {
// Intentionally ignored
}
}

It looks like there is a conflict between the Strictness Principle and the Open-closed Principle when you think about Strictness in the context of inheritance. Does it make sense to create everything with a restricted scope if subclasses require you to change the code instead of extending it?

If you want to subclass, it's probably part of the requirements, not speculation; therefore, it's better to design the class to be extensible from day one. Strictness should be the minimum necessary to achieve that extension, not more. Be aware, though, subclassing through classical inheritance is a code smell. You should consider other forms of class extensions instead of inheritance, such as composition or prototypal inheritance.

JavaScript

As of May 2016, JavaScript is not as robust as Java for handling visibility access. It requires Duck Typing and lexical closure techniques. Still, it does have some features that could leverage the Strictness Principle, and you can find that knowledge within the community.

For example, it's impossible to create something block-scoped in pre-ES2015; therefore, it's necessary to use a closure so that everything inside won't be accessible outside. The ability to control access through closures gave birth to the Revealing Module pattern (by Addy Osmani), which uses an IIFE (Immediately-Invoked Function Expression — by Ben Alman) as its execution engine. When using this pattern, you restrict the variables by default unless required by the parent scope.

var punch = (function() {
var innerPrepare = "Prepared for it...";
var innerAction = "then delivered the punch!"
return function() {
return innerPrepare +
" and " + innerAction;
};
}());
// The variables
// "innerPrepare"
// and "innerAction"
// are not exposed
console.log(
"This is the only thing " +
"that is exposed: " +
punch()
);
// Print:
// This is the only thing that is exposed: Prepared for it... and then delivered the punch!

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

Again, Mathias Bynens recommends using const by default due to its strict properties unless there is an additional need for rebinding (like the punch variable below on line 6).

{
let punch;
{
const innerPrepare = "Prepared for it...";
const innerAction = "then delivered the punch!"
punch = () =>
innerPrepare +
" and " +
innerAction;
}
// The variables
// "innerPrepare"
// and "innerAction"
// are not exposed
console.log(
"This is the only thing " +
"that is exposed: " +
punch()
);
}
// Print:
// This is the only thing that is exposed: Prepared for it... and then delivered the punch!

Angular

The Strictness Principle doesn't apply only to languages but also to frameworks.

Angular 1 has something called isolated scope for directives. By default, all directives will share the parent's scope, but that can cause unintended consequences. If you declare elements in the parent scope, the system will break when you move the directive elsewhere. If the isolated scope is used by default instead, then the directive will create a new scope that will only access the attributes you pass when you use the directive in the HTML template.

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

...
<pagination>
<page current="1"></page>
</pagination>
...
...
return {
restrict: "E",
scope: {
current: "="
},
controller: function($scope) {
// Value: 1
$scope.current;
// Value: undefined
$scope.anythingElse;
}
};
...

The Strictness Principle is not limited to programming language syntax. You can also apply it to other domains where there is a definition of scope:

  • The scope of a project and new features. You restrict the new features within a given area unless there is a good reason to tackle the same problems in other areas.
  • Discussions. Restrict discussions to a specific topic unless there is a legitimate need to increase the conversation's scope.
  • Test-Driven Development. Restrict the scope of your transformations according to the Transformation Priority Premise. Increase the scope of the transformations as the tests drive you to do so.

Avoid building something accessible by many different parts of the system unnecessarily, even if your tool, language, or framework allows that. Try to be as strict as possible to:

  • Prevent unintended side effects
  • Keep the focus on what is important
  • Reduce the cost of change in your software

Strictness is an important principle to control Software Entropy.

Leverage the Strictness, but don't be blinded by it.


Thanks for reading. If you have some feedback, reach out to me on Twitter, Facebook or Github.

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Instrument, monitor, fix: a hands-on debugging session

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️