🏠 Modularization

By Robert Laing

  1. Separation of Concerns
  2. <script type=“module” src=“main.js”></script>
  3. A module’s name is its filename without the .js extension
  4. A module only exports one thing: its name
  5. Why the default keyword is needed
  6. Why the export object is frozen
  7. JSDoc for modules
  8. Test-driven development
  9. Loose Coupling
  10. High Cohesion

Successful design is based on a principle known since the days of Julius Caesar: Divide and conquer. — Edward Yourdon and Larry L. Constantine, Structured Design

Separation of Concerns

HTML, CSS, and JavaScript are complementary languages used in the development of web pages and websites. HTML is mainly used for organization of webpage content, CSS is used for definition of content presentation style, and JavaScript defines how the content interacts and behaves with the user. — Wikipedia entry on Separation of Concerns

The Wikipedia entry for Separation of Concerns lists Subject-oriented programming and Aspect-oriented programming — created by Gregor Kiczales who’s MooC based on the free online textbook How To Design Programs I discuss further in documentation is fantastic — among the many methodologies developed to split software projects into manageable parts.

TL:DR There’s a huge amount of synonymous jargon from different schools on how best to split a software project into directories and files.

Over the centuries the Indians developed sign language for communicating phenomena of interest. Programmers from different tribes could use one that doesn’t require them to carry a blackboard on their ponies. — Perlism #70

Even as a sole developer without the problems of avoiding a large team of coders tripping over each other, organizing a large project into bite-sized parts improves the chances of getting it done much higher.

<script type=“module” src=“main.js”></script>

As per JavaScript tradition, there are confusingly many deprecated ways of doing modules and the ECMAScript standard is relatively recent.

To be allowed to import and export modules, the main client script needs to be declared in the HTML file like so:

<script type="module" src="main.js"></script>

That module clients have type="module" confused me, but that’s the convention.

You can only use import and export statements inside modules, not regular scripts. An error will be thrown if your <script> element doesn’t have the type=“module” attribute and attempts to import other modules. — Applying the module to your HTML

A module’s name is its filename without the .js extension

The convention I’ve adopted for JavaScript comes from dabbling with Erlang where a module’s name is the same as its filename without the extension. Golang has adopted the same convention.

A module is just a file. One script is one module. As simple as that. — javascript.info

The key thing is the module provides the client with a namespace with dot-qualified names of its functions, unless the module is a single function in which case the module and function have same name. Modules with several functions are object literals, but ones that can’t be translated to JSON

A module only exports one thing: its name

A module should export wun thing, typically a function or an object full of functions. The exportation may be consumed by several other modules, so it is a good idea to freeze the exportation to prevent accidents or cross contamination. An exportation is an interface. Interfaces should be simple and clean. — Douglas Crockford in How JavaScript Works

While the top of a module file can have any number of imports, it ends with a solitary export that looks like so:

export default Object.freeze(moduleName);

Why the default keyword is needed

Using export default... simplifies the syntax of the module’s client by allowing a default import.

import game from "./modules/game.js";

It is really unfortunate that that default word is there stinking things up. — Douglas Crockford, How JavaScript Works

Settling on only the “good parts” (default import for modules) we can ignore named import which requires curly braces, namespace import, side effect import…

Why the export object is frozen

If instead of ending with

export default Object.freeze(moduleName);

I end with

export default moduleName

jslint complains

1. Expected 'Object.freeze('. All export values should be frozen.

Object.freeze(object) can take an object and freeze it, making it immutable… Immutable objects have excellent security properties. This is important because current industry practices encourage the installation of untrustworthy code into our systems. — Douglas Crockford, How JavaScript Works

JSDoc for modules

How To Design Programs encourages following the following six steps when developing software:

  1. From Problem Analysis to Data Definitions
  2. Signature, Purpose Statement, Header
  3. Functional Examples
  4. Function Template
  5. Function Definition
  6. Testing

There’s some similarity with Agile programing in that both stress [test-driven development]https://agilealliance.org/glossary/tdd/). My critique of Agile is I’ve heard its salespeople say stuff like “Computers don’t read comments, people don’t read comments, so why write comments?”.

My view is structured comments, using JSDoc here, are essential and How To Design Programs teaches how do do that using its “Signature, Purpose Statement, Header” template

