Sharpening the Saw
I recently gave a tech talk to our engineering department about "Sharpening the Saw". The premise of the talk was heavily influenced by this blog post. I talked about my experience with "Sharpening the Saw" in the hopes of encouraging others to take a similar path. This is my attempt at repurposing the tech talk into a blog post.
What is Sharpening the Saw?
Sharpening the Saw can be defined as anything we do that (theoretically) makes us stronger software engineers. Examples include reading technical blog posts, reading technical books, completing online courses, or studying research papers. Your engineering team should encourage and support this practice.
My experience
Last year, I completed an 8-week Software Systems course at Bradfield Computer Science. Bradfield is a small (think boutique) computer science school based in San Francisco. They're also responsible for teachyourselfcs.com and the newly released CSPrimer. The course I did was Software Systems: Behind the Abstractions. SSBA gives an overview of the computer science landscape with a specific focus on getting behind the abstractions. Some of the highlights of the course include writing assembly code, writing a simple virtual machine from scratch, and writing an interpreter from scratch. The main takeaway from my time at Bradfield is that computer science is a field built on layers of abstractions and it's worth it to peel back each layer and get familiar with what's happening under the hood.
Why should I care what's happening under the hood?
Here's a Javascript analogy that, hopefully, helps explain my motivations for getting below the abstractions. If you spend time learning the latest Javascript framework then you'll achieve just that, learning the latest Javascript framework (nothing wrong with that). However, if you spend time getting comfortable with the inner workings of the event loop, for example, then you'll build a solid mental model of how all Javascript code runs. Equipped with a solid understanding of the fundamentals, learning a new framework becomes a breeze.
Here is a diagram of the layers of abstraction in a typical software and hardware stack. Most software engineers typically work at the very top of that diagram. But our code runs on the CPU (many layers down). Being familiar with what happens between the top layer and the CPU is a superpower.
Pop quiz!
Here is a simple C program:
#include <stdio.h>
int main(){
unsigned long n = 1L << 63;
printf("%lu\n", n);
return 0;
}
All we're doing here is taking the number 1 and bit-shifting it to the left by 63 places. The result is the very large number 9,223,372,036,854,775,808
aka 2^63
. What do we expect when we change line 5 to printf("%lu\n", n << 1)
? Assuming you're using a 64-bit machine, you can expect the program to print 0
. We've just demonstrated basic integer overflow in C. Our machine has 64-bit registers, we've pushed our 1
too far to the left and it's "dropped off the end" leaving us with 0
.
Now let's try the same exercise in Javascript. What happens when we run 1 << 63
in a Nodejs REPL? It gives us the seemingly random negative number -2147483648
. Is this also overflow? Yes, but it's different. Javascript implements numbers as IEEE 754 floats. Only the first 53 bits of the float will give us an accurate number, beyond 53 bits we lose precision. However, Javascript requires bitwise operations to behave like 32-bit signed integer operations (🤯). Anything beyond 32 bits will overflow. So this is a 32-bit integer overflow ((1 << 63) === (1 << 31)
returns true
). The result is negative because of how twos complement works.
Jump into a Python REPL and try the same exercise. 1 << 63
gives us the same result as C. How about 1 << 64
? Why doesn't it overflow? The broad strokes answer is that Python, under the hood, handles integers larger than 64 bits in software (not hardware). Python implements a software algorithm that breaks large numbers up into chunks and stores them in an array. Of course, all of this is abstracted away under the hood.
That's bit-shifting numbers in 3 different languages that give 3 different behaviors. Something as simple looking as 1 << 63
acts extremely different in 3 different contexts because of what's happening behind the abstractions.
Here's another similar exercise for the reader:
What's the result of running
"😂".length
in Javascript and why?
Maybe I'll do a follow-up post explaining the answer.
Sharpening your saw
Hopefully, this blog post convinces you that sharpening your saw is a worthwhile endeavor and that these lower-level topics are approachable and learnable. Gaining a deeper understanding of computer science concepts will pay dividends for the rest of your career (in both financial terms and career fulfillment). If you've read this far and are still curious you should check out the following resources:
- Bradfield School of Computer Science
- Teach yourself cs
- CS Primer (The 82-second video on the home page is worth watching)
You can find similar posts from Bradfield alumni here, here, and here