mibu
4/30/2017 - 10:17 AM

eliminateJsSmells.js

/*=======================================
=            convoluted code            =
=======================================*/
function englishToPigLatin(english) {
    /* const */
    var CONSONANTS = 'bcdfghjklmnpqrstvwxyz';
    /* const */
    var VOWELS = 'aeiou';

    if (english !== null && english.length > 0 &&
        (VOWELS.indexOf(english[0]) > -1 ||
            CONSONANTS.indexOf(english[0]) > -1)) {
        if (VOWELS.indexOf(english[0]) > -1) {
            pigLatin = english + SYLLABLE;
        } else {
            var preConsonants = '';
            for (var i = 0; i < english.length; ++i) {
                if (CONSONANTS.indexOf(english[i]) > -1) {
                    preConsonants += english[i];
                    if (preConsonants == 'q' &&
                        i + 1 < english.length && english[i + 1] == 'u') {
                        preConsonants += 'u';
                        i += 2;
                        break;
                    }
                } else {
                    break;
                }
            }
            pigLatin = english.substring(i) + preConsonants + SYLLABLE;
        }
    }

    return pigLatin;
}





//UNIT TEST FIRST

describe('Pig Latin', function() {
    describe('Invalid', function() {
        it('should return blank if passed null', function() {
            expect(englishToPigLatin(null)).toBe('');
        });

        it('should return blank if passed blank', function() {
            expect(englishToPigLatin('')).toBe('');
        });

        it('should return blank if passed number', function() {
            expect(englishToPigLatin('1234567890')).toBe('');
        });

        it('should return blank if passed symbol', function() {
            expect(englishToPigLatin('~!@#$%^&*()_+')).toBe('');
        });
    });

    describe('Consonants', function() {
        it('should return eastbay from beast', function() {
            expect(englishToPigLatin('beast')).toBe('eastbay');
        });

        it('should return estionquay from question', function() {
            expect(englishToPigLatin('question')).toBe('estionquay');
        });

        it('should return eethray from three', function() {
            expect(englishToPigLatin('three')).toBe('eethray');
        });
    });

    describe('Vowels', function() {
        it('should return appleay from apple', function() {
            expect(englishToPigLatin('apple')).toBe('appleay');
        });
    });
});

//Refactor
const CONSONANTS = ['th', 'qu', 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k',
    'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
];
const VOWELS = ['a', 'e', 'i', 'o', 'u'];
const ENDING = 'ay';

let isValid = word => startsWithVowel(word) || startsWithConsonant(word);
let startsWithVowel = word => !!~VOWELS.indexOf(word[0]);
let startsWithConsonant = word => !!~CONSONANTS.indexOf(word[0]);
let getConsonants = word => CONSONANTS.reduce((memo, char) => {
    if (word.startsWith(char)) {
        memo += char;
        word = word.substr(char.length);
    }
    return memo;
}, '');

function englishToPigLatin(english = '') {
    if (isValid(english)) {
        if (startsWithVowel(english)) {
            english += ENDING;
        } else {
            let letters = getConsonants(english);
            english = `${english.substr(letters.length)}${letters}${ENDING}`;
        }
    }
    return english;
}

/*-----  End of convoluted code  ------*/


/*=======================================
=            COPY PASTE CODE SMELLS           =
=======================================*/

//https: //github.com/danielstjules/jsinspect

//https: //github.com/kucherenko/jscpd

/*-----  End of COPY PASTE CODE  ------*/

/*===============================================
=            SWITCH STATEMENT SMELLS            =
===============================================*/
function getArea(shape, options) {
    var area = 0;

    switch (shape) {
        case 'Triangle':
            area = .5 * options.width * options.height;
            break;

        case 'Square':
            area = Math.pow(options.width, 2);
            break;

        case 'Rectangle':
            area = options.width * options.height;
            break;

        default:
            throw new Error('Invalid shape: ' + shape);
    }

    return area;
}

getArea('Triangle', {
    width: 100,
    height: 100
});
getArea('Square', {
    width: 100
});
getArea('Rectangle', {
    width: 100,
    height: 100
});
getArea('Bogus');


// "...software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification; 
//      that is, such an entity can allow its behaviour to be extended without modifying its source code." - One of Uncle Bob's SOLID Principles

