[ /js/proxies/stub ]

A lot of programmers put a lot of energy into optimizing things like how quickly they can type code (autocompletion), or how long it takes for some algorithm to complete on some weird platform (performance optimization).

I generally find that the biggest obstacle to my productivity is how fast I am able to debug a system. That often includes learning new systems which have unspecified behaviour.

Proxies

Proxies are a language feature in Javascript that are still unsupported in IE11:

Can I use proxies?

They're a powerful enough feature that there isn't really a suitable polyfill. As such, if you have to support internet explorer, which apparently still has 27.5% of the world's market share, you can't really rely on proxies.

Fortunately, that doesn't mean you can't use it when exploring new systems and debugging.

Stubs

First we should cover a bit about what proxies do. Proxies let you hook into some very basic features of Javascript.

The proxy defined below has methods which override the default getter and setter methods (for every property).

This particular proxy pattern proxies a function, and overrides only two methods (set and get).

var stub = function () {
    var p;
    var t = function(){
        return p;
    };
    p = new Proxy(t, {
        set: function (o, k, v) {
            o[k] = v;
            return true;
        }, get: function (o, k) {
            return typeof(o[k]) !== 'undefined'? o[k]: p;
        }
    });
    return p;
};
  • If you set a value, it will remember it.
  • If you try to get a value, it will return it (if it exists).
  • If it doesn't exist, it will return the proxy

For an example of how you might use this:

// define a 'stub' proxy
var P = stub();

// access a property
console.log(P.x); // returns P

// access a nested property
console.log(P.turtle.turtle); // turtles all the way down

// what if one of the properties is called as a method?
console.log(P.pewpew()); // it's a method that returns P

// etc
console.log(P.pewpew().bangbang().lolol()); // P

// as you can see
console.log(P.anything === P.anything_else().you_can_imagine());

// but things can change if you try to set values
P.set_value = null;

// OOPS
P.set_value(); // TypeError: P.set_value is not a function

Practical use cases

Inextricable logic and UI

You have a block of code that's roughly divided into two parts. There's code for logic, and code for UI, and they were supposed to be totally separate.

Unfortunately, there were some really short deadlines while it was being written, and things were done the fastest way possible. As it turned out, the UI code receives a reference to the logic module, and calls a bunch of its methods. Likewise, the logic module has a reference to the UI module, and it calls a bunch of its methods too.

Once again, you're on a really short timeline, but you don't have to ship a finished product, you just have to make it through a demo.

You want to keep the logic, but throw away the UI and expose an API for the logic. You could hunt through the logic module's source, and stub all the references to the UI manually, but that would take time you don't have.

So, you decide to use a proxy:

You find that one place where the UI is first referenced, and you replace it with a stub:

UI = stub();

Suddenly, the UI goes away, but whenever it's referenced, instead of the code blowing up, it just keeps going. There might be a few parts where this doesn't work quite as well as expected, but it might help you get through your demo.

The mutated API

Suppose you're working with some library that was once supposed to do something really simple. Unfortunately, its use case expanded bit by bit. It was used by many people, and so old APIs could never be deprecated. New APIs were introduced alongside the old ones.

It can be initialized with one or two values, and it has sensible defaults, but you can also initialize it with an object. The parameters in this object are poorly documented, and their names come in a variety of flavours (camelCase, snake_case, horribleMonster_hybrid_Case).

Even worse, you're forced to use a forked version of this library which was compiled and minified, so it's not easy to just read the code. This might sound ridiculous and contrived, but I know a lot of developers have been in situations like this (myself included).

// make a noisy stub that tells you everything it does
var noisy = function (label) {
    var p;
    var t = function(){
        return p;
    };
    p = new Proxy(t, {
        set: function (o, k, v) {
            console.error(label + 'set[%s] => %s', k, v);
            o[k] = v;
            return true;
        }, get: function (o, k) {
            console.error(label + 'get[%s] (%s)', k, o[k]);
            return typeof(o[k]) !== 'undefined'? o[k]: p;
        }
    });
    return p;
};

/*  Pass in separate instances of a noisy stub
    for each argument.
    check your logs to see all
    the sets and gets for each one. */
$.api_call(noisy('a'), noisy('b'), noisy('c'));

In closing

Proxies can provide a clear view into the internals of otherwise obscure codebases.

Getting the information you want takes some customization.

Setter and getter methods are very basic, and there's a lot more to the proxy API.

If you have more ideas or use cases, I'd like to hear them. Get in touch.