Agent Skills in Claude Code and Visual Studio Code: Setup, Benefits, Risks, and a Step-by-Step Example

Image
 AI coding assistants are useful for one-off questions, but many development tasks are not one-off tasks. A team may follow the same testing procedure before every release, use a specific format for code reviews, or require certain checks whenever a database migration is created. Repeating those requirements in every prompt is inefficient. Putting all of them into a permanent instruction file is not always ideal either, because the assistant may receive irrelevant information for tasks that do not need it. This is the problem that Agent Skills are designed to solve. A skill packages reusable instructions, supporting files, examples, and optional scripts into a folder. Claude Code or GitHub Copilot in Visual Studio Code can discover that folder and load its contents when the current task matches the skill’s purpose. Both environments support the open Agent Skills format, which makes it possible to share some skills across tools. This guide explains what skills are, how they differ ...

One Name Can Save a PR: The Power of Readable Code

One Name Can Save a PR: The Power of Readable Code

Imagine opening a PR and seeing this code in the first file you review:

public List<Map<String, Object>> proc(List<Map<String, Object>> d, int t) {
    List<Map<String, Object>> res = new ArrayList<>();
    for (Map<String, Object> item : d) {
        if ((int) item.get("s") == t) {
            res.add(item);
        }
    }
    return res;
}

There seems to be no functional problem. It compiles. Tests probably pass. The logic might be correct. But as a reviewer, your mind starts spinning with questions:

What is d? Is it a list of documents, data structures, or something else? Is t a type? A time value? A tier level? What does s stand for? Status? Size? Score? Is proc a process, procedure, procurement, or something else entirely?

You're not reading the code. You're deciphering it like it's written in a cipher. And the moment you start expending mental energy decoding variable names and function purposes, the quality of your review drops precipitously. You don't have cognitive energy left to look at what really matters: logic errors, edge cases, design issues, performance concerns.

This is why naming isn't just style or personal preference. Naming is a prerequisite for effective code review.



What Bad Names Do to Reviewers

Research shows developers spend over 58% of their time reading and understanding code, not writing it. And the readability impact of good or bad naming is enormous — variable names have the biggest single effect on how quickly developers can understand code.

Now change only the names in that code — the actual logic doesn't change at all:

public List<Map<String, Object>> filterOrdersByStatus(
        List<Map<String, Object>> orders, int targetStatus) {
    List<Map<String, Object>> filtered = new ArrayList<>();
    for (Map<String, Object> order : orders) {
        if ((int) order.get("status") == targetStatus) {
            filtered.add(order);
        }
    }
    return filtered;
}

Not a single line of logic was changed. The algorithm is identical. But now the reviewer knows what this does the moment they read the first line. Now they can focus their mental energy on real issues: "Should we be filtering at the query level instead of the application layer? What happens if targetStatus is negative? Are we handling all status values correctly?"

Good names unlock better review quality.

The AI Naming Trap: Local Optimization vs. Global Consistency

When AI generates code, it's often good at individual names. `calculateTotalPrice()` and `validateEmail()` aren't bad by themselves. But AI doesn't see the larger context of your codebase. AI doesn't know your team's conventions. AI doesn't check consistency across the entire project.

AI might generate:

function getUserData(userId) { ... }
function fetchUserInfo(id) { ... }
function retrieveUserDetails(userId) { ... }

All three functions are placed in the same PR, potentially doing similar things. A good reviewer immediately asks: "Do these three functions do different things, or should they be combined? The names use different terms (getUserData, fetchUserInfo, retrieveUserDetails) but seem to describe similar operations. Are they intentionally different?"

AI focuses on making individual functions readable. Humans must ensure consistency and coherence across the entire codebase.

Naming by Type: Different Rules for Different Names

Function Names: Verb + Noun, But Which Verb Matters

The verb you choose carries semantic weight beyond just the action:

  • get: Fast lookup from available data, assumption that the result exists ("it's already there")
  • find: Search for something that may not exist — typically returns null or Optional
  • fetch: Includes external calls — HTTP requests, database queries, file I/O
  • load: Indicates initialization, configuration loading, or file I/O operations
  • create/build: Creating a new instance or composite object

So when you see `async function getOrders()` that makes an HTTP call to an API, a good reviewer asks: "This includes an HTTP request with potential failures and delays, so shouldn't it be `fetchOrders`?" The verb choice telegraphs assumptions to future readers.

Boolean Functions: Use Question Prefixes

// Hard to read - unclear if this returns a boolean
if (activeUser(user)) { }

// Clear - reads like an English question
if (isUserActive(user)) { }

// Also clear options
if (hasActiveSubscription(user)) { }
if (canAccessPremiumFeatures(user)) { }
if (shouldNotifyUser(user)) { }

Functions returning booleans should use question prefixes: `is`, `has`, `can`, `should`, `contains`. They become readable questions. This single convention eliminates an entire class of confusion.

Variable Names: Clarity and Context

The worst variable names fall into two categories:

  • Too short: `int n`, `String s`, `Object x` — no semantic meaning, forces readers to hunt for context
  • Too abstract: `List<Order> items`, `Object data`, `String value` — gives no indication of what this data represents

