Unbraiding Javascript

Seek and Destroy Complexity

I help run Thunder Plains developer conference

I help run the OKCJS (OKC Javascript User Group)

I am a software architect at Telogical systems

"Unbraiding" Javascript?

Rich Hickey

Simple

  • 1 task
  • 1 concept
  • 1 dimension
  • simplex - 1 fold/ 1 braid
  • not to be confused with easy
  • Objective/measurable
  • Nonentanglement

 

Easy

  • Convenient /Familiar
  • Near at hand
  • Near to our understanding

 

I mean this.

Not necessarily this.

-Simplicity Matters, 2012

Advice From in the Trenches

Some time ago, our shop decided to take code cleanliness seriously

We wanted to go beyond

TDD, BDD, and DDD

What really makes our code

  • hard to change
  • hard to maintain
  • hard to get up-and-running fast
  • reuseable or non-reuseable

THIS IS WHAT WE LEARNED THE HARD WAY

We've seen some S***

Complexity is the Enemy

Turns out that most of our real problems can be traced back to

code and design complexity

 

In an industry where we mostly talk about frameworks and tooling, larger scale craftsmanship is underdiscussed.

NOT

  • how Angular (or any framework) works
  • how fast a loop runs
  • if we have 100% test coverage

is usually our fault

lives at every level of the stack

is measurable

is defeatable

lowers responsiveness to change

is a sign of/hides deeper problems

Complexity

Complexity

Complexity

Complexity

Complexity

Complexity

drives technical debt

Complexity

hides in plain sight

Complexity

We learn from those before us

Robert Martin

Clean Code: A Handbook of Agile Software Craftsmanship

Kyle Simpson

You Don't Know JS

Rick Hickey

Simplicity Matters

Addy Osmandi

Javascript Design Patterns

Martin Fowler

Refactoring : Improving the Design of Existing Code

Seek

What are we looking for?

What is simple code?

What is complexity smell?

How will we measure it?

Simple Code

Loose Coupling

Simple 

Easy to Remove/ Change

High

Cohesion

Changes are Local

Low SLOC

Low 

Cyclomatic Complexity

Easy

Maintainability

Consistent Style

No Errors

Code

Cohesion and Coupling

Coupling

is the degree to which one program unit relies upon other units

Tight

More interdependency

More Coordination

More Information Flow

Loose

Less interdependency

Less Coordination

Less information flow

Cohesion

is the degree to which the elements of a module belong together

Low

High

Lets think about things and stuff

Legos / Puzzle Pieces

Different Boxes of Legos

Discussing coupling and cohesion depends on the "level" you're at

 

We try to maximize both

Loose coupling + High Cohesion

though depending on your perspective in some situations we end up trading one for another

(but some forms of cohesion/coupling are preferable to others)

Not all coupling is the same

Not all cohesion is the same

NO SIMPLE ANSWERS

module

application

framework

library

file

feature

service

function

Content Coupling

 

Common Coupling

 

External Coupling

 

Control Coupling

 

Stamp Coupling

 

Data Coupling

 

Message Coupling

 

Undesirable

Desirable

Relies on Internal Workings of other module

Share same global data

Share same data format, protocol or interface

One module controls flow of another

Modules share data ,but only use part of it

Modules Share Data via parameters

Communication via parameters, or message passing

Types

of

Coupling

Coincidental Cohesion

 

Logical Cohesion

 

Temporal Cohesion

 

Procedural Cohesion

 

Communicational Cohesion

 

Sequential Cohesion

 

Functional Cohesion

 

Modules grouped arbitrarily. (Utilities module)

Grouped because they do similar things (like all input routines, or controllers)

Grouped by when they are processed

parts of a module follow a certain sequence

Modules operate on the same data

Output of one module is input of the next

Modules grouped because they contribute to a single well-defined task

Undesirable

Desirable

Types

of

Cohesion

Smell

Rigidity

Fragility

Immobility

Its hard to change.

A change cascades throughout the system.

You change something, and it breaks everywhere

