Tutorial on Good Lisp Programming style. (1993)

Peter Norvig.

What is good style?

Good programming style

Elegance is not optional

Good style in any language leads to programs that are:

It also helps correctness, robustness, compability. Our maxims of good style are:

What to believe

Don’t believe everything we tell you. Worry less about what to believe and more about why. Know where your “style rules” come from:

It’s all about communication

Expression + Undestanding = Commincation

Programs communicate with:

Know the context

When reading code

Know who wrote it and when.

When writing code

Annotate it with comments, sign and date your comments.

Some things to notice:

Value systems are not absolute

Style rules cannot be viewed in isolation. They often overlap in conflicting ways.

The fact that style rules conflict with one another reflect the natural fact that real-world goals conflict. A good programmer makes trade-offs in programming style that reflect underlying priority choices among various major goals:

Why good style is good

Good style helps build the current program, and the next one:

Style is not just added at the end. it plays a part in:

Why style is practical: memory

Good style replaces the need for great memory:

Why style is practical: reuse

Structured programming encourages modules that meet specifications and can be reused within the bounds of that specification.

Stratified design encourages modules with commonly-needed functionality, which can be reused even when teh specificationi changes, or in another program.

You should aim to reuse:

Say what you mean

Say what you mean in data (be sepcific, concise):

Say what you mean in code (be concise, conventional):

In annotations (be explicit, helpful):

Be explicit

Optional and keyword arguments

If you have to loop up the default value, you need to supply it. You should only take teh default if you truly believe you don’t care or if you’re sure the default is well-understood and well-accepted by all.


If you know type information, declare it. Don’t do what some people do and only declare things you know the compiler will use. Compilers change, and you want your program to naturally take advantage of those changes without the need for ongoing intervention.

Also, declarations are for communication with human readers too.


If you’re thinking of something useful that others might want to know when they read your code and that might not be instantly apparent to them, make it a comment.

Be specific

Be as specific as your data abstractions warrant, but no more.

Be concise

Thest for the simplest case. If you make the same test in two places, there must be an easier way.

Be helpful

Documetnation should be organized around tasks the user needs to do, not around what your program happens to provied.

Be conventional

Build your own functionality to parallel existing features. Obey naming conventions. Use built-in functionality when possible:

Be consistent

Be consisten about which operators you use in neutral cases so that it is apparent when you’re doing something unusual

Choose the right language

Choose the appropriate language, and use appropriate features int he language you choose.

Dynamic is good for:

Dynamic is bad for:

Understanding conditions vs errors

Learn the difference between errors and conditions. All errors are conditions; not all conditions are errors.

Distinguis three concepts:

Error detection

Pick a level of error detection and handling that matches your intent. Usually you don’t want to let bad data go by, but in many cases you also don’t want to be in the debugger for inconsequential reasons.

Strike a balance between tolerance and pickiness that is appropiate to your apllication.

Write good error messages


All programming languages allow the programmer to define abstractions. All modern languages provide support for:

Lisp and other languages with closures support:

Also Lisp is unique in the degree which it supports:

Design: where style begins


Desgin: decomposition

We will cover the following kinds of abstraction:

Data abstraction

Write code in terms of the problem’s data types, not the types that happen to be in the implementation.

Functional abstraction

Every function should have:

Control abstraction

Most algorithms can be characterized as:

These functions abstract common control patterns. Code that use them is:

Avoid complicated lambda expressions

When a higher-order function would need a complicated lambda expression, consider alternatives:

Syntactic abstraction

Syntactic abstraction defines a new language that is appropiate to the problem.

This is a problem-oriented (as opposed to code-oriented) approach.

An interpreter for simplifying

Write an interpreter o a compiler to simplify your language.

Use macros appropriately

The design of macros:

Things to think about:

Macros for control structures

Good to use if it fills a hole in orthogonality of your lang.

Programming in the large

Be aware of stages of software development:

These can overlap. The point of exploratory programming is to minimize component design time, getting quickly to implmentation in order to decide if the architecture and requirements are right.

Know how to put together a large program:

Using comments effectively

Use comments to/for:

Documentation: say what you mean


Make your program run well in the environment(s) you use.

But be aware that you or someone else may want to use it in another environment someday.

Mean what you say


Expect the unexpected

Read other people’s code