Tag: Dive Deep

Worth A Million Words

Worth A Million Words

The other day I posted a rundown of my work setup. I thought it’d be fun as a follow-up to give a demo of various microphone options I considered. Besides, you might not often get to see my face, and isn’t it more fun to show than tell?

Shokz OpenRun Pro (headset mic) – It’s mobile, but sounds terrible
Macbook Pro (built-in mic) – Ubiquitous and setup-free, but sounds muffled
Razer Kiyo Pro (webcam mic) – Better, but still a bit of background honkiness
Blue Yeti X (external mic) – Aww yeah, that’s what I’m talkin’ about

What do you think? At what point do you believe there are diminishing returns? Or do you disagree with my quality ordering altogether?

Oh, and for anyone wondering, the quote is from Confucius.

Gut Check

Gut Check

It’s that time again: New Year’s resolutions! Per my meta-resolution, this year’s book reading target is 44, but I wanted to add a twist to it.

Leaders who are right a lot “seek diverse perspectives and work to disconfirm their beliefs.” In 2022, 16 of the 45 books I read in were by authors who were either not male or not white. That’s 36%, a sizable fraction; in line with what I did in 2021 (33%) and 2020 (38%), and much better than my rates in 2019 (18%) and 2018 (15%).

But none of those metrics are anywhere close to reality: about 91% of the world’s population is either not male or not white. That’s a lot of valuable perspectives I’m missing out on.

Given my backlog and some strategic book choices I have lined up, getting all the way to 40 out of 44 isn’t realistic, but I can still do better than my recent history. So in 2023, my plan is to double last year’s fraction to 32 out of 44 (73%). Ambitious but doable. And I can’t wait.

Already finished my first one, in fact: Radical Candor. And I’m a chapter into The Analects, by Confucius. It’s embarrassing that I’ve never read anything by this giant of ancient wisdom. Already found a couple sayings that stand out:

The Master acquires his information by being affable, kind, and respectful, and by showing restraint in his action and a willingness to yield. The way the Master asks for information is different from that of other people.

Do not worry that other people do not know you. But be concerned that you do not know them.

Good stuff!

Adventures With APIs

Adventures With APIs

I’ve written before about the advantages of knowing how to dig around in DevTools to reverse engineer website interfaces. This week I’ve had three further instances of doing this work to good effect.

The Friendly Skies

Firstly, I travel a lot, enough that I now have Executive Platinum status on American Airlines. This means I’m first in line for complimentary upgrades, but only if there are seats available. So I wanted an easy way to go straight to a complete seat map to look at availability without needing to go through a full search on the website. Turns out there’s a magic URL that does just that, and all you need to do is pass it some parameters. So I present to you, a quick and dirty seat map lookup form. Give it a try!

While that’s cool, I wondered if I could make it simpler by leveraging a flight data API like the one from FlightLabs. That was pretty straightforward as well. Though I couldn’t embed it in WordPress, I did script it up in Python for your enjoyment. Just pass it a flight number, and it’ll do the rest. Neat!

Shake It Off

This week Taylor Swift tour tickets went on sale, and needless to say it broke Ticketmaster, despite their best attempts to add friction via pre-registration to enter a lottery to win a code to join a queue to enter a room to maybe get lucky enough to click fast enough to buy tickets. Sadly I was unsuccessful at securing seats despite dozens attempts across several days. But I did learn something about the API Ticketmaster used to check queue status, so all was not lost.

When I first joined the queue, the following was displayed:

Of course I was curious: how many more than 2000 people were there… 5000? 25000? A million? So I opened up DevTools, and took a look at the calls coming back from the server to check status. Lo and behold, there was a wealth of info in an easily digested JSON block:

Wouldn’t it have been helpful to display that information to the user? At least the exact users in line, and the expected service time value. No idea why it wasn’t shown, other than Ticketmaster not being known as a terribly customer obsessed company.

Automating ******** Across ****

The final example can’t speak of publicly other to say my penchant for automation will save my employer a sizable amount of money. In these uncertain economic times, that’s always a good thing.

That Lovin’ Feeling

That Lovin’ Feeling

I’ve done a fair share of production debugging in my career. There’s a heroic Dopamine rush that comes with it, that feeling of diving deep on a problem in a critical situation, finding the solution, and then implementing it to the delight of your customers and teammates (well, either delight or they graciously allow you to live another day).

A similar feeling is experienced any time you build something with your own two hands and see it come to life; that joy is why this blog is named what it is, because I love to build. But as a manager of a technical team, I don’t get the chance to directly build solutions as often as I might like, and when I do, it often represents a failure of some kind. To be successful as a leader, one needs to learn to let the joy of building go, at least the hands-on kind.

Instead, what a manager must cultivate is the joy of watching others succeed, especially those to whom they are charged to mentor as direct reports. Being thrilled when a person shares a difficult problem they solved; rejoicing when a struggling individual responds to your coaching with positive growth. Giving others the spotlight and serving their needs while hidden from view. Learn to get a Dopamine hit from such experiences, and you’ll do well.

And here’s the real kicker: when you achieve it, you’ll have scaled your ability to enjoy your job beyond what was possible before, because you now have a whole team of people whose successes are a source of happiness, versus just your own. Ultimately you’re still building, but now it’s through others.

Expect The Unexpected

Expect The Unexpected

Like many other languages, Python has the notion of default values for function parameters, for example:

def compute_final_price(cost, sales_tax_rate=0.08):
    return cost * (1.0 + sales_tax_rate)

This code behaves as you would expect:

compute_final_price(10.0, 0.07)  # returns 10.7
compute_final_price(10.0)  # returns 10.8

However, interesting things happen in more complex cases, because the default value is evaluated once, at the time the function is defined, and not when it’s executed.

my_rate = 0.05

def compute_final_price(cost, sales_tax_rate=my_rate):
    return cost * (1.0 + sales_tax_rate)

compute_final_price(10.0)  # returns 10.5
my_rate = 0.06
compute_final_price(10.0)  # still returns 10.5

You can prove to yourself the value is evaluated at definition time by running help(compute_final_price), and you’ll notice the default value has already been computed:

Help on function compute_final_price in module __main__:

compute_final_price(cost, sales_tax_rate=0.05)

The above can be pernicious if the default value is an object that’s modified inside the function:

def add_to_list(item, my_list=[]):
    my_list.append(item)
    return my_list

add_to_list(4, [1, 2, 3])  # returns [1, 2, 3, 4]
add_to_list(1)  # returns [1]
add_to_list(1)  # returns [1, 1]

A common workaround for the above is to do the following:

