Challenge 13: Compound Interest

Right now Phoenix, AZ is in the darkest depths of winter, and the temperature is hovering around a frosty 54 degrees F. If I turn on the heater, it means Winter has won. In light of that, and the new year, let’s talk about everybody’s favorite subject: Compound Interest. Challenge 13 asks the programmer to write a program that computes the value of an investment over a set period of time. The program should prompt for the starting amount, the number of years to invest, the interest rate, and the number of periods per year to compound. It then provides the formula:

A = P(1 + r/n)**(nt)

Where P is the principal, r is the rate of interest, n is the number of times the interest is compounded per year, t is the time in number of years, and A is the amount at the end of the investment.

In looking back at this one, I feel like it’s kinda busy. I hadn’t included the useful_methods.rb requirement, and had included my getFloat() method, as well as a prototype chooseOne() method, called thisOrThat():

def thisOrThat(prompt,this,that)

   puts prompt

   begin

       thisOrThat = gets.chomp

       raise unless thisOrThat == this || thisOrThat == that

   rescue

       puts “Please enter \”#{this}\” or \”#{that}\”.”

       retry

   end

   thisOrThat

end

I do like the symmetry of this method, specifically the line:

raise unless thisOrThat == this || thisOrThat == that

However, I definitely don’t need it in there, especially when there’s a more advanced version of this method. I removed the two methods I didn’t need, and required the useful_methods.rb file, and then went on to integrate the chooseOne() method. To do that, I had to change all occurrences of “thisOrThat”, which is a reasonably large change, and can be a little hairy, because it doesn’t just change the occurrences of like objects with that text; it changes all occurrences of that string of text. So I went back through and made sure to make corrections.

Something I’m working on is controlling Scope Creep, used in this case to look for examples in the code where a method is doing too much. It’s a useful metric when evaluating code to ask yourself: “What is the purpose of this method? Is it doing more than that? Could I split this into smaller, more succinct methods?” I decided my calculateAccruedOrPrincipal(finalValue) method was doing too much. Just take a look at how much work it’s doing:

def calculateAccruedOrPrincipal(finalValue)

   rate_of_interest = getFloat(“Enter the percentage rate of interest (5% should be 5)”)

   time = getFloat(“Enter the time in number of years”)

   number_of_compound_periods = getFloat(“Enter the number of times the interest is compounded per year”)

   if finalValue == “A”

       principal = getFloat(“Enter how much you’re starting with”)

       accrued = principal * (1 + rate_of_interest * 0.01 / number_of_compound_periods) ** (number_of_compound_periods * time)

      return [principal,rate_of_interest,time,number_of_compound_periods,accrued,finalValue]

   else

       accrued = getFloat(“Enter the amount that you would like to accrue over time.”)

       principal = accrued * (1 / (1 + rate_of_interest * 0.01 / number_of_compound_periods) ** (number_of_compound_periods * time))

      return [accrued,rate_of_interest,time,number_of_compound_periods,principal,finalValue]

   end

end

So not only is it taking finalValue (either “A” or “P”) as it’s only argument, but then it asks for the rate of interest, time, number of compound periods, and either the principal or the accrued, but then it calculates and returns either of those final values. So in answer to those questions:

  1. What is the purpose of this method?
    1. Calculate the accrued or principal values
  2. Is it doing more than that?
    1. Yes: 
      1. it’s prompting for the standard variables
      2. It’s performing logic to figure out what to prompt
      3. It’s performing computations
  3. Could I split it into smaller methods?
    1. Yes:
      1. In the body, ask for the standard variables
      2. One method to calculate accrued, and takes numbers as arguments
      3. One method to calculate principal, and takes numbers as arguments

Let’s see what that looks like:

def calculateAccrued(principal,rate_of_interest,time,number_of_compound_periods)

   accrued = principal * (1 + rate_of_interest * 0.01 / number_of_compound_periods) ** (number_of_compound_periods * time)

end

def calculatePrincipal(accrued,rate_of_interest,time,number_of_compound_periods)

   principal = accrued * (1 / (1 + rate_of_interest * 0.01 / number_of_compound_periods) ** (number_of_compound_periods * time))

end

finalValue = chooseOne(“Type \”A\” to calculate an Accrued amount starting with a set principal, or \”P\” to calculate your starting Principal, given a savings goal.”,[“A”,”P”])

knownValue = finalValue == “A” ? getFloat(“Enter how much you’re starting with”) : getFloat(“Enter the amount that you would like to accrue over time.”)

rate_of_interest = getFloat(“Enter the percentage rate of interest (5% should be 5)”)

time = getFloat(“Enter the time in number of years”)

number_of_compound_periods = getFloat(“Enter the number of times the interest is compounded per year”)

if finalValue == “A”

   accrued = calculateAccrued(knownValue,rate_of_interest,time,number_of_compound_periods)

   puts “$%#.2f invested at %#.2f%% for %#.2f #{time == 1 ? “year” : “years”} compounded %#.2f times per year is $%#.2f.” % [knownValue,rate_of_interest,time,number_of_compound_periods,accrued]

else

   principal = calculatePrincipal(knownValue,rate_of_interest,time,number_of_compound_periods)

   puts “In order to get $%#.2f with an interest of %#.2f%% over %#.2f #{time == 1 ? “year” : “years”} compounded %#.2f times per year, you need to invest $%#.2f” % [knownValue,rate_of_interest,time,number_of_compound_periods,principal]

end

That seems to work pretty well! I like that it’s a lot more tidy: one method each to calculate the final value, the known value is labelled as such, and the obscure importantInfoArray (what information is considered important?) has been replaced with an array that has all the important information spelled out.

I’m glad I was able to review this and find some significant parts of the code to refactor and make work. That’s day 15 of 57 complete!

Published by corbettbw

I am a Ruby developer in Phoenix, AZ. I'm interested in the intersection of technology and social justice, love weird science facts, and my dog, Coco; a cute black lab/pit bull mix, who won't stop eating rocks.

Leave a comment