You can't reuse your code, without performing painful code surgery.

Viscosity of Environment

Repetition

Building and testing take a long time, so people just don't do it. 

Ctrl V + Ctrl C, basically. This is the path to pain

Non-Opacity

Code is hard to understand is code that won't get worked on.

 

Refactor ideas before you refactor code.

Something that relies on order is inherently more complex than something that does not

Something that depends on abstractions rather than concretions is inherently less complex

Something with well defined boundaries is less complex than something that isn't

Unnecessary Order

Overly Concrete

Poor Boundaries

Viscosity of Design

Cutting corners will make you aquire more technical debt in the long run

Its easier to make a knot than untie it.

Don't throw a code mattress in your code swimming pool.

And the big one....

Every piece of code you write, is a

meme, and it wants to survive.

 

It survives as memes do, via replication - more reuse.

 

As it survives it becomes more abstract, and more reuseable.

 

More functions can use it, and eventually more modules, and eventually more applications, shops and people.

 

Try to make every line survive,

or delete it.

My Opinion

Destroy

Complexity

Use a Modern Build System

And Standardize It for yourself, shop or organization.

  • Good: File Concatination [Grunt, Gulp]
  • Better: AMD [Require.JS]
  • Slightly Better: Client Package Manager [Bower]
  • Best: CommonJs Modules [Browserify, Webpack]
  • Bestest: CommonJs Modules + Task Runner

Project Build requires only one step

Arrange and organize files unrestricted by load order, arbitrary technology constraints

Enforces Standard Convention (and checks with tools)

Decouples Construction from Runtime (loosens coupling)

Take serious steps to untangle your javascript build process from the browser!!

Use Complexity Analysis Tooling

Run it Every Single Time you Build, Refactor and Commit.

Plato

Best tool I know about?

The beginning is the most important part of the work.

Channel the powers of Maurice Halstead, today!

  • Easily Visualize SLOC
  • Maintainability Index
  • Estimated Errors
  • Cyclomatic Complexity

SLOC

Source Lines of Code

Estimated Errors

  • The Halstead length (LTH) is OP+OD
  • The Halstead Vocabulary (VOC) UOP+UOD
  • The Halstead Difficulty (DIF) is (UOP/2) * (OD/UOD)
  • Halstead Volume (VOL) = LTH * log2(VOC)
  • Halstead Effort (EFF) = DIF * VOL
  • Halstead Bugs (BUG) = VOL/3000 or EFF^(2/3)/3000

Don't wig about this. More = bad. See? simple.

More = bad. Bugs Hide in Long Files.

Cyclomatic Complexity

The cyclomatic complexity of a section of source code is the number of linearly independent paths within it.

 

E - N + P 

E = number of edges in the flow graph.
N = number of nodes in the flow graph.
P = number of nodes that have exit points
 initRenderBuffer: function() {
    this.elBuffer = document.createDocumentFragment();
    this._bufferedChildren = [];
  },

Complexity of 1

 _initialEvents: function() {
    if (this.collection) {
      this.listenTo(this.collection, 'add', this._onCollectionAdd);
      this.listenTo(this.collection, 'remove', this._onCollectionRemove);
      this.listenTo(this.collection, 'reset', this.render);

      if (this.sort) {
        this.listenTo(this.collection, 'sort', this._sortViews);
      }
    }
  },

Complexity of 3

And now ...

Real Examples,

for Real People

Cyclomatic Complexity

High Cyclomatic Complexity = Bad

Know Branches,

Know Problems

No Branches,

No Problems

0

4

8

12

14

2Many

Complexity-o-Meter

The Highly Unscientific/Anecdotal

How much is too much?

Lets consult...

Maintainability Index (0-100)

Putting it all SLOC, Cyclomatic Complexity and Estimated Errors....

Low = Bad

For Javascript, above 70 is pretty darn good.

Maintainability Index =

