Programming isn’t really engineering
This Ruby project has been very enjoyable. I’ve been getting my hands dirty (figuratively) by writing code, thinking hard about things I haven’t thought about in a long time, and re-familiarizing myself with the Ruby language. It’s delightful.
In my previous post, I wrote about being the kind of engineer that drives the train - in my career, I’ve spent quite a bit of time being the one who supervises the machines that keep the business running. Physically and digitally stacking together the pieces, plugging them together, and providing the oil and grease in just the right places to guarantee it keeps running until the next shift. In many cases, I’ve been able to automate a lot of this, too - using tools other people had built to compose a system that does what the company needs it to. This is a complex task that requires lots of knowledge on how these components work under the hood. This is what I call “little e engineering.” Driving the train.
Programming is not that. Programming is also not “big E Engineering” - doing the hard work of architecting the train in the first place. Laying out the plans and designs and doing the math to predict tolerances and material strength and making sure that once built, the thing is capable of doing the task required. That’s “big E Engineering.”
Programming is a mechanical skill - a trade. Like a machinist or a carpenter - using tools and components to build something new. Big E Engineers and little e engineers both program in the tech field. In fact - there’s a lot of actual machinists who program now, too, writing specs in a thing called “G-code” to tell CNC machines and 3D printers how to make physical things.
Engineering is the planning
Big E Engineering is the practice of taking applied science and learning and using that to design and architect things that are fit for purpose. Architects are engineers. So are many (but not all) software developers. So are many little e engineers (they’re often also called system architects in that role). And like most Big E Engineers, there’s visual languages that can be used to work out tools and processes in building software to make sure that the software is Fit For Purpose.
There’s another really cool thing that software engineers have that helps guarantee that the software does what it’s designed to do - Testing. There’s a few different types of Big T Testing, and what I did in my last session is called Unit Testing. It’s pretty nifty.
To start with, a program is essentially a mathematical function. It takes inputs and produces outputs. Like this function for calculating the circumference of a circle:
f(d) = πd
(And we all love pi!)
Since I’m writing this in Ruby, the Ruby version of this function would look like this:
def circumference(diameter)
3.14159 * diameter
end
In order to do actual Big E Engineering to software, you have to plan and design it. Unit Testing is a way to do exactly that. You write more code - the tests - that plug inputs into your functions and confirm that the outputs are what you expect. Ruby has a library (a pre-built collection of functions that you can use as a programmer) called MiniTest that you can use to do these tests. So you write a test program that loads up your code, and have the test program exercise the known inputs against your functions and compare against the expected outputs. Some programmers go really hard in the paint and write their tests before they ever write a line of code for the program in question.
The trick is…. you have to figure out how your code might break and write tests to make sure that if it does break, the results are expected. And then you rinse and repeat.
Get to the point, Keith
So, with all that put together, I actually found a bug in my project. I’m building an outliner - it makes outlines. I talked about this in the last post and I’ll write more about it later, but basically I’m writing data functions to do things to an outline. One of those functions was supposed to change the order of an item in the outline by moving it down one position in the list. That function looked like this:
def move_down
return nil if parent.nil?
my_index = parent.children.index(self)
return nil if my_index == parent.children.size - 1
parent.children.delete(self)
parent.children.insert(my_index, self)
return self
end
I’m new at writing tests - when I first started writing code, the idea of Unit Tests hadn’t been made popular outside of academic circles, and later on in my career I wasn’t making software for a living at all.
The test looks like this:
def test_movedown
node = OutlineNode.new('Top Node')
one = OutlineNode.new('One')
node.add_child(one)
two = OutlineNode.new('Two')
node.add_child(two)
one.move_down
assert_equal('Two', node.children.first.text)
end
That assert_equal bit is what makes this an actual test - we’re “asserting” that the two values passed into the assert_equal statement are actually equal - one of them, the 'Two', is the actual value we’re comparing against, and the other is the data inside the OutlineNode. When I ran this test, it was broken! It reported that the test failed. I looked at the test’s code and realized it was fully correct, so I looked at the move_down function. And then I saw the bug. If you’re also a programmer, you may have already caught the bug while reading it.
I didn’t actually move the item in the list at all. I deleted it and saved it back in its original position!
So I added + 1 to the last parent.children.insert statement and re-ran my test - and confirmed it was working as expected.
Big E Engineers working on physical items have the luxury of this thing called physics - a science that has figured out math for how objects interact. Big E Engineers can use that math to predict how their machines or buildings or whatever will work. Software Engineers don’t have that luxury, and because of that they get lazy. The real world enforces physics on you whether you want it to or not. Software doesn’t have that rigorous reality, so software engineers must resort to things like Testing to confirm their internal logic is correct.
The flaw in this is that if you don’t write the tests, or you don’t realize how these parts can fail, you can miss the tests, and then you have software that breaks in new and interesting ways you didn’t expect.
Ain’t that fun?
“The first principle is that you must not fool yourself — and you are the easiest person to fool.” - Richard Feynman