extract

Extract Until You Drop

 

Yesterday I posited the idea of constructing your code from short functions – ideally, composed of fewer than ten lines of code. 

Today, I’d like to introduce an approach to help you achieve these short functions while at the same time finding the right level of abstraction in your functions. As far as I know, the concept came from Robert C. Martin (aka Uncle Bob):

Extract Until You Drop

What does that mean? It means breaking down large functions by iteratively extracting functionality into private helper functions until it no longer makes sense. 

The ‘no longer making sense‘ part deserves qualification: It implies stopping when further extraction reduces, rather than increases, readability—we have found the optimal level of abstraction. It’s the point where either more or less extracting will, in our opinion, degrade function understandability – if ever so slightly.

It’s subjective, for sure. Your worse might be my better – and vice versa. For a given function, one developer might stop extracting at six lines, while another manages to reduce to four—the difference is irrelevant. However, it is relevant that this condensed function is highly understandable. That’s what it’s all about.

Here’s an example from a financial application I wrote to help with my budgeting. It retrieves transaction data from my bank account and saves them to a database:

  private async Task<AccountTransactionImportResult> TryImportTransactions(ImportAccount account)
  {
     var dateRange = DateRange = CalcLatestTransactionPeriod();
     var savedTx = await GetSavedTransactions(account, dateRange);
     var bankTx = await GetBankTransactions(account, dateRange);
     var newTx = CalcNewCompletedTransactions(savedTx, bankTx);
     if (newTx.Any())
        await SaveNewCompletedTransactions(newTx);
     var pendingTx = bankTx.PendingTransactions;
     await SavePendingBankTransactions(pendingTx);
     return CreateSuccessResult(account, dateRange, newTx, pendingTx);
  }

At nine lines long, it’s a bit of a ‘biggie’. Here we have an opportunity to try out ‘Extract Until You Drop‘. 

As we read through the listing, we realise that it describes a high-level workflow. Irrelevant detail has already been extracted and moved into well-named helpers. It’s straightforward to see what it does. 

On closer examination, we may realise we could extract the calculating and saving of newly completed transactions. Let’s create a new function for this code:

  private async Task<CompletedAccountTxList> CalcAndSaveNewCompletedTransactions(
     OrderedAccountTransactions savedTx,
     OrderedAccountTransactions bankTx)
  {
     var newTx = CalcNewCompletedTransactions(savedTx, bankTx);
     if (newTx.Any())
        await SaveNewCompletedTransactions(newTx);
     return newTx;
  }

If we also remove the pendingTx reference, we then get: 

  private async Task<AccountTransactionImportResult> TryImportTransactions(ImportAccount account)
  {
     var dateRange = DateRange = CalcLatestTransactionPeriod();
     var savedTx = await GetSavedTransactions(account, dateRange);
     var bankTx = await GetBankTransactions(account, dateRange);
     var newTx = await CalcAndSaveNewCompletedTransactions(savedTx, bankTx);
     await SavePendingBankTransactions(bankTx.PendingTransactions);
     return CreateSuccessResult(account, dateRange, newTx, bankTx.PendingTransactions);
  }

Whether this is an improvement is for you to decide. I think it’s a moderate improvement. After all, we have removed the calculation and saving of newly completed transactions that probably were not needed in a high-level function like TryImportTransactions(). We also reduced the number of lines to six—safely within the single-digit band. By removing the conditional if statement, we have made the high-level workflow linear and easier to follow. 

So overall, an improvement, I think.

We could have left TryImportTransactions() as it was. That would have been acceptable. However, we would have been hitting up against the upper limit of the single-digit-line rule.

What do you think? Did this extraction represent an improvement? 

Either way, having nine or six lines of code is a vast improvement over having 100 lines! I hope we can agree on that. 

Having used ‘Extract Until You Drop‘ for around a decade now, I am still surprised how using it allows me to find the right abstraction level every time. I cannot praise it more. Please consider extracting code from your functions until it no longer makes sense.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply