Skip to main content
Lisp, despite being a powerful and flexible language, has several common anti-patterns that can lead to code that’s difficult to maintain, inefficient, or doesn’t follow the language’s idioms. Here are the most important anti-patterns to avoid when writing Lisp code.
Avoid excessive use of global variables (special variables in Common Lisp, denoted with asterisks). While they can be useful for truly global state, overusing them creates hidden dependencies, makes testing difficult, and can lead to unexpected behavior. Instead, pass dependencies explicitly through function parameters or use lexical closures.
Don’t ignore errors with ignore-errors unless you have a good reason. Instead, use handler-case to catch specific conditions and handle them appropriately. For more complex error recovery, use Common Lisp’s powerful condition system with restart-case to provide multiple recovery strategies.
Avoid reinventing functions that are already part of the language or standard libraries. Lisp has a rich set of built-in functions for list processing, sequence operations, and more. Using them makes your code more readable and often more efficient.
Be cautious with recursive functions that aren’t tail-recursive, as they can lead to stack overflow with large inputs. Use tail recursion (where the recursive call is the last operation) or iteration for operations that might involve large numbers of steps.
Avoid using eval for runtime code execution. eval is powerful but dangerous, as it can execute arbitrary code and makes your program harder to analyze and optimize. Instead, use funcall or apply to call functions dynamically.
Make effective use of the Common Lisp Object System (CLOS) for object-oriented programming. CLOS provides powerful features like multiple inheritance, multiple dispatch, and method combinations that can make your code more modular and expressive.
Use macros judiciously. While Lisp’s macro system is powerful, overusing macros can make code harder to understand and debug. Use functions when they suffice, and only use macros when you need to control evaluation or create new syntax. When writing macros, be careful to avoid variable capture by using gensym or with-gensyms.
Use higher-level loop abstractions like map, reduce, remove-if, or the loop macro instead of writing low-level iteration code. These abstractions make your code more declarative and often more concise.
Minimize side effects in your functions. Functions with side effects are harder to test, reason about, and compose. Try to write pure functions that take inputs and return outputs without modifying global state.
Organize your code into packages based on functionality. This improves maintainability, reduces naming conflicts, and allows for better encapsulation of implementation details.
Use appropriate data structures and operations for your needs. Lists in Lisp are efficient for operations at the beginning (cons, car, cdr) but inefficient for random access or operations at the end. Consider using vectors, hash tables, or other data structures when appropriate.
Use the format function for formatted output instead of concatenating strings or using multiple print statements. format provides a powerful mini-language for controlling output formatting.
Identify and compute loop invariants (values that don’t change during the loop) outside the loop. This avoids unnecessary recomputation and makes your code more efficient.
Use destructuring to extract values from complex data structures. Lisp provides powerful destructuring capabilities through destructuring-bind, multiple-value-bind, and pattern matching in macros like loop and iterate.
Include documentation strings for your functions, classes, and packages. Documentation strings provide valuable information to users of your code and can be accessed programmatically through functions like documentation.
Write tests for your Lisp code. Testing helps ensure your code works correctly and continues to work as you make changes. Use testing frameworks like FiveAM, Prove, or RT to structure and run your tests.
Follow Lisp naming conventions. Use kebab-case (words separated by hyphens) for function and variable names. Use *earmuffs* for special variables and +plus-signs+ for constants. Consistent naming makes your code more readable and idiomatic.