In my first post on Generic Programming I talked about the
iterative approach to improving algorithms.
In this post I would like to discuss another concept from the From Mathematics to Generic Programming book that I am
currently reading. This concept is called The Law of Useful Return.
The Law of Useful Return says:
If
you have already done the work to get some useful result, don’t throw it
away. Return it to the caller because
they may be able to use it.
What do we mean by this law.
Well the easy way to explain it is to look at an example. In the From Mathematics to Generic Programming book,
they explain this law based on a mathematical equation however, most developers
that I know do not get that excited about complex mathematical equations
therefore I am going to take a non-mathematical approach to explaining this.
Lets say that we were creating a pump that is connected to
the Internet for real time monitoring (think IoT). This pump could be used to pump different
types of liquids and would be able to return the temperature of the liquid as
it is being pumped. In our application,
that monitors the pump, we could then retrieve the temperature and send out an
alert if it is outside of the acceptable range for the particular liquid that
is being pumped. Our function to monitor
the temperature may look something like this:
func
isTemperatureWithinRange() -> Bool {
//Get liquid temperature
var temp = getLiquidTemp()
//Check if temp is within acceptable
range
if temp < MIN_TEMP || tem >
MAX_TEMP {
return false
}
return true
}
This function would work well and if the temperature was outside
the acceptable range it would return false triggering an alert. The problem that could arise from this is
eventually we will want to display the actual temperature of the liquid being
pumped, therefore someone may add another function that would simply retrieve
the temperature of the liquid and return it.
This function may look something like this:
func getTemperature()
-> Double {
//Get liquid temperature
return getLiquidTemp()
}
Now in order to retrieve the temperature of the liquid and
to also check if it is within the acceptable range we would need to call the getLiquidTemp() function twice. Since this function retrieves the temperature
from a remote device, making two separate calls like this is definitely not
optimal. What we could have done to
avoid this problem was to return the temperature with the Boolean value that
indicates if the temperature was within the acceptable range. It would be very easy to do since we are
already retrieving the temperature as part of the check. The function that returns both values could look
something like this:
func getTemperature()
-> (acceptable:Bool, temperature: Double) {
//Get liquid temperature
var temp = getLiquidTemp()
//Check if temp is within acceptable
range
if temp < MIN_TEMP || tem >
MAX_TEMP {
return (acceptable:false, temperature:
temp)
}
return (acceptable:true, temperature:
temp)
}
Be careful not to follow this law to closely because we only
want to return data that is useful within our application. When we create functions like this we really
need to ask ourselves not only what information do we currently need but also
what information may be useful. In our
example above, returning the temperature, since we already retrieved it, could
definitely prove useful even if it is not needed with our current requirements. Writing our API correctly (generically) the
first time will save us from having to change the interface for our APIs in the
future. This means a lot less
refactoring of our code.
To me, one of the ideas behind the Law of Useful Return is
to avoid making our APIs so granular that we end up performing the same
functions multiple times. To illustrate
this idea another way lets look at a second example. Lets say that we have a NSDate object and we
need to retrieve the month from it. We
could very easily design our API like this:
func getMonth(date:
NSDate) -> Int {
let components = NSCalendar.currentCalendar().components(.Month,
fromDate: date)
return components.month
}
This works great for our present need however what happens if
in the future we also need to retrieve the day of the month. We could write a second function to retrieve
the day of the month however we would then need to make two function calls if
we needed both the day of the month and month itself. A better way to design our API is to think
ahead and realize that if we are retrieving the month from the NSDate object maybe
the day of the month and year would be useful.
We would then create a function that returns the month, day and year
like this:
func getDate(date: NSDate) -> (month:
Int, day: Int, year: Int) {
let components = NSCalendar.currentCalendar().components([NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Year],
fromDate: date)
return (month: components.month, day:
components.day, year: components.year)
}
By designing our API to return the month, day and year
rather than just the month our function becomes much more generic and useful
not just for our present needs but also for our future needs.
When we create APIs that only meet our present requirements
we end up having to do a lot of extra code refactoring in the future when we
have to change those API to meet our future needs. When we are designing our APIs we should always
remember to think beyond our present needs and think about how we can make our
APIs generic enough to also meet our future needs.
There are going to (hopefully) be
a number of posts in the Generic Programming and POP with Swift series
therefore I made a separate post that contains links to all of the articles in
this series. The post is located
here: http://masteringswift.blogspot.com/2016/03/generic-programming-and-protocol.html
No comments:
Post a Comment