Clean Code applied to JavaScript

Contents

Code smell and refactoring - I

“A code smell is a surface indication that usually corresponds to a deeper problem in the system.” Martin Fowler
“Bad code smells can be an indicator of factors that contribute to technical debt.” Robert C. Martin
“ Technical debt is a concept in software development that reflects the implied cost of additional rework caused by choosing an easy solution now instead of using a better approach that would take longer.”

Code smell and refactoring - II

  • Code refactoring is the process of restructuring existing computer code without changing its external behavior .
    • Refactoring improves nonfunctional attributes of the software.
    • Advantages include improved code readability and reduced complexity.
    • These can improve source-code maintainability.
    • Create a more expressive internal architecture to improve extensibility .

Before you start programming

Readable for humans - I

                        
const users = [
  {
    id: 1,
    name: "Carlos Caballero",
    memberSince: "1997-04-20",
    favoriteLanguageProgramming: ["JavaScript", "C", "Java"],
  },
  {
    id: 2,
    name: "Antonio Villena",
    memberSince: "2014-08-15",
    favoriteLanguageProgramming: ["Go", "Python", "JavaScript"],
  },
  {
    id: 3,
    name: "Jesús Segado",
    memberSince: "2015-03-15",
    favoriteLanguageProgramming: ["PHP", "JAVA", "JavaScript"],
  }
];
                        
                    
                        
    const users = [
    { id: 1, name: "Carlos Caballero", memberSince: "1997-04-20", favoriteLanguageProgramming: ["JavaScript", "C", "Java"] },
    { id: 2, name: "Antonio Villena", memberSince: "2014-08-15", favoriteLanguageProgramming: ["Go", "Python", "JavaScript"] },
    { id: 3, name: "Jesús Segado", memberSice: "2015-03-15", favoriteLanguageProgramming: ["PHP", "JAVA", "JavaScript"] },
    ];
                            
                        
                            
        const users = [{ id: 1, name: "Carlos Caballero", memberSince: "1997-04-20", favoriteLanguageProgramming: ["JavaScript", "C", "Java"] }, { id: 2, name: "Antonio Villena", memberSince: "2014-08-15", favoriteLanguageProgramming: ["Go", "Python", "JavaScript"] }, { id: 3, name: "Jesús Segado", memberSice: "2015-03-15", favoriteLanguageProgramming: ["PHP", "JAVA", "JavaScript"] } ];
                            
                        

Develop in English

                            
const benutzer = {
    id: 1,
    name: "John Smith",
    mitgliedVon: "1997-04-20",
};
Gehaltserhöhung(benutzer, 1000);
                            
                        
            
const użytkownik = {
    id: 1,
    imię: "John Smith",
    członekZ: "1997-04-20",
};
wzrostWynagrodzeń(benutzer, 1000);
                                
                            
                                
                const user = {
                    id: 1,
                    name: "John Smith",
                    memberSince: "1997-04-20",
                };
                increaseSalary(user, 1000);
                                
                            

Teamwork - I

  • .editorconfig: helps developers define and maintain consistent coding styles between different editors and IDEs.
                        
                                root = true
                                
                                [*]
                                end_of_line = lf
                                insert_final_newline = true
                                
                                [*.{js,py}]
                                charset = utf-8
                                
                                [*.py]
                                indent_style = space
                                indent_size = 4
                        
                    
                            
                                    [Makefile]
                                    indent_style = tab
                                    
                                    [*.js]
                                    indent_style = space
                                    indent_size = 2
                                    
                                    [{package.json,.travis.yml}]
                                    indent_style = space
                                    indent_size = 2
                                    
                              

Teamwork - II

  • Lint as a term can also refer more broadly to syntactic discrepancies in general, especially in interpreted languages like JavaScript and Python.
                            
                                    {
                                        "globals": {
                                            "myModule": true
                                        },
                                        "env": {
                                            "browser": true
                                        },
                                        "rules": {
                                            "no-unused-vars": "error",
                                            "quotes": ["warning", "double"]
                                        }
                                    }
                            
                        
                                
                                        const a = 'a';
                                        const b = a;
                                        const c = b;
                                        
                                  

