Article 3PKCK CodeSOD: CHashMap

CodeSOD: CHashMap

by
Remy Porter
from The Daily WTF on (#3PKCK)

There's a phenomenon I think of as the "evolution of objects" and it impacts novice programmers. They start by having piles of variables named things like userName0, userName1, accountNum0, accountNum1, etc. This is awkward and cumbersome, and then they discover arrays. string* userNames, int[] accountNums. This is also awkward and cumbersome, and then they discover hash maps, and can do something like Map<string, string>* users. Most programmers go on to discover "wait, objects do that!"

Not so Brian's co-worker, Dagny. Dagny wanted to write some C++, but didn't want to learn that pesky STL or have to master templates. Dagny also considered themselves a "performance junkie", so they didn't want to bloat their codebase with peer-reviewed and optimized code, and instead decided to invent that wheel themselves.

Thus was born CHashMap. Now, Brian didn't do us the favor of including any of the implementation of CHashMap, claiming he doesn't want to "subject the readers to the nightmares that would inevitably arise from viewing this horror directly". Important note for submitters: we want those nightmares.

Instead, Brian shares with us how the CHashMap is used, and from that we can infer a great deal about how it was implemented. First, let's simply look at some declarations:

 CHashMap bills; CHashMap billcols; CHashMap summary; CHashMap billinfo;

Note that CHashMap does not take any type parameters. This is because it's "type ignorant", which is like being type agnostic, but with more char*. For example, if you want to get, say, the "amount_due" field, you might write code like this:

 double amount = 0; amount = Atof(bills.Item("amount_due"));

Yes, everything, keys and values, is simply a char*. And, as a bonus, in the interests of code clarity, we can see that Dagny didn't do anything dangerous, like overload the [] operator. It would certainly be confusing to be able to index the hash map like it were any other collection type.

Now, since everything is stored as a char*, it's onto you to convert it back to the right type, but since chars are just bytes if you don't look too closely" you can store anything at that pointer. So, for example, if you wanted to get all of a user's billing history, you might do something like this"

 CHashMap bills; CHashMap billcols; CHashMap summary; CHashMap billinfo; int nbills = dbQuery (query, bills, billcols); if (nbills > 0) { // scan the bills for amounts and/or issues double amount; double amountDue = 0; int unresolved = 0; for (int r=0; r<nbills; r++) { if (Strcmp(bills.Item("payment_status",r),BILL_STATUS_REMITTED) != 0) { billinfo.Clear(); amount = Atof(bills.Item("amount_due",r)); if (amount >= 0) { amountDue += amount; if (Strcmp(bills.Item("status",r),BILL_STATUS_WAITING) == 0) { unresolved += 1; billinfo.AddItem ("duedate", FormatTime("YYYY-MM-DD hh:mm:ss",cvtUTC(bills.Item("due_date",r)))); billinfo.AddItem ("biller", bills.Item("account_display_name",r)); billinfo.AddItem ("account", bills.Item("account_number",r)); billinfo.AddItem ("amount", amount); } } else { amountDue += 0; unresolved += 1; billinfo.AddItem ("duedate", FormatTime("YYYY-MM-DD hh:mm:ss",cvtUTC(bills.Item("due_date",r)))); billinfo.AddItem ("biller", bills.Item("account_display_name",r)); billinfo.AddItem ("account", bills.Item("account_number",r)); billinfo.AddItem ("amount", "???"); } summary.AddItem ("", &billinfo); } } }

Look at that summary.AddItem ("", &billinfo) line. Yes, that is an empty key. Yes, they're pointing it at a reference to the billinfo (which also gets Clear()ed a few lines earlier, so I have no idea what's happening there). And yes, they're doing this assignment in a loop, but don't worry! CHashMap allows multiple values per key! That "" key will hold everything.

So, you have multi-value keys which can themselves point to nested CHashMaps, which means you don't need any complicated JSON or XML classes, you can just use CHashMap as your hammer/foot-gun.

 //code which fetches account details from JSON CHashMap accounts; CHashMap details; CHashMap keys; rc = getAccounts (userToken, accounts); if (rc == 0) { for (int a=1; a<=accounts.Count(); a++) { cvt.jsonToKeys (accounts.Item(a), keys); rc = getAccountDetails (userToken, keys.Item("accountId"), details); } }
 // Details of getAccounts int getAccounts (const char * user, CHashMap& rows) { // <snip> AccountClass account; for (int a=1; a<=count; a++) { // Populate the account class // <snip> rows.AddItem ("", account.jsonify(t)); }

With this kind of versatility, is it any surprise that pretty much every function in the application depends on a CHashMap somehow? If that doesn't prove its utility, I don't know what will. How could you do anything better? Use classes? Don't make me laugh!

As a bonus, remember this line above? billinfo.AddItem ("duedate", FormatTime("YYYY-MM-DD hh:mm:ss",cvtUTC(bills.Item("due_date",r))))? Well, Brian has this to add:

it's worth mentioning that our DB stores dates in the typical format: "YYYY-MM-DD hh:mm:ss". cvtUTC is a function that converts a date-time string to a time_t value, and FormatTime converts a time_t to a date-time string.

proget-icon.png [Advertisement] ProGet can centralize your organization's software applications and components to provide uniform access to developers and servers. Check it out! TheDailyWtf?d=yIl2AUoC8zAlK6Yoa4ZQC8
External Content
Source RSS or Atom Feed
Feed Location http://syndication.thedailywtf.com/TheDailyWtf
Feed Title The Daily WTF
Feed Link http://thedailywtf.com/
Reply 0 comments