GitHub Copilot Code Review and Refactoring

Code review finds problems before they reach production. Refactoring improves the structure of existing code without changing what it does. GitHub Copilot assists with both — it reads your code and highlights issues, suggests improvements, and rewrites problematic sections. This topic covers how to use Copilot for both tasks effectively.

What Code Review with Copilot Looks Like

Traditional code review requires a colleague to read your code and give feedback. Copilot can simulate that review within seconds by analyzing code you share with it in Chat. It spots patterns associated with bugs, performance issues, security risks, and maintainability problems.

TRADITIONAL REVIEW TIMELINE:
  Submit code → Wait 1–2 days → Receive feedback → Fix → Re-submit
  Total time: often 3–5 days

COPILOT-ASSISTED REVIEW:
  Write code → Ask Copilot → Get feedback in 10 seconds → Fix immediately
  Total time: minutes

Requesting a Code Review in Chat

Select the code you want reviewed, paste it into Chat, and ask Copilot to review it. Be specific about what you want Copilot to focus on.

GENERAL REVIEW REQUEST:
You: Review this function for any issues:
[paste function]

TARGETED REVIEW REQUESTS:
"Review this for security vulnerabilities"
"Review this for performance problems"
"Review this for edge cases I might have missed"
"Review this against SOLID principles"
"Review this for readability and naming"

Common Issues Copilot Catches in Reviews

ISSUE 1 — Missing Error Handling:
ORIGINAL:
async function getUser(id) {
    const user = await db.findById(id);
    return user;
}

COPILOT REVIEW:
⚠ No try/catch block. If the database call fails,
  the error propagates as an unhandled Promise rejection.

SUGGESTED FIX:
async function getUser(id) {
    try {
        const user = await db.findById(id);
        if (!user) throw new Error(`User ${id} not found`);
        return user;
    } catch (error) {
        logger.error('getUser failed:', error);
        throw error;
    }
}

──────────────────────────────────────────────────────

ISSUE 2 — SQL Injection Risk:
ORIGINAL:
function searchUsers(query) {
    return db.raw(`SELECT * FROM users WHERE name LIKE '%${query}%'`);
}

COPILOT REVIEW:
⚠ Direct string interpolation into SQL is dangerous.
  A malicious user could inject SQL commands.

SUGGESTED FIX:
function searchUsers(query) {
    return db.raw('SELECT * FROM users WHERE name LIKE ?', [`%${query}%`]);
}

──────────────────────────────────────────────────────

ISSUE 3 — Memory Leak Pattern:
ORIGINAL (React):
useEffect(() => {
    const timer = setInterval(fetchData, 5000);
}, []);

COPILOT REVIEW:
⚠ The interval is never cleared. When this component
  unmounts, the interval keeps running and references
  stale state, causing a memory leak.

SUGGESTED FIX:
useEffect(() => {
    const timer = setInterval(fetchData, 5000);
    return () => clearInterval(timer);   ← cleanup function
}, []);

Refactoring: What It Is and When to Do It

Refactoring means improving the internal structure of code without changing its behavior. You refactor when code works but is hard to read, maintain, or extend. Common signals that refactoring is needed:

SIGNALS THAT CODE NEEDS REFACTORING:
  ✗ Function is longer than 30–40 lines
  ✗ Same code appears in 3 or more places (duplication)
  ✗ A function does more than one clearly distinct thing
  ✗ Nested conditions go 3 or more levels deep
  ✗ Variable names are single letters or vague (x, temp, data)
  ✗ No tests exist for the code (hard to refactor safely)

Refactoring with /simplify

BEFORE — Deeply nested conditions:
function getShippingCost(order) {
    if (order.country === 'US') {
        if (order.weight < 1) {
            if (order.express) {
                return 12.99;
            } else {
                return 5.99;
            }
        } else {
            if (order.express) {
                return 24.99;
            } else {
                return 14.99;
            }
        }
    }
    return 39.99;
}

IN CHAT: /simplify

