
I enjoy finding the occasional online mooc to participate in, as time permits, even if it’s something I’m already familiar with. Hearing or reading about an old concept in a slightly different way can bring new insights, make us rethink something we’ve been taking for granted, or just help us make a new connection.
“Don’t assume that just because you can get something to work once, there’s nothing more to understand.” - Bob Frankston
— Programming Wisdom (@CodeWisdom) September 1, 2016
For the last few weeks, it’s been an intro to computer science on edX. The instructor’s heavy usage of Python is actually what interested me (I’m trying to learn more about the language), but then he spent some time last week talking about the notion of abstraction.
I think of a projector as a black box. . . . [M]ost of us don’t know what’s inside it, how it actually works. If you open it up, you see a bunch of electronics. And unless you’re a really good electrical engineer, you probably don’t understand how it works. It doesn’t matter, because you know the interface, you know the input-output behavior. And in fact, that black box comes with a standard interface, that says, if I plug an appropriate piece of electronics into it, that box can communicate with it. And it’s going to produce an output. It’s going to show slides on the wall.
That’s the notion of abstraction. It’s the idea that once I’ve built something — in this case a projector, might be a piece of code — once I’ve built something, I don’t need to know what’s inside it as long as I know how it works. So abstraction, in some sense, comes with a contract that says, if you give me appropriate inputs, I’m going to behave in an appropriate way.
That’s what we as developers do all the time, isn’t it? We abstract away some lower level of the computer from our customers, or from other developers working at higher levels, so that someone somewhere doesn’t have to reinvent the wheel. And we do it by using tools developed by other developers that abstract yet deeper levels – levels we won’t have to bother with if they did their job well.
As long as we know what should go in and what should come out, we can all just assume each layer of abstraction will work as it’s supposed to. And then our end-users can (ideally!) assume the abstraction that is our program will work as *it’s *supposed to, too.
At some point, we have to assume things work (hopefully backed up by a solid test suite, and more than just “gut feelings”), and build on existing foundations, or nothing would get done.
source: abstruse goose
For instance, Python provides a “math” library that provides some built-in functions. I don’t have to be concerned with how it manages to find the square root of a number, just that it does. Then I can take that abstraction and build upon it, creating (for example) functions that find the cube and cube root of some number.
Now someone can import my “MoreMath” class below, with its static math-related functions, and use those without having to reinvent that particular wheel again.
import math
class MoreMath:
@staticmethod
def cbrt(number):
return math.pow(number, 1/3)
@staticmethod
def cubed(number):
return math.pow(number, 3)
print("The cube root of 125 is: {}".format(MoreMath.cbrt(125)))
print("The cubed value of 5 is: {}".format(MoreMath.cubed(5)))
For those more familiar with C#, here’s some very similar code. I could even move MoreMath
into a separate class library and provide that source code or DLL assembly to others, who could then use my functions without having to know the inner workings (as you can see, it’s quite some inner workings too… don’t strain yourself trying to understand what it’s doing 😉).
using System;
public class Program
{
public static void Main()
{
Console.WriteLine("The cube root of 125 is: {0}", MoreMath.Cbrt(125));
Console.WriteLine("The cubed value of 5 is: {0}", MoreMath.Cubed(5));
}
}
public class MoreMath
{
public static double Cbrt(double input)
{
return Math.Pow(input, 1/3d);
}
public static double Cubed(double input)
{
return Math.Pow(input, 3);
}
}
He also brought up commenting your code, and I’m a bit torn, but it did give me something to think about. I’m not a fan of comments, or at least I think they’re a last resort beyond other forms of self-documentation.
Now, you’re not required to put in a doc string or a spec, but good programmers always do. In part because somebody else might use this and you want to tell them how you expect it to be used or because a year from now, two years from now, you may come back to this function and you’d like to know what was I thinking when I built it.
So I encourage you to build your own docstrings even though it takes a little more time because it helps you understand what’s expected of a piece of code and where can I use it.
But documentation has its place, like all tools. It’s neither a golden hammer nor something you can do completely without.
PyCharm uses documentation as a hint to what the variable types are, and makes helpful suggestions to the developer. Since you don’t specify variable types in Python, some documentation may be prudent so you and your team don’t have to repeatedly figure out what a method expects as input.
Here, the cbrt
function clearly expects a number but I documented it as a string – PyCharm no likey.
Here’s another Python function demonstrating some helpful documentation:
def recurPower(base, exp):
'''
base: int or float.
exp: int >= 0
returns: int or float, base^exp
'''
# Your code here
if (exp == 0):
return 1
elif (exp == 1):
return base
else:
return base * recurPower(base,exp-1)
I might not have given the above much thought, if not for the fact that I’ve been using Erlang at work for nearly a year and found the same lack of variable types so frustrating. Every time someone on our team looks at a function, they have to trace back where it’s getting called from to determine what the parameter types are. That’s an error-prone waste of time.
Fortunately, Erlang comes with a form of documentation called “specs” and a tool called Dialyzer that work nicely together to warn developers when they’ve done something naughty that’s going to cause a runtime exception. Thanks to the combined effort of everyone on the team we’ve made that issue mostly one of the past.
-spec extract_names_from_record(list(#person{})) -> list(string()).
extract_names_from_record(People) ->
lists:map(fun(Person) ->
Person#person.user_name
end, People).
My experience in Erlang made me notice what the instructor was saying about Python, and what he said about Python reinforced my feelings about our efforts in Erlang.
What kind of courses do you like to take? Do you ever find yourself reviewing the basics, or only the newest hotness?
Did what you learn help you see something in a new light? Leave a comment below!