inessential by Brent Simmons

My Wildly Incorrect Bias About Corporate Engineers

Before I went to work for Audible (five years ago now — time flies!) I had a bias about engineers that worked for large corporations. I assumed that they weren’t as good as indies and engineers at small companies, or else they’d actually be indies or work at small shops like Omni.

Obviously I knew there had to be exceptions, particularly at Apple, or else we wouldn’t have had great things like AppKit and UIKit and everything else we’ve built on over these years. But the bias persisted.

* * *

Before Audible, the largest company I’d ever worked at (Newsgator) had just over 100 people. When I worked at Omni it had roughly half that number.

I’ve spent half my career working at even smaller companies, with just me and Sheila (Ranchero Software) or at places with three people (Q Branch) or like six people (UserLand Software).

And of course I was arrogant enough to think that I was better — much better — than any corporate engineer. While a corporate engineer might own some small part of an app or framework — or just a single button, as the (lame) joke went back in the day — I was shipping entire apps on my own or with a very small team. Popular, valuable, newsworthy apps that people loved.

And I wasn’t the only one: think of Flying Meat, Rogue Amoeba, Bare Bones, Red Sweater, The Iconfactory and many more.

* * *

And so I learned very quickly when I started at Audible that I was very wrong. I was impressed, and grew more impressed as time went on, by my fellow engineers’ rigor, talent, professionalism, care, and, especially, ability to work with other people toward common goals.

While I’m the die-hard introvert who just wants to go into a room and sit in front of a Mac and write some code and get things done, I learned that my co-workers — even if they, like me, kinda just wanted to sit and write code — were great at app development as a team sport. I was impressed with how they wanted to grow and did grow — always leveling-up their individual skills and their ability to work on a team and across teams.

And what a team it was! It’s not a new observation, but the indies I mentioned above, and the ones I didn’t, tend to be white men born in the United States — the people who could most afford to fail, in other words, because for them (for me, absolutely) there’s always another opportunity.

My team didn’t look like that — it was quite a contrast with my previous experience. Many more women, people of color, people born outside the United States. (But note that there’s always more progress to be made!)

The engineers on my team could write apps as well, if not better in many cases, than the indies I know. And the ones who aren’t quite there yet — well, just give them a little more time. They’ve all given me reason to believe in them.

I regret my bias about engineers working in corporate environments, and I’m so glad I learned the truth almost from day one on starting at Audible.

* * *

For a couple years I did a lot of hiring — a lot of interviews — at Audible. And I noticed something: there was a strong correlation between being hirable and having worked with other people.

The folks who’d worked largely by themselves, or on just one small team, weren’t as good candidates as the folks who’d worked with more people. This, of course, went against my original bias that indies are the best engineers — but by then I knew that a candidate who’d worked with lots of other people had been exposed to more code, more dilemmas, more challenges (technical and human), and they were not just more ready to work on a larger team but more knowledgeable. Even their individual skills were greater.

Advice time: if you’re a newer engineer, find ways to work with other people. Not just because you’re more likely to get hired at a place like Audible — but because, no matter where you want to work, you’ll be better at it.

You can’t just sit alone in front of your computer all day and write code and expect to be a great engineer.

Lesson learned!

* * *

With retirement imminent — this is my last job, and June 6 is my last day (maybe I’ve buried the lede here) — I want to thank my team publicly for how they’ve made me a better engineer and, more importantly, a better person. From the bottom of my heart.

I learned more from them than I could ever have taught; I got the better part of this deal.

Thank you, team! So much. ❤️

Harris for President

Donald Trump is a gross villain and a traitor to our country. He’s a convicted felon, adjudicated rapist, and head of a criminal organization; he works with criminals and he pardons criminals; he’s a narcissist and violent insurrectionist, racist and misogynist; he’s the master of lies and corruption and self-serving.

He plans to rule as a fascist dictator, and this time has the backing to do so, for the benefit of him and his ultra-wealthy friends. Not for you.

For everybody else, the various enemies within — everybody who isn’t a straight white male who goes along with the program — there will be concentration camps, deportation, prison, and rumors and threats of each. There will be more deaths in hospital parking lots.

I have voted for Kamala Harris. I ask you to vote for her too.

I happen to think Harris would be very good, possibly even great, as president. But it hardly matters!

Voting for her is how we stop this. And we have to stop this.

Seattle Xcoders 20th Anniversary Meetup

This Thursday, Oct. 17, 2024, is the 20th anniversary of the Seattle Xcoders! We’d love to see you there, at 7 pm at Bale Breaker and Yonder Cider taproom in Ballard.

