Writing SOLID code

Reference

  • Singleton
  • Tight Coupling
  • Untestability
  • Premature Optimization
  • Indescriptive Naming
  • Duplication

Singleton

  • Anti-pattern
  • Uses global state
  • Hidden dependencies

Tight Coupling

If making a change to one module/component requires a change in another -

coupling exists!

Untestability

If writing tests is hard, it means that the code is bad!

Tight coupling!

Premature Optimization

... is the root of all evil. There is only cost and no benefit.

Donald Knuth​

Indescriptive Naming

Programming languages are for humans.

Don't abbreviate!

Duplication

Don't Repeat Yourself (DRY)

  • Single Responsibility Principle
  • Open/Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Single Responsibility Principle

Just because you can add everything you want into your class doesn’t mean that you should.

Open/Closed Principle

Software entities (classes) should be open for extension, but closed for modification.

Liskov Substitution Principle

Objects should be replaceable with instances of their subclasses without altering the correctness of the program.

Interface Segregation Principle

Many specific interfaces are better than one general-purpose interface.

Dependency Inversion Principle

Abstractions should not depend upon details

Details should depend upon abstractions.

Dependency Inversion Principle

Dependency Inversion Principle

Object Calisthenics

A set of rules (recommendations) how to write better object oriented code.

1. One level of indentation per method

Too many levels of indentation = 

bad readability

Hint: Split the method into several smaller ones.

// Before
function validateCollection() {
    this.collection.forEach(function (item) {
        item.forEach(function (property) {
            if (typeof property !== 'string') {
                throw new Error('invalid');
            }
        });
    });

    return true;
}

// After
function validateCollection() {
    this.collection.forEach(function (item) {
        this.validateItem(item);
    });
}

function validateItem(item) {
    item.forEach(function (property) {
        this.validateProperty(property);
    });
}

function validateProperty(property) {
    if (typeof property !== 'string') {
        throw new Error('invalid');
    }
}

2. Don't use the "else" keyword

Nested and branching conditionals =

bad readbility

Hint: Early return.

function validateProperty(property) {
    if (property.isString()) {
        if (property.equals('invalid')) {
            return false;
        } else {
            return true;
        }
    } else {
        throw new Error('Not a string');
    }
}

function validateProperty() {
    if (!property.isString()) {
        throw new Error('Not a string');
    }

    if (property.equals('invalid')) {
        return false;
    }

    return true;
}

2. Don't use the "else" keyword

3. Wrap all primitives and strings

If it has a behaviour, encapsulate.

3. Wrap all primitives and strings

var date = '2016-05-31';
// 120 bucks
var money = 120;
// mph
var velocity = 80;

var date = new Date('2016-05-31');
var money = new Money(120, 'USD');
var velocity = new Velocity(80, 'mph');
velocity.increaseBy(20);
velocity.decreaseBy(150); // --> error, cannot be negative

4. First class collections

Encapsulate a set of elements in an object and add specific behaviours (methods).

First class collections

// Array
var userData = [
    // ...
];
var user = null;

for (var i = 0; i < userData.length; i++) {
    if (userData[i].username === 'testuser') {
        user = userData[i];
        break;
    }
}

// Collection
var users = new UserCollection(userData);

users.count();
users.findByUsername('testuser');

5. One dot per line

  • Law of Demeter: Don't talk to strangers.
  • Doesn't apply for "fluent interfaces".
var totalMembers = 0;
users.getUser('testuser').getGroups().forEach(function(group) {
     totalMembers += group.getMembers().count();
});

// User
function getTotalGroupMembers() {
    return this.getGroups().getTotalMembers();
}

// Group collection
function getTotalMembers() {
    var number = 0;
    this.forEach(function(group) {
        number += group.getMemberCount();
    });
    
    return number;
}

// Group
function getMemberCount() {
    return this.members.count();
}

5. One dot per line

Don't abbreviate

Write code for humans, not machines.

Naming!

7. Keep all entities small

  • Original rule: No class over 50 lines of code.
  • For JavaScript: Soft limit of about 50-150 lines without the comments.

8. Instance variables limit

  • Original rule: No more than two instance variables per class.
  • JavaScript: Soft limit of around 5 instance variables.

9. No getters and setters

Implement behaviours instead of exposing class data through getters/setters --> better encapsulation.

9. No getters and setters

var account = new Account(120);
var addedAmount = 30;

var currentAmount = account.getBallance();
account.setBallance(currentAmount + addedAmount);

// instead:
account.addAmmount(30);

Writing SOLID code

By Ivan Novakov

Writing SOLID code

  • 1,116