Agent Skills in Claude Code and Visual Studio Code: Setup, Benefits, Risks, and a Step-by-Step Example
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.
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.
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.
The verb you choose carries semantic weight beyond just the action:
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.
// 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.
The worst variable names fall into two categories:
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.
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.
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.
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.
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.
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/profileConfiguration 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 = 5When reviewing names in code, ask:
Comments
Post a Comment