you-probably-dont-want-an-object



you-probably-dont-want-an-object

1 1


you-probably-dont-want-an-object

talk given at LondonJSConf, 2014

On Github timruffles / you-probably-dont-want-an-object

You probably don't want an object

@timruffles @sidekicksrc

What the hell is an object?

EverythingIsAnObject... ?

var iAmAnObject = {};
var soAmI = 5;
var ifYouAreSoAmI = null;
var imAnObject = [];

'Object' is overloaded

Definitions in JS

  • everything that isn't null/undefined
  • empty objects used as key-value stores
  • things that have functions in them that reference 'this'

So which is it?

None of the above

Let's ask Alan Kay

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

Alan Kay

So what do Alan Kay's objects look like?

Technical diagram

  • messaging/late binding
  • hiding of state

Messaging & late-binding

  • component sends a message
  • another components receives and does... whatever it likes

Results of messaging

  • decouples intent from implementation (polymorphism)
  • can't see/modify state of receiver

What the hell are objects?

'Objects' are a pattern

  • not the specific implementation

Alan Kay's objects are

  • entities that accept messages
  • that access hidden state
  • and do something in response to the message

OOP's objects

I don't mean

Everything

  • where's the state?
var a = 10;
a.toString() // === intToString(a);

Key value data

module.exports = /^hello$/; // uses k/v obj as namespace

var someData = JSON.parse(resp.body); // k/v data

'Don't use new'

  • ?!?!

I mean the root

[objects are] like biological cells and/or individual computers on a network, only able to communicate with messages.

The pattern

  • yes inheritance sucks, but that's just one implementation
function Account(value) {
  return function(command) {
    switch(command) {
      case "withdraw": 
        return value -= arguments[1];
      case "deposit": 
        return value += arguments[1];
      case "value": 
        return value;
      default:
        throw new Error("No such message:" + command);
    }
  }
}
var account = Account(100)
account("deposit",100); // 200
account("withdraw",50); // 150
account("value"); // 150

So

  • a little cell, which has changable state
  • which accepts messages into its 'cell wall'

We've defined objects

And I'm here to say

It's an idea we abuse

It complicates our code

...and hides good ideas from us

What do objects imply?

  • EverythingIsAnObject
  • we need to track state
  • we model things in hierarchies
  • we need to hide data

Let's see how often that's true

...for web apps

'Controllers'

  • What the hell is a controller?
// locomotive js
var PhotosController = new Controller();
PhotosController.show = function() {
  // this._photo is "private", and not available in the view
  this.title = this._photo.title;
  this.description = this._photo.description;
  this.render();
};
PhotosController.destroy = function() {};
PhotosController.resize = function() {};

So much wrong

HTTP

The Hypertext Transfer Protocol (HTTP) is an application-level protocol for distributed, collaborative, hypermedia information systems. It is a generic, stateless, protocol

Stateless

  • OO is about state-ful objects
  • HTTP is (one of many) stateless protocols
  • so using objects to respond to a request is...

OO cruft

Solving its own 'problems'

