8
\$\begingroup\$

This is code for performing a calculation that should be repeated with different levels and temperatures. I am concerned about the organization of this code as I feel it may contain too many helper functions and seems a little unnecessarily complicated. Only the getCharge() method needs to be exposed to the browser.

There is sample usage of this code after the initial definition.

var hprVessel = function (level, temp) {
/**
* @params {Number} level - Percentage load for HPR 
* @params {Number} temp - temperature of fluid
*/
    var liquidCoefficients = [
        83.4485407734612,
        -0.111449280945489,
        0.000100022560084721,
        -0.00000206240663899928
    ];

    var vaporCoefficients = [
        0.895329190066827, 
        0.016635773286873, 
        -0.0000894924196507015, 
        0.00000206296636296276
    ];

    var vessel = {
        // Vessel-specific Params
        diameter: 1.9583 * level,
        height: 0.2937 * level,
        length: 15.9583 * level,

        // Calculations
        getLiquidVol: function () {     
            var r = this.diameter/2;
            var sectionArea = Math.pow(r, 2) * Math.acos((r - this.height) / r);
            var triangleArea = Math.sqrt(Math.pow(r, 2) - Math.pow((r-this.height), 2))*(r-this.height);

            return (this.length * sectionArea * triangleArea);
        },
        getVesselVol: function () {
            return (Math.PI * Math.pow(this.diameter, 2) * this.length);
        },
        getVaporVol: function () {
            return (vessel.getVesselVol() - vessel.getLiquidVol());
        },
        getLiquidCharge: function (temp) {
            return (vessel.getLiquidVol() * vessel.calcDensity(liquidCoefficients, temp));
        },
        getVaporCharge: function (temp) {
            return (vessel.getVaporVol() * vessel.calcDensity(vaporCoefficients, temp));
        },
        chargeHPR: function (temp) {
            return (1000 + vessel.getLiquidCharge(temp) + vessel.getVaporCharge(temp));
        },

        // Helper functions
        calcDensity: function (regressionCoefs, temp) {
            var liqDensityArray = regressionCoefs.map(function (coef) {
                return coef * Math.pow(temp, regressionCoefs.indexOf(coef));
            });

            return vessel.sum(liqDensityArray);
        },
        sum: function (arr) {
            return arr.reduce( function (prev, current) {
                return (prev + current);
            }
        }
    };

    return {
        getCharge: function() {
            return vessel.chargeHPR(temp);
        }
    };
}

var vessel = hprVessel(1, -30);
var result = vessel.getCharge();
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$
diameter: 1.9583 * level,
height: 0.2937 * level,
length: 15.9583 * level,

It might be wise to actually put these numbers in a constant and naming them appropriately. That way, in the long run, you won't forget what these values are.

return (/* stuff */);

I notice you also use parens in your returns. Unless they're a personal preference, they're actually optional (What do you think this is? PHP? :P)

Now this code appears to benefit more using prototypal inheritance rather than a factory pattern. That's because every time you call hprVessel, it builds an object of functions. With prototypal inheritance, your instance will only hold instance properties, aka "vessel-specific data". The functions will be shared across your vessel instances via the prototype.

Here's a short example:

function HPRVessel(level, temp){

  // Instance data, aka vessel-specific data
  this.temp = temp;
  this.level = level;
  this.diameter = HPRVessel.SOME_CONSTANT * level,
  this.height = HPRVessel.ANOTHER_COSNTANT * level,
  this.length = HPRVessel.YET_ANOTHER_CONSTANT * level,

}

// pseudo-static properties
// Here we append general HPRVessel constants
HPRVessel.liquidCoefficients = [
  83.4485407734612,
  -0.111449280945489,
  0.000100022560084721,
  -0.00000206240663899928
];

HPRVessel.SOME_CONSTANT = 1.9583
HPRVessel.ANOTHER_COSNTANT = 0.2937
HPRVessel.YET_ANOTHER_CONSTANT = 15.9583

// Here, we define shared stuff. Best practice is to just put methods on it.
HPRVessel.prototype.getCharge = function(){
  // Anything on the instance is accessible via `this`
  return this._chargeHPR(this.temp);
};

// Since there's no "private", we use the convention of prefixing `_` to tell
// curious people not to call these methods directly
HPRVessel.prototype._chargeHPR= function(){
  return (1000 + this._getLiquidCharge(this.temp) + this._getVaporCharge(this.temp));
};

// Usage:
var vessel = new HPRVessel(1, -30);
var result = vessel.getCharge();
\$\endgroup\$
3
  • \$\begingroup\$ Good suggestions! If I understand prototypes correctly, should my code be more performant if I wrap the methods with a prototype since they will only be declared once across all instances? \$\endgroup\$
    – treeblah
    Commented Jan 5, 2016 at 0:27
  • 1
    \$\begingroup\$ It goes a little deeper than just sharing methods. Modern browsers do all kinds of Chicken Voodoo Magic with Just In Time compilers. For instance, many browsers will actually create a compiled machine code level class to represent your object in JavaScript getting you near native execution times. Code using function closures is harder to optimize. Admittedly your code won't exhibit a difference in performance unless it is run in a loop over many iterations or in "spammy" events like mousemove. \$\endgroup\$ Commented Jan 5, 2016 at 3:24
  • \$\begingroup\$ I am expecting to call this function a large number of times in order to perform operations on a pretty big data set so it definitely helps to optimize. \$\endgroup\$
    – treeblah
    Commented Jan 5, 2016 at 16:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.