| name | function-naming |
| description | Validates function naming when adding new functions, refactoring existing functions, or reviewing function signatures. |
Function Naming Skill
When to apply: When adding new functions, refactoring existing functions, or reviewing function signatures.
Priority: HIGH - Naming affects API clarity and maintainability across 30+ years of codebase evolution.
Naming & Style
Quick Reference Table
| Operation Type | Prefix | Parameter | Example |
|---|
| Per-object query | query_* | object_t* first | query_prompt(object_t *) |
| Per-object setter | set_* | object_t* first | set_prompt(object_t *, const char *) |
| Per-object getter | get_* | object_t* + output param | get_pending_command(object_t *, char *) |
| Global lookup | find_* or lookup_* | search/index param | lookup_user_at(int), find_object_by_name(const char *) |
| Global collection | get_all_* | returns array/collection | get_all_heart_beats(void) |
| Global iterator | next_* | maintains internal state | next_user_command(void) |
| Global check | descriptive verb | void or simple params | has_pending_commands(void), grant_all_turns(void) |
| Internal helper | descriptive | any | varies - static only |
Core Principles
1. NO Module Prefixes
❌ NEVER use module prefixes like comm_, user_cmd_, etc.
Rationale: Neolith's 30-year-old codebase uses semantic names without prefixes:
load_object(), destruct_object() (NOT simulate_load_object())
set_heart_beat(), query_heart_beat() (NOT backend_set_heart_beat())
When you might be tempted:
- Adding functions to a specific module (still use semantic names)
- Avoiding name conflicts (use more specific semantic names instead)
2. Scope Must Be Clear from Signature
Per-object operations - ALWAYS take object_t* as first parameter:
✅ query_prompt(object_t *ob)
✅ set_prompt(object_t *ob, const char *text)
❌ set_prompt(const char *text)
Global operations - NO object_t* parameter:
✅ has_pending_commands(void)
✅ next_user_command(void)
✅ lookup_user_at(int index)
❌ query_pending_commands(void)
3. Distinguish Iterator from Collection
Iterators (next_*) - Mutate internal state:
✅ next_user_command(void)
✅ next_reset_object(void)
Collections (get_all_*) - Return snapshot, no side effects:
✅ get_all_heart_beats(void)
✅ get_all_users(void)
❌ get_heart_beats(void)
4. Query vs. Get vs. Set
Use query_* for simple attribute retrieval (per-object):
✅ query_prompt(object_t *ob)
✅ query_idle(object_t *ob)
Use get_* when output parameter needed (per-object):
✅ get_pending_command(object_t *ob, char *buf)
✅ get_environment(object_t *ob, object_t **env)
Use set_* for attribute modification (per-object):
✅ set_prompt(object_t *ob, const char *text)
✅ set_heart_beat(object_t *ob, int flag)
Special case - get_all_* for global collections:
✅ get_all_heart_beats(void)
Validation Checklist
When adding or modifying a function, verify:
✅ Scope Validation
✅ Prefix Validation
✅ Signature Validation
✅ Consistency Validation
Common Mistakes
❌ Mistake 1: Module Prefix
❌ comm_set_prompt(object_t *ob, const char *text)
✅ set_prompt(object_t *ob, const char *text)
❌ Mistake 2: Implicit Command Giver
❌ set_prompt(const char *text)
✅ set_prompt(object_t *ob, const char *text)
void f_set_prompt(void) {
set_prompt(command_giver, SVALUE_STRPTR(sp));
}
❌ Mistake 3: Ambiguous Global vs Per-Object
❌ query_pending_commands(void)
✅ query_buffered_commands(object_t *ob)
✅ has_pending_commands(void)
❌ Mistake 4: Iterator Named as Simple Getter
❌ get_user_command(void)
✅ next_user_command(void)
❌ Mistake 5: Collection Named Ambiguously
❌ get_heart_beats(void)
✅ get_all_heart_beats(void)
Examples from Neolith Codebase
Per-Object Operations (simulate.c, comm.c)
const char* query_ip_name(object_t *ob);
time_t query_idle(object_t *ob);
object_t* query_snoop(object_t *ob);
void set_heart_beat(object_t *ob, int flag);
void set_prompt(object_t *ob, const char *text);
int get_pending_command(object_t *ob, char *buf);
Global Operations (backend.c, comm.c)
object_t** get_all_heart_beats(void);
char* next_user_command(void);
object_t* next_reset_object(void);
object_t* lookup_user_at(int index);
object_t* find_object_by_name(const char *name);
int has_pending_commands(void);
void grant_all_turns(void);
Apply Cache Accessors (Special Case)
int has_process_input_apply(object_t *ob);
void cache_process_input_apply(object_t *ob, int exists);
int has_write_prompt_apply(object_t *ob);
void cache_write_prompt_apply(object_t *ob, int exists);
Rationale: These describe object program attributes but are currently stored on interactive_t (technical debt). Names emphasize caching behavior so they work regardless of future storage location. See docs/internals/lpc-program.md.
Refactoring Guidance
When Changing Existing Functions
Implicit → Explicit signatures:
- Change function signature to take
object_t* first
- Update all call sites to pass explicit object
- Update efun wrapper to pass
command_giver:
void f_set_prompt(void) {
set_prompt(SVALUE_STRPTR(sp));
}
void f_set_prompt(void) {
set_prompt(command_giver, SVALUE_STRPTR(sp));
}
Renaming for clarity:
get_user_command() → next_user_command() (indicates iteration)
get_heart_beats() → get_all_heart_beats() (indicates collection)
When Adding New Module
DO:
- Use semantic names based on operation type
- Follow table above for prefix selection
- Make scope clear from signature
DON'T:
- Add module prefix to function names
- Create parallel implicit/explicit APIs
- Use
get_* for both collections and per-object operations
Integration with Development Workflow
Code Review Checks
- Run through validation checklist above
- Compare with existing similar functions
- Check for module prefix violations
- Verify scope matches signature
Testing
When testing new functions:
- Verify behavior matches name (no surprises)
- Test both per-object and global behavior as appropriate
- Check iterator state management if using
next_*