Mozilla

Black Box Driven Development in JavaScript

Sooner or later every developer finds the beauty of the design patterns. Also, sooner or later the developer finds that most of the patterns are not applicable in their pure format. Very often we use variations. We change the well-known definitions to fit in our use cases. I know that we (the programmers) like buzzwords. Here is a new one – Black Box Driven Development or simply BBDD. I started applying the concept before a couple of months, and I could say that the results are promising. After finishing several projects, I started seeing the good practices and formed three principles.

What is a black box?

Before to go with the principles of the BBDD let’s see what is meant by a black box. According to Wikipedia:

In science and engineering, a black box is a device, system or object which can be viewed in terms of its input, output and transfer characteristics without any knowledge of its internal workings.

In programming, every piece of code that accepts input, performs actions and returns an output could be considered as a black box. In JavaScript, we could apply the concept easily by using a function. For example:

var Box = function(a, b) {
    var result = a + b;
    return result;
}

This is the simplest version of a BBDD unit. It is a box that performs an operation and returns output immediately. However, very often we need something else. We need continuous interaction with the box. This is another kind of box that I use to call living black box.

var Box = function(a, b) {
    var api = {
        calculate: function() {
            return a + b;
        }
    };
    return api;
}

We have an API containing all the public functions of the box. It is identical to the revealing module pattern. The most important characteristic of this pattern is that it brings encapsulation. We have a clear separation of the public and private objects.

Now that we know what a black box is, let’s check out the three principles of BBDD.

Principle 1: Modulize everything

Every piece of logic should exist as an independent module. In other words – a black box. In the beginning of the development cycle it is a little bit difficult to recognize these pieces. Spending too much time in architecting the application without having even a line of code may not produce good results. The approach that works involves coding. We should sketch the application and even make part of it. Once we have something we could start thinking about black boxing it. It is also much easier to jump into the code and make something without thinking if it is wrong or right. The key is to refactor the implementation till you feel that it is good enough.

Let’s take the following example:

$(document).ready(function() {
    if(window.localStorage) {
        var products = window.localStorage.getItem('products') || [], content = '';
        for(var i=0; i<products.length; i++) {
            content += products[i].name + '<br />';
        }
        $('.content').html(content);
    } else {
        $('.error').css('display', 'block');
        $('.error').html('Error! Local storage is not supported.')
    }
});

We are getting an array called products from the local storage of the browser. If the browser does not support local storage, then we show a simple error message.

The code as it is, is fine, and it works. However, there are several responsibilities that are merged into a single function. The first optimization that we have to do is to form a good entry point of our code. Sending just a newly defined closure to the $(document).ready is not flexible. What if we want to delay the execution of our initial code or run it in a different way. The snippet above could be transformed to the following:

var App = function() {
    var api = {};
    api.init = function() {
        if(window.localStorage) {
            var products = window.localStorage.getItem('products') || [], content = '';
            for(var i=0; i<products.length; i++) {
                content += products[i].name + '<br />';
            }
            $('.content').html(content);
        } else {
            $('.error').css('display', 'block');
            $('.error').html('Error! Local storage is not supported.');
        }
        return api;
    }
    return api;
}
 
var application = App();
$(document).ready(application.init);

Now, we have better control over the bootstrapping.

The source of our data at the moment is the local storage of the browser. However, we may need to fetch the products from a database or simply use a mock-up. It makes sense to extract this part of the code:

var Storage = function() {
    var api = {};
    api.exists = function() {
        return !!window && !!window.localStorage;
    };
    api.get = function() {
        return window.localStorage.getItem('products') || [];
    }
    return api;
}

We have two other operations that could form another box – setting HTML content and show an element. Let’s create a module that will handle the DOM interaction.