//STRATEGY DESIGN PATTERN
(function(shapes) { // triangle.js
    var Triangle = shapes.Triangle = function(options) {
        this.width = options.width;
        this.height = options.height;
    };
    Triangle.prototype.getArea = function() {
        return 0.5 * this.width * this.height;
    };
}(window.shapes = window.shapes || {}));

function getArea(shape, options) {
    var Shape = window.shapes[shape],
        area = 0;

    if (Shape && typeof Shape === 'function') {
        area = new Shape(options).getArea();
    } else {
        throw new Error('Invalid shape: ' + shape);
    }

    return area;
}

getArea('Triangle', {
    width: 100,
    height: 100
});
getArea('Square', {
    width: 100
});
getArea('Rectangle', {
    width: 100,
    height: 100
});
getArea('Bogus');


//ADDING A NEW SHAPE
// circle.js
(function(shapes) {
    var Circle = shapes.Circle = function(options) {
        this.radius = options.radius;
    };

    Circle.prototype.getArea = function() {
        return Math.PI * Math.pow(this.radius, 2);
    };

    Circle.prototype.getCircumference = function() {
        return 2 * Math.PI * this.radius;
    };
}(window.shapes = window.shapes || {}));

//MAGIC STRINGS
var shapeType = {
    triangle: 'Triangle'
};

function getArea(shape, options) {
    var area = 0;
    switch (shape) {
        case shapeType.triangle:
            area = .5 * options.width * options.height;
            break;
    }
    return area;
}

getArea(shapeType.triangle, {
    width: 100,
    height: 100
});
//CONST & SYMBOLS
const shapeType = {
    triangle: new Symbol()
};

function getArea(shape, options) {
    var area = 0;
    switch (shape) {
        case shapeType.triangle:
            area = .5 * options.width * options.height;
            break;
    }
    return area;
}

getArea(shapeType.triangle, {
    width: 100,
    height: 100
});

// EDGE CASES
/*eslint no-switch:[0] */
switch (test) {
    case 'val':
        break;
}
/*eslint-disable */
switch (test) {
    case 'val':
        break;
}
/*eslint-enable */
/*eslint-disable no-switch */
switch (test) {
    case 'val':
        break;
}
/*eslint-enable no-switch */
switch (test) {
    case 'val':
        break;
} // eslint-disable-line

switch (test) {
    case 'val':
        break;
} // eslint-disable-line no-switch


/* jshint ignore:start */
/* jshint ignore:end */
// jshint ignore:line

/*-----  End of SWITCH STATEMENT SMELLS  ------*/

/*======================================
=            THE THIS ABYSS            =
======================================*/
function Person() {
    this.teeth = [{
        clean: false
    }, {
        clean: false
    }, {
        clean: false
    }];
};

Person.prototype.brush = function() {
    //smelly code
    var that = this;
    this.teeth.forEach(function(tooth) {
        that.clean(tooth);
    });

    console.log('brushed');
};

Person.prototype.clean = function(tooth) {
    tooth.clean = true;
}

var person = new Person();
person.brush();
console.log(person.teeth);

// ALTERNATIVES
//1) bind
Person.prototype.brush = function() {
    this.teeth.forEach(function(tooth) {
        this.clean(tooth);
    }.bind(this));

    console.log('brushed');
};
// 2) 2nd parameter of forEach
Person.prototype.brush = function() {
    this.teeth.forEach(function(tooth) {
        this.clean(tooth);
    }, this);

    console.log('brushed');
};
// 3) ES6
Person.prototype.brush = function() {
    this.teeth.forEach(tooth => {
        this.clean(tooth);
    });

    console.log('brushed');
};
//4a) Functional Programming
Person.prototype.brush = function() {
    this.teeth.forEach(this.clean);

    console.log('brushed');
};
// 4b) Functional Programming
Person.prototype.brush = function() {
    this.teeth.forEach(this.clean.bind(this));
    console.log('brushed');
};



/*-----  End of THE THIS ABYSS  ------*/

/*=================================================
=            CRISP CONCATENATION SMELL            =
=================================================*/
var build = function(id, href) {
    return $("<div id='tab'><a href='" + href + "' id='" + id + "'></div>");
}


