Install
$ agentstack add skill-uwuclxdy-agent-skills-clean-code ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Clean Code Principles
Apply these principles when writing, reviewing, or refactoring code. They are organized from the most fundamental (naming) to code organization, formatting, and commenting best practices.
1. Naming & Readability
1.1 Avoid Disinformation
Never use variable names that create false expectations, contain misleading technical terms, or use visually confusing characters.
Do:
- Use names that honestly reflect the data structure, like
accountsMaporaccountsGroup. - Use distinct names for classes/variables so autocomplete doesn't trick you.
Don't:
- Name a map/object
accountListjust because it holds multiple items — to a programmer, "list" means an array/indexed structure. - Create names that vary in tiny ways (e.g.,
ABCManagerForEfficientProcessingOfUsersvs...PersistingOfUsers). - Use characters that look identical (e.g., lowercase
land number1, or uppercaseOand number0). - Bad:
int a = l; if (O == l) a = O1; else l = O1;
Disinformation creates "code lies" where developers' perceptions differ from actual behavior, causing confusion and bugs.
1.2 Make Meaningful Distinctions
If two things have different names, they must do different things. Avoid meaningless noise words and number series.
Do:
- Reveal the actual role of variables (e.g.,
array,condition,transformation). - Use specific, role-based names for classes (e.g.,
OrderValidator,OrderRepository,OrderCalculator).
Don't:
- Use numbered variables that look identical but do different things.
- Bad:
function filterAndMap(data1, data2, data3) - Add meaningless noise suffixes when you can't think of a better name (e.g.,
OrderManager,OrderHandler,OrderController,OrderProcessorall existing side by side). - Add redundant prefixes/suffixes (e.g.,
getUser(),getUserInfo(),getUserData()with no clear distinction).
Meaningless distinctions force every developer to waste time reading the underlying implementation just to understand the difference between two functions or classes.
1.3 Use Pronounceable Names
Variable names should sound like natural human language so they can be easily read and spoken aloud.
Do:
- Use real words that describe the intent.
- Good:
let generationTimestamp = new Date(); - Good:
class Customer { private generationTimestamp: Date; private productSerialNumber: string; }
Don't:
- Mash abbreviations together into cryptic strings.
- Bad:
let genymdhms = new Date();(generation year month day hour minute second) - Bad:
let prcssr = new Processor(); let dtaRcrd = fetchData();
Programming is a social activity. Unpronounceable names prevent teams from discussing code verbally and create a major onboarding bottleneck.
1.4 Use Searchable Names
The length of a variable name should match the size of its scope. Avoid single letters or raw numbers for anything outside a tiny local scope.
Do:
- Use single letters (
i,j) only for local variables in very short scopes (like a tinyforloop). - Create named, searchable constants for numbers.
- Good:
MAX_CLASSES_PER_STUDENT = 7; - Good:
taskDays = taskEstimate[j] * WORK_DAYS_PER_WEEK; sum += taskDays / NUMBER_OF_TASKS;
Don't:
- Use single letters in larger scopes (searching for the letter
ewill return thousands of useless results). - Hardcode "magic numbers" into business logic.
- Bad:
for (let e = 0; e = OPENING_HOUR && currentHour = OPENING_HOUR...) - Use single-letter variables and explain them with a comment.
- Bad:
let d; // elapsed time in days - Leave raw "magic numbers" in your code.
- Bad:
setTimeout(logoutUser, 86400000);
Every comment is a second thing to maintain. Code changes, and if you forget to update the comment, it becomes misinformation. Tying the meaning directly to the variable or function name ensures it stays permanently accurate.
2. Function Structure & Size
2.1 Keep Functions Small
Functions should be small, and blocks inside control structures should be exactly one line long.
Do:
- Extract complex math, loops, and logic into clearly named helper functions.
- Make blocks inside
if,else, andwhilestatements exactly one line long (a function call). - Good:
if (file.size > MAX) { return uploadFileInParts(file, id); } else { return uploadFileAsSinglePart(file, path); }
Don't:
- Write long functions that mix high-level conditions with low-level mathematical implementation details and nested loops.
Long functions bury your logic under layers of noise. Small, clearly named functions tell a story of what is happening without forcing the reader to decipher how.
2.2 Do One Thing
A function should do exactly one thing. The litmus test: you cannot extract another meaningful function from it.
Do:
- Test your functions: Can you label sections of the code with comments like
// Validation,// Storage,// Notification? If so, extract them into separate functions. - Test your extractions: If you extract a function and the new name is just a restatement of the code (e.g.,
uploadBasedOnSize), it's doing one thing. If the extracted name represents a separate concept (e.g.,uploadFileInParts), the original was doing too much.
Don't:
- Write "micromanager" functions that handle validation, calculation, database storage, and email notifications all at once.
A function doing multiple things has multiple reasons to change, multiple ways to break, and requires massive test suites.
2.3 Maintain One Level of Abstraction
A function should only contain code at a single, consistent level of abstraction, delegating deeper details to the next level down.
Do:
- Structure code like a top-down narrative. The "CEO" function delegates to "Manager" functions, which delegate to "Specialist" functions.
- CEO level:
validateStock(); calculateTotalBill(); executeStripeCharge(); notifyWarehouse(); - Manager level (
calculateTotalBill):sumItems(); applyDiscount(); calculateTax();
Don't:
- Mix pure intent (high-level logic) with raw syntax (low-level logic) in the same function.
- Bad:
validateStockAvailability(); const finalBill = subtotal - discount + tax; Stripe.charges.create({ amount: finalBill * 100, source: order.token }); fetch("https://warehouse...");
Mixing levels creates messy "micromanager" functions. Keeping functions at one level of abstraction allows readers to understand the overarching narrative without getting bogged down in API syntax.
3. Arguments & Data Flow
3.1 Minimize Function Arguments
The ideal number of arguments is zero. If you hit three or more, wrap them in an object. Never use boolean flags or output arguments.
Do:
- Strive for zero arguments (
fetchData()), one argument (fileExists(path)), or two if they are a natural pair (moveTo(x, y)). - Group related arguments into objects.
- Good:
saveUser(user)(whereuseris an object containing name, email, age, etc.)
Don't:
- Pass 3, 4, or 5 arguments individually.
- Bad:
saveUser(name, email, age, city, isPremium) - Use output arguments that modify their inputs instead of returning a value (
findMax(numbers, result)). - Use boolean flags (
createUser(true)) that force different code paths — this explicitly violates the "Do One Thing" rule. - Pass two unrelated arguments as peers (
sendEmail(message, smtp)). Instead, make one the owner:smtp.sendEmail(message).
Arguments carry mental weight, demand order memorization, and cause test cases to explode exponentially.
3.2 Command Query Separation (& No Hidden Side Effects)
A function should either perform an action (Command) or answer a question (Query), but never both. It should never harbor hidden side effects.
Do:
- Split functions into distinct queries and commands.
- Good:
if (!attributeExists("role")) { setAttribute("role", "admin"); } - Ensure functions only do what their names say. Let side effects happen elsewhere.
- Good:
if (checkPassword(password)) { initializeSession(); }
Don't:
- Write functions that change state and return booleans.
- Bad:
if (setAndCheckIfExists("role", "admin")) { ... } - Hide side effects inside seemingly harmless functions.
- Bad: A
checkPassword()function that invisibly callsSession.initialize()or resets user properties.
Mixing commands/queries creates ambiguity (did it return true because it was set, or because it already existed?). Hidden side effects create dangerous bugs, like emptying a user's shopping cart just because they verified their password mid-session.
4. Architecture & Error Handling
4.1 Handle Switch Statements with Polymorphism
Switch statements inherently do multiple things. Bury them in abstract factories and use polymorphism instead.
Do:
- Create an interface (e.g.,
Employee). - Bury the switch statement in a Factory (
EmployeeFactory.create(type)). - Use polymorphism to let individual classes handle their own logic.
- Good:
const employee = factory.create('fullTime', data); employee.calculatePay(); employee.getBenefits();
Don't:
- Copy and paste the same
switch(employee.type)block across multiple functions (calculatePay,getBenefits,getSchedule).
Scattered switch statements mean that adding a new type requires hunting through the entire codebase to update every single switch. Polymorphism eliminates this.
4.2 Prefer Exceptions Over Error Codes (& DRY)
Throw exceptions instead of returning error codes. Isolate try/catch blocks into their own functions. Never duplicate logic.
Do:
- Use
try/catchblocks for a clean list of steps. - Good:
try { createAccount(); createProfile(); } catch (error) { showError(); } - Extract try/catch logic away from normal processing logic (one function for the try/catch, which calls a separate function containing the actual logic).
- Extract repeated code (like API
fetchboilerplates) into single, authoritative functions.
Don't:
- Return error codes that force the caller to write deeply nested
if/elsechecks. - Share
ErrorCodeenums globally, tying your entire codebase together. - Mix error handling with business logic.
- Repeat yourself — don't copy/paste
timeout: 5000andif (!res.ok) throw erroracross twenty API calls.
Error codes bury the "happy path" under nested error checking. Shared enums require massive recompilations when changed. Violating DRY (Don't Repeat Yourself) guarantees silent bugs when you update logic in one place but forget the others.
4.3 Write Try-Catch-Finally First
Define the error-handling structure before the business logic, so the function's contract — what it expects and what it throws — is fixed up front.
Do:
- Write the
try-catch-finallyskeleton first, define the custom exception you expect to throw, then fill thetryblock with business logic. - Good:
``javascript function withdraw(accountId, amount) { try { // business logic added here later } catch (e) { throw new TransactionFailed(e); } finally { // cleanup (e.g., release locks) } } ``
Don't:
- Write the business logic first and slap a
try/catcharound it as an afterthought.
Writing the try/catch first creates a clear contract: every caller immediately knows which exceptions the function can throw and which it handles, so failures don't cascade into breaks across the codebase.
4.4 Separate Error Handling from Business Logic
Keep the algorithm and its error handling in separate functions, using exceptions instead of nested if-statements that return error codes.
Do:
- Extract the algorithm into its own function that throws, and keep a separate function whose only job is to catch.
- Good:
```javascript function sendMessage(msg) { try { deliverMessage(msg); // error handling knows nothing about the algorithm } catch (error) { notifySender(msg.token, error); } }
function deliverMessage(msg) { // algorithm knows nothing about error handling const sender = verifySession(msg.token); const channel = resolveChannel(msg.to); const content = moderate(msg.text); broadcast(channel, sender, content); } ```
Don't:
- Interleave error checking and algorithm steps with deeply nested
if/elsethat returns error codes. - Bad:
``javascript function sendMessage(msg) { const sender = verifySession(msg.token); if (sender !== null) { const channel = resolveChannel(msg.to); if (channel.active) { // ... more nesting ... return "sent"; } else { return "closed"; } } else { return "expired"; } } ``
When error handling wraps every step of an algorithm, the algorithm disappears. Your eyes ping-pong between logic and failure branches, and you can read neither clearly.
4.5 The Law of Demeter
A method should only call methods on its immediate dependencies ("friends"), never navigate through them to reach the internals of "strangers."
Do:
- Tell the immediate object what you need it to do directly.
- Good:
let bufferOutputStream = ctxt.createScratchFileStream(classFileName);
Don't:
- Chain method calls to navigate deep into an object's internal structure.
- Bad:
``javascript let outputDir = ctxt.getOptions() .getScratchDir() .getAbsolutePath(); ``
Method chains expose the internal structure of your dependencies and couple your code to their hidden navigation path. When that internal structure changes, your code breaks.
4.6 Avoid Hybrid Structures (Objects vs. Data Structures)
Keep objects (which hide data and expose behavior) strictly separate from data structures (which expose data and have no behavior); never blend the two.
Do:
- Use pure data structures (DTOs, clean active records) to hold data, and put business logic in separate objects.
- Good:
```java class ProductDTO { // pure data structure public String name; public double price; }
class Pricing { // pure object (behavior) double applyDiscount(ProductDTO p, double pct) { ... } } ```
Don't:
- Build "hybrid" classes that carry private data with getters/setters and significant business logic.
- Bad:
```java class Product { private double price; double getPrice() { return price; } void setPrice(double p) { price = p; }
// business logic trapped inside a data structure double applyDiscount(double pct) { return price * (1 - pct / 100); } } ```
Hybrids are the worst of both worlds. Like objects, they make it hard to add new functions; like data structures, they make it hard to add new types. You pay both taxes and reap neither benefit.
4.7 Data/Object Anti-Symmetry
Choose procedural code (data structures + separate procedures) when you expect to add new operations, and object-oriented code when you expect to add new types.
Do:
- Use data structures with separate procedure classes when new functions arrive frequently.
- Good:
``java class Square { public double side; } // data structure class Geometry { double area(Object shape) { ... } // add perimeter(), diagonal()... freely } ``
- Use objects where each type owns its behavior when new types arrive frequently.
- Good:
``java interface Shape { double area(); } class Square implements Shape { public double area() { ... } } // add Triangle freely ``
Don't:
- Reflexively treat everything as an object. With objects, adding one new function forces you to modify every single class.
What is easy for procedural code (add functions without touching data structures) is hard for OO code, and what
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: uwuclxdy
- Source: uwuclxdy/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.