Why Do You Need To Know Package Cohesion Fundamentals?
View original- current
Summary (TL;DR)
The article explains three package cohesion principles Common Reuse Principle CRP, Common Closure Principle CCP, and Reuse Release Equivalence Principle REP and how to apply them to organize JavaScript files in directories. CRP says files reused together should be in the same package. CCP says files that change together should be together. REP says files that can be released together should be packed together. The author demonstrates refactoring a file structure from organizing by language css, js to organizing by feature login page, header component. This makes code more comprehensible, reduces conflicts, and simplifies delegation and scaling. Tests and related files are moved into feature directories, removing the need for separate src and test directories.
Why Do You Need To Know Package Cohesion Fundamentals?
It's pretty common to find projects where files are separated by the language they're written.
Let's take a very common file structure example for an application called Customer Service:
- The
app.jsandbase-app.cssfiles represent everything that is applicable to the whole application, like base styles, initialization, etc. - The
login-component.jsandlogin-component.cssfiles are responsible for the root component that will render the login page of the app. - The
header-component.cssandheader-component.jsfiles represent the main navigation menu which is a header and presents the company's logo.
In JVM-based languages, a package is a concept that represents a way of organizing classes in packages to make them more manageable. The same way that there are principles for software design, there are also principles for organizing packages.
Even though most papers refer to these principles as for "Object Oriented" applications. They can also be applied to a myriad of other paradigms.
In JavaScript, there's no documented concept for a package system. However, if you consider a directory as the package and a JavaScript file as the class, then you can say the package system is the File System and the classes are the files (check this note).
In a JavaScript project, the File System is the Package System
If the File System can be used as the packaging mechanism, and there are principles to make packages more manageable, you can conclude it's possible to use the same principles to organize files.
Packaging principles are grouped between cohesion and coupling. However, for the purpose of this post, I'll focus on how to refactor the file structure to increase cohesion.
The Common-Reuse Principle (CRP) states that:
The classes in a package are reused together. If you reuse one of the classes in a package, you reuse them all.
— The “Granularity” paper by Bob Martin (Uncle Bob)
In the Customer Service file structure, you’ll never use the login-component.js and the header-component.js JavaScript files together. They can exist on the same page visually, but their functionality will never be used simultaneously for the same purpose.
According to CRP, they should exist in separate packages:
The header-component.css and the login-component.css styles are used together only with their related JavaScript files. Therefore, they should also be packed together:
If it’s not organized like this, then it's hard for you to look at the contents of the package and know which related functionality of the files are afforded by the system to be reused together.
Without thinking about common reuse, it's hard for you to look at the contents of a package and know which related files can be reused together.
The Common-closure principle (CCP) states that:
The classes in a package should be closed together against the same kinds of changes. A change that affects a package affects all the classes in that package.
— The “Granularity” paper by Bob Martin (Uncle Bob)
In the Customer Service file structure, if login-component.js changes and the change is related to the style of the component, then it makes sense to have the style and the JavaScript files packed together.
For example, if the style of a “remember me” checkbox is changed in the login-component.js file, the new style will be changed in the existing login-component.css file. That justifies having the files together in the same package due to the common closure.
It’s not a definitive rule that the classes must always change together.
The Granularity paper says:
[…] such that they almost always change together;
You can choose to change the validation of the login-username-field.js without having to change the CSS styles. That still qualifies the login-username-field.css and login-username-field.js to belong together in the login-page package according to CCP, because they almost always change together:
This principle helps you to know what can happen if one of the files is changed and which parts can be related to it. If it’s not organized like this, you’ll have to inspect a lot of different files in different directories to understand the impact a change can have.
Changes in the JavaScript file may require changing the related CSS styles. Therefore, they share a common closure.
The Reuse/Release Equivalence Principle (REP) states that:
The granule of reuse is the granule of release. Only components that are released through a tracking system can be effectively reused. The granule is the package.
— The “Granularity” paper by Bob Martin (Uncle Bob)
This one is unnecessarily complicated.
It’s essentially saying that whatever can be released together can be packed together. The consumer of the package doesn’t need to care about related files elsewhere. Everything inside the package should be related enough to be able to be consumed separately from the rest of the system.
Despite what it looks like, REP is not applicable only to systems that are intended to be released to the public.
In the Customer Service application, there’s only a single team responsible for maintaining every JavaScript file, CSS file, and tests. However, if you separate the pieces that could be released, then you can group the base-app in a separate package and the tests with each of their related files, not in a separate test directory:
This way you can delegate the development ownership of the packages to another team at any time:
- If they end up working in the same repository, the chances of conflict will be drastically reduced.
- If they end up working in a different repository, then it will be very easy to extract all the contents of the package.
At this point of the refactoring work you don't even need the "src" directory anymore:
REP makes the distribution of responsibilities and scaling much easier.
Besides, you can publish the whole directory as an npm package if someday you want to make it available to the public.
It makes sense to pack together whatever can be released together.
If you look carefully, there's a pattern here.
If you organize your directories by feature, instead of by language or technology, and respect the package cohesion principles, you'll be able to understand what can be reused (CRP), what can be changed (CCP) and what can be released separately as necessary (REP).
That allows the File System to be more comprehensible.
The definition of a "feature" is subjective to the project and the company. It depends if it's a terminology that makes sense within the business domain.
A feature can also be separated by more than a "component feature" level:
- The
header-componentcan be put inside anavigationpackage if it turns out there's a need to have multiples ways to navigate through the website. - The
login-pagecan be put inside anauthenticationpackage if it turns out there are multiple ways for the user to log in.
Semantics and organization matter more than meets the eye. They are essential to ensure cohesion between a group of files that form a package.
The Common-Reuse Principle helps you identify how files are reused together.
The Common Closure Principle helps you identify how files are enclosed together.
The Reuse/Release Equivalence Principle helps you to identify how files are grouped by the possibility of being released.
JavaScript has packages as any other language.
But they are not explicit.
It's your responsibility as a developer to spot them.
See also Why Do You Need To Know Package Coupling Fundamentals?
If you liked this, you might like readplace.com, built for exactly this kind of reading.
Thanks for reading. If you have some feedback, reach out to me on Twitter, Facebook or Github.