Published 6 min read

Encoding Software Design Principles In Skills

In my last article, I wrote about Matt Pocock’s /grill-me skill, and I mentioned that he developed a new version, grill-with-docs, that I hadn’t yet adopted. A few weeks later I tried out that new skill, and I now use it exclusively. It presents a qualitative improvement, with a more substantial idea: it incorporates opinionated software engineering principles into the skill.

In Matt’s introduction to the skill, he mentions Domain-Driven Design, a 20+ year old software design philosophy kicked off by a massive book by Eric Evans in 2003. I really appreciated Martin Fowler’s short introduction to the idea, but Matt drills it down even more:

The idea is simple: create shared language used by three groups:

  1. The codebase
  2. Developers building it
  3. Domain experts who understand the problem (but not the implementation)

The /grill-with-docs skill implements this idea by maintaining a CONTEXT.md which serves as a glossary for your application. As you build new features with the skill, it uses this established terminology to communicate with you, and it identifies new concepts to add to the glossary. The result is a document that keeps communication clear and efficient between you and the AI, and it’s also just a great reference document for anyone trying to make sense of the codebase.

This context document is picked up by one of his other skills, /improve-codebase-architecture. This skill encodes the idea of “deep modules” from the book A Philosophy of Software Design by John Ousterhout. The book centers on complexity and introduces deep modules as a way to reduce it: shallow modules have a large (“wide”) interface that abstracts little, but deep modules have simple interfaces that hide a lot of complexity. The skill tells the agent to ask questions that operationalize the ideas in the book:

  • Where does understanding one concept require bouncing between many small modules?
  • Where are modules shallow — interface nearly as complex as the implementation?
  • Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no locality)?
  • Where do tightly-coupled modules leak across their seams?
  • Which parts of the codebase are untested, or hard to test through their current interface?
Other code review skills

The /thermo-nuclear-code-quality-review skill from the Cursor team passes to the agent similarly opinionated software engineering ideas. It’s looser on its terminology (“Do not allow random spaghetti growth in existing code,” “For every meaningful change, ask: Is there a "code judo" move that would make this dramatically simpler?”) but it’s more specific in other ways; it specifies a 1000-line file limit and a binary approval bar.

