Blog


What Belongs in CLAUDE.md

Separating Rules from Reference in 49,505 Characters

Not all documentation serves the same purpose. A style guide tells you what to do on every page. A glossary tells you what a word means when you encounter it. A phone directory tells you how to reach someone when you need her. These are different instruments, and combining them into a single document does not produce a style guide that is also a glossary and a phone directory. It produces a document that is too long to scan and too broad to maintain. I recently learned this lesson in a context I had not anticipated: the Markdown file that governs my AI co-developer’s behavior.

Continue reading

What an AI Code Review Actually Finds

Sixteen Issues, Ranked by Severity, in a Shipping Codebase

Reviewing your own code is hard. Not because you lack the skill, but because you lack the distance. You wrote the code; you know what it is supposed to do; and that knowledge of intent inoculates you against noticing what the code actually does in its edge cases, its error paths, and its quiet inconsistencies. I recently asked Claude Code to perform a comprehensive code review of Konjugieren, my German verb-conjugation app, and the results were instructive: not for the showstopping defects it found (there were none), but for the characteristic distribution of what it did find. Sixteen issues across three severity tiers. I fixed eleven, declined two with explanation, and learned something about the complementary strengths of human judgment and AI exhaustiveness.

Continue reading

Parallel Translation at 216x Human Speed

Localizing 65,000 Words with Seven Agents

A professional translator produces roughly 2,000 to 3,000 words per day. At that rate, localizing 65,000 words of app content from English to German would take a single translator three to four weeks. Seven AI agents, running in parallel with a fan-out/fan-in architecture, completed the same work in thirty-three minutes. The effective rate was 216 times faster than a human translator. This post describes how that happened, what went wrong, and what the speedup actually means.

Continue reading

You Help Claude, Claude Helps You

A Feedback Loop for AI-Assisted Development

The standard narrative about AI-assisted software development is seductively unidirectional: describe what you want, the AI writes the code, and you ship faster. This narrative is not wrong. It is merely incomplete. Over six weeks of building an iOS app with Claude Code, I discovered that the highest-impact practice was not writing better prompts. It was maintaining the bidirectional feedback loop: correcting the AI’s persistent misconceptions and curating the shared documentation that governs every future session.

Continue reading

High-Quality Pull-Request Descriptions

Much Benefit

One of the primary duties of a software developer is enhancing and fixing existing codebases. We do this by raising pull requests (PRs), getting them approved, and merging them to the codebase. I have been performing this duty for the entirety of my fifteen-year career as a software developer, and I’ve amassed a toolkit for this process. One tool is raising error-free PRs. I wrote about that here. The post you are reading is about another tool: writing a high-quality PR description. The tips in this post, if adopted, will help you get PRs approved more quickly, spark joy in your PR-reviewer coworkers, and facilitate debugging far into the future.

My target audience is primarily software developers. But non-developers who are curious about what we do might enjoy this post. Endnotes following it define terms that are likely unfamiliar to the developer-curious.

Continue reading

Live-Coding Exercises

Non-Obvious Tips for Preparation and Execution

One of the most-read posts on this blog is this one about typical iOS take-home coding exercises. The post has 3,797 views at time of writing, and several readers have privately thanked me for writing it. But, in my experience, the application process for many companies involves not a take-home coding exercise but rather a live-coding exercise. The candidate typically has forty-five minutes to implement an app from scratch that is similar to the app described in the post mentioned above but without unit tests or dependency injection.

The live-coding exercise is a different beast. Much of the knowledge required for a take-home coding exercise is applicable to a live-coding exercise, but the extreme time constraint of a live-coding exercise means that success is unlikely without extreme practice, preparation, and time-saving. Worse, the competitiveness of the job market means that, even if you complete 80% of the requirements of a live-coding exercise, you will be rejected in favor of another candidate who completes 100%.

In this post, I describe practice, preparation, and execution that make success in a live-coding exercise more likely. In an accompanying YouTube video, I apply this knowledge and complete a live-coding exercise within forty-five minutes.

This post is not about preparing for and succeeding in data-structure-and-algorithm interviews. Learning materials for those interviews are available elsewhere.

Continue reading

Introducing iOSExpert

Don't just crack the iOS interview. Crush it!

Loyal readers of this blog may have noticed a decrease in post frequency since January 2023. The reason for this decrease is that I spent most of 2023 creating a video course, iOSExpert. This post describes iOSExpert and presents some learnings from the creation process.

Continue reading

Cracking the iOS-Developer Coding Challenge, SwiftUI Edition

