Herein is an attempted distillation of my approach to Common Lisp programming, including the aspects of general programming style, how and when to use certain aspects of the language, and whatnot; this document is surely marked by exposure to other Common Lisp programmers and how they've disagreed. I use one hundred columns for my files because I've found one hundred to be a pleasant number, clearly affected by use of base ten. I advise against blindly following limits imposed, such as that eighty column limit; many who use this will likely be unable to tell you why and delegate to authorities; I believe this is due to dancing around tribal fires rather than giving critical thought; I suggest to not follow any rule or limit unless one gives it thought first, especially if it impedes activity.
Note that all examples are in uppercase, because this helps to disambiguate and emphasize. I always use uppercase when discussing Common Lisp for these reasons and also, of course, because the symbols are actually uppercase and this is merely usually hidden.
Common Lisp is a well-designed language, and so it's advisable to always mimick it in one's designs, until that point one is experienced enough to intelligently deviate.
An important aspect of Common Lisp is how it enforces few true restrictions on the programmer, and I vehemently disagree with imposing some. Common Lisp program organization isn't at all tied to files and shouldn't be. My libraries or programs use a single file as this is most convenient for me; any extra files should be used for external documentation and other such things. In what viewing of any others' programs I do, I too often see dozens of useless files and the actual program relegated to a subdirectory, which I find ridiculous. I also occasionally see package names which mimick hierarchy in the notations of other languages, and I find pretending to have this is in poor form.
A DEFPACKAGE or IN-PACKAGE beginning a file to LOAD shouldn't assume the CL package is accessible in *PACKAGE* and so I begin libraries taking this into account. Fretting over the value of *READTABLE* is unreasonable, however, as it controls a more intimate aspect. Avoid creating unnecessary symbols in such code; uninterned symbols should be preferred to strings, where either may be used to denote, because the symbols will be read and modified appropriately, unlike strings. Follows is an example:
(CL:DEFPACKAGE #:VERISIMILITUDE ...) (CL:IN-PACKAGE #:VERISIMILITUDE)
Common Lisp enforces no naming convention, but surrounding a special variable name with asterisks is common and the language itself does so, although I don't as I dislike the way it looks. Surrounding a constant name with plus signs is recommended, yet isn't done anywhere in the language, and I don't do this either, as I similarly dislike the way it looks. I also dislike the convention of making an internal symbol ugly by using percent signs; the double colon syntax is ugly enough and it's clear a symbol which isn't exported is an internal detail which shouldn't be used. A name should be written in lowercase, using complete words, and using hyphens in place of spaces. Predicate names end using a P or -P, based on whether the name is a single word or multiple and also indicates a predicate; if a multi-word predicate name ends in P, that implies it's not a predicate, but a variation on one.
In the particulars, of formatting, it's preferable to reduce nesting, indentation, and to enable the most to be read without needing to acknowledge nesting. For the functions which permit it, I always place the simpler expressions first. Indentation should use two spaces or agree with a parameter in place above it; tabs shouldn't be used to indent Common Lisp. The former is superior to the latter:
(+ 1 (- 2 3) (* 4 5 6)) (+ (* 4 5 6) 1 (- 2 3))
An IF should be formatted in the following way, with all inferior forms indented identically:
(IF (...) (...) (...))
I don't use WHEN or UNLESS, preferring IF, COND, OR, and AND. The latter two can seem infeasible to use in some situations, but all Common Lisp expressions can be thought of as returning values, and a careful use of such can simplify control flow and reduce hierarchy:
(AND (VERISIMILITUDEP ...) ;This predicate is, of course, purely pedagogical. (> ...) (PRINC "This appears to be true.") ;This works because PRINC only returns NIL when showing NIL. (RETURN ...))
The value of COND lies primarily in reducing hierarchy and its implicit PROGN. The situations which encourage use of IF or COND are many and I sometimes experiment to see which is most pleasant in the particular situation. Using IF is simpler and nesting this isn't by itself poor, even if PROGN must be used. A COND with a single case may be preferable to an IF, by reducing nesting and indentation. Follows is the most usual and proper way to format a COND:
(COND ((...) ...) ((...) ...) ...)
Where feasible, avoid using symbols not already interned into the system for variable names and like purposes; the COMMON-LISP package has nine hundred and seventy-eight symbols, and a new program adds more, so use them. Clarity is a prime concern and so always following this isn't feasible, but many symbols, such as FIRST, SECOND, BLOCK, and those symbols interned in new packages are usually rather fit for the purpose. The former is superior to the latter:
(LET ((COUNT 0)) (DO-EXTERNAL-SYMBOLS (SYMBOL '#:CL COUNT) (DECLARE (IGNORABLE SYMBOL)) (INCF COUNT))) (LET ((SYMBOLS 0)) (DO-EXTERNAL-SYMBOLS (THIS 'CL SYMBOLS) (DECLARE (IGNORABLE THIS)) (INCF SYMBOLS)))
A macro is the best place to optimize its expansion, knowing the most of it, and so should always be optimized. This is simple to do, such as by using a LET instead of repeating a known segment, or by removing code which the macro can know is unnecessary. This is far better than leaving it to a more general compiler to attempt to discover. Delegation is fine in some cases, such as assuming a macro will optimize its own expansion similarly, or not bothering to optimize usage of a special operator, as optimizing those truly is the place of the compiler.
When needing to interface with the environment in a particular way, such as to adhere to a standard, it may be unreasonable to make assumptions about the character set in use or not. Using a stream of (UNSIGNED-BYTE 8) isn't guaranteed to behave a particular way by the standard but is one of the only streams of a numerical type that can be used consistently between implementations with a predictable result and so using it in many cases is what I recommend; this is particularly arduous at times, and so an alternate approach is to design the program to operate on, say, strings which will necessarily already have been loaded into the environment and have the program work on these equivalent strings.
I write Common Lisp to get the advantages of the language, several of which are negated when another language is stitched into the environment, and so I advocate against ``Foreign Function Interfaces'' for Common Lisp programs.
Avoid using the declarations TYPE and FTYPE regarding exported function parameter types as behaviour on invalidation is undefined; using FTYPE for the result is fine, however; prefer to use CHECK-TYPE. In general, declarations are most useful when the relevant form is entirely under the control of the programmer and no one else, regarding those such as DYNAMIC-EXTENT and IGNORE.
I prefer to write Common Lisp that will perform well even in an implementation which doesn't perform common optimizations such as recognizing equivalent expressions scattered about, and I do so through use of LET. This is occasionally arduous enough to not bother with. Another easy optimization uses #. to ensure a form is evaluated once and replaced with its value, with the prime disadvantage being that *READ-EVAL* must be T.
Ease of reusing forms across many different usages is through #N# and #N= and I use these heavily.
I don't use libraries others have written, in part because I prefer solitary working and in part due to growing to enjoy being the sole author of my work. When I DEFPACKAGE, I :USE except in the cases where conflicts would arise, and I need no concern about package changes because I control them; any particular Common Lisp package is unlikely to change heavily after a time, anyway. I write software released under copyleft licenses, because I don't want my work to be used to abuse others; those who advocate for using libraries tend to simultaneously shy away from and want to replace those that are copyleft. In any case, more libraries is a good thing, I think, and it's easy to generalize or take a component of a larger program and separate it so, which also encourages making such comprehensive.
Also with regards to libraries, I find it in poor form to use a minor library where unnecessary. In trying to use others' work and finding a ``convenience function'', that's an inconvenience. LOOP is preferable to ITERATE, purely because LOOP is standard. Define your minor functions yourself.
Regarding comments, I prefer the semicolon and I don't follow the convention of using multiples when commenting on different aspects of the program, using only one semicolon. Comments are preferably a complete sentence without ``TODO'' or other nonsense in them. Comments should always be written for the person reading the program and so indicate those qualities. Comments intended for a user should be written as documentation strings, also as complete sentences, and give more abstract information. Comments and documentation strings don't substitute for separate documentation which can provide the history of the program, its greater purposes, authorial intent, and other such things in a nice way. It's preferable to avoid giving comments their own line, instead using the remainder of a code line:
(...) ;This comment uses the remainder of the line and is better than the other. ;This comment wastes a line by not using the remainder of the following line and is worse. (...)
It's preferable to use the most specific form available, unless using a more general form enables an organizational improvement. The former is superior to the latter in most cases:
(SETF NUMBER 0 INTEGER (1+ NUMBER) STRING (PRINC-TO-STRING INTEGER)) (SETF NUMBER 0) (INCF INTEGER) (SETF STRING (PRINC-TO-STRING INTEGER))
The LAMBDA list and variations have keywords which should be used appropriately; required parameters are simplest, most common, and efficient; &OPTIONAL and &REST parameters follow; &KEY parameters are useful for documental purposes, having names, and for enabling future expansion, but are also likely the least efficient, and so I don't recommend preferring &KEY parameters over the others. If a LET* or LET would be used near the beginning of a definition, attempt to use &AUX; this won't be feasible in some cases, but needing LET over LET*, needing to avoid a binding over the entire definition, and needing to test values before the binding can be relatively rare enough to enable heavy use of &AUX. When writing a macro, it's best to use &BODY appropriately, as this communicates intent and whatnot.
A LOOP form should use KEYWORDs to avoid unnecessary symbol interning and be formatted as so:
(LOOP :FOR INTEGER :FROM FIRST :REPEAT COUNT :COLLECT INTEGER)
A macro should strive to have an expansion which is pleasant to a human reader and also stress using pure standard Common Lisp where possible. The expansions of LOOP and FORMATTER are typically worst, in this respect; I'm disgusted to look upon a macroexpansion that may as well contain the following: