AI Workflows: Code Smells

finding structural problems before they cause failures

1. Summary

Code smells are patterns that signal structural problems without necessarily causing failures. A smell does not crash the program; it makes the program harder to understand, modify, or extend correctly. Finding smells before making changes avoids the situation where a bug introduced by a refactor turns out to trace back to a structural problem that predated it. A smell-finding session has one governing constraint: read-only. The goal is a list of findings with locations and proposed resolutions, not a set of edits. Keeping the session read-only separates discovery from decision. Each finding can then become a targeted fix session, a refactoring session, or a deliberate accept-and-document decision - on your schedule, not Claude's. A codesmells.md sets the target files, the categories to check, and the output format. The read-only constraint and the output format belong in the md file so they are present at session start and cannot be forgotten mid-session.

2. codesmells.md Structure

# Code Smells Context

## Target
Files or modules to examine: [list them]

## Mode
Read-only analysis. Do not change any files.
For each smell: name it, cite file:line, describe the problem, suggest a resolution.

## Categories to Check
- Mixed responsibilities (single-responsibility violations)
- Duplicated logic (DRY violations)
- Unsafe interfaces (preconditions not enforced by the type system)
- Dead or commented-out code left in place
- Long methods with deep nesting
- Magic strings or hardcoded literals

## Output Format
Report as a table: Smell | File:Line | Description | Resolution
Customize the categories for the language and codebase. A Python codebase might add mutable default arguments and bare except clauses. A C++ codebase might add raw resource management. Keeping the list to six or fewer categories produces focused output; a longer list produces a wall of findings with no obvious priority.

3. Common C++ Code Smells

SmellWhat to look for
Mixed responsibilities One class or header doing two unrelated jobs - data storage and display, parsing and serialization
Duplicated logic The same 3-5 line sequence appearing more than twice; manual iterator construction repeated across files
Unsafe precondition Method that requires an earlier call; violation is UB or a silent wrong result with no diagnostic
Commented-out code Blocks disabled with "may use later" comments; adds noise and gives false impression of interface scope
Deep nesting Methods with 4+ levels of indentation; control flow requires reading the whole body to understand one branch
Magic strings Hardcoded tag names, option keys, or format strings scattered through the code with no named constant
Raw resource management new/delete or file handles managed manually where a smart pointer or RAII wrapper would eliminate the lifetime burden

4. Example Prompts

Scan a single file:
Read codesmells.md.
Read [file].

Find every smell in the categories listed in codesmells.md.
Report as a table: Smell | File:Line | Description | Resolution.
Do not change any files.
Scan across multiple files:
Read codesmells.md.
Read [file1].
Read [file2].
Read [file3].

Scan all three files for the categories in codesmells.md. Report as a single table
sorted by severity: unsafe preconditions first, then structural smells, then cosmetic.
Do not change any files.
Targeted category scan:
Read codesmells.md.
Read [file].

Check only for duplicated logic and unsafe preconditions.
For each finding, explain in one sentence why it is a structural risk.
Do not change any files.

5. Case Study: CppNoSqlDB

This smell survey was run on CppNoSqlDB before any structural work was planned. Three files were scanned: the core database header, the query conditions class, and the XML persistence class. The goal was a prioritized list of findings so that the structural work could be sequenced by risk rather than discovered opportunistically during edits.

codesmells.md

# Code Smells Context

## Target
CppNoSqlDB -- C++ NoSql key-value database:
  DbCore/DbCore.h    core database and element types
  Query/Query.h      query conditions and execution
  Persist/Persist.h  XML persistence and sharding

## Mode
Read-only analysis. Do not change any files.
For each smell: name it, cite file:line, describe the problem, suggest a resolution.

## Categories to Check
- Mixed responsibilities (single-responsibility violations)
- Duplicated logic (DRY violations)
- Unsafe interfaces (preconditions not enforced by the type system)
- Dead or commented-out code left in place
- Long methods with deep nesting
- Magic strings or hardcoded literals

## Output Format
Report as a table: Smell | File:Line | Description | Resolution

Prompt

Read codesmells.md.
Read DbCore/DbCore.h.
Read Query/Query.h.
Read Persist/Persist.h.

Find every smell in the categories listed in codesmells.md. Report as a table
sorted by severity: unsafe preconditions first, then structural smells, then cosmetic.
Do not change any files.

Findings

Smell Location Description Resolution
Unsafe raw-pointer precondition Query.h:57, 84 Conditions<P> stores pDbElem_ as a raw pointer set via value(). Calling match() before value() dereferences a null pointer with no diagnostic. Change match() to accept const DbElement<P>& directly. Remove the pointer and the setter; the precondition disappears.
Display logic in core header DbCore.h:308-399 ~90 lines of showKeys, showHeader, showElem, showRecord, showDb defined inside the database header. <iomanip> is included solely for these functions. Move to Display.h. Database callers include DbCore.h; display callers include Display.h.
Class with two responsibilities Persist.h:51-68 Persist<P> owns both shard-key filtering (shardKeys_, shard(), addShardKey()) and XML serialization (toXml(), fromXml()). The two concerns share no state. Split into ShardFilter<P> and XmlStore<P>. Persist<P> can remain as a facade composing both.
Duplicated key-search pattern DbCore.h:99-104,
Persist.h:94-100
containsChildKey() and containsKey() both construct begin/end iterators manually for the same std::find check. removeChildKey() (DbCore.h:122) extends the pattern further. Replace with std::find(cbegin, cend, key) != cend() one-liner. If the pattern recurs, add a containsKey(Keys, Key) free function to Definitions.h.
Long method with deep nesting Persist.h:179-241 fromXml() has 5+ levels of if/for nesting. Tag dispatch on XML strings is embedded inside the traversal loop, making any single branch difficult to follow in isolation. Extract parseRecord() and parseValue() helpers. fromXml() becomes the outer loop only.
Magic strings in serialization Persist.h:121-143 XML tag names ("dbRecord", "key", "value", "name", "description", "children", "payload") are hardcoded literals in toXmlRecord() with matching strings in fromXml(). Define named constants in a XmlTags namespace or struct. A typo then causes a compile error rather than a silent load failure.
Commented-out code Query.h:70-80 Payload matching functions left disabled with "Not currently using... may use later" comments inside the class body, obscuring the class's actual interface. Remove the commented block. If the feature is wanted, add it under a branch or issue; dead code in headers misleads readers about what the class does.

6. Following Up

A smell survey produces a prioritized list, not a work order. Before starting any fix, decide whether to fix, defer, or accept each finding. Some smells are load-bearing - removing them changes more code than the smell is worth. Document accepted smells with a comment so future readers know the choice was deliberate. Each smell maps naturally to another workflow: Do not carry smell findings forward into a fix session from the same conversation. Start a new session with the specific finding as explicit input. The smell session has done its job; the fix session should start clean.