Don't just crack it. Crush it!

In a recent post, I presented an approach for succeeding on take-home iOS-developer coding challenges. (For brevity, I henceforth refer to these particular coding challenges as “coding challenges”.) The model solution in that post used UIKit because, at the time I wrote the post, I had already completed coding challenges using that framework. But SwiftUI may be a good, or indeed the best, option.

My goal in this post is to help readers who are are considering or have been assigned a SwiftUI-based coding challenge.

This post presents factors for the UIKit-or-SwiftUI decision. This post then addresses certain challenges posed by a SwiftUI solution, including architecture, dependency injection, testing, image caching, and Identifiable.

In the course of discussing these considerations and challenges, this post introduces a SwiftUI model solution, KatFancy, and uses that solution for illustrative purposes.

To derive maximum benefit from this post, readers should review the original post before reading this one. Most of the content of that post is relevant to all coding challenges.

Continue reading

Dependency Injection of URLs and URLSessions

Much Benefit

URLSession “and related classes provide an API for downloading data from … endpoints indicated by URLs.” Most iOS developers are familiar with using the URLSession singleton, shared, which has “reasonable default behavior”, including retrieving data from the actual endpoint represented by the URL specified.

But using shared in all circumstances has some drawbacks.

  1. In a production app, during development of a new feature, the endpoint may not exist until late in the development cycle. Using shared means that development of the client-side UI of a new feature is blocked until development of the endpoint is complete.
  2. Because using shared necessarily involves network access, use of shared in unit tests can cause those unit tests to be slow or to fail altogether.
  3. The endpoint may not return the data needed to exercise all functionality of the app. For example, the app may have a special no-data-was-retrieved state, but if the actual endpoint has data, this state can’t be triggered.

This post presents a solution to these three problems: using a stubbed version of URLSession and using alternate URL variants, both via dependency injection.

Paul Hudson described the URLSession-stubbing technique in this excellent article. My post contains two refinements to his article, both described in the section Acknowledgement.

Continue reading

Improving Long and Long-Running Terminal Commands

Four Weird Tricks

I recently contributed to SwiftSyntax, a subproject of the Swift open-source project. Building Swift and its subprojects from scratch and then unit-testing them takes about three hours, and the Terminal command is both long and complicated. A build-and-test run can output more than a megabyte to Terminal. Some of this output is potentially useful for diagnosing build-or-test failures. As an iOS developer, I haven’t spent much time in Terminal, but, in the course of running long and long-running Terminal commands recently, I reacquainted myself with some Unix tricks that I developed in the late 90s while working primarily on AIX, which, like macOS, is a Unix. These tricks could potentially benefit anyone running Terminal commands that are long, that take a long time to complete, or that generate a lot of output.

Continue reading

Two Applications of Life Experiences

Troubleshooting & Persuasion

I took an extended break from the software industry. For one of those years, I was an IT guy. For eight of those years, I was a lawyer. I haven’t hitherto mentioned this period on my blog, one mercenary goal of which is to enhance my iOS-developer brand. But I value the skills I acquired during these years because they remain useful. This post describes two examples.

Continue reading

Software Development as Creative Expression

The Importance of Norms and Style

Science is about revealing objective truth, for example the orbit of Earth around the Sun or the ultimate interchangeability of matter and energy. Kurt Krebsbach has argued that “computer science”, despite having the word “science” in its name, is not a science. If Krebsbach is right, what is software development, which I define, for the purposes of this post, as the practical application of computer science? I view software development as a form of creative expression, often fun, that sometimes has the side-effect of creating a useful artifact, a piece of software. This post posits that norms and style are important in software development, as in English-prose composition, another form of creative expression.

Continue reading

SwiftUI

Now, Overview Available

I recently modified one of my apps, Conjugar, to use SwiftUI rather than UIKit for its settings screen. I hereby present, for the reader’s edification and enjoyment, some observations and learnings from this process. I cover:

  • Spurious reasons not to learn SwiftUI
  • How to learn
  • Naming
  • Dependency injection in a mixed UIKit/SwiftUI app
  • Stack Overflow filling a gap
  • Animation
  • Unit-testing SwiftUI

Continue reading

Trailing Closures

Not Considered Harmful

A significant portion of my workday consists of browsing and grokking code that other people have written. I have had the experience of being frustrated, upon encountering a trailing closure, not knowing the name or therefore purpose of the argument being passed. This frustration initially caused me to consider forswearing trailing closures in my side projects. But with the benefit of contemplation and research, I have concluded that trailing closures are sometimes useful. This post describes how I reached this conclusion, recounts the history of trailing closures, and describes an analog from Kotlin.

