DISCLAIMER: quite long, programming related post.
A few days ago Marco Ramilli reviewed a useful password strength checker written in JavaScript. The JavaScript "score" function is (here without any DOM manipulation-"side effect"):
function passwordStrength(password)
{
var desc = new Array();
desc[0] = "Very Weak";
desc[1] = "Weak";
desc[2] = "Better";
desc[3] = "Medium";
desc[4] = "Strong";
desc[5] = "Strongest";
var score = 0;
//if password bigger than 6 give 1 point
if (password.length > 6) score++;
//if password has both lower and uppercase characters give 1 point
if ( ( password.match(/[a-z]/) ) && ( password.match(/[A-Z]/) ) ) score++;
//if password has at least one number give 1 point
if (password.match(/\d+/)) score++;
//if password has at least one special caracther give 1 point
if ( password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) ) score++;
//if password bigger than 12 give another 1 point
if (password.length > 12) score++;
return {"score": score, "desc": desc[score] } ;
}
This function can be written in a fairly more compact manner taking avdantage of some nice JavaScript programming fatures. (Remembering that JavaScript is the World’s Most Misunderstood Programming Language)
First you can declare the array with a single assignament:
var desc = ["Very Weak", "Weak", "Better", "Medium", "Strong", "Strongest"]
Second you can avoid repeating the "if (condition) score++" (if you are lazy like me you should hate writing the same code over and over), have the tests stored in an array and executed via "eval" in a "for (element in collection)" statement, this way you keep the tests separated from the score evaluation; this is no big deal in a so simple and short program, but it’s a good practice (and attitude) to have rules and rules engine logic separeted.
var tests = [
"(password.length > 6)",
"( ( password.match(/[a-z]/) ) && ( password.match(/[A-Z]/) ) )",
"(password.match(/\d+/))",
"password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) )",
"(password.length > 12)",
]
for (test in tests){
if eval(test) {
score++;
}
}
Now the password strength checker function can be rewritten as:
function passwordStrength(password)
{
var desc = ["Very Weak", "Weak", "Better", "Medium", "Strong", "Strongest"];
var tests = [
"(password.length > 6)",
"( ( password.match(/[a-z]/) ) && ( password.match(/[A-Z]/) ) )",
"(password.match(/\d+/))",
"password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) )",
"(password.length > 12)",
];
var score = 0;
//every passing test gets the score incremented of 1 point
for (test in tests){
if eval(test) {
score++;
}
};
return {"score": score,"desc": desc[score]};
}
This is a more compact formulation than the original one, which means less bytes down the tube which turns to less bandwidth consumption, and it is also more manageable (the initial disclaimer is always valid: the example is quite simple so you get no big savings, it’s more about the attitude).
A note on eval: (ab)use of eval is somewhat considered harmful and watched with despise in some circles, so you can easily substitute the expression eval(test), relying on a more explicit use of the "metaprogramming" facilities of the language, with:
(new Function("return " + test))()
Here you build an anonoymous Function whose body is made out of the return statement (otherwise, as no return statement is specified in the test body, the return type of the function would be undefined, kind of void for the Java-inclined) and the test body; the function is then called () and the test gets evaluated.
By having "rules engine" and rules separated you can further improve the code robustness: an implicit assumption is made on the number of tests and descriptions you provide, i.e. you must provide N tests and N+1 descriptions. How can this become more flexible? You can broad the initial assumption by stating you can have an arbitrary number of tests (each one with a separate score) and an indipendent number of level descriptions. For example’s sake you have 4 descriptions (you throw away the "Better" and the "Strongest" ones), 6 tests and the last one gets you 2 points if passed.
First you have a simple design decision to make: how you relate the strength description to the score?
Opting for a simple design you can fully specify the mappings (defining the minimum score for each description), e.g. using a dictionary / hashmap like {0: "Very Weak" , 2: "Weak", 3: "Medium", 5: "Strong"} for the descriptions. Hence you have:
var desc = {0: "Very Weak" , 2: "Weak", 3: "Medium", 5: "Strong"}
The score for each test can be specified taking advantage of an hashmap in wich the body of the test is the key and the score the value.
var tests = {
"(password.length > 6)": 1,
"( ( password.match(/[a-z]/) ) && ( password.match(/[A-Z]/) ) )": 1,
"(password.match(/\d+/))": 1,
"password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) )": 1,
"(password.length > 12)": 2,
}
To take into account the custom score for each test you also have to make a fairly trivial change to the "rules engine", instead of score++ you have:
score += tests[test];
Now that the score has been calculated you have to retrieve the description; descriptions are "indexed" in the hashmap by their minimum score level, so you have to retrieve the entry with the maximum possible key value less or equal than your actual score. This problem can be solved by a helper function that starts with retrieving the entry for the actual score and in case of failure (i.e. desc[score] evaluates to undefined) it calls recursively itself with the score decremented by one unit, and so on until it eventually reaches a valid key. A note on the ? operator and associative arrays in JavaScript, the test fails for the value undefined, to which the expression desc[score] evaluates if there is no entry for the key score.
function desc_f(score)
{
return desc[score] ? desc[score] : desc_f(score-1);
}
Now you can substitute desc[score] with desc_f(score) in the return statement of passwordStrength and your’re done with satisfying the evolved requirements.
function passwordStrength(password)
{
var desc = {0: "Very Weak" , 2: "Weak", 3: "Medium", 5: "Strong"};
var tests = {
"(password.length > 6)": 1,
"( ( password.match(/[a-z]/) ) && ( password.match(/[A-Z]/) ) )": 1,
"(password.match(/\d+/))": 1,
"password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) )": 1,
"(password.length > 12)": 2,
};
var score = 0;
for (test in tests){
if eval(test) {
score += tests[test];
}
};
function desc_f(score)
{
return desc[score] ? desc[score] : desc_f(score-1);
};
return {"score": score,"desc": desc_f(score)};
}
If you didn’t get overly bored you should have appreciated the power and simplicity offered by a "responsible use" of JavaScript. If you really didn’t get overly bored and you feel like expereminting something on your own, you can implement your own strategy for description-score mapping; there is plenty of room to introduce arbitrarly complex strategies.
These ramblings have been inspired by some (off|on)line chat with Giulio Piancastelli (who suggested me the Function() alternative to the "evil eval"), his Kata Four in JavaScript post and by these nice presentations on JavaScript Metaprogramming: @media Ajax by Dan Webb and @Columbus Ruby Brigade by Adam McCrea.