Teamwork - III

    JavaScript Style Guide():
  • Standard JavaScript Style Guide.
  • Google JavaScript Style Guide.
  • Airbnb JavaScript Style Guide.
    • 2/4 spaces or tabs – for indentation
    • Single or doubles quotes for strings
    • No semicolons or semicolons
    • Never start a line with (, [, or `

Variables

Use meaningful and pronounceable variable names - I

Use intention revealing names
                    
                                const d; 
                        
                    
                            
                                    const d; // elapsed time in days
                                
                            
                        
                            const elapseTimeInDays;
                        
                    
Use pronounceable names
                                
                                        class DtaRcrd102 {
                                            private Date genymdhms;
                                            private Date modymdhms;
                                        }
                                
                            
                                
                                        class Customer {
                                            private Date generationTimestamp;
                                            private Date modificationTimestamp;
                                        }
                                
                            

Use meaningful and pronounceable variable names - II

Don't use the variable's type in the name
                        
                                const aCountries = [] 
                                const sName = ''
                                const dAmount = 3.2;
                            
                        
Use the same vocabulary for the same type of variable
                                
                                        getUserInfo();
                                        getClientData();
                                        getCustomerRecord();
                                    
                             
                                
                                        getUser();
                                    
                                

Use meaningful and pronounceable variable names - III

Don't add unneeded context
                                        
                                                const Car = {
                                                    carMake: 'Honda',
                                                    carModel: 'Accord',
                                                    carColor: 'Blue'
                                                  };
                                                  
                                                  function paintCar(car) {
                                                    car.carColor = 'Red';
                                                  }
                                        
                                    
                                        
                                                const Car = {
                                                    make: 'Honda',
                                                    model: 'Accord',
                                                    color: 'Blue'
                                                  };
                                                  
                                                  function paint(car) {
                                                    car.color = 'Red';
                                                  }
                                        
                                    

Use meaningful and pronounceable variable names - IV

Don't use magic number and strings
                                            
                                                    // What the heck is 86400000 for?
                                                    setTimeout(blastOff, 86400000);

                                                    user.rol = "Administrator";
                                            
                                        
                                    
                                            const MILLISECONDS_IN_A_DAY = 86400000;
                                            const ADMINISTRATOR_ROL = "Administrator";

                                            setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

                                            user.rol = ADMINISTRATOR_ROL;
                                    
                                

Functions

Fuctions - I

Use default arguments instead of short circuiting or conditionals
                            
                                    function setName(name) {
                                        const newName = name || 'Juan Palomo';
                                    }
                                
                            
                                        
                                                function setName(name  = 'Juan Palomo') {
                                                    // ...
                                                }
                                            
                                        
Function arguments (2 or fewer ideally)
                                    
        function newBurger(name, price, ingredients, vegan) {
            // ...
        }
                                    
                                 
                                    
function newBurger({ name, price, ingredients, vegan }) {
    // ...
} 
newBurger({
    name: 'Chicken',
    price: 1.25,
    ingredients: ['chicken'],
    vegan: false,
});
                                        
                                    

Fuctions - II

Avoid Side Effects - Global variables
                                
            let fruits = 'Banana Apple';
            
            function splitFruits() {
                fruits = fruits.split(' ');
            }
            
            splitFruits();
            
            console.log(fruits); // ['Banana', 'Apple'];
                                    
                                
                                            
    function splitFruits(fruits) {
        return fruits.split(' ');
    }
        
    const fruits = 'Banana Apple';
    const newFruits = splitFruits(fruits);
        
    console.log(fruits); // 'Banana Apple';
    console.log(newFruits); // ['Banana', 'Apple'];
                                
                            
Avoid Side Effects - Objects Mutables
                                        
    const addItemToCart = (cart, item) => {
        cart.push({ item, date: Date.now() });
    };                                                  
                                            
                                     
                                        
    const addItemToCart = (cart, item) => {
        return [...cart, {
                    item, 
                    date: Date.now(),
                }];
    };
                                            
                                        

Fuctions - III

Functions should do one thing
                                    
    function emailCustomers(customers) {
        customers.forEach((customer) => {
            const customerRecord = database.find(customer);
            if (customerRecord.isActive()) {
                email(client);
            }
        });
    }
                                        
                                    
                                                
        function emailActiveCustomers(customers) {
            customers
                .filter(isActiveCustomer)
                .forEach(email);
            }
            
        function isActiveCustomer(customer) {
            const customerRecord = database.find(customer);
            return customerRecord.isActive();
        }
                                                    
                                                

Fuctions - IV

Functions should only be one level of abstraction
                                    
function parseBetterJSAlternative(code) {
    const REGEXES = [
        // ...
    ];
    
    const statements = code.split(' ');
    const tokens = [];
    REGEXES.forEach((REGEX) => {
        statements.forEach((statement) => {
        // ...
        });
    });
    
    const ast = [];
    tokens.forEach((token) => {
        // lex...
    });
    
    ast.forEach((node) => {
        // parse...
    });
}                                                
                                        
                                    
                            
const REGEXES = [ // ...];
function tokenize(code) {    
    const statements = code.split(' ');
    const tokens = [];
    REGEXES.forEach((REGEX) => {
        statements.forEach((statement) => {
            tokens.push( /* ... */ );
        });
    });
    return tokens;
}
function lexer(tokens) {
    const ast = [];
    tokens.forEach((token) => ast.push( /* */ ));
    return ast;
}
function parseBetterJSAlternative(code) {
    const tokens = tokenize(code);
    const ast = lexer(tokens);
    ast.forEach((node) => // parse...);
}
                                
                            

Fuctions - V

Favor functional programming over imperative programming
                                                
const items = [{
    name: 'Coffe',
    price: 500
  }, {
    name: 'Ham',
    price: 1500
  }, {
    name: 'Bread',
    price: 150
  }, {
    name: 'Donuts',
    price: 1000
  }
];

let total = 0;
for (let i = 0; i < items.length; i++) {
  total += items[i].price;
}
                                                    
                                                
                                                            
const items = [{
    name: 'Coffe',
    price: 500
  }, {
    name: 'Ham',
    price: 1500
  }, {
    name: 'Bread',
    price: 150
  }, {
    name: 'Donuts',
    price: 1000
  }
];

const total = items
  .map(output => output.price)
  .reduce((total, price) => total + price);
                                                                
                                                            

Fuctions - VI

Use method chaining
                                                    
    class Car {
        constructor({ make, model, color } = car) {
            /* */
        }
        setMake(make) {
            this.make = make;
        }
        setModel(model) {
            this.model = model;
        }
        setColor(color) {
            this.color = color;
        }
        save() {
            console.log(this.make, this.model, this.color);
        }
    }    
    const car = new Car('WV','Jetta','gray');
    car.setColor('red');
    car.save();
                                                    
                                                    
                                                                
class Car {
    constructor({ make, model, color } = car){}
    setMake(make) {
        this.make = make;
        return this;
    }
    setModel(model) {
        this.model = model;
        return this;
    }
    setColor(color) {
        this.color = color;
        return this;
    }
    save() {
        console.log(this.make, this.model, this.color);
        return this;
    }
}
const car = new Car('WV','Jetta','gray')
.setColor('red')
.save();
                                                                    
                                                                

Exceptions

Exceptions - I

Don't ignore caught errors
                                    
try {
    functionThatMightThrow();
} catch (error) {
    console.log(error);
}
                                    
                                
                                    
try {
    functionThatMightThrow();
} catch (error) {
    console.error(error);
    notifyUserOfError(error);
    reportErrorToService(error);
}
                                                                                    
                                                                                
Don't ignore rejected promises
                                
getdata()
.then((data) => functionThatMightThrow(data))
.catch((error) => {
    console.log(error);
});
                                                                        
                                                                    
                                        
    getdata()
    .then((data) => functionThatMightThrow(data))
    .catch((error) => {
        console.error(error);
        notifyUserOfError(error);
        reportErrorToService(error);
    });
                                
                            

Fuctions - III

Functions should do one thing
                                    
    function emailCustomers(customers) {
        customers.forEach((customer) => {
            const customerRecord = database.find(customer);
            if (customerRecord.isActive()) {
                email(client);
            }
        });
    }
                                        
                                    
                                                
        function emailActiveCustomers(customers) {
            customers
                .filter(isActiveCustomer)
                .forEach(email);
            }
            
        function isActiveCustomer(customer) {
            const customerRecord = database.find(customer);
            return customerRecord.isActive();
        }
                                                    
                                                

Exceptions - II

Exceptions Hierarchy
                                    
    export class UserException extends Error {
        constructor(message) {
            super(`User:  ${message}`);
        }
    }
    export class AdminException extends Error {
        constructor(message) {
            super(`Admin:  ${message}`);
        }
    }
    //...
    const id = 1;
    const user = this.users.find({id});
    if(user){
        throw new UserException("This user already exists");
    }
                                    
                                

Avoid conditionals complexity

Avoid conditionals complexity - I

Don't use flags as function parameters
                                                
function book(customer, isPremium) {
    // ...
    if(isPremium){
        premiumLogic();
    }else{
        regularLogic();
    }
}                               
                                                
                                                            
function bookPremium (customer){
    premiumLogic();
}
function bookRegular (customer){
    regularLogic();
}
                
                                                                
                                                            

Avoid conditionals complexity - II

Encapsulate conditionals
                                                
if (platform.state === 'fetching' && isEmpty(cart)) {
    // ...
}                                              
                                                    
                                                
                                        
function showLoading(platform, cart) {
    return platform.state === 'fetching' && isEmpty(cart);
}
    
if (showLoading(platform, cart)) {
    // ...
}
                                            
                                        

Avoid conditionals complexity - III

Replace Nested Conditional with Guard Clauses
                                
function getPayAmount() {
    let result;
    if (isDead){
        result = deadAmount();
    }else {
        if (isSeparated){
            result = separatedAmount();
        } else {
            if (isRetired){
                result = retiredAmount();
            }else{
                result = normalPayAmount();
            }
        }
    }
    return result;
}

                                                                    
                                        
function getPayAmount() {
    if (isDead) return deadAmount();
    if (isSeparated) return separatedAmount();
    if (isRetired) return retiredAmount();
    return normalPayAmount();
}
                                    
                                

Avoid conditionals complexity - IV

Remove conditionals using polymorphism
                                                                        
function Auto() {
}
Auto.prototype.getProperty = function () {
    switch (type) {
        case BIKE:
            return getBaseProperty();
        case CAR:
            return getBaseProperty() - getLoadFactor();
        case BUS:
            return (isNailed) ? 
            0 : 
            getBaseProperty(voltage);
    }
    throw new Exception("Should be unreachable");
};
                                                                            
                                                                        
                                            TypeScript
                                            
abstract class Auto { 
    abstract getProperty();
}
    
class Bike extends Auto {
    getProperty() {
        return getBaseProperty();
    }
}
class Car extends Auto {
    getProperty() {
        return getBaseProperty() - getLoadFactor();
    }
}
class Bus extends Auto {
    getProperty() {
        return (isNailed) ? 
                0 : 
                getBaseProperty(voltage);
    }
}
// Somewhere in client code
speed = auto.getProperty();
                                        
                                    

Avoid conditionals complexity - V

Remove conditionals using Strategy pattern (composition)/Command pattern
                                                                            
                                                                                 
function logMessage(message = "CRITICAL::The system ..."){
    const parts = message.split("::"); 
    const level = parts[0];

    switch (level) {
        case 'NOTICE':
            console.log("Notice")
            break;
        case 'CRITICAL':
            console.log("Critical");
            break;
        case 'CATASTROPHE':
           console.log("Castastrophe");
            break;
    }
}
                                                                                
                                                                            
            
const strategies = {
    criticalStrategy,
    noticeStrategy,
    catastropheStrategy,
}
function logMessage(message = "CRITICAL::The system ...") {
    const [level, messageLog] = message.split("::");
    const strategy = `${level.toLowerCase()}Strategy`;
    const output = strategies[strategy](messageLog);
}
function criticalStrategy(param) {
    console.log("Critical: " + param);
}
function noticeStrategy(param) {
    console.log("Notice: " + param);
}
function catastropheStrategy(param) {
    console.log("Catastrophe: " + param);
}
logMessage();
logMessage("CATASTROPHE:: A big Catastrophe");
                                            
                                        

Async

Async - I

Use Promises, not callbacks
                                                        
import { get } from 'request';
import { writeFile } from 'fs';

get(API_ENDPOINT, 
   (requestErr, response) => {
    if (requestErr) {
        console.error(requestErr);
    } else {
        writeFile(MY_FILE, response.body, 
            (writeErr) => {
                if (writeErr) {
                    console.error(writeErr);
                } else {
                    console.log('File written');
                }
            });
        }
    });
                                                            
                                                        
                                
import { get } from 'request';
import { writeFile } from 'fs';

get(API_ENDPOINT)
  .then((response) => {
    return writeFile(MY_FILE, response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });
                                                                        
                                                                    

Async - II

Async/Await are even cleaner than Promises
                                                            
import { get } from 'request';
import { writeFile } from 'fs';

get(API_ENDPOINT)
    .then((response) => {
        return writeFile(MY_FILE, response);
    })
    .then(() => {
        console.log('File written');
    })
    .catch((err) => {
        console.error(err);
    });
                                    
import { get } from 'request-promise';
import { writeFile } from 'fs-promise';

async function asyncFunction() {
    try {
        const response = await get(API_ENDPOING);
        await writeFile(MY_FILE, response);
        console.log('File written');
    } catch(err) {
        console.error(err);
    }
}

Testing

TDD: Test-Driven Development

  • You can't write any production code until you have first written a failing unit test.
  • You can't write more of a unit test than is sufficient to fail, and not compiling is failing.
  • You can't write more production code than is sufficient to pass the currently failing unit test.

FIRST

  • Fast
  • Independent
  • Repetition
  • Self-Validating
  • Timely

Test Types

  • Unit Tests: Testing of individual functions or classes by mocking input and making sure the output is as expected.
  • Integration Test: Testing several modules to ensure they work together as expected.
  • Functional Tests: Testing a scenario on the product itself (on the browser, for example) regardless of the internal structure to ensure expected behavior.

Example (Jasmine) - I

                        
                            const math = require('../src/math');

                            describe("math module:", () => {
                                describe("#sum", function(){
                                    it("should be 0 when the array is empty", () => {
                                        expect(math.sum([])).toBe(0);
                                    });
                                    it("should be x when the array contains an unique value x", function(){
                                        const data = [
                                            [[0], 0],
                                            [[1], 1],
                                            [[999], 999]
                                        ];
                                        data.forEach(function(test) {
                                            expect(math.sum(test[0])).toBe(test[1]);
                                        });
                                    });
                                });
                            });
                        
                    

Example (Jasmine) - II

                        
                                    it("should be the sum of each element of the array", () => {
                                        const data = [
                                            [[1, 2], 3],
                                            [[1, 2, 3], 6],
                                            [[1, 2, 3, 4], 10]
                                        ];
                                        data.forEach(function(test) {
                                            expect(math.sum(test[0])).toBe(test[1]);
                                        });
                                    });
                                    describe("should return an exception", () => {
                                        it("when the array is null", () => {});
                                    })
                                });
                            });
                        
                    

eXtreme Programming: XP

eXtreme Programming: XP - Values

Extreme Programming Explained: Embrace Change (1999) - Kent Beck
  • Assuming simplicity (YAGNI: "You aren't gonna need it")
  • Feedback
    • Feedback from the system (unit tests or integration tests)
    • Feedback from the customer (acceptance tests)
    • Feedback from the team (requirements are estimated by them)
  • Courage (always design and code for today and not for tomorrow)
  • Respect
    • Programmers should never commit changes that break compilation.
    • Members respect their own work seeking for the best design for the solution.
    • Nobody on the team should feel unappreciated or ignored.

eXtreme Programming: XP - Practices

  1. Pair programming
  2. Planning game
  3. Small releases
  4. Metaphor
  5. Simple design
  6. Testing
  7. Refactoring
  8. Collective ownership
  9. Continuos integration
  10. 40-hour week
  11. On-site costumer
  12. Coding standards

Thank You