Code Smells
Checklist of code smells to identify during refactoring. Both generic C issues and ikigai-specific violations.
Generic C Code Smells
Function-Level
| Smell | Signal | Action |
|-------|--------|--------|
| Long function | >50 lines | Extract functions by responsibility |
| Deep nesting | >3 levels | Extract inner logic, use early returns |
| Long parameter list | >4 params | Introduce parameter object (struct) |
| Magic numbers | Literal values in logic | Define named constants |
| Duplicate code | Copy-pasted blocks | Extract shared function |
| Comments explaining what | // increment counter | Rename to be self-documenting |
| Dead code | Unreachable branches | Delete it |
| Complex conditionals | if (a && b \|\| c && !d) | Extract to named boolean or function |
Structural
| Smell | Signal | Action |
|-------|--------|--------|
| God struct | Struct with 10+ fields | Split by responsibility |
| Feature envy | Function uses another module's data extensively | Move function to that module |
| Primitive obsession | Passing raw char*, int everywhere | Introduce domain types |
| Shotgun surgery | One change requires editing many files | Consolidate related code |
| Divergent change | One file changes for unrelated reasons | Split by responsibility |
ikigai-Specific Violations
Naming
| Violation | Example | Fix |
|-----------|---------|-----|
| Missing ik_ prefix | config_load() | ik_cfg_load() |
| Wrong abbreviation | ik_configuration_load() | ik_cfg_load() (use approved abbrev) |
| Inconsistent module prefix | ik_config_load() vs ik_cfg_parse() | Pick one, use consistently |
| Missing _ptr suffix | bool *visible; (raw pointer) | bool *visible_ptr; |
| Missing g_ prefix | volatile sig_atomic_t shutdown; | g_shutdown |
Error Handling
| Violation | Example | Fix |
|-----------|---------|-----|
| Unchecked res_t | ik_cfg_load(...); use(cfg); | TRY(ik_cfg_load(...)) or check is_err() |
| Wrong mechanism | if (!ptr) return ERR(...) for OOM | OOM → PANIC(), not res_t |
| assert for runtime | assert(file_exists(path)) | External input → res_t, not assert |
| Missing LCOV marker | if (!ptr) PANIC("OOM"); | Add // LCOV_EXCL_BR_LINE |
| Error on wrong context | ERR(tmp, ...) before talloc_free(tmp) | Use parent context for errors |
Memory (talloc)
| Violation | Example | Fix |
|-----------|---------|-----|
| malloc instead of talloc | char *buf = malloc(size); | talloc_array(ctx, char, size) |
| Missing parent | talloc_new(NULL) in non-main | Pass parent context from caller |
| Orphaned allocation | talloc(ctx, ...) never freed/stolen | Attach to proper parent or free |
| Hidden allocation | Function mallocs internally | Accept TALLOC_CTX* parameter |
Dependency Injection
| Violation | Example | Fix |
|-----------|---------|-----|
| Global state | static config_t *g_config; | Pass config as parameter |
| Hidden I/O | load_from_disk() inside init | Load externally, pass data in |
| Service locator | get_service("db") | Pass db_t* parameter |
| Constructor does work | init() opens files, connects | Accept opened resources as params |
Style
| Violation | Example | Fix |
|-----------|---------|-----|
| Block comment | /* comment */ | // comment |
| Primitive type | int count; | int32_t count; |
| Wrong include order | System before project headers | Project headers first |
| static helper function | static void helper() {...} | Inline at call site |
Investigation Workflow
- Scan for patterns - Use grep/search for smell signals
- Triage by impact - Fix high-frequency smells first
- Verify with tests - Ensure refactoring doesn't break behavior
- One smell at a time - Don't mix refactoring types in one commit