6
\$\begingroup\$

I have the following code; whereas currency and views are in reality many JSON objects from an SAP export, that I can not rely upon to be in the same format. That is why I'm regexing over these inputs. My code looks clumsy to me. Can it be improved?

var currency = "20.002,03 €"; // sometimes this String is "-" or " "
var views    = "38";          // sometimes this String is "-" or " "

var reDigit = /[\d\,\.]+/g;
var map = {',':'.', '.':''};

if (typeof currency != 'undefined' && typeof views != 'undefined') {

    currency = currency.match(reDigit);
    views = views.match(reDigit);

    if (currency != null && views != null ) {

        currency = currency[0];
        views = views[0];

        currency = currency.replace(/[,.]/g, function(k) {
            return map[k];
        });
        return currency / views;
    } else {
        return undefined;
    };
} else {
    return undefined;
}

In the currency variable, dots need to get eliminated and commas turned into floating points. I need to cater for cases when currency and views variables do not have a number in their string or do have a dash or a blank in them.

Would you say this script can be optimsed to be more efficient?

\$\endgroup\$
4
\$\begingroup\$

Simplify the overall logical flow

Notice the multiple else conditions in every if statement when some required condition is not true:

if (...) {
    if (...) {
        return currency / views;
    } else {
        return undefined;
    }
} else {
    return undefined;
}

These else blocks are repetitive. It would be better to think of the logic as multiple levels of checks, where you only return after all checks passed, reaching the innermost level. If at any checks fail, execution drops out of the nested if blocks and fall back to the return statement of the function at the end, like this:

if (...) {
    if (...) {
        return currency / views;
    }
}
return undefined;

More readable without the map

Instead of this:

currency = currency.replace(/[,.]/g, function(k) {
    return map[k];
});

I would find it more readable to use two replace calls instead:

currency = currency.replace(/\./g, '');
currency = currency.replace(/,/g, '.');

You could even chain them together, but that would be less readable.

Another reason why this is more readable is that in the original solution, the map is defined far away from where it's used. That can invite mistakes, such as adding patterns in map but forgetting to adjust the regex in the replace call, or the other way around.

Naming

reDigit is not a good name for /[\d\,\.]+/g. The word "digit" suggests \d (0 ... 9), but you have more there. How about reMoney or reMoneyDigits instead? That would make it clear that it's more than just digits.

Validation

Are you sure all inputs are valid? The code will work and return values for clearly bad input too, such as:

var currency = "hello20.002,03 €";
var currency = "20.0.0..2,03 €";
var currency = "20.a0b0c2d,03 €";
var currency = "20.002,03... €";

For the last example it will return NaN.

Will all this be ok in your application?

Even if you think such inputs are impossible, I think you should check for them. One day when you might corrupted inputs, which, in the absence of solid defenses could propagate in your systems and manifest as strange problems that are extremely difficult to debug. It's better to catch problems early, as close to their source as possible.

The only way you can assume the inputs are always valid, if they have been prevalidated before reaching this code. And in that case, this code should be in a private method, only callable from the place where the validation was performed, to guarantee that it can't be called with invalid data.

Dividing strings

I noticed a bit late, but curiously, the code is actually dividing strings:

'123' / 3
// -> 41

Ah, sweet JavaScript, only you can be capable of such madness.

You should use parseFloat for currency, and perhaps parseInt for view, to make them proper numbers. Note that this will change some things. For example:

'123..' / 3
// -> NaN
parseFloat('123..') / 3
// -> 41
\$\endgroup\$
  • 2
    \$\begingroup\$ Thank you so much! Are there plans to integrate a Crypto Currency donation system into this site? :) \$\endgroup\$ – meshfields Nov 11 '14 at 11:43
  • \$\begingroup\$ You're welcome! (and I wish!!) \$\endgroup\$ – janos Nov 11 '14 at 11:45
  • \$\begingroup\$ I will have a look at the parsers. I know of type coercion of JavaScript and thought I was getting away with making shamelessly use of it. Is there a reason why not to do '123' / 3 ? \$\endgroup\$ – meshfields Nov 11 '14 at 11:51
  • \$\begingroup\$ parseFloat will eliminate the decimals after the floating point, thus it is certainly no alternative. It's a financial application. \$\endgroup\$ – meshfields Nov 11 '14 at 13:34
  • \$\begingroup\$ Aren't you mixing up with parseInt? I recommended parseInt only for view, which has an integer value in your example. parseFloat will not remove decimals after the first floating point. \$\endgroup\$ – janos Nov 11 '14 at 13:38
1
\$\begingroup\$

Interesting question,

from a once over:

  • One of your else blocks has a superfluous semicolon
  • Your code does not take care of negative numbers at all.. Seems like a bug.
  • You can use the + operator instead of parseInt, it makes for better looking code

    var a = '123';
    var b = +a;
    //b will now have the integer 123  
    
  • Instead of comparing to undefined I would use falsey evaluation:
    if (currency && views) {
    this will also stop you from dividing by zero if views is zero (!)

  • From SAP, you might get dollar amounts in dollar notation which uses a dot decimal point and comma thousand separators, it would make your code fail
  • Some currencies have more than 2 decimals, some have 0 decimals This means that 100.123 DJF could be either 100123 or 100.123, you cannot know unless you have the meta-data of the currency.

If I were you I would ask the SAP folks to either

  • Stick to 1 formatting approach ( preferably the native JS one )
  • Provide the currency metadata ( decimals, and maybe number formatting data for the country of the currency )
\$\endgroup\$
  • \$\begingroup\$ Thanks for your additions. "+ operator instead of parseInt", do you have an example? With Dollar your are correct, I will have to implement a check for the currency sign, at the moment we are only getting Euro. Getting the Currency Metadata... interesting idea. \$\endgroup\$ – meshfields Nov 12 '14 at 9:45
  • 1
    \$\begingroup\$ Added an example for + \$\endgroup\$ – konijn Nov 12 '14 at 13:05
0
\$\begingroup\$

Just one small thing I noticed: There is no constant called undefined in JavaScript, it's just a variable name, and since that variable is usually not defined this happens to work.

However if someone happens to define that variable (e.g. var undefined = 0;) you'll be in trouble, so make sure to either declare the variable yourself, or in this case you don't need to use it at all. Just use return; which returns undefined.

\$\endgroup\$
0
\$\begingroup\$

Thanks to the great support I was able to optimse my code from the question to this:

var deEurofy = function (euro) {
    var euro = euro.match(/[\s-\d,\.]+/g);
    if (euro) {
        return +euro[0].replace(/\./g, '').replace(/,/g, '.').replace(/\s/g, ''); // return decimal
    }
    return undefined;
}

var eurofy = function (decimal) {
    return decimal.toString().replace(/\./g, ',') + ' €'; // return euro
}

if (currency && FakturierteAIs) {
    currency = deEurofy(currency);

    if (currency && views !== 0) {
            return currency / views;
        }
    }
}
return;

http://jsfiddle.net/nottinhill/qmd1ete6/

Readability and Error-Checking has improved, Negative values are allowed, Division through zero is catered for and RegEx functions are re-usable now.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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