Everyone is welcome! It’s not just for people who write code — it’s for designers, testers, support folks, and everyone who helps make Apple-ecosystem apps. Even if you just like those kinds of apps and like talking about them, come join us!

We’re usually outside by these propane fire things, but I’m not sure this time — we might have a room or some area or something. We should be easy to find, at any rate.

It’s not actually a meeting with presentations — it’s just hanging out and talking. Which we do every first, third, and fifth Thursday (you can subscribe to our calendar). One of these days we’ll get back to presentations — but the social part is valuable, and so we keep it up.

PS Looks like the food truck is Impeckable Chicken, which I’ve heard good things about. :)

PPS I’m usually easy to spot: quite well into middle age, with nothing like the amount of hair I once had. Black jeans, usually a black sweatshirt. Glasses. Doc Martens. Not tall.

NetNewsWire and Conditional GET Issues

I had thought that NetNewsWire’s conditional GET support was rock-solid — and so my first reaction was to be very surprised to learn that it’s not!

My second reaction was to be appreciative — Rachel’s work here on setting up a test server and reporting on the results is really great. My goal has always been to make NetNewsWire a model net citizen, and learning where it’s not is super valuable. So: much respect and thanks to Rachel for this.

The Data

Let’s look at some data and try to figure out what’s happening. Here’s Rachel’s report for NetNewsWire.

Things to know: these are all requests for a NetNewsWire-specific feed, and the copy of NetNewsWire making these requests is on my personal laptop. That laptop is occasionally used for development, which can throw things off, but not often. You can even see in the data a gap lasting just over two weeks where there were no requests (I was on vacation).

(You can also see some anomalies from when I had it on my dev machine also — ignore every row where ip is v6, since that’s my dev machine.)

Another thing to know: this is testing direct feed-reading, as with the On My Mac (or iPhone/iPad) and iCloud accounts. With systems such as Feedly, Feedbin, and so on, we get the data from the sync system and not directly from the site.

Ignoring Timing Issues

Let’s set aside, at least for today, the timing issues. That situation could be improved, but it very much reflects that this is a desktop app with a command that allows you to refresh feeds manually, without having to wait for the next poll.

Conditional GET Issues

First, a refresher on how this should work.

When a server returns a Last-Modified header, the client should return that exact same string in follow-up requests in an If-Modified-Since header. The server then looks at the If-Modified-Since header and decides to either return a 200 plus the feed — if it has been modified since — or return a 304 Not Modified response and an empty body.

It’s the same story with the Etag header. The client should save it and return it in follow-up requests in an If-None-Match header.

This is great because it can save a ton of bandwidth, which is great for server and app alike. And NetNewsWire’s been doing this since the early 2000s.

But clearly there’s a bug! In some cases, NetNewsWire is not picking up and saving the changed Last-Modified and Etag headers. Sometimes it does, and sometimes it keeps using whatever it already had and ignores the new ones.

What could account for this? Let’s look at the logic.

Feed processing logic

Here’s what happens when a feed download completes without errors and the content is non-empty:

First we check the hash of the raw feed data against the hash of the raw feed data the last time it actually changed. If those hashes match, then the app stops processing, because the feed hasn’t changed: it’s exactly the same as last time.

This is an optimization that deals with the fact that many servers unfortunately don’t support conditional GET. It allows the app to skip feed parsing and updating the database. Saves a bunch of work. Good for battery life.

If the hashes don’t match, then processing continues: it parses the feed and then sends the parsed articles to the code that updates the database.

After that it updates and saves the hash of the raw feed data, and finally it stores the conditional GET info — it saves any Last-Modified and Etag header values to send with the next request.

This isn’t actually the code, but it’s what the logic looks like:

downloadDidComplete(httpResponse, feed, feedData)
	hash = feedData.md5
	if hash == feed.previousHash then return
	parsedFeed = parse(feedData)
	updateDatabase(feed, parsedFeed)
	feed.previousHash = hash
	feed.conditionalGetInfo = conditionalGetInfoFromResponse(httpResponse)

My theory

There’s a great chance you’ve already spotted what I think is the issue: it’s that optimization where we check the hash of the raw feed data and return if it matches the previous hash.

Here’s what I think has happened in some of the tests: the raw feed data was unchanged, but one or both of the Last-Modified and Etag header values did change.

NetNewsWire never picked up the changes to those headers, because that code didn’t run — it had already bailed when it saw that the raw feed data was unchanged.