Continue reading

Hobby Apps

What Kind to Make?

In the past couple of years, I have spoken to several aspiring iOS developers about what kind of hobby app they should make after escaping the tutorial trap. By “hobby app”, I mean an app for which one does not intend to be paid at all by an employer or enough by users to cover one’s living expenses. As someone who used hobby apps to jump-start his iOS-development career and who continues to develop hobby apps, I am interested in this question. I share my thinking in this post with the hope of providing thought-food to anyone considering development of a hobby app.

Continue reading

Dependency Injection in Practice

Preparation and Techniques

Dependency injection makes unit testing possible and development easier. This post describes the process of preparing an app for dependency injection, as well as implementing three approaches to dependency injection: constructor injection, Swinject, and The World.

Continue reading

Non-Optional, Constant Properties

One Advantage of Programatic Layout over Interface Builder

In a tutorial I created last year, I described advantages of programmatic layout (PL) over Interface Builder (IB). I recently became aware of another: PL permits use of non-optional, constant properties of view controllers. IB does not. I share this advantage here for the benefit of readers.

Continue reading

Migrating This Website to HTTPS

How I Learned to Stop Worrying and Embrace the Secure-Socket Layer

I recently converted racecondition.software to HTTPS. This post discusses this change and will act as a smoke test, by which I mean that if this post doesn’t show up in my RSS reader, I will have more work to do.

Continue reading

Changing Immigration's Business Model to Subscriptions

Motivation and Benefits

I recently changed the business model of my iOS app Immigration from paid-up-front to free-with-subscription. This post describes the reasons for and results of this change. The target audience for my blog has heretofore been, and will always remain, iOS-app developers, but, in a break with tradition, the target audience for this post is people affected by this change, in particular the app’s users. Because they are mainly lawyers, I hereby give myself permission to use legalese like “heretofore”.

Continue reading

Implementing SiriKit in RaceRunner

How I Stopped Worrying and Learned to Love an Intent Domain

My run-tracking app, RaceRunner, has features focused on racing and training for races. One of these features is alternate methods of ending runs. Here is an example.

The typical way to stop a run in a run-tracking app is to tap a button. RaceRunner supports this. But because of the physical exertion involved in running a race, a runner is sometimes in no condition to unlock an iPhone and tap a button at the end of a race. Even unlocking can be tricky because sweat often prevents TouchID from working, so instead the passcode must be tapped. So RaceRunner supports two alternative ways of ending a run. First, a run can stop automatically after a certain distance. This is great for time trials or if the runner does not trust the race organizers’ distance measurement. (A time trial involves running a certain distance, typically a race distance, as fast as possible.) Second, a spectator can use RaceRunner to stop the runner’s run. Both of these alternate means of stopping have problems. The certain-distance method may result in a recorded time that differs from actual time. The spectator method requires a cooperative spectator with an iPhone. So I implemented a third method: Siri.

Having just released a new version of RaceRunner with Siri support, I thought I’d share some learnings and pedagogic resources for other developers interested in implementing Siri support.

Continue reading

Free and Low-Cost App Assets

Some Learnings from Five Years of Side-Project Development

I make iOS apps as a means of supporting my family and as a creative outlet. On the creative side, I have released three apps in the past five years: Immigration, RaceRunner, and Conjugar. Like many side-project apps, mine have had small budgets for asset creation. But they have greatly benefitted from free and low-cost assets (FALCAs). In this post, I introduce five sources for these FALCAs: Coolors, icon websites, Google Images, Sound Jay, Incompetech, and Free App Store Preview Music.

Continue reading

How My Code Has Improved in Three Years

Some Learnings from RaceRunner

RaceRunner is a run-tracking app I wrote in Swift three years ago. This app got my foot back in the door as a professional software developer, and I continue to use it. Since RaceRunner’s release, I have periodically updated the code to support new versions of iOS.

I’ve heard some software developers say that they can’t bear to look at code they wrote a long time ago. There are aspects of RaceRunner that would not pass my own code review today. But rather than being embarrassed by or ashamed of how I wrote RaceRunner, I find that a review my old code illustrates my improvement as a software developer. This improvement elicits both pride in how far I have come in three years and excitement at how far I might go in the next.

The purpose of this blog post is to examine this improvement through the lens of one part of one source file in RaceRunner.

Continue reading