Best Practice
Quick Unity best-practice check — run after editing C# files to catch anti-patterns, performance issues, and runtime safety problems.
Script path fallback: qq scripts are invoked as bare commands (e.g.
unity-test.sh). If "command not found", use${CLAUDE_PLUGIN_ROOT}/bin/<command>instead.
Respond in the user's preferred language (detect from their recent messages, or fall back to the language setting in CLAUDE.md).
Quick best-practice check for Unity C# code. Run this right after editing code — it scans for anti-patterns, performance traps, and runtime safety issues against 18 rules.
Arguments: $ARGUMENTS
Scope Detection
Intelligently determine what to check:
- If the user specified files or a scope → use that
- If .cs files were edited in this conversation → check those files
- If there are uncommitted changes →
git diff --name-only HEAD -- '*.cs' - If none of the above → ask the user what to check
Do NOT review the entire codebase by default. Focus on what just changed.
Review Rules
Deterministic Policy Layer
If qq-policy-check.sh is available, run it first against the selected scope:
qq-policy-check.sh --json <files...>
Treat those results as the first-pass findings. Do not ask the model to rediscover the same high-confidence issues from scratch. Use the model for:
- additional contextual review
- prioritization
- explanation
- fix suggestions
Anti-Pattern Detection
-
FindObjectOfType / FindObjectsOfType
- Severity: High
- Alternative: use the appropriate Registry/Manager singleton for your project
- Exception: Editor code (
Assets/Editor/, inside#if UNITY_EDITORblocks) may use these
-
Message system calls with untyped
object[]parameters- Severity: Medium
- Alternative: strongly-typed interfaces or events
-
Accessing shared data in Awake/Start
- Severity: High
- Alternative: implement the appropriate lifecycle interface and access data in the ready callback
-
Caching a read-only interface reference then mutating through it
- Severity: High
- Alternative: read-only access only
-
SendMessage / BroadcastMessage / SendMessageUpwards
- Severity: Medium
- Uses reflection, no compile-time safety, string-based (typos cause silent failures)
- Alternative: C# events, UnityEvents, or interface-based dispatch
-
Unsolicited UI code changes
- Severity: Notice
- UI code should not be modified unless the user explicitly requested it
Performance
-
GetComponent in Update / FixedUpdate / LateUpdate
- Severity: High
- GetComponent uses native interop + type lookup per call; in hot loops this causes measurable CPU overhead and GC pressure
- Alternative: cache component references in Awake/Start or use
[SerializeField] - Also flag GetComponent inside OnCollision*, OnTrigger*
-
Per-frame heap allocations
- Severity: High
- Flag inside Update/FixedUpdate/LateUpdate:
new List,new Dictionary, string+or$""interpolation,.ToString(), LINQ queries (.Where,.Select,.ToList), lambda closures - Alternative: pre-allocate and reuse, use StringBuilder, use non-alloc APIs
-
Coroutines started without cleanup
- Severity: High
- StartCoroutine without corresponding StopCoroutine or StopAllCoroutines in OnDisable causes orphan coroutines when objects are pooled or re-enabled
- Alternative: cache coroutine references, stop in OnDisable
-
gameObject.tag ==string comparison- Severity: Medium
- Allocates a string on the heap every call
- Alternative: use
CompareTag()(allocation-free)
Runtime Safety
-
Event subscription without unsubscription
- Severity: High
- Every
+=event subscription must have a matching-=unsubscription - Subscribe in OnEnable, unsubscribe in OnDisable; failing to do so causes memory leaks and double-firing
-
Missing [RequireComponent] for GetComponent dependencies
- Severity: Medium
- If Awake/Start calls
GetComponent<T>()and the result is used without null check, the class should have[RequireComponent(typeof(T))] - Makes hidden dependencies explicit and auto-adds them in the Inspector
Architecture Checks
-
Circular dependency risk
- Check that new
usingdirectives do not violate the project's established dependency direction
- Check that new
-
Missing .asmdef references
- If a file uses a namespace from another Service module, verify the corresponding .asmdef reference exists
-
Incorrect namespace conventions
- Severity: Medium
- Check that namespaces follow the project's established naming scheme
-
Public fields instead of [SerializeField] private
- Severity: Medium
- Public fields on MonoBehaviours break encapsulation; any script can modify them
- Alternative:
[SerializeField] privatefor Inspector-assigned fields
Code Quality
-
Excessive null checks
- Project style: minimal null checks, rely on exceptions to surface problems
- Only validate at system boundaries (user input, external APIs)
-
Missing documentation comments
- Public classes and complex methods should have summary comments
Project-Specific Rules
Add your project's coding standards and anti-patterns here. Example:
- No raw SQL queries
- All public APIs must have documentation
Output Format
Group output by severity:
## 🔴 Critical (High severity — must fix)
- [file:line] Issue description → suggested fix
## 🟠 Moderate (Medium severity — should fix)
- [file:line] Issue description → suggested fix
## 🟡 Suggestions (Notice — nice to fix)
- [file:line] Issue description → suggested fix
## ✅ Code Quality Highlights
- Brief list of things done well
Execution
- Determine scope (see Scope Detection above)
- Run
qq-policy-check.sh --jsonif available - Read all relevant .cs files
- Check each of the 18 rules above, but treat deterministic policy findings as already-established
- Also read AGENTS.md (if it exists) for project-specific architecture rules
- Merge deterministic findings with model findings in the output format above
- If critical issues are found, ask whether to auto-fix them
Handoff
After the check completes, recommend the next step:
- No issues found → "Clean. Want to run
/qq:testto verify, or/qq:claude-code-reviewfor a deeper review?" - Issues found and fixed → "Fixed N issues. Want to re-run
/qq:best-practiceto confirm, or proceed to/qq:test?" - Issues found, user declined fix → "N issues remain. Proceed to
/qq:testanyway, or fix first?"
--auto mode: skip asking, take the strictest path:
→ auto-fix all issues → re-run self until clean → /qq:claude-code-review --auto