Showing posts with label refactoring. Show all posts
Showing posts with label refactoring. Show all posts

Wednesday, September 2, 2015

Refactoring Express Router Middlewares

This past weekend I went back to an old comic to fix some bad code that it used. It had been bothering me for a long time. I knew it was not great when I made it, but since it was a bit of a rush-job (to capitalize on the sudden popularity of a dress color meme) I just lived with it. But no more! It had to be cleaned-up!

The problem with it was that the comic in question stored data about how people interacted with it, but never persisted that data to disk. Every time I re-started the comic application, the previous data was lost. No problem, I just put in a timer that wrote the data as a JSON string to disk every 30 minutes. When the application starts, it reads the file.

But when I was adding this stuff, it bothered me how all the code for the routes for this comic were scattered about in the main app.js file. They really should have been put in a separate file and configured as a router. Again, no problem. I moved the four routes into a separate router module and included it from app.js.

But then I noticed something else that bothered me. I had a bunch of router middlewares now that all used very repetitious blocks of code. Seemed like it was time to make more improvements.

The code originally looked like this.

//------------ set up routes for /data/*

app.use('/data', dataRoutes({
    express: express,
    auth: ensureAuthenticated,
    dataSource: cfact
}));

//------------ set up routes for /memeGen/*

app.use('/memeGen', memeRoutes({
    express: express,
    config: conf
}));

//------------ set up routes for /fb/*

app.use('/fb', fbRoutes({
    express: express,
    config: conf
}));

//------------ set up routes for /colors/*

app.use('/colors', colorRoutes({
    express: express
}));

//------------ set up routes for /images/*

app.use('/images', imgRoutes({
    express: express,
    auth: ensureAuthenticated,
    dataSource: cfact
}));

All the middlewares were passed a very similar object into their constructor, but it was not always identical. Some needed only a single item, some needed three. But they all overlapped. I decided to create the options object only once and pass it to all the middlewares. They could then use whatever they needed and ignore what they didn't. But instead of copying-and-pasting a block of code whenever I want to add another one, I created an array of their names and a function to set everything up. Now I have this:

var express = require('express');

var routers = ["data", "images", "memeGen", "colors", "fb"];

var app = express();

// ... other stuff ...

(function setupRouters() {

    var opts = {
            express: express,
            auth: ensureAuthenticated,
            dataSource: cfact,
            config: conf
    };

    routers.forEach(function(val) {

        try {
            console.log("loading router [" + val + "] ...");
            var r = require("./routers/" + val);
            app.use("/" + val, r(opts));
        } catch (e) {
            console.error("error loading router [" + val + "] - " + e);
        }

    });

})();

With the array of router names configured, I create and call a function which goes over that list and creates each router. It sets each one up with the path to match its name. It's now very easy for me to add new router middlewares.

I always enjoy a good refactoring. Much more than I enjoy taking a picture of myself (see today's comic).

Amphibian.com comic for 2 September 2015

Saturday, June 15, 2013

Hack It, then Refactor It

I've spent the last several days refactoring my OUYA game. It seems strange to spend so many hours working on the code and the great accomplishment is that it still works. I just felt the need to explain why I do this and how it works for me.

I believe that I am a very results-oriented person. Not to say that the ends always justify the means, but I recognized many years ago that getting a product working is much more important that making sure that all the T's are dotted and the I's are crossed. I've also been known to get forgiveness instead of permission on many occasions.

You can be writing the most elegant, perfect, optimized code in all the world but if no one ever sees or uses the product then what have you really accomplished? This is true for everything, not just software products. Once (1998) I decided to replace the stock 3.8L V6 engine in my 1989 Mercury Cougar with a 5.8L small-block V8. I had 3 months until I needed the car to run again so I could go back to college. As far as we could tell, this feat of automotive engineering had never before been accomplished. The engine did not physically fit in the car. But I wasn't building it to put on display, so we hacked and bent and cut and torched and pounded with a hammer. It wasn't pretty, but it ran. I drove it back to college and almost every day while I was there. I also spent the next 3 years fine-tuning it. Parts were replaced with new ones as they were fabricated. The more powerful engine totally blew out the rest of the drive-train in time, but that got fixed as well. I could have spent those 3 years making it perfect before it ever left the garage, but it was much better to be driving it all that time.

Basically, I learned that success means shipping a product. So how does this relate to hacking and refactoring software? It's pretty much the same thing.

I wanted to get stuff on the screen of the OUYA and get things moving around with the controller as quickly as I could. I needed to see it working, even if the code was less than perfect. I needed to see those results so that I could continue. Letting it "sit in the garage" for too long would just discourage me and I would lose interest.

However, I know better than to leave the code that way for too long. If I am going to build a complete game, my code has to be structured such that it can be built on. It can't be brittle. It has to use the proper design patterns. So every few weeks, I stop adding new features and refactor. I clean up the ugly code and make sure I have a solid foundation before building more. But in the mean time I also have something that my kids and I can play (You don't have to release your product to everyone to count it as a release. My Cougar only had one user, after all).

It's just what works for me. And it works for other people as well. Check out the works of Kent Beck and Martin Fowler. They've written lots of stuff on design patterns and refactoring code.

Also, read this paper.

And as for my Cougar, I no longer drive it but it taught me a lot about successful engineering projects.