Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Keeping Code From Failing

Keeping Code From Failing

Presentation from Node Meetup Malmö, April 2015

Pelle Wessman

April 23, 2015
Tweet

More Decks by Pelle Wessman

Other Decks in Programming

Transcript

  1. Theory • Mistakes & Confusion → Bugs • Bugs →

    Frustration • Frustration → • (Bugs may also cause loss of business, be the source of major user frustration, cause a future world war and ultimately be the death of many many people)
  2. Avoiding Mistakes & Confusion • Linting / Static code analysis

    • Documentation / Version control • (Tests – out of scope of this presentation) • Automation
  3. ”lint was the name originally given to a particular program

    that flagged some suspicious and non-portable constructs (likely to be bugs) in C” –Wikipedia
  4. ”JavaScript is a sloppy language, but inside it there is

    an elegant, better language. JSLint helps you to program in that better language and to avoid most of the slop.” –JSLint
  5. JSHint • The most common basic JavaScript linter • A

    less opinionated successor to JSLint – the first JavaScript linter • Configurable • Focuses on finding mistakes and potential bugs
  6. ”Please note, that while static code analysis tools can spot

    many different kind of mistakes, it can't detect if your program is correct, fast or has memory leaks.” –JSHint
  7. JSHint usage • npm install jshint -g
 jshint file.js •

    Editor plugins – Sublime, Atom, TextMate, Vim etc • Task managers – Grunt, Gulp etc
  8. {
 "asi": false,
 "bitwise": true,
 "camelcase": false,
 "curly": true,
 "eqeqeq":

    true,
 "freeze": true,
 "immed": true,
 "indent": 2,
 "latedef": "nofunc",
 "newcap": true,
 "noarg": true,
 "noempty": true,
 "nonbsp": true,
 "nonew": true,
 "quotmark": "single",
 "strict": true,
 "undef": true,
 "unused": true
 }
 .jshintrc
  9. JSHint comments • jshint + global are scoped to functions

    • /* jshint undef: true, unused: true */ • /* global MY_GLOBAL */
 /* global -Promise */ • /* jshint ignore:start */
 /* jshint ignore:end */ • // jshint ignore:line
  10. /* jshint node: true */
 /* global -Promise */
 


    'use strict';
 
 var _ = require('lodash');
 var Promise = require('promise');
 var VError = require('verror');
 // + lots more epic code Configured node.js file
  11. /**
 * @param {Number} value - A number containing bit

    flags
 * @param {Number} flag - A bit flag, a number with a single active bit, to check for
 * @return {Boolean} True if the flag was found, otherwise false
 */
 exports.hasFlag = function (value, flag) {
 /*jshint bitwise:false */
 return value & flag;
 };
 JSHint comments in practise
  12. Style • Good style → Less confusion • Makes code

    easier to read and change • Represents changes better in version control • Good version control → Less confusion
  13. root = true
 
 [*]
 end_of_line = lf
 insert_final_newline =

    true
 indent_style = space
 indent_size = 2
 charset = utf-8
 trim_trailing_whitespace = true
 
 .editorconfig
  14. EditorConfig • Mainly used through editor plugins:
 
 Sublime, Atom,

    Vim etc – even Visual Studio! • But also through task managers – Grunt, Gulp etc • More info @ http://editorconfig.org/
  15. JSCS • Complements JSHint • Configurable • Can auto-correct •

    Contains presets for common style guides – jQuery, Google, Crockford
  16. JSCS usage • npm install jscs -g
 jscs project/ •

    Editor plugins – Sublime, Atom etc • Task managers – Grunt, Gulp etc
  17. JSCS comments • /* jscs: enable */
 /* jscs: disable

    */
 or using // like: // jscs: disable • // jscs:disable requireCurlyBraces, requireDotNotation
 [some violating code]
 // jscs: disable
  18. JS Standard Code Style • Non-configurable by design – it’s

    a feature • Somewhat controversial – eg. semi-colon less • Intends to be used instead of JSHint and JSCS • Based on ESLint • https://github.com/feross/standard • Semi-colon fork: https://github.com/Flet/semistandard
  19. ESLint • Contains both classic linting and style guide compliance

    checks • Replaces both JSHint and JSCS • Configurable • Extendable • Contains widest range of rules – node.js specific ones etc • http://eslint.org/
  20. Dependency-Check • Ensures all modules used are defined as dependencies

    in package.json • Warns if dependencies in package.json aren’t used • npm install dependency-check -g
 dependency-check /module/path • Created by Max Ogden, co-maintained by me
  21. // Generated on 2013-06-18 using generator-webapp 0.2.3
 'use strict';
 var

    LIVERELOAD_PORT = 35729;
 var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT});
 var mountFolder = function (connect, dir) {
 return connect.static(require('path').resolve(dir));
 };
 
 // # Globbing
 // for performance reasons we're only matching one level down:
 // 'test/spec/{,*/}*.js'
 // use this if you want to recursively match all subfolders:
 // 'test/spec/**/*.js'
 
 module.exports = function (grunt) {
 // load all grunt tasks
 require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
 
 // configurable paths
 var yeomanConfig = {
 app: 'app',
 dist: 'dist'
 };
 
 grunt.initConfig({
 yeoman: yeomanConfig,
 watch: {
 options: {
 nospawn: true
 },
 coffee: {
 files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
 tasks: ['coffee:dist']
 },
 coffeeTest: {
 files: ['test/spec/{,*/}*.coffee'],
 tasks: ['coffee:test']
 },
 compass: {
 files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
 tasks: ['compass:server']
 },
 livereload: {
 options: {
 livereload: LIVERELOAD_PORT
 },
 files: [
 '<%= yeoman.app %>/*.html',
 '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
 '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
 '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
 ]
 }
 },
 connect: {
 options: {
 port: 9000,
 // change this to '0.0.0.0' to access the server from outside
 hostname: 'localhost'
 },
 livereload: {
 options: {
 middleware: function (connect) {
 return [
 lrSnippet,
 mountFolder(connect, '.tmp'),
 mountFolder(connect, yeomanConfig.app)
 ];
 }
 }
 },
 test: {
 options: {
 middleware: function (connect) {
 return [
 mountFolder(connect, '.tmp'),
 mountFolder(connect, 'test')
 ];
 }
 }
 },
 dist: {
 options: {
 middleware: function (connect) {
 return [
 mountFolder(connect, yeomanConfig.dist)
 ];
 }
 }
 }
 },
 open: {
 server: {
 path: 'http://localhost:<%= connect.options.port %>'
 }
 },
 clean: {
 dist: {
 files: [{
 dot: true,
 src: [
 '.tmp',
 '<%= yeoman.dist %>/*',
 '!<%= yeoman.dist %>/.git*'
 ]
 }]
 },
 server: '.tmp'
 },
 jshint: {
 options: {
 jshintrc: '.jshintrc'
 },
 all: [
 'Gruntfile.js',
 '<%= yeoman.app %>/scripts/{,*/}*.js',
 '!<%= yeoman.app %>/scripts/vendor/*',
 'test/spec/{,*/}*.js'
 ]
 },
 mocha: {
 all: {
 options: {
 run: true,
 urls: ['http://localhost:<%= connect.options.port %>/index.html']
 }
 }
 },
 coffee: {
 dist: {
 files: [{
 expand: true,
 cwd: '<%= yeoman.app %>/scripts',
 src: '{,*/}*.coffee',
 dest: '.tmp/scripts',
 ext: '.js'
 }]
 },
 test: {
 files: [{
 expand: true,
 cwd: 'test/spec',
 src: '{,*/}*.coffee',
 dest: '.tmp/spec',
 ext: '.js'
 }]
 }
 },
 compass: {
 options: {
 sassDir: '<%= yeoman.app %>/styles',
 cssDir: '.tmp/styles',
 generatedImagesDir: '.tmp/images/generated',
 imagesDir: '<%= yeoman.app %>/images',
 javascriptsDir: '<%= yeoman.app %>/scripts',
 fontsDir: '<%= yeoman.app %>/styles/fonts',
 importPath: '<%= yeoman.app %>/bower_components',
 httpImagesPath: '/images',
 httpGeneratedImagesPath: '/images/generated',
 httpFontsPath: '/styles/fonts',
 relativeAssets: false
 },
 dist: {},
 server: {
 options: {
 debugInfo: true
 }
 }
 },
 // not used since Uglify task does concat,
 // but still available if needed
 /*concat: {
 dist: {}
 },*/
 // not enabled since usemin task does concat and uglify
 // check index.html to edit your build targets
 // enable this task if you prefer defining your build targets here
 /*uglify: {
 dist: {}
 },*/
 rev: {
 dist: {
 files: {
 src: [
 '<%= yeoman.dist %>/scripts/{,*/}*.js',
 '<%= yeoman.dist %>/styles/{,*/}*.css',
 '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}',
 '<%= yeoman.dist %>/styles/fonts/*'
 ]
 }
 }
 },
 useminPrepare: {
 options: {
 dest: '<%= yeoman.dist %>'
 },
 html: '<%= yeoman.app %>/index.html'
 },
 usemin: {
 options: {
 dirs: ['<%= yeoman.dist %>']
 },
 html: ['<%= yeoman.dist %>/{,*/}*.html'],
 css: ['<%= yeoman.dist %>/styles/{,*/}*.css']
 },
 imagemin: {
 dist: {
 files: [{
 expand: true,
 cwd: '<%= yeoman.app %>/images',
 src: '{,*/}*.{png,jpg,jpeg}',
 dest: '<%= yeoman.dist %>/images'
 }]
 }
 },
 svgmin: {
 dist: {
 files: [{
 expand: true,
 cwd: '<%= yeoman.app %>/images',
 src: '{,*/}*.svg',
 dest: '<%= yeoman.dist %>/images'
 }]
 }
 },
 cssmin: {
 dist: {
 files: {
 '<%= yeoman.dist %>/styles/main.css': [
 '.tmp/styles/{,*/}*.css',
 '<%= yeoman.app %>/styles/{,*/}*.css'
 ]
 }
 }
 },
 htmlmin: {
 dist: {
 options: {
 /*removeCommentsFromCDATA: true,
 // https://github.com/yeoman/grunt-usemin/issues/44
 //collapseWhitespace: true,
 collapseBooleanAttributes: true,
 removeAttributeQuotes: true,
 removeRedundantAttributes: true,
 useShortDoctype: true,
 removeEmptyAttributes: true,
 removeOptionalTags: true*/
 },
 files: [{
 expand: true,
 cwd: '<%= yeoman.app %>',
 src: '*.html',
 dest: '<%= yeoman.dist %>'
 }]
 }
 },
 // Put files not handled in other tasks here
 copy: {
 dist: {
 files: [{
 expand: true,
 dot: true,
 cwd: '<%= yeoman.app %>',
 dest: '<%= yeoman.dist %>',
 src: [
 '*.{ico,png,txt}',
 '.htaccess',
 'images/{,*/}*.{webp,gif}',
 'styles/fonts/*'
 ]
 }, {
 expand: true,
 cwd: '.tmp/images',
 dest: '<%= yeoman.dist %>/images',
 src: [
 'generated/*'
 ]
 }]
 }
 },
 concurrent: {
 server: [
 'coffee:dist',
 'compass:server'
 ],
 test: [
 'coffee',
 'compass'
 ],
 dist: [
 'coffee',
 'compass:dist',
 'imagemin',
 'svgmin',
 'htmlmin'
 ]
 }
 });
 
 grunt.registerTask('server', function (target) {
 if (target === 'dist') {
 return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
 }
 
 grunt.task.run([
 'clean:server',
 'concurrent:server',
 'connect:livereload',
 'open',
 'watch'
 ]);
 });
 
 grunt.registerTask('test', [
 'clean:server',
 'concurrent:test',
 'connect:test',
 'mocha'
 ]);
 
 grunt.registerTask('build', [
 'clean:dist',
 'useminPrepare',
 'concurrent:dist',
 'cssmin',
 'concat',
 'uglify',
 'copy',
 'rev',
 'usemin'
 ]);
 
 grunt.registerTask('default', [
 'jshint',
 'test',
 'build'
 ]);
 };

  22. Grunt usage • npm install grunt-cli -g
 npm install grunt

    --save-dev
 grunt <task-name> • Install any extra tasks as additional developer dependencies • Create a Gruntfile.js that sets up all your tasks
  23. /*jslint node: true, white: true, indent: 2 */
 
 "use

    strict";
 
 module.exports = function (grunt) {
 grunt.initConfig({
 pkg: grunt.file.readJSON('package.json'),
 jshint: {
 files: [
 'Gruntfile.js',
 'lib/**/*.js',
 'migrations/**/*.js',
 'public/js/script.js',
 'test/**/*.js'
 ],
 options: { jshintrc: '.jshintrc' }
 },
 lintspaces: {
 files: ['<%= jshint.files %>'],
 options: { editorconfig: '.editorconfig' }
 },
 mocha_istanbul: {
 options: {
 root: './lib',
 mask: '**/*.spec.js'
 },
 unit: {
 src: 'test/unit'
 },
 basic: {
 src: 'test'
 },
 coveralls: {
 src: 'test',
 options: {
 coverage: true,
 reportFormats: ['lcovonly']
 }
 }
 },

  24. watch: {
 jshint : {
 files: ['<%= jshint.files %>'],
 tasks:

    ['fast-test']
 }
 }
 });
 
 
 grunt.loadNpmTasks('grunt-notify');
 grunt.loadNpmTasks('grunt-lintspaces');
 grunt.loadNpmTasks('grunt-contrib-jshint');
 grunt.loadNpmTasks('grunt-contrib-watch');
 grunt.loadNpmTasks('grunt-mocha-istanbul');
 
 grunt.registerTask('setTestEnv', 'Ensure that environment (database etc) is set up for testing', function () {
 process.env.NODE_ENV = 'test';
 });
 
 grunt.registerTask('travis', ['lintspaces', 'jshint', 'setTestEnv', 'mocha_istanbul:coveralls']);
 grunt.registerTask('test', ['lintspaces', 'jshint', 'setTestEnv', 'mocha_istanbul:basic']);
 grunt.registerTask('fast-test', ['lintspaces', 'jshint', 'setTestEnv', 'mocha_istanbul:unit']);
 grunt.registerTask('default', 'test');
 
 
 grunt.event.on('coverage', function(lcov, done){
 require('coveralls').handleInput(lcov, function(err){
 if (err) {
 return done(err);
 }
 done();
 });
 });
 };

  25. npm test In package.json add: "scripts": {
 "test": "node -e

    \"require('grunt').tasks(['test']);\"",
 "travis": "node -e \"require('grunt').tasks(['travis']);\"",
 },

  26. Husky • Links git-hooks to npm scripts • Useful for

    running linting and/or tests before commit or push • npm install husky --save-dev • "scripts": {
 "prepush": "npm test"
 },
  27. Lintlovin • An opensource linting setup shared between Bloglovin’s JavaScript-projects

    • Automatically sets up and maintains a base Grunt-setup • Symlinks in .editorconfig, .jshintrc & .jscsrc • Configurable default tasks • Extendable with extra Grunt tasks • npm install lintlovin --save-dev
  28. Lintlovin Tasks • JSHint • EditorConfig-based whitespace linting • JSCS-enforced

    modified Crockford style guide • Mocha + Istanbul based test runner with coverage reports. Auto-activates on detected test-scripts • Dependency-Check • Pre-push Husky setup • A watch-script
  29. /*jslint node: true */
 'use strict';
 
 var lintlovin =

    require('lintlovin');
 
 module.exports = function (grunt) {
 lintlovin.initConfig(grunt, {}, {
 jsFiles: ['example/**/*.js'],
 });
 };
 Lintlovin:ified Gruntfile.js
  30. Add a .travis.yml file like: language: node_js
 node_js:
 - '0.10'


    - '0.12'
 - 'iojs'
 matrix:
 fast_finish: true
 allow_failures:
 - node_js: '0.12'
 - node_js: 'iojs'
 Travis CI Setup
  31. /*jslint node: true */
 'use strict';
 
 var lintlovin =

    require('lintlovin');
 
 module.exports = function (grunt) {
 lintlovin.initConfig(grunt, {}, {
 enableCoverageEvent: true
 });
 
 grunt.event.on('coverage', function (lcov, done) {
 if (!process.env.TRAVIS) { return done(); }
 
 require('coveralls').handleInput(lcov, function (err) {
 if (err) { return done(err); }
 done();
 });
 });
 };
 Coveralls.io Lintlovin Setup
  32. Node Malmö •The ”Node Meetup Malmö” Group @ Facebook •@nodemalmo

    on Twitter •And always on http://www.foocafe.org/
  33. Thanks! Presenter: Pelle Wessman On the social web: @voxpelli On

    the indie web: voxpelli.com Image of Turning Torso in first slide from: http://commons.wikimedia.org/wiki/ File:The_Turning_Torso,_Malmo.JPG