var DOM = function(selector) {
    var api = {}, el;
    var element = function() {
        if(!el) {
            el = $(selector);
            if(el.length == 0) {
                throw new Error('There is no element matching "' + selector + '".');
            }
        }
        return el;
    }
    api.content = function(html) {
        element().html(html);
        return api;
    }
    api.show = function() {
        element().css('display', 'block');
        return api;
    }
    return api;
}

The code is doing the same thing as in the first version. However, we have a test function element that checks if the passed selector matches anything in the DOM tree. We are also black boxing the jQuery element that makes our code much more flexible. Imagine that we decide to remove jQuery. The DOM operations are hidden in this module. It is worth nothing to edit it and start using vanilla JavaScript for example or some other library. If we stay with the old variant, we will probably go through the whole code base replacing code snippets.

Here is the transformed script. A new version that uses the modules that we’ve created above:

var App = function() {
    var api = {},
        storage = Storage(),
        c = DOM('.content'),
        e = DOM('.error');
    api.init = function() {
        if(storage.exists()) {
            var products = storage.get(), content = '';
            for(var i=0; i<products.length; i++) {
                content += products[i].name + '<br />';
            }
            c.content(content);
        } else {
            e.content('Error! Local storage is not supported.').show();
        }
        return api;
    }
    return api;
}

Notice that we have separation of responsibilities. We have objects that play roles. It is easier and much more interesting to work with such codebase.

Principle 2: Expose only public methods

What makes the black box valuable is the fact that it hides the complexity. The programmer should expose only methods (or properties) that are needed. All the other functions that are used for internal processes should be private.

Let’s get the DOM module above:

var DOM = function(selector) {
    var api = {}, el;
    var element = function() {}
    api.content = function(html) {}
    api.show = function() {}
    return api;
}

When a developer uses our class, he is interested in two things – changing the content and showing a DOM element. He should not think about validations or change CSS properties. In our example, there are private variable el and private function element. They are hidden from the outside world.

Principle 3: Use composition over inheritance

One of the popular ways to inherit classes in JavaScript uses the prototype chain. In the following snippet, we have class A that is inherited by class C:

function A(){};
A.prototype.someMethod = function(){};
 
function C(){};
C.prototype = new A();
C.prototype.constructor = C;

However, if we use the revealing module pattern, it makes sense to use composition. It is because we are dealing with objects and not functions (* in fact the functions in JavaScript are also objects). Let’s say that we have a box that implements the observer pattern, and we want to extend it.

var Observer = function() {
    var api = {}, listeners = {};
    api.on = function(event, handler) {};
    api.off = function(event, handler) {};
    api.dispatch = function(event) {};
    return api;
}
 
var Logic = function() {
    var api = Observer();
    api.customMethod = function() {};
    return api;
}

We get the required functionality by assigning an initial value to the api variable. We should notice that every class that uses this technique receives a brand new observer object so there is no way to produce collisions.

Summary

Black box driven development is a nice way to architect your applications. It provides encapsulation and flexibility. BBDD comes with a simple module definition that helps organizing big projects (and teams). I saw how several developers worked on the same project, and they all built their black boxes independently.

35 comments