//1) Tweet Sized JavaScript Templating Engine
function t(s, d) {
    for (var p in d)
        s = s.replace(new RegExp('{' + p + '}', 'g'), d[p]);
    return s;
}

var build = function(id, href) {
        var options = {
            id: id
            href: href
        };

        return t('<div id="tab"><a href="{href}" id="{id}"></div>', options);
    }
    // 2) ECMAScript 2015 (ES6) Template Strings
var build = (id, href) =>
    `<div id="tab"><a href="${href}" id="${id}"></div>`;

// 3) ECMAScript 2015 (ES6) Template Strings (Multiline)
var build = (id, href) => `<div id="tab">
  <a href="${href}" id="${id}">
</div>`;


/*-----  End of CRISP CONCATENATION SMELL  ------*/

/*======================================
=            JQUERY INQUIRY            =
======================================*/
$(document).ready(function() {
    $('.Component')
        .find('button')
        .addClass('Component-button--action')
        .click(function() {
            alert('HEY!');
        })
        .end()
        .mouseenter(function() {
            $(this).addClass('Component--over');
        })
        .mouseleave(function() {
            $(this).removeClass('Component--over');
        })
        .addClass('initialized');
});


//refactor
// Event Delegation before DOM Ready
$(document).on('mouseenter mouseleave', '.Component', function(e) {
    $(this).toggleClass('Component--over', e.type === 'mouseenter');
});

$(document).on('click', '.Component', function(e) {
    alert('HEY!');
});

$(document).ready(function() {
    $('.Component button').addClass('Component-button--action');
});



/*-----  End of JQUERY INQUIRY  ------*/

/*=================================================
=            TEMPERAMENTAL TIMER SMELL            =
=================================================*/
setInterval(function() {
    console.log('start setInterval');
    someLongProcess(getRandomInt(2000, 4000));
}, 3000);

