From agenticat
Language-agnostic principles (naming, functions, error handling, comments, formatting, readability, duplication, maintainability) for writing, reviewing, or refactoring.
How this skill is triggered — by the user, by Claude, or both
Slash command
/agenticat:clean-codeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use these when you write, review, or refactor code.
Use these when you write, review, or refactor code.
Treat these as directional principles. Where a repo's established precedent or a language's own idioms conflict with a rule here, follow the local convention and match the surrounding code.
Never use variable names that create false expectations, contain misleading technical terms, or use visually confusing characters.
Do:
Don't:
ABCManagerForEfficientProcessingOfUsers vs ...PersistingOfUsers).If two things have different names, they must do different things. Avoid meaningless noise words and number series.
Do:
array, condition, transformation).OrderValidator, OrderRepository, OrderCalculator).Don't:
OrderManager, OrderHandler, OrderController, OrderProcessor all existing side by side).Names should read as natural words and match the size of their scope, so they can be spoken aloud and found by search. Avoid cryptic abbreviations, single letters outside tiny scopes, and raw magic numbers.
Do:
let generationTimestamp = new Date();i, j) only in tiny scopes, like a short for loop.Don't:
e returns thousands of useless hits).Do not encode type or scope information into variable names. Let your IDE and compiler handle types.
Do:
let firstName: string;Don't:
let strFirstName: string;Refactor complex logic into well-named variables, functions, constants. The code then explains itself without comments.
Do:
const isBusinessHour = currentHour >= OPENING_HOUR && currentHour < CLOSING_HOUR;
const isBusinessDay = currentDay !== SATURDAY && currentDay !== SUNDAY;
const isStoreOpen = isBusinessHour && isBusinessDay && !isHoliday;
if (isStoreOpen) { showOpenBanner(); }
Don't:
if statement to explain what it does.
// Show banner if the store is currently open above if (currentHour >= OPENING_HOUR...)Functions should be small. Keep blocks inside control structures short, often a single call.
Do:
if, else or while statements, ideally a single function call.Don't:
A function should do exactly one thing. The litmus test: you cannot extract another meaningful function from it.
Do:
// Validation, // Storage, // Notification, extract each into its own function.uploadBasedOnSize), the ORIGINAL function was already doing one thing. If the extracted name represents a genuinely separate concept (e.g., uploadFileInParts), the original function was doing too much.Don't:
A function should only contain code at a single, consistent level of abstraction, delegating deeper details to the next level down.
Do:
validateStock(); calculateTotalBill(); executeStripeCharge(); notifyWarehouse();Don't:
validateStockAvailability(); const finalBill = subtotal - discount + tax; Stripe.charges.create({ amount: finalBill * 100, source: order.token }); fetch("https://warehouse...");Fewer arguments read cleaner. If you reach three or more, wrap them in an object. Avoid boolean flags and output arguments.
Do:
fetchData()), one argument (fileExists(path)), or two if they are a natural pair (moveTo(x, y)).saveUser(user) (where user is an object containing name, email, age, etc.)Don't:
saveUser(name, email, age, city, isPremium)), output arguments that mutate inputs instead of returning (findMax(numbers, result)), or boolean flags (createUser(true)) that force two code paths from one function (violates "Do One Thing").sendEmail(message, smtp)); make one the owner: smtp.sendEmail(message).A function should either perform an action (Command) or answer a question (Query), but never both. It should never harbor hidden side effects.
Do:
if (checkPassword(password)) { initializeSession(); }Don't:
checkPassword() function that invisibly calls Session.initialize() or resets user properties.Switch statements inherently do multiple things. Bury them in abstract factories and use polymorphism instead.
Language caveat: this is object-oriented advice. With compiler-checked exhaustive matching, a match/switch over a closed set of variants is idiomatic and safe. In Rust the compiler enforces exhaustiveness and flags any unhandled case when a new variant is added; Go has no such compiler check (a switch missing cases builds clean and go vet passes), so exhaustiveness there needs the external nishanths/exhaustive linter. Reach for polymorphism when the type set is open; a compiler-checked match already handles a closed one.
Do:
EmployeeFactory.create(type)) behind an interface (e.g., Employee).const employee = factory.create('fullTime', data); employee.calculatePay(); employee.getBenefits();Don't:
switch(employee.type) block into function after function (calculatePay, getBenefits, getSchedule).Throw exceptions instead of returning error codes. Keep the algorithm and its error handling in separate functions. Where failure modes are known, write the try-catch-finally skeleton (and the exceptions it throws) first, then fill in the logic.
Do:
try/catch for a clean list of steps.
try { createAccount(); createProfile(); } catch (error) { showError(); }function sendMessage(msg) {
try {
deliverMessage(msg); // catch knows nothing about the algorithm
} catch (error) {
notifySender(msg.token, error);
}
}
function deliverMessage(msg) { // algorithm knows nothing about error handling
const channel = resolveChannel(msg.to);
broadcast(channel, moderate(msg.text));
}
Don't:
if/else checks.ErrorCode enums globally, tying the whole codebase together.Extract repeated logic (API fetch boilerplate, timeout: 5000, if (!res.ok) throw error) into a single authoritative function; duplicating it guarantees silent bugs when you update one copy and forget the others.
A method should only call methods on its immediate dependencies ("friends"), never dig through them to touch the internals of "strangers."
Do:
let bufferOutputStream = ctxt.createScratchFileStream(classFileName);Don't:
Keep objects (hide data, expose behavior) separate from data structures (expose data, no behavior); never blend the two into hybrids. Choose procedural code (data structures plus separate procedures) when you expect to add new operations; choose object-oriented code when you expect to add new types. Expose abstract behavior, not getters/setters that mirror the exact stored fields.
Do:
class Square { public double side; } // data structure
class Geometry {
double area(Object shape) { ... } // add perimeter(), diagonal()... freely
}
Don't:
Avoid comments that explain what code does, since they eventually rot into outdated lies. Comment only to explain why, or to leave a necessary warning; when you must, keep it specific, tied to a real constraint, and readable as plain text.
Do:
// prevents abuse while keeping free tier viable// WARNING: Takes too long to run. Skip during quick test cycles.// 3-20 alphanumeric chars// TODO: add timeout check; response > 5s freezes the UI/// rustdoc, /** */ JSDoc): the public surface is the one place comments earn their keep, since it's the contract callers read.Don't:
// User must be 18 or older above code that was updated to if (user.age >= 21).// R.J. said this might cause a race condition... not sure if it worksNever use comments to store old code, authorship, or history logs; and don't use comments to compensate for poorly structured code.
Do:
git log for old versions, authorship, change history.function generateReport(data) {
const validItems = filterValid(data);
const results = processItems(validItems);
return buildReport(results);
}
Don't:
// Created by John Doe - March 2019).// 11-Oct-2001: Renamed getUserInfo..., } // end outer for loop, // ======== HELPERS ========).Order a source file top-down: highest-level summary first, deeper implementation below.
Do:
Don't:
Use blank lines to separate distinct concepts. Keep related lines vertically dense so the hierarchy stays scannable.
Do:
Don't:
Declare variables exactly where they're used to cut mental baggage. Follow your team's formatting standards without argument.
Do:
Don't:
let report; ... // 20 lines of irrelevant code ... report = buildReport();| Category | Do | Don't |
|---|---|---|
| Naming | Names that reveal intent, are pronounceable, and match scope length | Cryptic abbreviations, identical-looking chars, noise suffixes (Manager/Data), Hungarian encodings (strName) |
| Self-documenting code | Extract complex logic into well-named booleans, helper functions, named constants (ONE_DAY_IN_MS) | Comments explaining messy code, single-letter variables (d), raw magic numbers (86400000) |
| Functions | Small functions, one thing each, single level of abstraction | Massive functions mixing high-level decisions with low-level API calls |
| Arguments | Zero or one arg; group 3+ into an object | Long arg lists, boolean flags, output arguments, hidden side effects |
| Control flow | Commands (do things) or Queries (answer things), never both | Functions that mutate state and return values simultaneously |
| Errors | Throw exceptions; write the try-catch-finally skeleton first; keep error handling in its own function, separate from the algorithm | Return error codes forcing nested if/else; global error enums; bolt try/catch on as an afterthought |
| Duplication | Extract shared logic into a single authoritative place (DRY) | Copy-pasting switch statements or API boilerplate into file after file |
| Types | Polymorphism via interfaces/factories for type-dependent behavior | Repeated switch(type) blocks duplicated in every function |
| Coupling | Ask immediate dependencies to act directly (Law of Demeter) | Chain calls (a.getB().getC().getD()) to reach into internal structure |
| Objects vs. data | Pure data structures (DTOs) hold data; behavior lives in separate objects | Hybrid classes carrying both getters/setters and business logic |
| Procedural vs OO | Procedural when adding operations, OO when adding types (anti-symmetry) | Forcing everything into objects regardless of how the code evolves |
| Data abstraction | Expose abstract behavior that hides how data is stored | Getters/setters that mirror the exact stored fields |
| Comments | Explain why, warnings, specific plain-text TODOs, public API docs | Explain what code does, mumbling comments, HTML-formatted comments |
| Version control | Rely on git log for history and authorship; delete dead code | Commented-out code, authorship tags, journal logs, closing brace comments |
| File structure | Headline function at top, callers above callees, related utilities grouped | Random function order, scrolling endlessly to find implementations |
| Vertical spacing | Blank lines between concepts, tight grouping for related lines | Walls of text with no spacing, or blank lines between every single line |
| Variable proximity | Declare variables right where they are used; follow team formatting standards | Variables declared 20 lines before use; personal style over team conventions |
Guides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Resolves in-progress git merge or rebase conflicts by analyzing history, understanding intent, and preserving both changes where possible. Runs automated checks after resolution.
npx claudepluginhub uwuclxdy/agenticat --plugin skills