PhotosController.show = function() {
  // this._photo is "private", and not available in the view

Where is the problem?

var ooHtml = new Template({controller: this, template: tpl})
  .render();
var html = markdown(template,data);

What the hell is a model?

The 'User'

Typical code in user model

  • validation
  • checking password
  • sessions
  • data associated with user
  • signup flow
  • triggering emails etc etc...

What is the common theme?

'Vaguely relates to a customer'

Modelling fallacy

  • it's not a simulation
  • our job here is: take HTTP request, load data, turn into HTML string

What's wrong with modelling

  • casuses coupling a heap of unrealated concerns
  • concepts from auth, domain, presentation...
  • we're writing code on computers, not a simulation

Where is its state useful?

Nowhere

Stateless

  • we're using HTTP
  • we don't keep a user object around between requests
  • we load all data per request: from DB, session etc

The 'module as object' pattern

Most 'models' are modules

  • just a namespace
  • but loads more complex
  • now has constructor dependencies

Detecting a hidden module

  • unrelated functions
  • accessing different fields
  • made harder to use as they need initialisation
  • some that don't access fields at all

EverythingIsAnObject, state everywhere: not so great

Hiding data

Is data scary?

Not really

Change is

Launching rockets

  • code that does things: DB, AJAX etc
  • vs code that computes
  • OO keeps both in same place

Data is very generic

Generic means wonderful abstractions

  • works on any container
  • map transforms each 'value' inside
  • contains say "is there a value like X?"
_.map      // [a] -> (a -> b) -> [b]
_.contains // [a] -> a -> Boolean

Let's make it specific

  • custom API for something simple
  • what has been abstracted?
function User() {}
User.prototype = {
  getName: function() {
  },
  fullName: function() {
  }
};

Why do we hide data at all?

  • if it changes

Let's avoid change

Data

  • simply taking data and presenting it is stateless: HTTP
  • immutability

Immutability

Mutate === change

  • mutate is just jargon for change
  • "oh the DNA has mutated" = it changed

Benefits

  • if I have the 16th of October, 2007 it stays that way
var someDate = new Date(Date.parse("2007/10/16"));
passDateSomewhere(someDate);

// what happens if...
var timestamp = Date.parse("1995/05/23");
releaseDateOfSomething.setTime(timestamp);


// somewhere else, screaming commences

Wouldn't this be terrible?

  • if I have the number 0.30000000000000004
var almostZeroPointThree = 0.1 + 0.2;
passNumber(almostZeroPointThree);

almostZeroPointThree.setValue(104)

// ARGH!

A date does not have state

  • a date is a value
  • values don't change: e.g Point(x,y), Query(string: x, options: {})

A rule for values

  • if you can't meaningfully say "oh which one?" about an instance
  • e.g "Oh which 4th of July 1987?"
  • it really should be a value

Objects are bad at values

Values for data structures

  • we can do the same with structures

Immutable list

function append(headValue,tailQueue) {
  var head = {};
  Object.defineProperties(head,{
    item: { value: headValue, writable: false },
    tail: { value: tailQueue, writable: false }
  })
  return head;
}
function vals(queue) {
  var items = [];
  while(queue) { items.push(queue.item); queue = queue.tail; }
  return items;
}

var a = append;
var q1 = append(2,append(1));
var q2 = append(3,q1);
var q3 = append(4,q1);
console.log(vals(q1),vals(q2),vals(q3)); // [2,1] [3,2,1] [4,2,1]

Use

  • we get undo immediately
  • can safely share structure without copy
var todos = undefined;
function addTodo(todo) {
  return append(todo,todos);
}
function undoAdd(todos) {
  return todos.tail;
}

todos = append("buy eggs",todos);
todos = append("buy cheers",todos);
todos = undoAdd(todos);
console.log(todos.item); // "buy eggs"

Instead of _.clone

  • try mori
  • jargon for the idea: "persistent data structures"

So nothing needs to change?

Obviously not true

We still need to store/hear about values

  • is user logged in
  • tell me when that changes

Events get us quite far

  • we're just re-rendering in response to new data
pubSub.on("login:change",renderLoginState);

Streams get us further

Streams

  • amazing for pure-data (e.g node)
gulp.task('scripts', function() {
  return gulp.src(paths.scripts)
    .pipe(coffee())
    .pipe(uglify())
    .pipe(concat('all.min.js'))
    .pipe(gulp.dest('build/js'));
});

Also in the browser

  • Bacon.js, RxJS
  • oh look, map again
var leftTurns = $(".turnLeft").asEventStream("click")
  .map(toEvent("turnLeft"));
  var rightTurns = $(".turnRight").asEventStream("click")
  .map(toEvent("turnRight"));

function toEvent(name) {
  return function() {
    return {type: name, at: Date.now()};
  }
}

var commands = leftTurns.merge(rightTurns);

commands.onValue(applyCommand);

But...

  • Still need to know current value
  • Any solution?

Properties

Login Logout
var logins = $(".login").asEventStream("click").map(true);
var logouts = $(".logout").asEventStream("click").map(false);


var loginState = logins.merge(logouts)
  .scan(false,function(_,loginState) { return loginState });

function not(x) { return !x }

loginState.assign($(".login"),"prop","disabled");
loginState.map(not).assign($(".logout"),"prop","disabled");

So... no more objects?

Actually objects are great

When they're used in the right places

e.g Promises

Hidden state and large API

  • many readers
  • contract: can't change state, only read
  • painful to do without objects

Other examples

  • buffers
  • Property in streams
  • shared state of something in time/hardware, not state of stuff we're modelling

I don't think 'objects suck'

I'm just saying they're abused

And they've blinded us to good ideas

Why did this happen?

Learning derived ideas, not originals with justifications

  • Design patterns
  • Inheritance
  • SOLID
  • DCI
  • Dependency injection containers

Far from 'best practice', many are painkillers for OO abuse

How to avoid this happening again?

Keep an eye on your ends, not means

  • don't be religious
  • "Oh no, a helper function" - ItsNotAnObject

Check your assumptions

  • don't take things on authority
  • try things out for yourself

Today's 'one right way' is tomorrow's mistake

Don't be an X-programmer, be a programmer

Any questions?

@timruffles

Creative commons work