function someLongProcess(duration) {
    setTimeout(
        function() {
            console.log('long process: ' + duration);
        },
        duration
    );
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

// alternative
setTimeout(function timer() {
    console.log('start setTimeout')
    someLongProcess(getRandomInt(2000, 4000), function() {
        setTimeout(timer, 3000);
    });
}, 3000);

function someLongProcess(duration, callback) {
    setTimeout(function() {
        console.log('long process: ' + duration);
        callback();
    }, duration);
}

/*-----  End of TEMPERAMENTAL TIMER SMELL  ------*/

/*=============================================
=            REPEAT REASSIGN SMELL            =
=============================================*/
data = this.appendAnalyticsData(data);
data = this.appendSubmissionData(data);
data = this.appendAdditionalInputs(data);
data = this.pruneObject(data);

//1) Nested Function Calls
data = this.pruneObject(
    this.appendAdditionalInputs(
        this.appendSubmissionData(
            this.appendAnalyticsData(data)
        )
    )
);
//2) forEach
var funcs = [
    this.appendAnalyticsData,
    this.appendSubmissionData,
    this.appendAdditionalInputs,
    this.pruneObject
];

funcs.forEach(function(func) {
    data = func(data);
});

// 3) reduce
var funcs = [
    this.appendAnalyticsData,
    this.appendSubmissionData,
    this.appendAdditionalInputs,
    this.pruneObject
];

data = funcs.reduce(function(memo, func) {
    return func(memo);
}, data);
// 4) flow https://lodash.com/docs#flow
data = _.flow(
    this.appendAnalyticsData,
    this.appendSubmissionData,
    this.appendAdditionalInputs,
    this.pruneObject
)(data);
/*-----  End of REPEAT REASSIGN SMELL  ------*/
/*====================================================
=            INAPPROPRIATE INTIMACY SMELL            =
====================================================*/
function ShoppingCart() {
    this.items = [];
}
ShoppingCart.prototype.addItem = function(item) {
    this.items.push(item);
};

function Product(name) {
    this.name = name;
}
Product.prototype.addToCart = function() {
    shoppingCart.addItem(this); //TIGHTLY COUPLED DEPENDENCIES
};

var shoppingCart = new ShoppingCart();
var product = new Product('Socks');
product.addToCart();
console.log(shoppingCart.items);

//1. DEPENDENCY INJECTION
function ShoppingCart() {
    this.items = [];
}
ShoppingCart.prototype.addItem = function(item) {
    this.items.push(item);
};

function Product(name, shoppingCart) {
    this.name = name;
    this.shoppingCart = shoppingCart;
}
Product.prototype.addToCart = function() {
    this.shoppingCart.addItem(this);
};

var shoppingCart = new ShoppingCart();
var product = new Product('Socks', shoppingCart);
product.addToCart();
console.log(shoppingCart.items);


//2. MESSAGE BROKER
var channel = postal.channel();

function ShoppingCart() {
    this.items = [];
    channel.subscribe('shoppingcart.add', this.addItem);
}
ShoppingCart.prototype.addItem = function(item) {
    this.items.push(item);
};

function Product(name) {
    this.name = name;
}
Product.prototype.addToCart = function() {
    channel.publish('shoppingcart.add', this);
};

var shoppingCart = new ShoppingCart();
var product = new Product('Socks');
product.addToCart();
console.log(shoppingCart.items);

/*-----  End of INAPPROPRIATE INTIMACY SMELL  ------*/

/*===================================================
=            INCESSANT INTERACTION SMELL            =
===================================================*/
var search = document.querySelector('.Autocomplete');

search.addEventListener('input', function(e) {
    // Make Ajax call for autocomplete

    console.log(e.target.value);
});


//throttle
var search = document.querySelector('.Autocomplete');

search.addEventListener('input', _.throttle(function(e) {
    // Make Ajax call for autocomplete

    console.log(e.target.value);
}, 500));

//DEBOUNCE
var search = document.querySelector('.Autocomplete');

search.addEventListener('input', _.debounce(function(e) {
    // Make Ajax call for autocomplete

    console.log(e.target.value);
}, 500));



/*-----  End of INCESSANT INTERACTION SMELL  ------*/

/*=================================================
=            ANONYMOUS ALGORITHM SMELL            =
=================================================*/
var search = document.querySelector('.Autocomplete');

search.addEventListener('input', _.debounce(function(e) {
    // Make Ajax call for autocomplete

    console.log(e.target.value);
}, 500));

// 1. STACK TRACE
// DEBOUNCE
var search = document.querySelector('.Autocomplete');

search.addEventListener('input', _.debounce(function matches(e) {
    console.log(e.target.value);
}, 500));

// 2. DEREFERENCING
document.querySelector('button')
    .addEventListener('click', function handler() {
        alert('Ka-boom!');
        this.removeEventListener('click', handler);
    });
//3. CODE REUSE
var kaboom = function() {
    alert('Ka-boom');
};

document.querySelector('button').addEventListener('click', kaboom);

document.querySelector('#egg').addEventListener('mouseenter', kaboom);
/*-----  End of ANONYMOUS ALGORITHM SMELL  ------*/

/*==============================================
=            UNCONFIRMED CODE SMELL            =
==============================================*/
$(document).ready(function() {
    // wire up event handlers

    // declare all the things

    // etc...
});
//MAKES IT HARD TO UNIT TEST

//1. SINGLETON MODULE
(function(myApp) {
    myApp.init = function() {
        // kick off your code
    };

    myApp.handleClick = function() {}; // etc...
}(window.myApp = window.myApp || {}));

// Only include at end of main application...
$(document).ready(function() {
    window.myApp.init();
});

// 2. CONSTRUCTOR FUNCTION
var Application = (function() {
    function Application() {
        // kick off your code
    }

    Application.prototype.handleClick = function() {};

    return Application;
}());

// Only include at end of main application...
$(document).ready(function() {
    new Application();
});

/*-----  End of UNCONFIRMED CODE SMELL  ------*/


/*==================================================
=            TWO-WAY DATA BINDING SMELL            =
==================================================*/
// "...you can set the directionality of it to be 2-Way Data Binding. 
//That actually seems to be a good idea until you have a large scale application 
//and then it turns out you have no idea whats going on... and turns out to be an anti-pattern for large apps."
// --Misko Hevery https://www.youtube.com/watch?v=uD6Okha_Yj0#t=1785
//HARD TO TRACK EXECUTION & DATA FLOW

// 1. solution REACT FLUX ARCHITECTURE

/*-----  End of TWO-WAY DATA BINDING SMELL  ------*/