Good variable names contain three things: what it is, what state it's in, and if relevant, what unit it measures.

// Before: abstract, unclear names
const [data, setData] = useState(null);
const [flag, setFlag] = useState(false);
const [e, setE] = useState<Error | null>(null);

// After: clear, semantic names
const [order, setOrder] = useState<Order | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);

The second version makes it obvious what each state represents. The first forces readers to understand through usage patterns.

Class Names: Avoid the Generic Graveyard

If your class name ends in `Manager`, `Handler`, `Processor`, `Helper`, or `Util`, it's a warning sign that the class probably has too many unrelated responsibilities:

class UserManager {
    createUser() { }        // responsibility: user creation
    sendWelcomeEmail() { }  // responsibility: email
    authenticateUser() { }  // responsibility: authentication
    generatePasswordResetToken() { }  // responsibility: tokens
}

This class handles registration, email, authentication, and password resets. Four different domains, four different reasons to change. The right approach is splitting into `UserRepository` (persistence), `AuthService` (authentication), and `UserEmailService` (notifications).

Generic class names are often symptoms of poor separation of concerns.

Comments: When They're Necessary vs. When They're Noise

Not every line of code needs a comment. In fact, most code shouldn't have explanatory comments:

// Bad - just translating what the code already says
// Gets the user
User user = userRepository.findById(userId);

// Gets their email
String email = user.getEmail();

// Sends them a notification
emailService.send(email, notificationMessage);

These comments add nothing. The code already says what it does. Reading the comment doesn't increase understanding.

But comments are essential when they explain why — the business reasons, constraints, or external requirements that code alone cannot express:

// The payment API blocks retries for the same order within 3 seconds
// to prevent duplicate processing. Therefore, we must wait at least
// 3000ms before attempting a retry. See payment API docs v2.1.
Thread.sleep(3000);

This comment says something code cannot say — the external constraint and the business reason. Remove this comment and future maintainers might optimize away what they think is a wasteful delay.

Common Naming Anti-Patterns to Avoid

Anti-Pattern 1: Hungarian Notation

// Outdated style
int iCount = 0;
String strName = "John";
List<Order> lstOrders = new ArrayList<>();

// Better - types are clear from context
int count = 0;
String name = "John";
List<Order> orders = new ArrayList<>();

Hungarian notation (prefixing with type) was useful when IDEs couldn't show types. Modern IDEs make the type visible instantly. The prefix just adds noise.

Anti-Pattern 2: Acronyms Without Context

// Bad - what does FCM mean?
async function sendFCM(userId, message) { }

// Good - context is clear
async function sendPushNotification(userId, message) { }

// If FCM is unavoidable, document it
// FCM = Firebase Cloud Messaging
async function sendFCMNotification(userId, message) { }

Anti-Pattern 3: Generic Names That Could Mean Anything

// What is "process"?
function processData(data) { }
function processOrder(data) { }

// More specific
function validateAndPersistOrder(data) { }
function calculateOrderDiscount(order) { }

The word "process" is so generic it could mean anything. Be specific about what processing is happening.

The Test of a Good Name: Fresh Eyes

Here's a simple test: Would a new team member unfamiliar with this codebase understand what this code does just by reading the names, without needing to trace through the implementation?

If not, change the names.

Names are the first thing read in any code and the thing that lasts longest in memory. Pointing out naming issues isn't a minor style thing. It's an investment in the future maintenance of this codebase and the productivity of everyone who will read this code.

Naming in Different Contexts

Database Column Names: Must be SQL-safe and readable in queries.

-- Bad: ambiguous
SELECT s, p, u FROM t WHERE a = 1;

-- Good: clear meaning
SELECT status, price, user_count FROM transactions WHERE account_id = 1;

API Endpoint Names: Should use nouns, not verbs (REST convention).

// Bad: verbs in endpoint
POST /api/getUser
POST /api/createOrder
PUT /api/updateProfile

// Good: nouns, HTTP method implies action
GET /api/users/{id}
POST /api/orders
PUT /api/profile

Configuration Variables: Indicate what they control and what values are valid.

// Bad
MAX_CONN = 100
TIMEOUT = 30

// Good
MAX_DATABASE_CONNECTIONS = 100
REQUEST_TIMEOUT_SECONDS = 30
CACHE_TTL_MINUTES = 5

Practical Naming Checklist

When reviewing names in code, ask:

  • Would someone unfamiliar with this code understand this name?
  • Is it specific enough? (avoid "data", "process", "handle", "do")
  • Is it consistent with team conventions?
  • Does it express intent, not mechanism?
  • Is it pronounceable? (you'll talk about this code)
  • If it's a function, does the verb accurately describe what it does?
  • If it's a boolean, does it read like a question?
  • If it's a collection, does the plural form make sense?
  • Is the scope appropriate for the name length? (local variables can be shorter; public APIs need longer, more descriptive names)
  • Does it follow the language's conventions? (camelCase in JavaScript, snake_case in Python/SQL)

Comments

Popular posts from this blog

Claude Code Multi-Agent Setup for Beginners: Models, Roles, and Your First Project

The Limits and Future of Vibe Coding

How to Test and Deploy Your First Vibe-Coded App