Break Down Complex Expressions for More Debuggable Code

Combining multiple operations into a single line of code may be an effective way to play "code golf", but it's no way to develop maintainable software.

Break Down Complex Expressions for More Debuggable Code

As a young developer, I prided myself on minimizing the number of lines of code I wrote to solve a problem.

This can be a good practice if you are doing it in the pursuit of simple solutions. For me, though, it was little more than code golf, where minimizing code was an end in itself. To achieve minimum lines of code, I would often combine multiple operations into a single line of code. This approach made my code needlessly difficult to read, debug, and refactor.

Breaking expressions into discrete steps stored in meaningful variables is a far better approach.

Readability is Greatly Enhanced

Nested expressions force readers to mentally parse lengthy logic with each read. This increases cognitive load versus explicit step-by-step logic.

For example, instead of this...

Result = Format(Date, "mm/dd/yyyy") & "-" & Format(DateAdd("h", 14, Date), "hh:mm tt") & " - " & Format(DateAdd("h", 16, Date), "hh:mm tt")

...break it down into separate expressions like this...

CurrentDate = Format(Date, "mm/dd/yyyy")
StartTime = Format(DateAdd("h", 14, Date), "hh:mm tt")   
EndTime = Format(DateAdd("h", 16, Date), "hh:mm tt")
Result = CurrentDate & "-" & StartTime & " - " & EndTime

Self-Documenting Code

One of the key benefits of breaking down complex expressions is that it helps produce code that is essentially self-documenting through its structure and naming.

Code that reads like prose is inherently easier to understand versus terse expressions.

Well-named variables used for each step describe the purpose and intention in a human-readable way. At a glance, the functionality of a routine can be inferred. For example, variables like CurrentDate, StartTime, and EndTime clearly convey what values they represent without comments.

Comments explaining "what" the code does become less crucial since the "what" is evidenced by each descriptive statement. Comments are better spent conveying "why" strategic decisions were made.

Maintenance is Simplified

Isolating logic blocks in discrete steps enhances flexibility.

If part of a calculation needs updating, like changing a time offset, it's easy to locate. Additional operations can also be inserted seamlessly between logical blocks without reworking the overall logic flow. Tightly coupled expressions require more work to modify a single part without breaking the overall logic.

Discrete steps allow shifting and enhancing logic as needs change over time.

Debugging is Streamlined

With expressions broken into steps, the Locals window supports methodical validation of each part in isolation.

For example:

  1. Execute to CurrentDate and inspect its value
  2. Step to StartTime and check its value
  3. Continue stepping to find which calculation failed

This process of elimination quickly reveals root causes versus nested expressions that are more difficult to step through. Adding temporary debugging code, such as Debug.Print statements is easier, too.

Overall, troubleshooting becomes a more structured process.

Avoiding Implicit Type Coercion Issues

One subtle bug risk in VBA comes from its loosely typed nature which allows implicit type coercion behind the scenes.

Operations on variables of different types may coerce them in unexpected ways. For example, concatenating a String and Integer with & interprets the Integer as a String implicitly. But this could cause unexpected behavior if types are mixed incorrectly.

Breaking expressions into individual steps allows us to assign values to strongly-typed variables. This structuring enforces compile-time validation of types between each statement. If the compiler flags a type mismatch, we are alerted immediately to resolve the incompatibility rather than suffer subtle runtime surprises.

In other words, this technique helps turn runtime errors into compile errors. And, as longtime readers are likely tired of hearing by now, compile errors are better than runtime errors.

Powerful Error Logging with vbWatchdog

When combined with the variable inspection powers of vbWatchdog, breaking logic into steps unlocks automatic error logging of all variable values.

A well-constructed vbWatchdog global error handler can log every variable--at every level of the call stack--showing exactly where things went wrong.

With expressions, only the final value would appear. Discrete steps provide full context to pinpoint issues, saving debugging time versus surgical inspection of code. This is a huge productivity booster over manual error handling.

Conclusion

In conclusion, breaking expressions into simpler, stepwise logic through descriptive variables offers tangible long-term benefits.

It significantly enhances code readability, maintainability as needs change, and debuggability through systematic validation of each chunk of logic. The integration with automatic error logging further amplifies these advantages. While increasing line count, the other benefits far outweigh this minor downside.

Overall, this approach leads to more robust code that is easier to read, easier to debug, and easier to sustain as an application evolves.


Acknowledgements

All original code samples by Mike Wolfe are licensed under CC BY 4.0