MAX(0,(171 - 5.2 * log(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * log(Lines of Code))*100 / 171)

  • Jquery : 78.81
  • Grunt : 70.77
  • Marinoette : 74.19

Some examples of well-maintained libraries

Show me the Metrics

Enough!

Lets look at Marionette.js

I feel the success that we have had with

code analysis

has been clear enough that

I will say plainly it is irresponsible

to not use it.

John Carmack

from In-Depth: Static Code Analysis

Lint Your Code

 

 

 

 

 

Every Day.

Enforce Stylistic Consistency

lint was the name originally given to a particular program that flagged some suspicious and non-portable constructs (likely to be bugs) in C language source code. 

The term is now applied generically to tools that flag suspicious usage in software written in any computer language.

Best one I know about?

update: ESLINT IS BETTER

Add it to your build pipeline.

Run it every time.

Determine your style guide democratically.

Never turn it off.

Ever.

Refactor when SLOC Gets Too High

When is SLOC

Too High?

Well that depends on your situation

and

the type of file

and 

whether or not its built

I'd say on average in my experience, for a typical module, before it is built i start to sound the alarm at ...

About 350 lines.

and less than 150 is ideal

(Insert cool story about discovering SLOC with Plato here)

That's me using Plato. Its so rad.

Low SLOC per file

  • keeps changes local
  • Loosens coupling
  • forces you to think of cohesion differntly
  • promote single responsibility
  • Modules should be small

Prefer Libraries and Frameworks that do NOT expect Global Objects

I know it can't always be avoided,but it is preferable, and does objectively speaking, lower complexity.

  • Modules that don't require a global namespace are easier to remove
  • Modules are less likely to need loading in a "certain order", thus less complexity.
  • Changes and modification are local, rather than global
  • Easier to test/isolation from the environment

No.

Hey Jesse,

Tell your cool story about Phaser.js

and all its global stuff.

Name Every Function

Every. Single. Function.

One of the stronger opinions in this talk, right?

My shop gave this a serious shot for a year and we'll never go back, and I'll tell you why.

"Anonymous functions omit a name which is often helpful in providing more readable/understandable code. A descriptive name helps self-document the code in question."

- Kyle Simpson YDKJS, Scopes and Closures, ch.3

Here's Kyle's Reasons:


Anonymous functions have no useful name to display in stack traces, which can make debugging more difficult.

 

Without a name, if the function needs to refer to itself, for recursion, etc., the deprecated arguments.callee reference is unfortunately required. Another example of needing to self-reference is when an event handler function wants to unbind itself after it fires.

 

Anonymous functions omit a name which is often helpful in providing more readable/understandable code. A descriptive name helps self-document the code in question.

I'm gonna add to that

Naming a function is your first line of defence in not performing needless repetition

It is the first step in refactoring: making a function more abstract than single use in one spot.

It promotes better opacity. What does a module do? Read the names.

If in other languages, we name a method after what is done not how it is done, then what is an anonymous function?

Named functions promote Vertical Separation.

Named functions are more reuseable, thus lowering SLOC

Named functions are more reuseable, thus improving mobility

Nothing is more concrete than a nameless, 1-off function

Thought experiment:

Imagine a classroom full of students. You could put nametags on the students, but instead you elect to put the students in boxes, with names on the boxes. Rather than refer to students directly, you refer to their package. Now, imagine that you have even more boxes, not only for Jane Doe, and Dicktom Harry, but also for "timeout" and "student of the month". You can freely interchange the unnamed students between boxes, but while class is running, you never know for certain which student is in which box, until you ask "the box" (not the student) a question.

 

One day the principal comes along and asks you why you never used name tags. What do you tell her?

 

Did you measurably simplify your job?

Still not seeing it?

Tell me what this code does

_.chain([{
        name: 'Jesse',
        balance: '0',
        email: 'derp@derp.derp'
    }, {
        name: 'Ben',
        balance: '90.2'
    }, {
        name: 'Joe',
        balance: '30'
    }]).filter(function(p) {

        return p > 45;
    }).sortBy('balance')
    .map(function(d) {

        var duedate = new Date();
        // Add two weeks
        duedate.setDate(now.getDate() + 14);

        return {
            message: 'Please remit' + d.balance + 'by' + duedate,
            mailTo: d.email
        }
    }).value()
_
  .chain(customers)
  .filter(customersWithHighBalance)
  .sortBy('balance')
  .map(customerToInvoice)
  .value()

Now this.

What happens the next time you need to filter customers that have a high balance?

What happens when you find some other thing that you filter by high balance, and it has the same property?

After your codebase begins to pick up many, many ways to transform data, which codebase is more likely to have more total functions?

What happens when a high balance changes from 45 to 50, and you need to change it in many places?

What happens when you've never seen the code before, ever and you need to find the thing that makes invoices out of customers?

What if this code was actually complicated business logic?

Naming our functions has significantly improved our codebase

Real Talk

Its got less total functions, and does the same stuff.

Its more modular, across projects

I'm telling you this because it worked for us.

Its much simpler.

Testing is signifigantly easier.

Furthermore..

  • Prefer Revealing modules to standard
  • Fight code accordions
  • Anything should be easily extracted

Avoid Long Function Argument Lists in favor of Mapped Data Structures

And furthermore, avoid overloading your functions.

  • Function params are ordered
  • Brittleness can be introduces at every entry point
  • A map can prevent solve some problems

They say too many params can spoil the code

How many params is too many?

We tend to get ancy at around 5

Prefer Functional Data Manipulation

Over Stateful Objects

Over Stateful Methods

And you should probably use Lodash, too.

Don't Reach .forEach()

(or for _.each())

And Lower the Lexical Complexity of Functional Expressions.

var someStankyTempArray = []; 
//just chilling out of your functional scope
//making life hard.

_.each(someOtherStankArray, function(someOject){

    var myNewObject = {};

    //do some transformation on someOject
    //and sprinkle that all over the new object.
    
    someStankyTempArray .push(someOject);
})

// boo.
var theArrayIActuallyCareAbout = someStankyTempArray;

OMG Stop, seriously.

Have you considered

.map()

.filter()/.reject()

.reduce()

You probably wanted .map

Favor Objects, and Unordered Lists of Objects over Primitives

Objects are not Lists; Arrays are

var specialForces = {
    id_1: {
        name: "jesse",
        favoriteFood: "burrito"
    },
    id_2: {
        name: "Dolf Lungren",
        favoriteFood: "suffering"
    }
}
var specialForces = [
  {
        id: 1,
        name: "jesse",
        favoriteFood: "burrito"
  },{
        id: 2,
        name: "Dolf Lungren",
        favoriteFood: "suffering"
  }
];

Should this ...

Be this ?

Can you 

  • sort
  • filter
  • find
  • transform
  • iterate

Prefer patterns like Mixins and Higher Order Functions to Prototypical Inheritance

Composition over Depth.

There is no Else If

Fail Fast in your Functions.

Rebecca Murphey's Examples

function howBig( num ) {
  if ( num < 10 ) {
    return 'small';
  } else if ( num >= 10 && num < 100 ) {
    return 'medium';
  } else if ( num >= 100 ) {
    return 'big';
  }
}
function howBig( num ) {
  if ( num < 10 ) {
    return 'small';
  }

  if ( num < 100 ) {
    return 'medium';
  }

  if ( num >= 100 ) {
    return 'big';
  }
}
function howBig( num ) {
  if ( num < 10 ) {
    return 'small';
  }

  if ( num < 100 ) {
    return 'medium';
  }

  return 'big';
}

Find reasons to "not" run the function early

Fail fast. Think like a recursive function 'base case'

See also:

Error-First convention

in

node

function doFileStuff(err, data) {
  if (err) {
      console.log('Ahh! An Error!');
      return; //<-- get out. there's a problem.
  }
  
  //all the 'real work' of the function.
  console.log(data);
}


fs.readFile('/foo.txt', doFileStuff);

Maybe the part the "decides" the control flow can be fully separated.

 

Maybe the "error" handling can be separated

 

Maybe data-defaults or validation can be separated

Which brings us to the next point....

This makes us consider function anatomy.

... and reused

Prefer Polymorphic Patterns

Over Excessive Control Flow

 

Prefer Polymorphic Patterns to

  • Inheritance
  • Switches
  • Pattern Matching
function(n){
    if(n > 64){
        (function(){
            //business logic
        })();
       return;
    } else if(n > 15 && n < 65){
        (function(){
            //business logic
        })();
       return;
    }
    else if(n < 16){
        (function(){
            //business logic
        })();
    } else {
      (function(){
            //business logic
       })();
    }
}

Tangled.

The thing that

'decides'

and the thing that

'does'

is all entangled.

 

 

You cannot change one without the other!

//another module
serveDrink(){
    serve[drinkFromOz(64)]()
}
//some module
Serve(){
    function large(){ 
     //business logic    
    }

    function medium(){
     //business logic
    }

    return {
        large: large,
        medium: medium
        small: small
    }
}
//one module
function drinkFromOz(n){

    if(n > 64){
        return 'large';
    }
    
    if(n < 16){
        return 'small';
    }

    return 'medium';
}

There are at least 3 separate ideas here...

What I do.

When I do it.

Coordination.

//another module
serveDrink(){
    serve[drinkFromOz(64)]()
}
//some module
Serve(){
    
    //(even these can be extrapolated)
    function large(){ 
     //business logic    
    }

    function medium(){
     //business logic
    }

    return {
        large: large,
        medium: medium
        small: small
    }
}
//one module
function drinkFromOz(n){

    if(n > 64){
        return 'large';
    }
    
    if(n < 16){
        return 'small';
    }

    return 'medium';
}

You can do even better...

Can these functions be pulled apart, and composited another way?

Can these conditionals be puled out into 'positive' statements?

I can nearly weld this shut. Where's than number coming from? Can it  be pulled into an object map?

Function Chains > Nesting

Queues > Chains

 

Fluent Interfaces are better than Object Property Modification

Organize Code via Application Domain

 not by

Pieces of Technology

Features of an Application are preferable to a "Controllers" folder.

Write Self Documenting Code

Avoid excessive comments

Avoid hungarian notation

Abstract conditionals in functions (positive conditionals preferable)

Comments can get obsolete, so minimize using them, as wrong comments are worse than no comments.


Comments are a duplication of the code. Everything which can be written in code, should be written in code.

 

If you are following a lot of the other advice in this talk, this should already be happening.

Generally, Avoid Hungarian notation - it tightly couples implementation and is harder to read.

Today's string is tomorrow's object

FALLACIES

Simplicity

By this, I mean things that produce the

 

illusion

of

simplicity,

 

without actually making a system

 

simpler.

A Data Structure of Simple Primitives is "Simpler" Than a List of "Complicated" Objects 

I Don't Use Semicolons and/or I Format My Code a Certain Special Way so its Automatically "Simpler" Because There's Less Typing and More Whitespace

I Use Short Variable Names, so All My Code is "Smaller", and Therefore More "Simple"

I Stopped Using Braces on My "If" Statements, Think of the Savings!

 

Its all just so clean and pure looking, think of all the pixels that aren't even there anymore.

if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
	goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
	goto fail;
	goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
	goto fail;

"But its my personal style"

goto fail;

I Do Everything ShortHand!

Sweet Code Poetry!

 

Math.floor? How about Bitwise Operators.

Time to parse an Int? I just use the + sign.

 

If you can't read my code, its just because you're probably really dumb.

Plus I type less. Double Whammy.

Now we get to chat.

Questions.

Discussion.

Tales of Triumph™

Synergy

@5imian

jesseharlin.net

simiansblog.com

okcjs.com

thunderplainsconf.com

Unbraiding Javascript

By Jesse Harlin

Unbraiding Javascript

Understand how to reduce unnecessary application complexity in Javascript, by implementing best practices and analysis. this talk has been delivered in Oklahoma city, Kanasas City, Rockford, Ill, and San Antonio

  • 3,275
Loading comments...

More from Jesse Harlin