JSDoc has a @module tag which is a more specific form of namepath.

Module files start with structured comments looking like this:

/**
 * Mutators of state, some needing dragObject to get next state
 * @module modules/game
 */

Any string can be used in the module name declaration, and the convention seems to be to include any subdirectory, in my case modules, and then the filename without a .js suffix.

JSDoc separates modules and namespaces

Because of its Java influence, JSDoc calls functions declared in module files as “methods” and separates them into static (aka public) and inner (aka private).

My public functions (those exported by agglutinating them to the module namespace) are written as function expressions, ie

/**
 * Example of an exported function
 * @function module:modules/modulename.myFunc
 * @param {Object} state - current state
 * @param {Object} dragObject - move to get next state
 */
modulename.myFunc = function (state, dragObject) {
  ...
};

while private functions use function declaration syntax:

/**
 * Example of auxiliary function local to the module
 * @function myFunc
 * @param {Object} state - current state
 * @param {Object} dragObject - move to get next state
 */
function myFunc(state, dragObject) {
  ...
}

What goes between the curly brackets use the same syntax, but function expressions need a closing semicolons while declarations don’t.

Test-driven development

Something I’ve found tricky is testing private auxiliary functions used inside modules. A hack I developed was commenting out the import and export statements during the early stages and loading them as “normal” JavaScript scripts into Jasmine’s HTML page. I’ve found it simpler to just tag everything on to the module name initially, editing them to be private late if desired.

An example of what Jasmine’s default example calls SpecRunner.html, which I use as a template for modulename.html with moduleSpec.js, looks like this:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Card Module Tests</title>
  <link rel="shortcut icon" type="image/png" href="lib/jasmine-6.3.0/jasmine_favicon.png">
  <link rel="stylesheet" href="lib/jasmine-6.3.0/jasmine.css">
  <script src="lib/jasmine-6.3.0/jasmine.js"></script>
  <script src="lib/jasmine-6.3.0/jasmine-html.js"></script>
  <script src="lib/jasmine-6.3.0/boot0.js"></script>
  <!-- optional: include a file here that configures the Jasmine env -->
  <script src="lib/jasmine-6.3.0/boot1.js"></script>
  <!-- include source files here... -->
  <script type="module" src="cardSpec.js"></script>
</head>
<body>
</body>
</html>

The cardSpec.js looks like this:

import card from "../modules/card.js";

describe("card functions", function() {

  it ('card.rank("./cards/ace_of_spades.svg")', function() {
    expect(card.rank("./cards/ace_of_spades.svg")).toBe("ace");
  });

  it ('card.suit("./cards/ace_of_spades.svg")', function() {
    expect(card.suit("./cards/ace_of_spades.svg")).toBe("spades");
  });

});

Aspect-oriented Programming

How To Design Programs is the textbook I wish I started on because it encourages the good habits of writing structured documentation and tests before coding, saving hours of debugging and refactoring later. It also encourages programers to start by selecting the correct basic “shape” of a function which are akin to the 23 listed by the “Gang of Four” in their book Design Patterns which gave examples using object-oriented languages such as C++.

Peter Norvig demonstrates that 16 out of the 23 patterns in Design Patterns are simplified or eliminated by language features in Lisp or Dylan. Related observations were made by Hannemann and Kiczales who implemented several of the 23 design patterns using an aspect-oriented programming language (AspectJ) and showed that code-level dependencies were removed from the implementations of 17 of the 23 design patterns and that aspect-oriented programming could simplify the implementations of design patterns — Wikipedia entry on Design Patterns

Loose Coupling

It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. — Alan Perlis, Perlism #9

My interpretation of low coupling is “one data structure” and high cohesion is grouping together the 100 functions that operate on it into a module. Functions in a module should take the same argument(s) (ideally just the one data structure).

The key question is: How much of one module must be known in order to understand another module? The more that we must know of module B in order to understand module A, the more closely connected A is to B… Obviously, what we are striving for is loosely coupled systems — that is, systems in which one can study (or debug, or maintain) any one module without having to know very much about any other modules in the system.

In my main script, object literals state and dragObject are non-local variables (aka free variables) which the various event listeners can mutate, and the render function can read the latest settings from. This can readily be made avaible in modules by passing them as parameters.