Jesse Harlin PRO
I am a developer, musician and usergroup leader living and working in Oklahoma, US.
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
Easy
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
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
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?
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
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
And Standardize It for yourself, shop or organization.
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!!
Run it Every Single Time you Build, Refactor and Commit.
Best tool I know about?
The beginning is the most important part of the work.
SLOC
Source Lines of Code
Estimated Errors
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)
Some examples of well-maintained libraries
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
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.
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
I know it can't always be avoided,but it is preferable, and does objectively speaking, lower complexity.
No.
Hey Jesse,
Tell your cool story about Phaser.js
and all its global stuff.
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?
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.
And furthermore, avoid overloading your functions.
They say too many params can spoil the code
How many params is too many?
We tend to get ancy at around 5
Over Stateful Objects
Over Stateful Methods
And you should probably use Lodash, too.
(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
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
Composition over Depth.
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';
}
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 to
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?
Fluent Interfaces are better than Object Property Modification
Features of an Application are preferable to a "Controllers" folder.
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
By this, I mean things that produce the
illusion
of
simplicity,
without actually making a system
simpler.
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;
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.
Questions.
Discussion.
Tales of Triumph™
Synergy
@5imian
jesseharlin.net
simiansblog.com
okcjs.com
thunderplainsconf.com
By Jesse Harlin
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
I am a developer, musician and usergroup leader living and working in Oklahoma, US.