8
\$\begingroup\$

Practicing Prototypal Code

I'm focusing on practicing a very prototypal structure with my code (non library oriented) to begin learning to reuse large portions of my application infrastructures where applicable in future projects. With this in mind, I don't have experience with prototypal programming before now, and would like to ensure that I'm not misusing the strategy.

Code Summary

The following server side JavaScript code has the purpose of handling a large number of Socket.io events in a modulated, prototypal way. My goal is code that is readable, but also reusable. I feel as if I may be sacrificing the clarity of my code in order to utilize a prototype for the handlers; it eliminates the inclusion of duplicate code in each handler, most of which helps with debug logging, but it may make the code less readable. I'm interested in learning whether or not this is an unavoidable aspect of using prototypes, or if there are ways that I may use prototypes without diminishing readability.

Review

Requesting general improvements, best practices, code readability, etc.

Most importantly, I'm interested in feedback on the comparison between the prototypal and non-prototypal event handling, and whether or not I'm using that strategy correctly.

Commenting

Another thing I wouldn't mind reviews to touch on is my style of commenting. I prefer to let the code speak for itself, but do like to keep each section and sub-section titled. I use all-caps on my in-line (sub-section) comments because I want them to stand out clearly when scrolling through.

Perhaps these practices should be changed. If so, please say so. Also, I notice that in reviews where large sections of code are involved, people will comment a paragraph of explanation / functionality description of that section. Is that ever a good practice?

Non-Prototypal Handlers

control_center.js

/* Server Control Center */

//REQUIRE CUSTOM MODULES

var debug         = new(require("custom_modules/debug"))("Control Center");
var socketHandler = require("custom_modules/socket_handler");

//APP INITIALIZE

debug.log("Application initialized...");

settings.js

module.exports = {

    "debug" : true

}

debug.js

module.exports = function debug(name) {

    this.settings = require('custom_modules/settings');
    this.name     = name;
    this.log      = function (message) {

        if (this.settings.debug === true) {

            console.log("[Debug][" + name + "]: " + message);

        }

    };

};

socket_handler.js

/* Socket Handler */

module.exports = function () {

    //REQUIRE NPM MODULES

    var socket = require('socket.io')(4546);

    //REQUIRE CUSTOM MODULES

    var debug    = new(require("custom_modules/debug"))("Sale Handler");

    var login    = require("custom_modules/login");
    var register = require("custom_modules/register");

    socket.on("connection", function (client) {

        //HANDLE CONNECTION

        debug.log("Client connected to socket!");

        // HANDLE CUSTOM EVENTS

        login.handle(client);
        register.handle(client);

    });

};

register.js

/* Register Handler */

//DEFINE MODULE EXPORTS

module.exports.handle = function (client) {

    //REQUIRE CUSTOM MODULES

    var debug = new(require("custom_modules/debug"))("Register Handler");

    //HANDLE EVENTS

    debug.log("Initialized");

    client.on("register", function (data) {

        debug.log("Handling registration...");

    });

};

login.js

/* Login Handler */

//DEFINE MODULE EXPORTS

module.exports.handle = function (client) {

    //REQUIRE CUSTOM MODULES

    var debug = new(require("custom_modules/debug"))("Sale Handler");

    //HANDLE EVENTS

    debug.log("Initialized");

    client.on("login", function (data) {

        debug.log("Handling login...");

    });

};

This is the code in a modulated fashion, without the utilization of prototypes for the Socket.io handlers. (I'm using a prototype for the debugging object only).

Prototypal Handlers

control_center.js , settings.js , debug.js , socket_handler.js === above

handler.js

/* Handler */

module.exports = function (client, handlerName, handlerFunc) {

    //DEFINE MODULE EXPORTS

    this.handle = function (client) {

        //REQUIRE CUSTOM MODULES

        this.debug = new(require("custom_modules/debug"))(handlerName);

        //HANDLE EVENTS

        this.debug.log("Initialized");

        handlerFunc();

    };

};

login.js

/* Login Handler */

module.exports.handle = new(require("custom_modules/handler"))(client, 
"Login Handler", function () {

    client.on("login", function (data) {

        debug.log("Handling login...");

    });

});

register.js

/* Register Handler */

module.exports.handle = new(require("custom_modules/handler"))(client, 
"Register Handler", function () {

    client.on("register", function (data) {

        debug.log("Handling registration...");

    });

});

This is the code in a modulated fashion, with the utilization of prototypes for the Socket.io handlers and debugging. The handler prototype causes the code to have a slightly larger net length with only 3 events being handled, but realistically I'll be handling 20+ events. Ultimately, the prototype pays off in code size, and allows easy re-use of those debugging functions in later projects, but it does seem to damage readability of the code.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I think that code review must be light on JavaScript reviewers. I posted some code a while back that got 8 up-votes and no review. 12 hours after posting this, not so much as a comment. \$\endgroup\$
    – user73428
    Commented Jul 19, 2014 at 14:15
  • \$\begingroup\$ Part of the problem is that 99% of the JavaScript people write is front-end. I know what Node.js is, but the only time I use it is for development tooling (Grunt, JSHint, QUnit). \$\endgroup\$ Commented Jul 20, 2014 at 22:21
  • 4
    \$\begingroup\$ @DavidHarkness I'll have to recruit a few node.js devs from SO.. \$\endgroup\$
    – user73428
    Commented Jul 20, 2014 at 22:24

1 Answer 1

7
+50
\$\begingroup\$

The Basics

As you said, I think as each handler grows in size and the base handler adds more common features, the prototypes will pay off in spades.

Honestly, the biggest readability issue for me is the require-and-instantiate style you're using. Is this common? I haven't seen it in any of the code for the tools I mentioned in my comment above.

Compare

this.debug = new(require("custom_modules/debug"))(handlerName);

with

var Debug = require("custom_modules/debug");

...

this.debug = new Debug(handlerName);

There are a couple bugs, likely from writing code in the browser. :)

  • The handler constructor should not accept a client parameter. It should be moved to each handler's callback function instead.
  • The callbacks must refer to debug as this.debug.