The assumption I made when I wrote this code was that if the raw feed data was unchanged then of course the Last-Modified and Etag header values would be unchanged too, so there was no need to check to see if they were new.

And I think that in real-world situations this is probably true pretty much all the time, and it’s only in tests like this where my assumption wouldn’t be true.

But I can’t say that for sure! This is a real bug, and we’ll fix it and add a test or tests to make sure it doesn’t happen again.

Here’s what the new logic should look like:

downloadDidComplete(httpResponse, feedData, feed)
	hash = feedData.md5
	if hash != feed.previousHash {
		parsedFeed = parse(feedData)
		updateDatabase(feed, parsedFeed)
		feed.previousHash = hash
	}
	feed.conditionalGetInfo = conditionalGetInfoFromResponse(httpResponse)

With the above logic, conditionalGetInfo gets updated no matter what.

PS There could be other bugs

My theory does point to a bug that should get fixed. But is it the only bug? Is it even the bug that causes the issues in these tests?

Though I’m pretty confident that this is the bug — seems pretty obvious, right? — more investigation and testing is warranted.

Reruns

It’s not a bug in your RSS reader if recent articles in this feed all suddenly appeared as unread. You may even have seeming duplicates.

Sorry about that! It’s due to my changing settings in my blog generator. Pages now have a .html suffix where before they had none. This change impacts permalinks, which also changes the guids in my RSS feed — and NetNewsWire and other RSS readers use the guid property to identify articles, which means these will show up as new articles.

(Note: I’ve created redirects so that old links pointing in will still work.)

Why NetNewsWire Isn’t Available for Vision Pro

I’ve been getting questions about NetNewsWire’s unavailability on Vision Pro. Why isn’t it there? When might it be there?

Here’s the scoop:

I consider it risky to support an app running on a device I don’t own. Imagine writing a Mac or iPhone app and not actually running it on one of those machines — you wouldn’t.

I realize that the app would act as if it were running on an iPad — but the Vision Pro is not really an iPad. It’s a device with very different interactions from the direct manipulation we’re used to on iPad. And the compatibility mode is a new thing because this device is a new thing — we don’t know how well it works and what the gotchas might be.

I could test it on the simulator, sure, but the simulator is a convenience for developers. It’s no replacement for running it on a real device.

And, yes, the app is open source, which could mean that other developers with a Vision Pro could help support it — but it’s important to remember that I’m the only person who has to support it. The NetNewsWire team is awesome, but I’m the one on the line for this.

So I want to be careful and go slowly, because if I made it available it would be extremely difficult to reverse the decision and take it away, even with an excellent reason.

I’m hoping that a consensus will form among developers that running apps on Vision Pro as iPad apps is fine, that it’s a cakewalk. If that happens, I’d go ahead and do it too. But it’s too soon for us to know that.

PS Why don’t I have a Vision Pro? I’m sure it’s an incredible technical achievement and an amazing experience — and pretty damn wonderful in just about every way — but it’s just not my thing. I like reading and writing mostly, plus making apps for reading and writing. My personal future of computing has been here for all these years — the Mac.

PPS Eventually the price will come down to where I’d consider buying one as a test device and for a little fun — but that may be a few years away. I’m hoping that we’ll find, sooner than that, that running as iPad on Vision Pro is a-okay.

Corporations Are Not To Be Loved

I started using Apple computers — and writing code for them, starting with BASIC — 43 years ago, before the Macintosh, even, and I’ve made this my career. I’ve had all these decades to really, thoroughly delight in these incredible machines and software, and to give a little back with my own apps.

Apple’s positive effect on my life should not be underestimated. My Mom once (lovingly, teasingly) said to me that my alternate career, had all this never happened, was “criminal genius.” Which might have been fun too, but possibly more stressful than I might have liked. At any rate, Apple has saved me from a life of crime, and I should love Apple for that.

But I need to remember, now and again, that Apple is a corporation, and corporations aren’t people, and they can’t love you back. You wouldn’t love GE or Exxon or Comcast — and you shouldn’t love Apple. It’s not an exception to the rule: there are no exceptions.

Apple doesn’t care about you personally in the least tiny bit, and if you were in their way somehow, they would do whatever their might — effectively infinite compared to your own — enables them to deal with you.

Luckily, Apple has just provided us all with a reminder. Just like the sixth finger in an AI-rendered hand, Apple’s policies for Distributing apps in the U.S. that provide an external purchase link are startlingly graceless and a jarring, but not surprising, reminder that Apple is not a real person and not worthy of your love.