More from Hixie's Natural Log
When you build something, you have to pick some design goals and priorities. Ideally you do so explicitly, but even if you don't, you're still implicitly doing so based on your design choices. These choices are trade-offs. If you want to write a quiet song, it won't be loud. If you are writing a software tool and you want to prioritize speed over simplicity, then it won't be as simple as if you'd prioritized simplicity over speed. There are two main signs that you've succeeded at your goals. The first, and more pleasant, is that you get compliments about how your thing is like you wanted it to be. "I love that song, it's so quiet!" "Your tool is so fast!" Why thank you, that's exactly what I was going for. The second sign, though, is that you will get complaints. Specifically, people will complain that your thing does not achieve the things you didn't set out to achieve. "I wish this song was louder", "this tool is so hard to use". That you are receiving complaints at all means that people are aware of your creation; that they are complaining about what you specifically set out to make a non-goal is a side-effect of the fact that you made that trade-off. The worst thing to do, when you receive such complaints, is take them to heart and try to fix them. This is because by definition you wanted these complaints. They are a sign that the thing you built is built as you wanted to build it. The people complaining want something different, they don't want your thing. It's just that your thing is so good that it's the thing they're compelled towards even though it doesn't prioritize the things they care about most. If you try to fix these complaints, you will, again by definition, be compromising on your goals. If you make the song have a loud part, then it's no longer a quiet song. You wanted a quiet song. Now it's a song that's quiet in parts and loud in parts. It probably still doesn't satisfy the needs of the people who want a loud song, and now it also doesn't satisfy the needs of the people who wanted your original quiet song. If you make your tool easier to use by compromising on the speed, then now you have a tool that's both not as fast as it could be and not as usable as it could have been if you'd started with that as a goal. It's important, therefore, to separate out complaints into those that are complaints you expect based on your design goals (which you should acknowledge but not fix), vs complaints that are either orthogonal to your goals (which you can fix without compromising your goals), or that are in line with your goals (which you should prioritize, since that's what you said to yourself was most important in the first place).
My involvement in web standards started with the CSS working group. One of the things that we struggled with as a working group was that we would specify how the technology should work, but the browser vendors' implementations weren't exactly what we intended, and web authors would then write web pages that worked with those browsers, even though that meant the web pages themselves were also not doing things like the specifications said they should. The folks I worked with at the W3C (especially the academics and people working for organizations that did not themselves implement browsers) would frequently bemoan this state of affairs, expressing surprise at how they, the people in charge of the standards, were not being respected by the people implementing the standards. One of the key insights I had very early on in my work, before working on HTML5, which really influenced the WHATWG and its work, is the realization that the power dynamics at work were not at all the power dynamics that the folks at the W3C described. The reality of the situation was that the power lay entirely in the hands of the users. The users chose browsers. A browser vendor that ignored what the users wanted would lose market share. Market share is everything in this space. Browser vendors want users because they can convert users into dollars (in various ways, but they typically boil down to someone showing them ads and paying the browser vendors for the privilege). In turn, the browser vendors had more power than the specifications. What they implement is, by definition, what the technology is. The specification can say in absolute clarity that the keyword "marigold" should look yellow, but if a browser vendor makes it look red, then no web author is going to use it to mean yellow, and many will use it to mean red. There is a feedback loop here: if one browser implements "marigold" to mean red, and some important web site (or many unimportant web sites) rely on it, and say something like "best viewed in ThisOrThat browser!" because that's the one they use and in that browser it looks red and red is what looks best, then the other browser vendors are incentivised to make sure that the web page looks good in their browser too. Regardless of what the specification says, therefore, they are going to make "marigold" look red and not yellow. When I realized this, I also realized a corollary: if you have two competing specifications that both claim to define the same technology, but one matches what the browsers already do while the other one does not, the browser vendors are going to find it more useful to follow the one that matches what they do. This is because they can trust that implementing that specification will get them more market share. It means they won't have to stop and think at every step, "will following this specification cause me to lose users?". It is easier for them to use a specification that takes into account their needs in this way. We actually tried to explain this to the W3C membership. There was a big meeting in 2004 at Adobe in San Jose, the "W3C Workshop on Web Applications and Compound Documents". We tried to convey the above (I didn't quite understand it in the stark "power dynamic" terms yet, or at least, I didn't really express it in those terms, but if you read our position paper you can see this insight starting to crystalize). At this meeting, we made a pitch for the W3C to continue to maintain HTML and to care about what the browser vendors wanted. Representatives from Microsoft and Sun (in many ways arch enemies at the time) supported us. I seem to recall Apple being more quiet about it at the meeting but also essentially supporting the principles. The W3C membership resoundly rejected this whole concept. One of the W3C staff even explicitly said something along the lines of "if you want to do this you should do it elsewhere". That's what led to the WHATWG being founded a few weeks later. The WHATWG was founded on this core principle — the specifications need to actually specify reality. When the browsers disagree with the spec, the spec is by definition incorrect and needs to change, regardless of how much technically superior the design in the spec is. Naturally, when you provide browser vendors with something that valuable, they will follow. You end up with a weird inverted power dynamic. The spec writer (when they follow this principle) has all the power, but only within the space that the browser vendors are themselves willing to play; and the browser vendors have all the power, but only within the space that the users are willing to put up with. It's very easy to appear to be in control when you tell people to do the thing they were going to do anyway (or at least, one of the things they were willing to do if they were to think about it). There is a (probably apocryphal) quote supposedly by Alexandre Auguste Ledru-Rollin that is often cited in mockery of bad leadership, but that perfectly matches the power dynamic here: "There go my people; I must find out where they are going so I can lead them". (Thanks to Leonard Damhorst for prompting me to write this post.)
I often get asked how many people contribute to Flutter. It's a hard question to answer because "contribute" is a very vague concept. There's tens of thousands of packages on pub.dev, all of which are written by contributors to the community. There's over 100,000 of issues filed in our issue database, filed by more than 35,000 people over the years (the exact number is hard to pin down because people sometimes delete their GitHub accounts; about 700 issues have been filed by people who have since deleted their account). Many more people still have used the "thumbs-up" reaction to indicate that an issue matters to them, with almost 165,000 thumbs up from about 45,000 people. All of these people are valuable contributors to Flutter. Usually, when pressed, people try to clarify by asking about "the core team". Again though it's hard to say exactly what that means, but let's assume they mean "people with commit access". That is, people we trust enough to have added to the GitHub repo as collaborators. This includes people who work on Flutter for companies like Google, Canonical, or Nevercode, and it includes people like me who are self-employed and/or contribute to Flutter on a volunteer basis. Currently that's about 280 people. So is that the answer? Well, no, not really. Some people have commit access but aren't active (maybe they got access because of their employer, but were then reassigned to work on another project, and the bureaucracy hasn't caught up with them yet — we only audit the membership occasionally because it's rather tedious to do). Some people have been very active recently but don't have commit access (e.g. because they were just laid off and a bot automatically removed their access; they might even resume working on Flutter in the future, as a volunteer or funded by another company). So what's the answer? I recently drilled down through our data to see if I could answer this. I will caveat the following numbers by saying that this changes all the time. We added a new team member just today (hi Nate!) who is not counted as a team member in the following numbers because we collected the data a few weeks ago (it takes literally days to scrape all the data from GitHub, and then hours to explore the resulting very large and very slow spreadsheet). Also, some of my definitions are a bit arbitrary, and slightly tweaking the limits would probably change the numbers noticeably. First, I collected a list of everyone who has ever created an issue, commented on an issue, put an emoji reaction on the first comment of an issue, or submitted a PR, excluding bots and people who deleted their GitHub account. (Actually Piinks did the actual data collection. Thanks!) I limited this to a subset of the GitHub repos of the flutter org that is relatively inclusive but does not count everything (we have a lot of historical repositories and so forth). This finds about 94,357 people. (So there you go. The Flutter team is about a hundred thousand people!) To avoid padding the numbers with people who left the project long ago, and to avoid counting "drive-by" contributors who came, did a bunch of work, and then left, I then limited the data set to people who contributed over a period of more than 180 days, and who last contributed sometime in 2024. Because of the definition of "contributed" described above, that means that someone who added a thumbs-up to an issue in December 2020 and then filed an issue in January 2024, and did nothing else, is included, but someone who submitted two PRs in March 2024 is not. Like I said, this is a bit arbitrary. Anyway, that leaves 3,839 people, of which 182 currently have commit access, 27 once had commit access but don't currently (these are mainly people who either got laid off recently and had their commit access revoked by an automated process, or people who were once team members, left, lost access from inactivity long ago, and then later came to comment on issues or file new issues — it's surprisingly common for people who once worked on Flutter full time to stick around even when their employment changes), and about 3,627 people who have never had commit access. Of those who have never had commit access, 2,407 have filed at least one issue or submitted at least one PR (accounting for a total of 12,383 issues and 2,613 PRs). Of those, 341 have filed 5 to 9 issues (2,242 issues total), and 296 have filed 10 or more issues in their lifetime (7,021 total issues). Similarly, of the "never had commit access" cohort, 73 people have sent 5 to 9 pull requests in their lifetime (458 total PRs) and 47 have sent 10 or more (1,321 PRs total). (For context, 4,663 people have ever submitted a pull request, and 429 have ever submitted more than 10 PRs.) Of the people who currently have commit access, 98 people have submitted more than one PR every 3 weeks on average since they first got involved (accounting for 49,173 PRs), 75 people have closed at least one issue every 3 weeks (accounting for 48,490 total issue closures), of which 10 are not in the first group (mostly that's our triage team), and 150 people have commented at least once every 3 weeks. A follow-up question a lot of people ask is "do they all work for Google?". This is a surprisingly hard question to answer. There are a lot of weird edge cases. For example, one person worked on Flutter for a company that Google hired to work on Flutter, but then quit that company, asked for their commit privileges to be removed, but continued to be active in the community. Several people who have quit Google (such as myself), or been laid off by Google, have continued to be active in one sense or another (I think I submit more code to Flutter now than I did in my last year at Google). It's also hard to answer because a lot more people at Google contribute to Flutter than just those on Google's Flutter team, and a lot of people on Google's Flutter team contribute in ways that don't show up on GitHub (e.g. product management, marketing, developer relations, internal tooling). Of the 98 people who have commit access, have been active for more than 180 days, have contributed at least once this year, and have submitted more than one PR every 3 weeks on average for the entire time they've been contributing, I estimate (based on what I know of people's employment and so forth) that about 85% are Googlers or somehow get their funding from Google, and about 15% are currently independent of Google. (This is by no means the entirety of the Google team contributing to Flutter; as I mentioned earlier, many folks at Google working on Flutter don't appear in these statistics.) I'm not sure what conclusion to draw from this; it's both more people than I expected to see funded by Google, which is great, and fewer people that aren't funded by Google, which is less great. On the other hand, it's still a significant number of non-Google-funded people. Is it enough? I think that really depends on what your goals are. I think if your goal is for Flutter to be an order of magnitude better than other UI frameworks, then frankly no, it's not enough. There is a ton of work to be done to get there. We know what it would take, but we don't have the people to do it today. On the other hand if your goal is to be a great framework, on par with others, then it's probably adequate. It would certainly be difficult to continue to be great with fewer people today. Of course, that may change as we complete big efforts, or as we take on new ones, or as the landscape changes, it's all hard to predict. That said, I would love to see more direct contributions from non-Google sources, if for no other reason but to end this silly "will Google cancel Flutter" line of questioning that has followed the project since its inception. It's a dumb question. Flutter's an open source UI framework. It will never die. It will become old and something else will shine brighter one day, just as happens with literally every other UI framework ever. That's just how our industry works. There's no reason to believe that'll happen any time soon though, and certainly no reason for it to happen earlier for Flutter than any other modern UI framework.
Despite my departure from Google, I am not leaving Flutter — the great thing about open source and open standards is that the product and the employer are orthogonal. I've had three employers in my career, and in all three cases when I left my employer I continued my job. With Netscape I was a member of the team before my internship, during my internship, and after my internship. With Opera Software, I joined while working on standards, kept working on standards, and left while working on the same standard that I then continued to work on at Google. So this is not a new thing for me. Flutter is amazingly successful. It's already the leading mobile app development framework, and I think we're close to having the table stakes required to make it the obvious default choice for desktop development as well (it's already there for some use cases). It's increasingly used in embedded scenarios. And Flutter is extremely well positioned to be the first truly usable Wasm framework as the web transitions to the more powerful, lower-level Wasm-based model over the next few years. In the coming month I will prepare our roadmap for 2024 (in consultation with the rest of the team). For me personally, however, my focus will probably be on fixing fun bugs, and on making progress on blankcanvas, my library for making it easy to build custom widget sets. I also expect I will be continuing to work on package:rfw, the UI-push library, as there has been increasing interest from teams using Flutter and wanting ways to present custom interfaces determined by the server at runtime without requiring the user to download an updated app.
I joined Google in October 2005, and handed in my resignation 18 years later. Last week was my last week at Google. I feel very lucky to have experienced the early post-IPO Google; unlike most companies, and contrary to the popular narrative, Googlers, from the junior engineer all the way to the C-suite, were genuinely good people who cared very much about doing the right thing. The oft-mocked "don't be evil" truly was the guiding principle of the company at the time (largely a reaction to contemporaries like Microsoft whose operating procedures put profits far above the best interests of customers and humanity as a whole). Many times I saw Google criticised for actions that were sincerely intended to be good for society. Google Books, for example. Much of the criticism Google received around Chrome and Search, especially around supposed conflicts of interest with Ads, was way off base (it's surprising how often coincidences and mistakes can appear malicious). I often saw privacy advocates argue against Google proposals in ways that were net harmful to users. Some of these fights have had lasting effects on the world at large; one of the most annoying is the prevalence of pointless cookie warnings we have to wade through today. I found it quite frustrating how teams would be legitimately actively pursuing ideas that would be good for the world, without prioritising short-term Google interests, only to be met with cynicism in the court of public opinion. Charlie's patio at Google, 2011. Image has been manipulated to remove individuals. Early Google was also an excellent place to work. Executives gave frank answers on a weekly basis, or were candid about their inability to do so (e.g. for legal reasons or because some topic was too sensitive to discuss broadly). Eric Schmidt regularly walked the whole company through the discussions of the board. The successes and failures of various products were presented more or less objectively, with successes celebrated and failures examined critically with an eye to learning lessons rather than assigning blame. The company had a vision, and deviations from that vision were explained. Having experienced Dilbert-level management during my internship at Netscape five years earlier, the uniform competence of people at Google was very refreshing. For my first nine years at Google I worked on HTML and related standards. My mandate was to do the best thing for the web, as whatever was good for the web would be good for Google (I was explicitly told to ignore Google's interests). This was a continuation of the work I started while at Opera Software. Google was an excellent host for this effort. My team was nominally the open source team at Google, but I was entirely autonomous (for which I owe thanks to Chris DiBona). Most of my work was done on a laptop from random buildings on Google's campus; entire years went by where I didn't use my assigned desk. In time, exceptions to Google's cultural strengths developed. For example, as much as I enjoyed Vic Gundotra's enthusiasm (and his initial vision for Google+, which again was quite well defined and, if not necessarily uniformly appreciated, at least unambiguous), I felt less confident in his ability to give clear answers when things were not going as well as hoped. He also started introducing silos to Google (e.g. locking down certain buildings to just the Google+ team), a distinct departure from the complete internal transparency of early Google. Another example is the Android team (originally an acquisition), who never really fully acclimated to Google's culture. Android's work/life balance was unhealthy, the team was not as transparent as older parts of Google, and the team focused on chasing the competition more than solving real problems for users. My last nine years were spent on Flutter. Some of my fondest memories of my time at Google are of the early days of this effort. Flutter was one of the last projects to come out of the old Google, part of a stable of ambitious experiments started by Larry Page shortly before the creation of Alphabet. We essentially operated like a startup, discovering what we were building more than designing it. The Flutter team was very much built out of the culture of young Google; for example we prioritised internal transparency, work/life balance, and data-driven decision making (greatly helped by Tao Dong and his UXR team). We were radically open from the beginning, which made it easy for us to build a healthy open source project around the effort as well. Flutter was also very lucky to have excellent leadership throughout the years, such as Adam Barth as founding tech lead, Tim Sneath as PM, and Todd Volkert as engineering manager. We also didn't follow engineering best practices for the first few years. For example we wrote no tests and had precious little documentation. This whiteboard is what passed for a design doc for the core Widget, RenderObject, and dart:ui layers. This allowed us to move fast at first, but we paid for it later. Flutter grew in a bubble, largely insulated from the changes Google was experiencing at the same time. Google's culture eroded. Decisions went from being made for the benefit of users, to the benefit of Google, to the benefit of whoever was making the decision. Transparency evaporated. Where previously I would eagerly attend every company-wide meeting to learn what was happening, I found myself now able to predict the answers executives would give word for word. Today, I don't know anyone at Google who could explain what Google's vision is. Morale is at an all-time low. If you talk to therapists in the bay area, they will tell you all their Google clients are unhappy with Google. Then Google had layoffs. The layoffs were an unforced error driven by a short-sighted drive to ensure the stock price would keep growing quarter-to-quarter, instead of following Google's erstwhile strategy of prioritising long-term success even if that led to short-term losses (the very essence of "don't be evil"). The effects of layoffs are insidious. Whereas before people might focus on the user, or at least their company, trusting that doing the right thing will eventually be rewarded even if it's not strictly part of their assigned duties, after a layoff people can no longer trust that their company has their back, and they dramatically dial back any risk-taking. Responsibilities are guarded jealously. Knowledge is hoarded, because making oneself irreplaceable is the only lever one has to protect oneself from future layoffs. I see all of this at Google now. The lack of trust in management is reflected by management no longer showing trust in the employees either, in the form of inane corporate policies. In 2004, Google's founders famously told Wall Street "Google is not a conventional company. We do not intend to become one." but that Google is no more. Much of these problems with Google today stem from a lack of visionary leadership from Sundar Pichai, and his clear lack of interest in maintaining the cultural norms of early Google. A symptom of this is the spreading contingent of inept middle management. Take Jeanine Banks, for example, who manages the department that somewhat arbitrarily contains (among other things) Flutter, Dart, Go, and Firebase. Her department nominally has a strategy, but I couldn't leak it if I wanted to; I literally could never figure out what any part of it meant, even after years of hearing her describe it. Her understanding of what her teams are doing is minimal at best; she frequently makes requests that are completely incoherent and inapplicable. She treats engineers as commodities in a way that is dehumanising, reassigning people against their will in ways that have no relationship to their skill set. She is completely unable to receive constructive feedback (as in, she literally doesn't even acknowledge it). I hear other teams (who have leaders more politically savvy than I) have learned how to "handle" her to keep her off their backs, feeding her just the right information at the right time. Having seen Google at its best, I find this new reality depressing. There are still great people at Google. I've had the privilege to work with amazing people on the Flutter team such as JaYoung Lee, Kate Lovett, Kevin Chisholm, Zoey Fan, Dan Field, and dozens more (sorry folks, I know I should just name all of you but there's too many!). In recent years I started offering career advice to anyone at Google and through that met many great folks from around the company. It's definitely not too late to heal Google. It would require some shake-up at the top of the company, moving the centre of power from the CFO's office back to someone with a clear long-term vision for how to use Google's extensive resources to deliver value to users. I still believe there's lots of mileage to be had from Google's mission statement (to organize the world’s information and make it universally accessible and useful). Someone who wanted to lead Google into the next twenty years, maximising the good to humanity and disregarding the short-term fluctuations in stock price, could channel the skills and passion of Google into truly great achievements. I do think the clock is ticking, though. The deterioration of Google's culture will eventually become irreversible, because the kinds of people whom you need to act as moral compass are the same kinds of people who don't join an organisation without a moral compass.
More in programming
Email is your most important online account, so keep it clean.
Kubernetes is not exactly the most fun piece of technology around. Learning it isn’t easy, and learning the surrounding ecosystem is even harder. Even those who have managed to tame it are still afraid of getting paged by an ETCD cluster corruption, a Kubelet certificate expiration, or the DNS breaking down (and somehow, it’s always the DNS). Samuel Sianipar If you’re like me, the thought of making your own orchestrator has crossed your mind a few times. The result would, of course, be a magical piece of technology that is both simple to learn and wouldn’t break down every weekend. Sadly, the task seems daunting. Kubernetes is a multi-million lines of code project which has been worked on for more than a decade. The good thing is someone wrote a book that can serve as a good starting point to explore the idea of building our own container orchestrator. This book is named “Build an Orchestrator in Go”, written by Tim Boring, published by Manning. The tasks The basic unit of our container orchestrator is called a “task”. A task represents a single container. It contains configuration data, like the container’s name, image and exposed ports. Most importantly, it indicates the container state, and so acts as a state machine. The state of a task can be Pending, Scheduled, Running, Completed or Failed. Each task will need to interact with a container runtime, through a client. In the book, we use Docker (aka Moby). The client will get its configuration from the task and then proceed to pull the image, create the container and start it. When it is time to finish the task, it will stop the container and remove it. The workers Above the task, we have workers. Each machine in the cluster runs a worker. Workers expose an API through which they receive commands. Those commands are added to a queue to be processed asynchronously. When the queue gets processed, the worker will start or stop tasks using the container client. In addition to exposing the ability to start and stop tasks, the worker must be able to list all the tasks running on it. This demands keeping a task database in the worker’s memory and updating it every time a task change’s state. The worker also needs to be able to provide information about its resources, like the available CPU and memory. The book suggests reading the /proc Linux file system using goprocinfo, but since I use a Mac, I used gopsutil. The manager On top of our cluster of workers, we have the manager. The manager also exposes an API, which allows us to start, stop, and list tasks on the cluster. Every time we want to create a new task, the manager will call a scheduler component. The scheduler has to list the workers that can accept more tasks, assign them a score by suitability and return the best one. When this is done, the manager will send the work to be done using the worker’s API. In the book, the author also suggests that the manager component should keep track of every tasks state by performing regular health checks. Health checks typically consist of querying an HTTP endpoint (i.e. /ready) and checking if it returns 200. In case a health check fails, the manager asks the worker to restart the task. I’m not sure if I agree with this idea. This could lead to the manager and worker having differing opinions about a task state. It will also cause scaling issues: the manager workload will have to grow linearly as we add tasks, and not just when we add workers. As far as I know, in Kubernetes, Kubelet (the equivalent of the worker here) is responsible for performing health checks. The CLI The last part of the project is to create a CLI to make sure our new orchestrator can be used without having to resort to firing up curl. The CLI needs to implement the following features: start a worker start a manager run a task in the cluster stop a task get the task status get the worker node status Using cobra makes this part fairly straightforward. It lets you create very modern feeling command-line apps, with properly formatted help commands and easy argument parsing. Once this is done, we almost have a fully functional orchestrator. We just need to add authentication. And maybe some kind of DaemonSet implementation would be nice. And a way to handle mounting volumes…
Unexamined life is not worth living said Socrates. I don’t know about that but to become a better, faster, more productive programmer it pays to examine what makes you un-productive. Fixing bugs is one of those un-productive activities. You have to fix them but it would be even better if you didn’t write them in the first place. Therefore it’s good to reflect after fixing a bug. Why did the bug happen? Could I have done something to not write the bug in the first place? If I did write the bug, could I do something to diagnose or fix it faster? This seems like a great idea that I wasn’t doing. Until now. Here’s a random selection of bugs I found and fixed in SumatraPDF, with some reflections. SumatraPDF is a C++ win32 Windows app. It’s a small, fast, open-source, multi-format PDF/eBook/Comic Book reader. To keep the app small and fast I generally avoid using other people’s code. As a result most code is mine and most bugs are mine. Let’s reflect on those bugs. TabWidth doesn’t work A user reported that TabWidth advanced setting doesn’t work in 3.5.2 but worked in 3.4.6. I looked at the code and indeed: the setting was not used anywhere. The fix was to use it. Why did the bug happen? It was a refactoring. I heavily refactored tabs control. Somehow during the rewrite I forgot to use the advanced setting when creating the new tabs control, even though I did write the code to support it in the control. I guess you could call it sloppiness. How could I not write the bug? I could review the changes more carefully. There’s no-one else working on this project so there’s no one else to do additional code reviews. I typically do a code review by myself with webdiff but let’s face it: reviewing changes right after writing them is the worst possible time. I’m biased to think that the code I just wrote is correct and I’m often mentally exhausted. Maybe I should adopt a process when I review changes made yesterday with fresh, un-tired eyes? How could I detect the bug earlier?. 3.5.2 release happened over a year ago. Could I have found it sooner? I knew I was refactoring tabs code. I knew I have a setting for changing the look of tabs. If I connected the dots at the time, I could have tested if the setting still works. I don’t make releases too often. I could do more testing before each release and at the very least verify all advanced settings work as expected. The real problem In retrospect, I shouldn’t have implemented that feature at all. I like Sumatra’s customizability and I think it’s non-trivial contributor to it’s popularity but it took over a year for someone to notice and report that particular bug. It’s clear it’s not a frequently used feature. I implemented it because someone asked and it was easy. I should have said no to that particular request. Fix printing crash by correctly ref-counting engine Bugs can crash your program. Users rarely report crashes even though I did put effort into making it easy. When I a crash happens I have a crash handler that saves the diagnostic info to a file and I show a message box asking users to report the crash and with a press of a button I launch a notepad with diagnostic info and a browser with a page describing how to submit that as a GitHub issue. The other button is to ignore my pleas for help. Most users overwhelmingly choose to ignore. I know that because I also have crash reporting system that sends me a crash report. I get thousands of crash reports for every crash reported by the user. Therefore I’m convinced that the single most impactful thing for making software that doesn’t crash is to have a crash reporting system, look at the crashes and fix them. This is not a perfect system because all I have is a call stack of crashed thread, info about the computer and very limited logs. Nevertheless, sometimes all it takes is a look at the crash call stack and inspection of the code. I saw a crash in printing code which I fixed after some code inspection. The clue was that I was accessing a seemingly destroyed instance of Engine. That was easy to diagnose because I just refactored the code to add ref-counting to Engine so it was easy to connect the dots. I’m not a fan of ref-counting. It’s easy to mess up ref-counting (add too many refs, which leads to memory leaks or too many releases which leads to premature destruction). I’ve seen codebases where developers were crazy in love with ref-counting: every little thing, even objects with obvious lifetimes. In contrast,, that was the first ref-counted object in over 100k loc of SumatraPDF code. It was necessary in this case because I would potentially hand off the object to a printing thread so its lifetime could outlast the lifetime of the window for which it was created. How could I not write the bug? It’s another case of sloppiness but I don’t feel bad. I think the bug existed there before the refactoring and this is the hard part about programming: complex interactions between distant, in space and time, parts of the program. Again, more time spent reviewing the change could have prevented it. As a bonus, I managed to simplify the logic a bit. Writing software is an incremental process. I could feel bad about not writing the perfect code from the beginning but I choose to enjoy the process of finding and implementing improvements. Making the code and the program better over time. Tracking down a chm thumbnail crash Not all crashes can be fixed given information in crash report. I saw a report with crash related to creating a thumbnail crash. I couldn’t figure out why it crashes but I could add more logging to help figure out the issue if it happens again. If it doesn’t happen again, then I win. If it does happen again, I will have more context in the log to help me figure out the issue. Update: I did fix the crash. Fix crash when viewing favorites menu A user reported a crash. I was able to reproduce the crash and fix it. This is the bast case scenario: a bug report with instructions to reproduce a crash. If I can reproduce the crash when running debug build under the debugger, it’s typically very easy to figure out the problem and fix it. In this case I’ve recently implemented an improved version of StrVec (vector of strings) class. It had a compatibility bug compared to previous implementation in that StrVec::InsertAt(0) into an empty vector would crash. Arguably it’s not a correct usage but existing code used it so I’ve added support to InsertAt() at the end of vector. How could I not write the bug? I should have written a unit test (which I did in the fix). I don’t blindly advocate unit tests. Writing tests has a productivity cost but for such low-level, relatively tricky code, unit tests are good. I don’t feel too bad about it. I did write lots of tests for StrVec and arguably this particular usage of InsertAt() was borderline correct so it didn’t occur to me to test that condition. Use after free I saw a crash in crash reports, close to DeleteThumbnailForFile(). I looked at the code: if (!fs->favorites->IsEmpty()) { // only hide documents with favorites gFileHistory.MarkFileInexistent(fs->filePath, true); } else { gFileHistory.Remove(fs); DeleteDisplayState(fs); } DeleteThumbnailForFile(fs->filePath); I immediately spotted suspicious part: we call DeleteDisplayState(fs) and then might use fs->filePath. I looked at DeleteDisplayState and it does, in fact, deletes fs and all its data, including filePath. So we use freed data in a classic use after free bug. The fix was simple: make a copy of fs->filePath before calling DeleteDisplayState and use that. How could I not write the bug? Same story: be more careful when reviewing the changes, test the changes more. If I fail that, crash reporting saves my ass. The bug didn’t last more than a few days and affected only one user. I immediately fixed it and published an update. Summary of being more productive and writing bug free software If many people use your software, a crash reporting system is a must. Crashes happen and few of them are reported by users. Code reviews can catch bugs but they are also costly and reviewing your own code right after you write it is not a good time. You’re tired and biased to think your code is correct. Maybe reviewing the code a day after, with fresh eyes, would be better. I don’t know, I haven’t tried it.
A little while back I heard about the White House launching their version of a Drudge Report style website called White House Wire. According to Axios, a White House official said the site’s purpose was to serve as “a place for supporters of the president’s agenda to get the real news all in one place”. So a link blog, if you will. As a self-professed connoisseur of websites and link blogs, this got me thinking: “I wonder what kind of links they’re considering as ‘real news’ and what they’re linking to?” So I decided to do quick analysis using Quadratic, a programmable spreadsheet where you can write code and return values to a 2d interface of rows and columns. I wrote some JavaScript to: Fetch the HTML page at whitehouse.gov/wire Parse it with cheerio Select all the external links on the page Return a list of links and their headline text In a few minutes I had a quick analysis of what kind of links were on the page: This immediately sparked my curiosity to know more about the meta information around the links, like: If you grouped all the links together, which sites get linked to the most? What kind of interesting data could you pull from the headlines they’re writing, like the most frequently used words? What if you did this analysis, but with snapshots of the website over time (rather than just the current moment)? So I got to building. Quadratic today doesn’t yet have the ability for your spreadsheet to run in the background on a schedule and append data. So I had to look elsewhere for a little extra functionality. My mind went to val.town which lets you write little scripts that can 1) run on a schedule (cron), 2) store information (blobs), and 3) retrieve stored information via their API. After a quick read of their docs, I figured out how to write a little script that’ll run once a day, scrape the site, and save the resulting HTML page in their key/value storage. From there, I was back to Quadratic writing code to talk to val.town’s API and retrieve my HTML, parse it, and turn it into good, structured data. There were some things I had to do, like: Fine-tune how I select all the editorial links on the page from the source HTML (I didn’t want, for example, to include external links to the White House’s social pages which appear on every page). This required a little finessing, but I eventually got a collection of links that corresponded to what I was seeing on the page. Parse the links and pull out the top-level domains so I could group links by domain occurrence. Create charts and graphs to visualize the structured data I had created. Selfish plug: Quadratic made this all super easy, as I could program in JavaScript and use third-party tools like tldts to do the analysis, all while visualizing my output on a 2d grid in real-time which made for a super fast feedback loop! Once I got all that done, I just had to sit back and wait for the HTML snapshots to begin accumulating! It’s been about a month and a half since I started this and I have about fifty days worth of data. The results? Here’s the top 10 domains that the White House Wire links to (by occurrence), from May 8 to June 24, 2025: youtube.com (133) foxnews.com (72) thepostmillennial.com (67) foxbusiness.com (66) breitbart.com (64) x.com (63) reuters.com (51) truthsocial.com (48) nypost.com (47) dailywire.com (36) From the links, here’s a word cloud of the most commonly recurring words in the link headlines: “trump” (343) “president” (145) “us” (134) “big” (131) “bill” (127) “beautiful” (113) “trumps” (92) “one” (72) “million” (57) “house” (56) The data and these graphs are all in my spreadsheet, so I can open it up whenever I want to see the latest data and re-run my script to pull the latest from val.town. In response to the new data that comes in, the spreadsheet automatically parses it, turn it into links, and updates the graphs. Cool! If you want to check out the spreadsheet — sorry! My API key for val.town is in it (“secrets management” is on the roadmap). But I created a duplicate where I inlined the data from the API (rather than the code which dynamically pulls it) which you can check out here at your convenience. Email · Mastodon · Bluesky
As I slowly but surely work towards the next release of my setcmd project for the Amiga (see the 68k branch for the gory details and my total noob-like C flailing around), I’ve made heavy use of documentation in the AmigaGuide format. Despite it’s age, it’s a great Amiga-native format and there’s a wealth of great information out there for things like the C API, as well as language guides and tutorials for tools like the Installer utility - and the AmigaGuide markup syntax itself. The only snag is, I had to have access to an Amiga (real or emulated), or install one of the various viewer programs on my laptops. Because like many, I spend a lot of time in a web browser and occasionally want to check something on my mobile phone, this is less than convenient. Fortunately, there’s a great AmigaGuideJS online viewer which renders AmigaGuide format documents using Javascript. I’ve started building up a collection of useful developer guides and other files in my own reference library so that I can access this documentation whenever I’m not at my Amiga or am coding in my “modern” dev environment. It’s really just for my own personal use, but I’ll be adding to it whenever I come across a useful piece of documentation so I hope it’s of some use to others as well! And on a related note, I now have a “unified” code-base so that SetCmd now builds and runs on 68k-based OS 3.x systems as well as OS 4.x PPC systems like my X5000. I need to: Tidy up my code and fix all the “TODO” stuff Update the Installer to run on OS 3.x systems Update the documentation Build a new package and upload to Aminet/OS4Depot Hopefully I’ll get that done in the next month or so. With the pressures of work and family life (and my other hobbies), progress has been a lot slower these last few years but I’m still really enjoying working on Amiga code and it’s great to have a fun personal project that’s there for me whenever I want to hack away at something for the sheer hell of it. I’ve learned a lot along the way and the AmigaOS is still an absolute joy to develop for. I even brought my X5000 to the most recent Kickstart Amiga User Group BBQ/meetup and had a fun day working on the code with fellow Amigans and enjoying some classic gaming & demos - there was also a MorphOS machine there, which I think will be my next target as the codebase is slowly becoming more portable. Just got to find some room in the “retro cave” now… This stuff is addictive :)