Pair Programming Techniques with Claude Sonnet 3.5

After exploring the basics of collaborating with Claude, let’s see how to take the interaction to the next level. The key is knowing how to structure the conversation to get the most out of the AI.
Collaborative Refactoring
When we need to refactor complex code, Claude shines at analysis and suggestions. Here’s a real example:
Me: I have this 500-line monolithic controller that handles every operation on orders.
How can I refactor it?
Claude: To help you effectively, I'd need to see:
1. The controller code
2. The main dependencies
3. The most common usage patterns
Can you share this information?
Me: [I share the controller code]
Claude: Let's analyze the code step by step. I can see several responsibilities mixed together:
- Authentication and authorization
- Input validation
- Order business logic
- Notifications
- Logging
I suggest we start by separating these concerns.
Let's begin with authentication.
Do you want to see how to extract this part into a separate service?
Me: Yes, show me how to separate authentication
Claude: Let's first create the authentication service interface:
interface AuthenticationService {
validateToken(token: string): Promise<User>;
checkPermissions(user: User, resource: string): Promise<boolean>;
}
This lets you:
1. Test the controller with a mocked authentication
2. Swap implementations without touching the controller
3. Reuse the logic in other controllers
Do you want to see the full implementation?
[the conversation continues...]
Prompt Engineering Example for Code Review
To get an effective code review, structure your prompt like this:
Can you do a code review of this class? Focus on:
- SOLID principles
- Error handling
- Performance
- Security
[code to review]
Claude: I'll analyze the code according to the requested criteria.
SOLID:
- Single Responsibility: The class handles both authentication and logging
- Open/Closed: There's no interface for extending the auth methods
Error Handling:
- generic catch could hide critical errors
- missing structured logs for debugging
Performance:
- The cache is invalidated too frequently
- DB queries are not optimized
Security:
- Password hash doesn't use a salt
- JWT token with no expiration
Do you want to dig deeper into any of these aspects?
Dev: Let's dig into the security part
Claude: Here are the changes needed for security:
1. Password hashing:
import { hash, compare } from 'bcrypt';
// Before
password: await crypto.hash(password)
// After
const SALT_ROUNDS = 12;
password: await hash(password, SALT_ROUNDS)
JWT token:
// Before
jwt.sign({ userId }, SECRET_KEY)
// After
jwt.sign({
userId,
exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1h
iat: Math.floor(Date.now() / 1000)
}, SECRET_KEY)
Do you want me to add rate limiting and brute force protection too?
AI-Guided Refactoring
For refactoring, use incremental prompts:
Dev: I need to refactor this monolithic controller:
[300-line controller code]
Claude: Let's start by identifying the responsibilities:
1. Authentication handling
2. Input validation
3. Business logic
4. HTTP responses
I'd suggest starting by separating authentication. Shall we go step by step?
Dev: Yes, let's start with authentication
Claude: First, let's extract the interface:
interface AuthService {
validateToken(token: string): Promise<User>;
refreshToken(token: string): Promise<string>;
}
Now let's create the implementation...
Collaborative Debugging
Claude excels at analyzing errors and suggesting solutions. Example:
Me: I have this error in production that I can't reproduce locally:
TypeError: Cannot read property 'items' of undefined
at OrderProcessor.calculateTotal (orderProcessor.ts:45)
Claude: To debug this error, I need to know:
1. The OrderProcessor code
2. Where the items data comes from
3. Under what conditions the error occurs
4. The production logs related to the error
Can you provide this information?
Me: [I share the code and the logs]
Claude: Looking at the code and the logs, I see two possible scenarios:
1. OrderProcessor receives an undefined order when the database is overloaded
2. The race condition on line 43 can cause items to be accessed before the order is fully loaded
I'd suggest adding these checks:
calculateTotal(order: Order) {
if (!order) {
throw new OrderNotFoundError('Order is undefined');
}
if (!order.items) {
throw new InvalidOrderError('Order has no items');
}
// rest of the code...
}
Do you also want me to show you how to add structured logging to better track these cases?
Test Generation
Claude can help identify edge cases and generate tests:
Me: I need to write tests for this payment service. Which scenarios should I test?
Claude: Let's analyze the service to identify the critical scenarios. I see it handles:
- Card payments
- PayPal
- Bank transfers
Each payment method has its own edge cases. Let's start with card payments:
describe('CreditCardPayment', () => {
it('processes a valid payment', async () => {
// test implementation
});
it('handles an expired card', async () => {
// test implementation
});
it('handles insufficient funds', async () => {
// test implementation
});
});
Do you want to see the detailed implementation of these tests, or would you rather move on to the PayPal scenarios?
[the conversation continues...]
Interaction Best Practices
To get the most out of Claude:
Provide Complete Context
Me: How can I improve this code?
[I share the order.php code]
Claude: To give you relevant suggestions, I'd need to know:
- What the purpose of this code is
- In what context it's used
- What the non-functional requirements are
- What technical constraints exist
Can you provide this information?
Me: It's an order management system for an e-commerce site. It uses PHP 7.4 and MySQL. It needs to handle about 1,000 orders a day.
Claude: Thanks for the context. I see several areas for improvement:
1. Error handling is missing
2. There's no input validation
3. The queries could be optimized
Shall we start with error handling?
Me: Yes, show me how you would implement error handling
Claude: Here's an example of structured error handling...
[the conversation continues with a detailed implementation]
Incremental Iteration
Me: I have a performance problem with this endpoint.
Claude: Let's tackle the problem step by step:
1. Can I see the endpoint code?
2. Do you have data on the current performance?
3. Are there any response time constraints?
Me: The endpoint runs a product search. It responds in 3 seconds and we'd like to get it under one second.
[I share the code]
Claude: Let's look at the potential bottlenecks:
1. The query doesn't use indexes
2. Pagination is missing
3. There's an N+1 query problem
Let's start by optimizing the query. Can I see the database schema?
Me: [I share the schema]
Claude: OK, I'd suggest these immediate changes:
1. Add a composite index on (category, price)
2. Implement pagination
3. Eager loading of relationships
Do you want to see the implementation of any of these?
Summing Up: Best Practices for Talking to Claude
Provide Context: Always include:
- Dependency versions
- Project constraints
- Specific use cases
Ask for Explanations: If a solution isn’t clear, have Claude walk you through its reasoning
- Verify the Solutions: Don’t blindly implement the suggestions
- Progressive Iteration: Tackle one problem at a time
Limitations and Workarounds
Claude has some limitations you need to be aware of:
- It Can’t Test the Code: Always give feedback on the implementation
- Limited Context: Keep Claude updated on previous changes
- Theoretical Patterns: Adapt the suggestions to your specific case
Conclusion
Advanced pair programming with Claude works best when:
- The prompts are specific
- It receives complete context
- It can analyze the problem incrementally
- It’s guided with specific questions
In the next article of the series, we’ll see how to use Claude for code optimization.