Readable, Maintainable, Testable Code

Below is a presentation Christian gave on Dec 11, 2013 at the Bad Habit Room, converted to text from the original source.

Title: Readable, maintainable, testable code
Date: December 11, 2013
Author: Christian G. Warden <christian.warden@greatvines.com>




Delivering Working Software
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Our job is to deliver software

* that works

* and to meet the committments we make

* and sometimes to adjust to changing priorities


We don't have to

* set priorities

* predict the future perfectly

* solve hard CS problems


How to Deliver Working Software
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

How do we make sure the software we deliver works?

* QA Team

* Dogfooding

* Tests


QA Team
* Done when the code seems to work
* But requires lots of roundtrips

Dogfooding
* Done when works for me
* But we don't sell booze

Write Tests
* Done when code obviously works
* But it takes longer (in the short run)


"There are two ways of constructing a software design. One way is to make it so
simple that there are obviously no deficiencies. And the other way is to make it
so complicated that there are no obvious deficiencies."    — C.A.R. Hoare.


The Mindset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Write code to be read by coworkers

* Write as if code will be published (good chance it will be)

* Write as if you're applying to grad school, writing a book proposal, applying
for a grant

* Write code to get LinkedIn Endorsements

* Whatever works for you


An analogy: Does the code work?
Probable cause vs Beyond a reasonable doubt


Why Tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Encourages maintainable code

* Minimizes regressions


How to write testable code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Small functions

* Localize and minimize state

* DRY: Use functions that already exist (will be easier with Docco)

* Command Query Separation


Small Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Small functions are easy to test.

Small functions are easy to read and understand.

* There aren't many code paths

* There isn't much state

* State is local


Command Query Separation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If a function is doing two or more of these things, consider splitting it up.

* Retrieving data (from a database, from the DOM, etc.)

* Transforming data (combining data structures such as collections, extracting a
subset of attributes, etc.)

* Writing data (to a database, to the DOM, etc.)

* Orchestrating the types of functions above

Examples:
HistoryModalController.buildSurveysTable
(8dcac7e4784a530bba1d82b234dcbbd24198d3a9)
StatusLogController.openStatusLog (d971e96c2ae208354853fc95ae57e8404c83573b)


Separate Event Handlers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.---------------------------
| $('button').on('tap', function() {
|   // do something
| });
`---------------------------

* This is doing two separate things: registering an event handler and doing
something when that event handler is called

.---------------------------
| Module.doSomething = function() {
|   // do something
| };
| 
| $('button').on('tap', Module.doSomething);
`---------------------------

* Module.doSomething can be tested more easily


Promises/Deferreds
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Avoid creating extra deferreds unnecessarily.

.---------------------------
| var promising = function() {
|   var deferred = $.Deferred();
|   getSomething()
|       .then(processSomething)
|       .then(deferred.resolve, deferred.reject);
|   return deferred.promise();
| };
`---------------------------

.---------------------------
| var promising = function() {
|   return getSomething()
|       .then(processSomething);
| };
`---------------------------

Valid use cases:

* When mapping between callback-passing and promise-based APIs.

.---------------------------
| SmartStore.removeSoup = function(soupName) {
|   var deferred = $.Deferred();
|   SmartStore.native.removeSoup(soupName, deferred.resolve, deferred.reject);
|   return deferred.promise();
| };
`---------------------------

* When turning a resolved promise into a rejected one or vice versa

.---------------------------
| someFunction()
|   .then(result) {
|       if (!validate(result)) {
|           return $.Deferred().reject();
|       }
|   });
`---------------------------


More Promises/Deferreds
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Keep call chains shallow

.---------------------------
| return first()
|   .then(function() {
|       return second()
|           .then(function() {
|               return third();
|           });
|   });
`---------------------------


.---------------------------
| return first()
|   .then(second)
|   .then(third);
`---------------------------


Alternative if you need to use response from first function in third function.