I found shadcn’s /improve skill interesting in that it explicitly codifies an output format and it suggests a direction for the codebase going forward (“Forward-looking: not what's broken, but what this codebase wants to become”).

While neither of these skills implement anything uncontroversial, I like that Matt’s skills explicitly reference established software engineering ideas, providing a language and a methodology that you and the AI can explore more deeply.

I love the idea of using skills to encode and operationalize established software engineering ideas. I’ve since read most of A Philosophy of Software Design and I appreciated its philosophy towards comments. Comments shouldn’t be overused—they should only describe things that aren’t obvious from the code. But they should be used a lot, because there are lots of opportunities to either provide higher-level intuition (what does this block of code actually do at a high level) or lower-level precision (eg. what are the units of this variable; what does this optional parameter do if missing).

I decided to create a /comments-review skill that implements this philosophy. My idea was that this skill could be used to audit existing comments, but you could also tell the AI to write new comments in accordance with the skill.

Instead of writing the skill from scratch, I wanted to see how Claude would approach the task of encapsulating Ousterhout’s philosophy into a skill. I bought the book on Kindle, so unfortunately I couldn’t feed it the PDF, but I took screenshots of the comments chapter and told claude to write a skill. This was helpful because Claude knows how to structure an effective skill much better than I do.

Claude tends to be verbose, so I fed it through Codex to make it more concise, and then I worked through the skill myself, editing it and adding direct quotes from the book. I made it more specific to my TypeScript project and added code samples from the book converted to TypeScript, as well as instructing it to use TSDoc (Ousterhout recommends picking a “document compilation tool, such as Javadoc”).

I set up two concepts from the book to be referenced throughout the skill, interface comments and implementation comments, that the AI initially hadn’t extrapolated. Then I sorted the book’s insights into “red flags” and “opportunities”; the skill shouldn’t just look for issues with comments, but look for ways to write new, good comments.

The whole skill is here, but it contains a lot of scaffolding so I’ve excerpted the actual philosophy below. AI had previously generated a lot of comments arbitrarily, so this was a great way to clean it up.

## Definitions
**Interface comment**: a comment block that immediately precedes the declaration of a module such as a class, data structure, function, or method. The comment describes the module’s interface.
**Implementation comment**: a comment inside the code of a method or function, which describes how the code works internally.
## All Interface Comments Must Follow TSDoc
All interface comments should follow TSDoc conventions from `https://tsdoc.org/`.
In existing comments, flag:
- exported classes, functions, methods, interfaces, types, or consts documented with `//` instead of a TSDoc `/** ... */` block
- malformed TSDoc tags, especially `@param` without the `@param name - description` shape
- missing or malformed `@returns` on documented non-void returns
- `@remarks`, `@throws`, `@deprecated`, or `{@link ...}` content written in non-TSDoc form when those concepts are present
- long detail in the summary paragraph that should be moved under `@remarks`
Do not require every exported declaration to have a comment; absence of interface comments is out of scope. Do not flag inline implementation comments for not being TSDoc.
Example:
```ts
/**
* Opens the payment session.
*
* @param request - The normalized checkout request.
* @returns The provider URL the caller should redirect to.
*/
```
All interface comments that you propose should follow TSDoc.
## Check For Red Flags
For each comment in scope, look for the red flags below.
### Red Flag: Comment References Current Session
The comment references a bug that the current session is missing, or otherwise would not make sense without the context of the current conversation.
Suggested fix: delete
### Red Flag: Comment Repeats Code
The comment restates the adjacent identifier or tokens in English without adding information.
Detection signal: at least half of the comment's content words appear in the adjacent identifier or statement, and the comment contributes no units, invariants, intent, boundary behavior, or non-obvious behavior.
Example to flag:
```ts
// Get pointer copy
const ptrCopy = getCopy(obj);
```
Suggested fix: delete the comment or rewrite it with different words that add real information.
### Red Flag: Missing Precision
Interface comments lack non-obvious information that the type does not communicate:
- units
- inclusive or exclusive boundaries
- null, undefined, empty, or default behavior
- invariants the value always satisfies
- meaningful error or rejection behavior
Only flag when the signature and nearby code genuinely do not communicate the missing information.
### Red Flag: Interface Comment Leaks Implementation
An interface comment describes how the implementation works instead of what callers can rely on.
Indicators include references to private fields, private helpers, internal data structures, iteration strategy, recursion, storage layout, or phrases such as `internally`, `implemented as`, `uses a map`, or `loops through`.
When flagging this, append:
```text
If the abstraction genuinely cannot be described without these implementation details, this class/method may be shallow; consider whether the interface should hide more.
```
### Red Flag: Interface Coverage Gap
An existing interface comment is missing meaningful contract information:
- a useful `@param` for a non-obvious parameter
- `@returns` behavior for a non-void return
- side effects
- preconditions
- thrown errors or rejected promises when callers can reasonably handle them
Only flag non-trivial gaps. If a tag exists but merely repeats the parameter or return name, flag it under `Don't Repeat The Code`.
### Red Flag: Implementation Comment Describes The Line Below
A single-line `//` comment immediately above a statement restates that statement without explaining why.
Example to flag:
```ts
// Add a horizontal scroll bar
add(new JScrollBar(JScrollBar.HORIZONTAL));
```
Suggested fix: delete the comment or replace it with the reason the statement exists.
### Red Flag: Cross-Module Decision
Flag comments that reveal a decision belongs somewhere central instead of being scattered across modules.
Primary signal: phrases such as `must match`, `in sync with`, `see also`, `consistent with`, `this assumes`, `mirrors`, or `matches the X in Y`.
Secondary signal: after reading all selected files, surface repeated non-local assumptions that clearly appear in comments across multiple reviewed files.
Suggested fix: Find a way to centralize the decision in code (e.g., pull out logic into a shared function) or in a document (like an ADR).
## Propose New Comments
Look for these opportunities in code that has no comments and code that already has comments. If a comment already exists but could take advantage of one or more of these opportunities, propose a change to the comment.
### Opportunity: Provide Higher-Level Intuition
Flag a code block (a function or a part of a function) for intuition-enhancing:
- The block contains complex logic (e.g., loops, nested conditionals, or async/await)
- The block contains many dependencies from other files
- The block is a long function (greater than 20 lines)
- The block may otherwise be hard to read for a developer new to the codebase
Then, write a high-level comment that enhances intuition, omitting details and helping the reader understand the overall intent and structure of the code.
#### Example
```ts
if (numProcessedPKHashes < readRpc[i].numHashes) {
// Some of the key hashes couldn't be looked up in
// this request (either because they aren't stored
// on the server, the server crashed, or there
// wasn't enough space in the response message).
// Mark the unprocessed hashes so they will get
// reassigned to new RPCs.
for (let p = removePos; p < insertPos; p++) {
if (activeRpcId[p] === i) {
if (numProcessedPKHashes > 0) {
numProcessedPKHashes--;
} else {
if (p < assignPos) {
assignPos = p;
}
activeRpcId[p] = RPC_ID_NOT_ASSIGNED;
}
}
}
}
```
### Opportunity: Provide Precision
Look for function arguments and return values where the name, type, and existing comment lack important details. For example:
- What are the units for this variable?
- Are the boundary conditions inclusive or exclusive?
- If a null value is permitted, what does it imply?
- Are there certain properties that are always true for the variable (invariants), such as “this list always contains at least one entry”?
Provide a comment that clarifies the meaning of the code and adds missing details.
When documenting a variable, think nouns, not verbs. In other words, focus on what the variable represents, not how it is manipulated.
#### Example
```ts
/* True means that a heartbeat has been received since the last time
* the election timer was reset. Used for communication between the
* Receiver and PeriodicTasks threads. */
private receivedValidHeartbeat: boolean;
```

I wanted all new comments to follow this methodology even before a review, so I added this to my AGENTS.md :

# Comments
Do not write comments that only make sense in the context of our conversation.
By default, comments agents write should satisfy the `/comments-review` standard:
**Do**
- Write comments when they clarify non-obvious contracts, invariants, units, boundary behavior, side effects, error behavior, or the high-level intent of complex code.
- Use TSDoc `/** ... */` blocks and standard tags such as `@param`, `@returns`, `@throws`, `@deprecated`, and `{@link ...}` when those details are present.
**Do Not**
- Write comments that are obvious from the code.
- Write comments that merely restate the adjacent identifier, statement, or control flow.
- Leak private helpers, storage details, iteration strategy, or other implementation details from interface comments.
- Write comments that reference our conversation or the bug we're fixing.

This process is really exciting to me, because there’s 40+ years of software engineering experience compiled into tried-and-true books (and other resources). AI probably knows all of it, but it’s crowded out by codebases that don’t follow best practices, and even if the AI does follow best practices there are many approaches to software development and you have to tell it which philosophy you want it to follow. Throughout the book, Ousterhout compares his philosophy to other approaches (especially Clean Code) and he often pushes against the grain. With skills, we have the ability to tell AI to align with a specific software engineering approach that we follow.