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