def add_to_list(item, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(item)
    return my_list

add_to_list(1)  # returns [1]
add_to_list(1)  # returns [1]

I’ve encountered folks that insist one should never use default values except for None and the above pattern, but I’ve found most of the time I’m not mutating such defaults, and the behavior described here is fine. But still, the more you know.

Crossing The Rubicon

Crossing The Rubicon

There are a plethora of resources for those just getting started with software development, and best I can tell, most of them will do the job adequately well. But there isn’t nearly as much on what is needed next: guidance on how to get from “early intermediate coder” to “seasoned software engineer”. In brief, here are some suggestions:

  1. Study existing high-quality code; if you don’t know what good looks like, it’s nearly impossible to produce it.
  2. Write a lot of code. There’s no substitute for practice and putting in your time.
  3. Find someone who can give you feedback on the code you write, and humbly iterate per the guidance.

In my experience, an efficient way to do all three is to find a couple open source projects that need support, dig into their code, and submit contributions. You start from a base of quality code, get to write more, and you’ll get feedback through pull requests. And the icing on the cake is that it’s all done in public.

Spend a few years of doing the above and you’ll be well on your way to next-level programming expertise. It’ll also teach you how to code with a distributed and decentralized community of stakeholders, and that’s no small thing.

Throwing In The Towel

Throwing In The Towel

I’ve said before that I enjoy thinking about organizations, continuously optimizing them to give the highest probability of the desired outcomes. Organizational ergonomics, if you will. I’m also a mathematician by training and a nerd, which is what made Applying the Universal Scalability Law to organizations such a fascinating read, because it puts statistical weight behind its arguments.

In the same vein, I thoroughly enjoyed the following two articles that take a mathematical approach to understanding variability in project estimation (a perniciously difficult problem in all technical work):

The latter’s provocative title hopefully piques your interest enough to read further. You won’t regret it!

My Kingdom For A Streak

My Kingdom For A Streak

Because I’m a LeBron fan, when it comes to Wordle, I care more about longevity of sustained greatness (i.e. my perfect streak of winning games) than I do about having epic individual performances (i.e. having a lot games where I’ve won in 2 or 3 guesses). That means I need a way to keep my statistics even if I move between devices or need to replace one.

Almost to quadruple 100!

It turns out (at least as of this writing, who knows if the NYT will change it) that statistics are stored in a simple JSON object in browser local storage. On a laptop this storage is easily manipulated using DevTools, and on mobile it’s a bit tougher but still not too bad, at least on Android.

Both of the above, however, rely on GUIs. It got me thinking if there was a scriptable approach. First, I found a Python library (natch) that can speak the Chrome DevTools protocol. Then I had to figure out how to use it to read and write to local storage. That wasn’t too hard (even though the example here is in Node, the technique was portable). Finally I needed to make the library connect to both a laptop-based browser (easy), and my Android phone via adb (not so easy). Luckily I stumbled on the correct magical incantation in this post. Put it all together, and boom, I can now backup my Wordle streak, and easily transfer it between devices, using a script:

import chrome_local_storage

laptop_storage = chrome_local_storage.ChromeLocalStorage(port=9222)
phone_storage = chrome_local_storage.ChromeLocalStorage(port=9223)

wordle_stats = laptop_storage.get('games/wordle', 'nyt-wordle-statistics')
phone_storage.set('games/wordle', 'nyt-wordle-statistics', wordle_stats)

Want to see how I did it? Well, you’re in luck, because I packaged up my implementation and published it on PyPI. The source code is also on Github. Enjoy!

Hooray For Heuristics

Hooray For Heuristics

Besides the two resolutions I made for 2022, I’ve decided to try out a meta-resolution: every year from here on out, I will resolve to read the same number of books as years I am old (inspired by the coincidence that I finished 42 books last year, which happened to match my age). I track all my reading on Goodreads, where you can follow along if you’d like.

Given the above challenge, I wanted to determine how much of a time investment was going to be involved, and especially wanted an easy way to break it into daily reading targets that would keep me on pace. To do so, I needed two pieces of data: an average book size in pages, and an average time spent per page. My gut feel for these values was 300 pages and 1 minute, which led to a nifty conclusion: if I let A be my age, and aim to read A pages per day, which takes roughly A minutes, I should be able to easily complete my goal of A books over the course of the year (365 > 300, but I expect I’ll miss days here and there). Plugging in my current age of 43, that means a modest investment of 43 minutes per day is all it takes to achieve what otherwise sounds like a difficult goal. Isn’t that neat?

Neat enough that I wanted to validate my assumptions. For average book size, I downloaded the last 10 years of my reading records from Goodreads (I’ve been doing this a while): 84218 pages divided by 288 books gives an average size of 292 pages. My guess was pretty dang close, cool!

To measure my reading speed, I timed how long it took me to read 10 pages of three representative books: Multipliers (business/engineering non-fiction), Lifting the Veil (religious non-fiction), and The End of Eternity (science fiction). Resultant times were 6.5, 10.5, and 10.5 minutes for 10 pages, respectively, which averages out to 0.9 minutes per page. Once again, my intuition was reasonable.

One final statistic worth pondering: if I can hold to this meta-resolution, how many more books can I expect to read before I shuffle off this mortal coil. Thanks to Google, I know average life expectancy for a male in the United States is 75, so we’ll say I’ve got 32 years left. Thanks to Gauss, I can easily compute a sum from 1 to N with the formula N * (N+1) / 2. The sum of 1 to 75 is thus 75 * 76 / 2 = 2850, and now we need to subtract off years 1 through 42, which sum to 42*43 = 903, for a final result of 2850-903 = 1947 books. My Goodreads backlog is only 99 books long, so I guess I better start adding to it. Any suggestions?