AFTER:
function getShippingCost(order) {
    if (order.country !== 'US') return 39.99;

    const rates = {
        light: { standard: 5.99, express: 12.99 },
        heavy: { standard: 14.99, express: 24.99 }
    };

    const weight = order.weight < 1 ? 'light' : 'heavy';
    const method = order.express ? 'express' : 'standard';
    return rates[weight][method];
}

Extracting Functions

Long functions that do multiple things should be split into smaller, single-purpose functions. Ask Copilot to extract logical sections into separate functions.

ORIGINAL — One function doing everything:
async function handleCheckout(cartData) {
    // Validate cart
    if (!cartData.items.length) throw new Error('Cart is empty');
    cartData.items.forEach(item => {
        if (item.quantity < 1) throw new Error('Invalid quantity');
    });

    // Calculate totals
    const subtotal = cartData.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
    const tax = subtotal * 0.08;
    const total = subtotal + tax;

    // Save order
    const order = await db.orders.create({ ...cartData, subtotal, tax, total });

    // Send email
    await emailService.sendOrderConfirmation(order);
    return order;
}

IN CHAT:
"Extract the validation, calculation, and email steps
into separate functions"

AFTER:
function validateCart(cartData) {
    if (!cartData.items.length) throw new Error('Cart is empty');
    cartData.items.forEach(item => {
        if (item.quantity < 1) throw new Error('Invalid quantity');
    });
}

function calculateTotals(items) {
    const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
    const tax = subtotal * 0.08;
    return { subtotal, tax, total: subtotal + tax };
}

async function handleCheckout(cartData) {
    validateCart(cartData);
    const totals = calculateTotals(cartData.items);
    const order = await db.orders.create({ ...cartData, ...totals });
    await emailService.sendOrderConfirmation(order);
    return order;
}

Removing Code Duplication

When the same pattern repeats in multiple places, ask Copilot to identify the common structure and create a reusable function.

DUPLICATED PATTERN:
const getUser = async (id) => {
    try {
        const result = await db.users.findById(id);
        return { success: true, data: result };
    } catch (err) {
        return { success: false, error: err.message };
    }
};

const getProduct = async (id) => {
    try {
        const result = await db.products.findById(id);
        return { success: true, data: result };
    } catch (err) {
        return { success: false, error: err.message };
    }
};

IN CHAT: "These functions are duplicated. Create a helper
         that removes the repetition."

COPILOT CREATES:
async function safeFind(collection, id) {
    try {
        const result = await collection.findById(id);
        return { success: true, data: result };
    } catch (err) {
        return { success: false, error: err.message };
    }
}

const getUser = (id) => safeFind(db.users, id);
const getProduct = (id) => safeFind(db.products, id);

Using Copilot for Pull Request Reviews

GitHub Copilot Enterprise includes a feature that analyzes pull request diffs and summarizes changes. For individual and business plans, you can paste a diff into Chat and ask for a review.

IN CHAT:
"Review this git diff and identify any potential issues:
[paste the diff output]"

COPILOT MAY RESPOND:
1. Lines 23–27: The new loop iterates over the full array each time.
   Consider caching the array length outside the loop.

2. Line 45: The function accepts user input directly without sanitization.
   This could allow XSS if rendered to HTML.

3. Line 61: The catch block is empty. Silent failures make debugging harder.
   Add at minimum a console.error() or a logger call.

Refactoring Safely with Tests

Never refactor code without tests. Tests verify that the refactored code still behaves the same way. Ask Copilot to generate tests before refactoring, run them, then refactor, then run them again.

SAFE REFACTORING SEQUENCE:
1. /tests   → Generate tests for the current function
2. Run tests → All pass (establish a baseline)
3. Refactor  → Ask Copilot to improve the code structure
4. Run tests → All should still pass
5. Fix       → If any fail, use /fix to identify the regression

Using Copilot for code review and refactoring produces cleaner, more maintainable codebases over time. The most effective developers use it as a first pass — getting Copilot's feedback before asking human reviewers — so that reviews focus on architecture and business logic rather than mechanical issues Copilot already caught.

Leave a Comment

Your email address will not be published. Required fields are marked *