.---------------------------
| var neededState {};
| return first()
|   .then(function(firstResult) {
|       neededState.first = firstResult;
|       return second(firstResult);
|   })
|   .then(_(third).partial(neededState));
`---------------------------


How to minimize complicated state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Functional constructs (map, filter, partial, etc.)
Read the underscore.js docs

* Avoid side effects

* Avoid mutating state.  Assign each variable once.

* Convert functions that operate on collections of objects into functions that
operate on a single one, and use map


Async Functional Programming
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Create higher order functions instead of complicated state
Ex: BaseController.makeTransitional, Utility.gate, Utility.debounce

.---------------------------
| Utility.gate = function(func, concurrency) {
|   concurrency = concurrency || 5;
|   var running = 0;
|   var queue = [];
|   return function() {
|       var deferred = $.Deferred();
|       var context = this;
|       var args = arguments;
|       var run = function() {
|           var next = queue.shift();
|           running++;
|           $.when(func.apply(next.context, next.args))
|               .then(next.deferred.resolve, next.deferred.reject, next.deferred.notify)
|               .always(function() {
|                   running--;
|                   if (queue.length > 0 && running < concurrency) {
|                       run();
|                   }
|               });
|       };
|       queue.push({ context: context, args: args, deferred: deferred });
|       if (running < concurrency) {
|           run();
|       }
|       return deferred.promise();
|   };
| }
`---------------------------

* Async functional constructs with $.when.apply

.---------------------------
| var batchProcessAsync = function(collection) {
| return $.when.apply(null, collection.map(process))
|   .then(function() {
|       // Combine all of the results
|       return _(arguments).chain()
|           .toArray()
|           .filter(...)
|           .map(...)
|           .value();
|   });
| }
`---------------------------


How to write tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Test both positive and negative conditions.

* Test all possible paths through code.  If there seem to be a lot, it's
probably time to refactor.

* Use spies to limit code being tested to the current spec.  Each test should
assume its dependencies work correctly.

.---------------------------
| Module.randomMultipleOf2 = function() {
|   return Module.random * 2;
| };
| describe('Module.randomMultipleOf2', function() {
|   beforeEach(function() {
|       spyOn(Module, 'random').andReturn(3);
|   });
|   it('should return a multiple of 2', function() {
|   });
| });
`---------------------------


When to write tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When fixing a bug, write tests first to make sure you understand the bug.

When refactoring, each new function should have tests.


Use grunt and karma
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Use grunt with karma
.---------------------------
| $ grunt karma:watch
| Running "karma:watch" (karma) task
| INFO [karma]: Karma v0.10.4 server started at http://localhost:9876/
| INFO [launcher]: Starting browser PhantomJS
| INFO [PhantomJS 1.9.2 (Linux)]: Connected on socket AxVMFTHB-a_woQco7wGz
| PhantomJS 1.9.2 (Linux): Executed 1 of 552 (skipped 551) SUCCESS (1.247 secs /
| 0.013 secs)
`---------------------------


* ddescribe/iit isolate specific specs/suites while writing tests
.---------------------------
| ddescribe('Module.function', function() {
| });
`---------------------------

.---------------------------
| describe('Module.function', function() {
|   iit('should return something awesome', function() {
|   });
| });
`---------------------------


Peer Review
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Check for coding convention issues

* Check for proper use of idioms consistent with codebase:
  * Use of promises
  * Use of functional constructs

* Check that tests cover changes in code

* Look for obvious bugs


Other Easy Stuff
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Use jshint with grunt
.---------------------------
| $ grunt watch
| Running "watch" task
| Waiting...
| >> File "mobile/ios/GreatVines Mobile/www/app/utility.js" changed.
| 
| Running "jshint:all" (jshint) task
| 
| ✔ No problems
| 
| Running "watch" task
| Completed in 0.207s at Mon Dec 09 2013 17:03:21 GMT-0800 (PST) - Waiting...
`---------------------------

* Coding Conventions
Avoid unnecessary distractions to comprehension

* Use descriptive variable names; avoid unnecessary abreviations
We're not programming punch cards


Other Useful Tools
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* mitmproxy


Estimating
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

How much will lunch cost?

80% Confidence Interval

99% Confidence Interval
* Should be wider with no new information
* More information required to make same CI narrower


What's Next
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Annotated source as documentation

* grunt-jsbeautify to verify coding conventions

* Split up functional tests

* Integration testing with CasperJS

* Automated code coverage testing


Annotated Source
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Short Comments (one or two sentences)

* Inline, usually before each function

* Put on their own lines

* Docco (like Underscore.js, Backbone.js)


Tests are specs; specs are tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Easy to test; easy to read; easy to maintain
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


References
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Things worth reading

* http://tech.pro/tutorial/1589/testable--tested-client-side-code

* http://flippinawesome.org/2013/12/02/eliminating-code-smell-with-grunt/

Javascript Books

* Javascript: The Good Parts

* Async Javascript

* Effective Javascript

Videos/Presentations

* Pure Javascript: http://cjohansen.no/talks/2012/sdc-functional/#1

Functional Programming

* Learn You a Haskell for Great Good