Large refactoring projects are risky because they involve understanding complex dependencies, predicting ripple effects, and maintaining backwards compatibility. Of course, you can ask the Amp agent to refactor this to the best of its ability, but bringing in the Oracle to assist can make these large-scale refactors much more effective. The Oracle excels at analyzing complex and interconnected systems and planning refactoring approaches that minimize risk while maximizing the intended benefits.
This guide demonstrates how to utilize Oracle to tackle a common use case, safely refactoring duplicate code patterns, while preserving existing functionality.
Our example: Task completion notification duplication
For this example, let’s assume we have a task management API that supports notifications when tasks are completed. As the system has grown, it has resulted in duplicate notification logic across different channels. There are two functions that send emails and Slack notifications, which share a large amount of code. This leads to maintenance issues, especially as we start to add other channels, such as SMS and other integrations. For those who want to see the exact code for a better understanding of the problem, here's a snippet of the redundant code we will ask the Oracle to assess:
// server.js \- Duplicate notification logic in task completion
app.patch('/tasks/:id/complete', async (req, res) \=\> {
const taskId \= parseInt(req.params.id);
const task \= tasks.find(t \=\> t.id \=== taskId);
if (\!task) {
return res.status(404).json({ error: 'Task not found' });
}
task.completed \= true;
task.completedAt \= new Date().toISOString();
// Send notifications \- duplicate logic across different methods
const user \= await getUserById(task.userId);
if (user.emailNotifications) {
await sendEmailNotification(task, user);
}
if (user.slackNotifications) {
await sendSlackNotification(task, user);
}
res.json(task);
});
// Duplicate notification methods
async function sendEmailNotification(task, user) {
// Validation
if (\!task.title) throw new Error('Task title required');
// Logging
console.log(\`Sending email notification for completed task: ${task.title}\`);
// External API call
const response \= await fetch('/api/email/send', {
method: 'POST',
body: JSON.stringify({
to: user.email,
subject: \`Task Completed: ${task.title}\`,
body: \`Your task "${task.title}" has been completed.\`
}),
headers: { 'Content-Type': 'application/json' }
});
// Response handling
if (\!response.ok) throw new Error('Email notification failed');
const result \= await response.json();
// Logging
console.log(\`Email notification sent for task ${task.id}\`);
return result;
}
async function sendSlackNotification(task, user) {
// Validation (identical)
if (\!task.title) throw new Error('Task title required');
// Logging (nearly identical)
console.log(\`Sending Slack notification for completed task: ${task.title}\`);
// External API call (different endpoint, same pattern)
const response \= await fetch('/api/slack/message', {
method: 'POST',
body: JSON.stringify({
channel: user.slackId,
text: \`✅ Task completed: "${task.title}"\`
}),
headers: { 'Content-Type': 'application/json' }
});
// Response handling (identical)
if (\!response.ok) throw new Error('Slack notification failed');
const result \= await response.json();
// Logging (nearly identical)
console.log(\`Slack notification sent for task ${task.id}\`);
return result;
}
The refactoring challenge
Within the code, we aim to eliminate duplication while maintaining backward compatibility and avoiding disruptions to existing task completion workflows. To figure out the best path forward, let's ask the Oracle to analyze this code and plan a safe refactoring approach.
Here's our prompt to Amp:
Analyze how the task notification methods are used throughout our codebase. Then I want you to work with the oracle to figure out how we can refactor the duplication between them while keeping changes backwards compatible and maintaining the same notification behavior for completed tasks.
As you can see, we specifically invoke the Oracle in the prompt by stating “...work with the oracle to figure out how we can refactor the duplication…”. This will then kick off the analysis process.
Oracle analysis: Understanding the system
As the process gets executed, first we will see the main agent search through the code to see how the methods are used throughout the codebase.
Then, as our prompt suggested, using the data collected Amp will use the Oracle to design the refactoring plan.
Once it is done it’s assessment, we can see the output as well as an ask from the system to see if we would like to execute the refactor based on the Oracle’s breakdown.
Using Amp to implement the refactoring
Assuming we like the Oracle’s output, we can then have Amp implement the Oracle's plan. To do so, we can send in a prompt similar to this one below:
Please implement the refactoring plan the oracle outlined. Proceed with each phase, but make sure that the implementation at each phase works as expected and does not break compatibility.
Then, the agent will go off to work refacotring based on the plan and testing to make sure backwards compatibility is maintained. Of course, with these types of refactors you’ll want to have some other layers of testing involved as well, but you can see from the output from Amp that it was able to successfully refactor the code based on the spec supplied by the Oracle.
Oracle guidance for different refactoring types
Although the main agent and model (currently Sonnet 4.0) in Amp is great for refactoring, the Oracle provides strategic analysis for various refactoring scenarios that could use a little bit more foresight and care. Situations where input from the Oracle can be helpful include:
- Legacy modernization: Gradual migration paths that minimize disruption
- Architecture changes: Impact analysis for major structural shifts
- Performance optimization: Identifying bottlenecks while preserving behavior
- Security hardening: Adding security features without breaking functionality
- Dependency updates: Managing version changes and compatibility issues
Since each of these refactor scenarios requires different approaches, the Oracle can independently make suggestions that are carefully thought out. The Oracle can help to comprehend these more complex changes and does a great job, in addition to the primary model, at giving insights on how to effectively refactor. This means that using the Oracle to refactor things like:
- Large codebases with complex dependencies
- Legacy systems with unclear usage patterns
- Cross-team code that affects multiple services
- Mission-critical systems requiring high reliability
While the default model is much better than simpler refactoring that likely doesn’t need the extra brainpower and logic, such as:
- Simple, isolated code cleanup
- Well-understood systems with comprehensive tests
- Greenfield projects without legacy constraints
Top comments (0)