Comments are now closed.

  1. m wrote on August 27th, 2014 at 06:27:

    Please don’t use this (anti)pattern below in examples:
    C.prototype = new A();
    Why preparing a prototype of one class should call constructor of another class (with all possible side-effects)?
    IMHO this (in year 2014) would be better:
    C.prototype = Object.create(A.prototype);

    1. Krasimir Tsonev wrote on August 27th, 2014 at 07:35:

      Note taken. I’ll have this in mind.

    2. Adam Freidin wrote on August 27th, 2014 at 07:46:

      And in the next line,…

      C.prototype.constructor = A;

      shouldn’t that be

      C.prototype.constructor = C;

      ?

      1. Krasimir Tsonev wrote on August 27th, 2014 at 08:33:

        Hi Adam, you are right.
        @RobertNyman: can you please fix it.
        C.prototype.constructor = A;
        to
        C.prototype.constructor = C;

        1. Robert Nyman [Editor] wrote on August 27th, 2014 at 08:37:

          Yep, changed it!

    3. Luke wrote on August 27th, 2014 at 20:27:

      What does it mean to “create” a prototype of another class, for another class’s prototype? How is it better?

      The MDN article has some examples of the method used in this article, and it seems to be a clean and easy-to-understand pattern:
      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype#Examples

  2. Pavel Ivanov wrote on August 27th, 2014 at 06:31:

    Nice one :) good job guys :)

  3. Norman Kabir wrote on August 27th, 2014 at 06:33:

    Excellent article. There’s a missing ‘l’ in “Principle 2″.

    1. Robert Nyman [Editor] wrote on August 27th, 2014 at 06:50:

      Thanks! Fixed.

  4. Felix Hammerl wrote on August 27th, 2014 at 06:44:

    Using a module pattern to create an instance is a nice way to hide private function, but there’s a downside to it. Attaching instance methods to a prototype is faster and more memory efficient, also that’s what the prototype system is for. In your case, every single instance has its very own function instead of every instance using the same method.

    In my experience, prefixing internally used methods with an underscore is good enough, as the underscore says: “Direct usage of this function is discouraged”. Should anyone break this rule, it will surface in code reviews. Readability is king and I don’t know if this actually easier to understand than this._foo(), as it is clearer what’s going on. Also, “new” is preferable to convenience constructors. There are frameworks for other languages out there that had convenience constructors, but later opined against it…

    1. Krasimir Tsonev wrote on August 27th, 2014 at 09:02:

      What a great comment. Thanks Felix. I’m in endless debate with myself what exactly to use. The prototype-like architecture or the revealing module pattern. I still can’t decide to be honest.

  5. Ted k’ wrote on August 27th, 2014 at 06:55:

    Nice!!! excellent!!!

  6. Erick Mendoza wrote on August 27th, 2014 at 07:36:

    Really great reading! This is something we should have permanently in our mindset as we develop any kind of JavaScript applications. I haven’t thought on how to apply the Principle #3, and now I see how convenient it is! Thank you.

    1. Krasimir Tsonev wrote on August 27th, 2014 at 08:35:

      Hi Erick,
      I’m very glad that you find the article interesting. It is a concept that I’m using last few years. Finally I found some time to define and document it :)

  7. Dave wrote on August 27th, 2014 at 07:48:

    Isn’t this approach basically what unit testing is about? What is the difference?

    1. Krasimir Tsonev wrote on August 27th, 2014 at 08:40:

      Exactly! The thing that I call Black Box Driven Development encourage the writing of units. I’m using test driven development for several years and when I started I was writing integration tests. Not unit tests because I normally didn’t have units. However, once I started following the concept described in this article the things changed.

  8. Marcos Rodrigues wrote on August 27th, 2014 at 08:24:

    Nice examples of refactoring, although I’d call it simply good object-oriented design.
    Some changes I’d suggest:
    – allow an argument for which key to fetch on the Storage class;
    – separate data-fetching, data-presentation, and view concerns.

    1. Krasimir Tsonev wrote on August 27th, 2014 at 08:58:

      +1 for the suggestions. I really like the idea about the key to fetch on the local storage. The second one also brings some great ideas for improvements. I see a little bit of *speculative generality* (http://goo.gl/yth5bX) there but overall they are good suggestions.

      1. Marcos Rodrigues wrote on August 27th, 2014 at 09:37:

        Thanks! Yeah, I thought that, regarding this simple example, these suggestions would be an overdesign, but since the principle proposed should be applicable to large-scale projects, these could be handy.

        Still, I think a data-presentation class could be used to improve readability even in this case. Here an idea:

        var products = storage.get();
        c.content(ProductsPresenter(products).list());

        1. Krasimir Tsonev wrote on August 27th, 2014 at 14:10:

          I agree. It makes sense.

  9. Rene wrote on August 27th, 2014 at 08:57:

    Nice article. :)

    But I would add a “addMethod” method to the api object instead of adding properties this way: api.customMethod = function() { … };

  10. azendal wrote on August 27th, 2014 at 09:49:

    Black bloxing does not only help on coding, it also helps while creating specifications for your programs (before creating them) to create simple mental model of the program, its responsibilities and results. This can be used to run “test” on the modeling stage of the development.

  11. AutomateAllTheThings wrote on August 27th, 2014 at 18:54:

    I’ve been advocating this style of development for the past 6 months, without ever having a good name for it! We were calling it “encapsulated design”, but now we much prefer your term.

    I can’t say enough about the benefits of this methodology. I think I’ll do a write-up about our setup, now that I know what to call it!

    1. Krasimir Tsonev wrote on August 28th, 2014 at 03:49:

      I’m glad that you like the name :) I also spent few weeks wondering how to call it :)

  12. Pulak wrote on August 27th, 2014 at 19:34:

    Very good article to read.

    The important point that I have understood from the article is modularising your codebase. Also, write your code in such a way that it is agnostic of any external library like jQuery.

    In “Principle 1″ code example, why are you returning api from api.int, api.content, api.show functions when you are already returning api from the end of the function?

    1. Krasimir Tsonev wrote on August 28th, 2014 at 03:52:

      In general, it is a good practice to return something (I mean every function). The more practical explanation would be that we can chain the methods like for example:
      .init().content().show()

  13. David Hanson wrote on August 27th, 2014 at 22:17:

    We dont need BBDD…..we already have SOLID which is even better.

    1. Krasimir Tsonev wrote on August 28th, 2014 at 03:55:

      +1 for mentioning SOLID. It is another approach that I like. In some cases I wasn’t able to apply all the principles but there are few great principles listed.

      1. David Hanson wrote on August 28th, 2014 at 05:48:

        So interestingly, if you develop with TypeScript and Angular you can do full SOLID

        TypeScript gives your interfaces and Angular gives you dependency injection and IoC,

  14. TedvG wrote on August 27th, 2014 at 23:57:

    Nice that you found out about modular principles and design :o) But more seriously: You can prevent all this hassle by using a modular object oriented language in the first place, as objects are modular and black boxes right away. E.g use Dart instead, it compiles also to (faster) Javascript. Javascript does not really lend itself to modular programming, it was never meant that way. Have fun

    1. Krasimir Tsonev wrote on August 28th, 2014 at 03:56:

      Hi TedvG,
      thank you for the comment. I’ll definitely check Dart in the upcoming months.

  15. Matt Perdeck wrote on August 28th, 2014 at 03:14:

    How is “Black Box Driven Development” different from object oriented programming?

    You might want to check out TypeScript. It makes creating object, inheritance, private methods, etc. much easier than pure JavaScript.

    1. Krasimir Tsonev wrote on August 28th, 2014 at 06:38:

      Hi Matt,

      thanks for the comment. Actually in BBDD I used OOP. I mean I’m not trying to invent a new thing. I just defined my workflow with a new buzzword :)

      I’m not a big fan of TypeScript-liked languages. That’s because I’m trying to use as less abstractions as possible in every level of my development cycle. It is the same with the CSS preprocessors. I even create one but I don’t like to use them anymore. This is of course personal opinion and depends on a lot of factors.

  16. Acaz wrote on August 28th, 2014 at 09:43:

    One more buzz word for a good object oriented code.

  17. Loops wrote on September 4th, 2014 at 01:08:

    Before to use black box, you must keep in mind that : Does anybody, anytime, may need to change a little thing on the private stuff ? If yes, do not use black box.

    In prototype oriented programming, you can overwrote a prototype method to make all new instances using the new method. With black box, the prototype ability of the class is lost in space and time. In fact, it’s like saying: I will use my bike like a “trottinette”.

Comments are closed for this article.