Things That Have Changed the Way I Code

Nathan Davis Olds
5 min readOct 23, 2015

--

If I were to graph my programming skills over time, I think it would look something like this.

Cycle of Learning

Every once in a while, I experience a shift in the way I approach writing code. These AH-HA explosions are triggered by moments of clarity followed by a rapid transformation of my programming practice. It is my theory that the frequency of these AH-HA moments is a more accurate measurement of coding skill than the amount of time spent working as a programmer.

I’d like to share one such AH-HA moment I’ve had in the last few years.

Represent key concepts with domain objects instead of core data types.

When coding, I’ve shifted from querying core data types, like String, Array, or Hash, in favor of creating small domain objects. This practice aids the conceptual understanding of the problem; making reading code, changing code, testing code, and improving code efficiency easier. Let me show you what I mean.

Here’s a code representation of our example business problem.

There are three offerings in which we need to find out if a household is eligible. Each offering has a different set of criteria. Offering one iterates through each household person, gathers up the countable_incomes for that person, and then compares the sum to under $1000. Offering Two is similar. It also iterates over each person but then isolates on pension income. Finally, it determines eligibility by checking the presence of pension income below our limit. Finally, offering three collects all household earned income. Then compares the total to the limit of $1500.

As you might imagine the key object here is the Income class.

An income is represented with an amount, an income_type_key, and a person. All these attributes are passed in a familiar fashion.

Categories holds a logical mapping of income_type_key to a list of categories. As we add various types of income we will need to update this map to make sure our income has the correct categories.

Another way to represent categories is through a hash.

I suspect that this code seems familiar to you. I know I write code like this all the time. And there is nothing wrong with it. It will function exactly as expected. However, in dynamic applications where we have a long-term commitment to the code, we owe it to our team and to ourselves to write maintainable code.

Whether it be with a hash or a case statement, when I see this style of code I know it is hiding another object. To get it out, use composition.

Composition is a style of coding which combines simple objects into more complex ones. In this case, Income has the smaller IncomeType inside it.

If we create a IncomeType class with a #categories method we can then create a class for every type of income we want to represent. We can clearly see and reason about the properties of each type.

Back in our Income class, we create a private method of income_type.

It tries to find a class based on the income_type_key otherwise it creates a blank income type. Now, in the categories method it is a simple call to the income_type to get the categories. Adding an income type is as simple as adding this value class.

In this way we’ve removed the comparison to strings or hashes and instead have conceptual domain object (IncomeType) to provide the information we need.

Back to Offerings

There are other decisions being made through comparisons.

The comparisons i.person == person, i.income_type_key == ‘pension’, and i.categories.include?(‘earned’) are classic examples of feature envy. Feature envy is when a method is more interested in a class other than the one that it is in. In these cases we are querying information from income then comparing to something else. Shouldn’t Income be the class that knows if it for a person? or is an income type? Or has a category? If we let this continue, these logical comparisons will litter our code. If any of these comparisons change, we would have to find all the places in our code we compare and then change those. That isn’t something I want to maintain long term.

To solve it, the comparison is moved into Income.

Now the offerings can ask income if it is for a person.

countable_incomes = household.incomes.select { |i| i.person? person }

and

countable_incomes = household.incomes.select do |i| 
i.person?(person) && i.income_type_key == ‘pension’
end

Income should also know which category it is in and if it ‘is’ as certain type. However now with our composition changes the IncomeType is what should know if it is of a type. So, let’s forward the decision to income type.

def category? candidate
income_type.category? candidate
end
def is? candidate
income_type.is? candidate
end

Moving categories and is? to IncomeType, the result looks like this:

Now, our offerings ask the income if it is of a type or category or for a person.

The advantage comes when there are changes to the business rules we are building this code on. Especially when adding a new income type or when an income type changes its category. We don’t have to touch the logic to introduce another income type to the configuration.

The code is cleaner, but there is one piece that I would like to take bit further. The approach of adding classes that represents each type bothers me. Aren’t they all instances of the IncomeType class instead of classes of their own? So, I’ve written a gem to help with this problem. I call it identitee.

With identitee, we can identify a code block with a key. The block is executed to build an object (in this case IncomeType). Later, this same object can be retrieved through its Identitee key and be used in our code.

Now in the income_types, folder we can write definitions for each income type.

In income, we now can find an income type by key. Income has be become a value object. Easy to maintain.

Back (again) to Offerings

Now let’s look back at our offerings. We are working a lot with arrays of Incomes. We filter by category and by type. We group by people. We total all incomes. There is a bunch repeated logic that would be best to standardize. We can do that by using Enumerable.

Enumerable is a mix-in providing collection classes with several traversal and searching methods, and with the ability to sort. Enumerable methods are familiar methods like map, select, inject, and each. Let’s start by creating an Incomes class and getting Enumerable to work.

This gives us the standard methods but let’s create some that filter our list of incomes.

Let’s add a filter for types

Let’s add group by person.

Finally, totaling the incomes.

Going back to the offerings we can use our filters to make eligibility methods very readable.

The only comparison is the one needed for the eligibility itself. Now the comparison is exactly in the correct place.

Take away

Representing key concepts with objects instead of core data types makes code easier to maintain. This is essential for long term relationships with complex problems.

Eliminate Feature Envy.

Breakdown into domain objects using composition.

Encapsultate collections using Enumerable.

I hope this also changes the way you code.

Originally published at natedavisolds.com.

--

--

Nathan Davis Olds

Versatile Leader in Product, Design, and Software Engineering. Software Engineer at Nava PBC. Post are my own.