Code removal debugging
Sometimes an app is broken and I’m not sure what to do next.
It’s loading a blank screen, there’s errors in the console, or its running very slow.
The setup
Imagine a React application that displays a list of customers, staff, and transactions from the current day for a shop.
The transaction list has hundreds of items because your shop is pretty popular. It sells gopher themed dumplings.
<Customers />
<Staff />
<Transactions />
The application loads slowly but you aren’t sure what code commit caused it.1
Delete stuff that might be the problem
In this situation I delete or comment out code until the performance improves or the error goes away.
I might not understand everything I’m removing or parts of the app might not work. It’s okay, because this is a strategy to get unstuck.
<!-- <Customers customers={customers} /> -->
<!-- <Staff staff={staff} /> -->
<Transactions transactions={transactions} />
I comment out the Customers
and Staff
components and the app is still performing slowly.
Add it back if it’s not
We now know the problem isn’t there and we can ignore those components.
What if I comment out Transactions
?
<Customers customers={customers} />
<Staff staff={staff} />
<!-- <Transactions transactions={transactions} /> -->
Now the application is performing normally but the transactions are not displayed.
We’ve narrowed down the problem to the Transactions
component so let’s uncomment it and look inside it.
const Transactions = (transactions) => {
const formattedTransactions = transactions.map(t => formatTransaction(t));
return (
<ul>
{
formattedTransactions.map(t => (
<li key={t.id}>{t.id} | {t.time}</li>
));
}
</ul>
)
}
const formatTransaction(transaction)
{
return {
id: t.id,
time: Intl.DateTimeFormat("en-US", {year: "numeric", month: "numeric", day: "2-digit"}).format()
};
}
Hardcode
It looks good to me but let’s replace formattedTransactions
with a hardcoded result to see if that function is the problem.
const Transactions = (transactions) => {
// const formattedTransactions = transactions.map(t => formatTransaction(t));
// Generate an array of 100 elements then create transactions from that array
const range = Array.from({length: 100}, (x, i) => i);
const formattedTransactions = range.map(i => {id: i, time: new Date('2023-04-01')});
return (
<ul>
{
formattedTransactions.map(t => (
<li>{t.id} | {t.time}</li>
));
}
</ul>
)
}
After hardcoding the result, the performance is still good. Now we know the problem is probably in the formatTransaction(t)
call.
Look closer
It turns out, the way time is formatted is causing an issue:
const formatTransaction(transaction)
{
return {
id: transaction.id,
time: Intl.DateTimeFormat("en-US", {year: "numeric", month: "numeric", day: "2-digit"}).format(transaction.date)
};
}
Each time the transaction’s time is formatted it uses a new instance of the Intl.DateTimeFormat
object. This is ok with a few transactions but with a hundred it takes long enough for the page to feel slow.
We can address this by creating a single Intl.DateTimeFormat
object and reusing it.
const dtf = Intl.DateTimeFormat("en-US", {year: "numeric", month: "numeric", day: "2-digit"});
const formatTransaction(transaction)
{
return {
id: transaction.id,
time: dtf.format(transaction.date)
};
}
And now our app is performing as expected. It can be easy to miss this sort of thing. But deleting code so we can figure out what can be safely ignored can help narrow down the problem.
Recap
- Remove code until the error or problem goes away
- Gradually add code back until the problem returns
- Narrow down which part of the code we think has the problem
- Replace parts of the code we think may be a problem with hardcoded versions
- Confirm the specific lines of code that cause the problem and fix the issue
There are more sophisticated ways to tackle this problem using profiling tools. However, sometimes it’s helpful to have a basic strategy like this.