A Little Style

I have a few other issues with the code in general:

  • Testing settings.debug === true seems overly-restrictive. Do you really want to disable debugging if they assign 1 instead of true?

  • I'd rather see proper JSDoc comments on properties and methods instead of "section comments".

  • handle should not instantiate a new logger instance for every client. This should be created once for each type of handler and stored in the constructor.

  • Exporting singleton instances of the handlers will make testing difficult if you need multiple instances of a single handler. Granted, they'll probably remain stateless which would allow singletons, but it just rubs me wrong. I would export the constructor instead and force clients to use new themselves.

  • Too

    much

    whitespace.

    Use blank lines to logically group related blocks. If they are so long as to need blank lines inside, refactor.

  • While it looks really nice when assignments are all lined up on the =, it is a PITA to maintain and easy to miss when you rename a variable in a file. I find it's just not worth it in the end.

Where Are the Prototypes?

The handlers don't seem to be making use of prototypes or the prototype chain at all. The constructor exported by handler.js is merely a factory method. Nothing is assigned to its prototype to be inherited by the subclasses. Instead, it builds a new object from scratch each time. Given that both debug and handle require state about the specific handler, I don't see an easy way around this.

As it stands now, I would probably rewrite handler.js as a factory method. It still returns an object

handler.js

(function () {
    var Debug = require('custom_modules/debug');

    module.exports = function (name, callback) {
        return {
            debug: new Debug(name);
            handle: function (client) {
                this.debug.log("Initialized");
                callback(client);
            }
        };
    };
}());

Creating a new handler is pretty much the same.

login.js

(function () {
    var handler = require("custom_modules/handler");

    module.exports = handler("Login Handler", function (client) {
        client.on("login", function (data) {
            this.debug.log("Handling login...");
        });
    });
});

For these to be truly prototypal, you need to assign some properties to the constructor's prototype. But this case is very unsuited to inheritance in general. Nothing is shared by the subclasses except a single call to a per-instance logger.

Bring in the Clones Prototypes

To demonstrate how it would look using prototypes, assume that debug.js exports a function to be shared by all components, and handle will call a template method in the subclass instead of accepting a callback in the constructor. This would be more beneficial if it had to do a bunch of common setup work and provided more methods useful to the subclasses.

Warning: This code does not stand well on its own. It is way too complicated for the tiny amount of work it does now. You can alleviate the verbosity by using a bit of sugar such as Prototype or John Resig's Simple JavaScript Inheritance.

handler.js

(function () {
    var debug = require('custom_modules/debug');
    var Handler = function () {};

    Handler.prototype.debug = function (message) {
        debug(this.name + " - " + message);
    };

    Handler.prototype.handle = function (name, callback) {
        this.debug(this.name + " - Initialized");
        this.handleImpl(client);
    };

    module.exports = Handler;
}());

login.js

(function () {
    var Handler = require('custom_modules/handler');
    var LoginHandler = function () {
        this.name = "Login Handler";
    };

    LoginHandler.prototype = new Handler;

    LoginHandler.prototype.handleImpl = function (client) {
        client.on("login", function (data) {
            this.debug("Handling login...");
        });
    };

    module.exports = LoginHandler;
}());

Ick! Even with generous whitespace, this is difficult to read. Here's the same code using Resig's class.js:

handler.js

(function () {
    var Class = require('class');
    var debug = require('custom_modules/debug');

    var Handler = Class.extend({
        debug: function (message) {
            debug(this.name + " - " + message);
        },

        init: function (name) {
            this.name = name;
        },

        handle: function (client) {
            this.debug(this.name + " - Initialized");
            this.handleImpl(client);
        }
    });

    module.exports = Handler;
}());

login.js

(function () {
    var Handler = require('custom_modules/handler');

    var LoginHandler = Handler.extend({
        init: function () {
            this._super("Login Handler");
        },

        handleImpl: function (client) {
            client.on("login", function (data) {
                this.debug("Handling login...");
            });
        }
    });

    module.exports = LoginHandler;
}());

This is definitely a lot easier to read, especially as the length increases. It also allows proper JSDoc comments (with some minor finagling), calling overridden methods in the superclass, chaining constructors, etc.

\$\endgroup\$
4
  • \$\begingroup\$ +1 and thank you. It appears, even after days of study, I'm apparently confused on what a JavaScript prototype is. Is was my understanding that function handler(){ /* Assign methods and properties for instances to inherit */ } ... login.handler = new handler() was the use of prototypes in JS. \$\endgroup\$
    – user73428
    Commented Jul 21, 2014 at 1:17
  • \$\begingroup\$ MDN's page on the JavaScript Prototype Chain covers the basics, and there are quite a few articles that give examples on how to take advantage of it. \$\endgroup\$ Commented Jul 21, 2014 at 2:27
  • 1
    \$\begingroup\$ I read that page earlier this week. Also some even better articles on the subject. By "days of study" I meant "days of studying JavaScript prototypes" \$\endgroup\$
    – user73428
    Commented Jul 21, 2014 at 2:36
  • \$\begingroup\$ After utilizing every pointer offered here, my code looks much more readable and standard. Excellent help with the prototypical aspect as well. Also, I produced a pure formatting review for another question here, without knowing a thing about best practices before hand. This article by Crockford walked me through it completely. Huge improvements in just 2 days of being here. \$\endgroup\$
    – user73428
    Commented Jul 24, 2014 at 22:12