Hashes (or Dictionaries) are Overused
I am officially a professional software engineer. Well, maybe not professional. But my day job is writing software. I write in Python by day and Ruby by night. During the past several months of doing this, I’ve felt more and more that I’ve been overusing hashes (known as dictionaries to Pythonistas). Why? Let’s have a look.
This post’s examples are modeled after hypothetical networking scenarios, but they can be applied generically to any sort of similar situation.
We’ll start off easy. Let’s think about interfaces and the properties they could have. For the sake of simplicity, we’ll assume that we’re discussing only layer 3 interfaces. Here’s a list of some physical and logical properties a layer 3 interface might have:
- IP Address
- Subnet Mask
- IP MTU
- Physical MTU
- Line Rate
- Physical Status
- Administrative Status
Obviously, this list could go on (nearly) forever. We’re going to stick to this list for now, though, again for the sake of simplicity.
How might we model this using a hash/dictionary? Here is how in Python:
And in Ruby:
The MTU values above aren’t important, but they assume the layer 2 encapsulation is Ethernet II.
Working with the Data
This looks fine. But what if we want to do something simple, like concatenate the subnet mask to the IP address? Well, that’s not too difficult. We could do something like this in Python:
Or in Ruby:
Okay, so it’s not terrible, but it’s a lot of typing and a lot of
duplicate work every time you need to do it, which introduces the
potential to forget the all-important
/ once or twice. Okay, so you
might write a function to do it for you so that you don’t need to
worry about the slash anymore. Let’s go to another example, then.
Our hash/dictionary above has a
line_rate key. This key is represented
as an integer, and its value is in bits per second. What if we need to
get the value represented in gigabits per second (which, arguably, is
slightly more readable)? Our implementation might look like this in
This time in Ruby:
Those aren’t great implementations. Okay enough for examples, though.
Okay, so we’re back to error-prone duplication of math. We might write a utility function again. This is starting to get annoying, though.
Now, let’s assume that we’re using interfaces in a lot of places
throughout our codebase. And let’s assume that we have multiple
engineers working on our codebase. What are the default values for the
various keys, and how do you enforce them? Take the description, for
example. Should it be
None (Python) or
nil (Ruby) or
string) if the interface doesn’t have a description set? Or should the
key just not be present in the hash/dictionary? Now that you’ve decided,
how do you enforce it? What about IP addresses and subnet masks? Should
they be strings or should they be an object from a super helpful library
(such as Python’s
ipaddress or Ruby’s
ipaddr)? Again, how do you
enforce that decision?
Yes, I know you could just as easily change a variable’s type in both Python and Ruby.
A lot of typing, a lot of duplication, and a lot of error-prone work. And really, at the end of the day, an interface is an object, right? And what we’re actually doing above is working with the object, right?
A Better Way
How can we write better software that’s less error-prone and more object-oriented? Stop using hashes/dictionaries to represent objects. We’ll use all of the same examples as above to accomplish the same goals in a much better form. First, Python:
You could just as easily have made
And in Ruby:
Now, we have a class that represents our interfaces. This class has default values for each of its attributes/properties. There are methods to convert or otherwise display data in a way that we like. This does everything we did before, but in a more portable and less error-prone fashion.
We could even define a custom
__str__ (Python) or
to_s (Ruby) method
for the objects. Let’s do that and see how it looks, just as a fun
And in Ruby:
Now, if you call
str(interface) (Python) or
you’ll get something like this:
interface gi1/1 ip address 10.0.0.1/24 mtu 1514 ip mtu 1500 description 10G Core to dtx1.example.com speed 10000 shutdown
And one more exercise, just for fun: determining if an interface is admin up. Python is up first:
You should get
False in Python and
false in Ruby. Neat, huh? How
would you ask your hash above if it’s enabled? You would have to resort
to another helper method or just duplicating your
throughout your codebase.
I hope you can see how useful classes can be and how overused hashes or dictionaries can be. Of course, this is all up to interpretation, and there is bound to be someone who disagrees. Keep this under your toolbelt, though. There are plenty of other things that can be done when you examine dictionaries vs. classes for implementing your objects.