This appendix describes a number of coding conventions that we expect you to follow in 15100. Note that failure to follow these guidelines can result in point deductions on programming assignments!
The main purpose of these guidelines is to improve the readability and consistency of your code. Code is read much more often than it is written and inconsistency in style, bad indentation practice, and excessively long lines all make it harder to read.
Note
|
In somce cases, this guide describes features that have not yet been introduced for use in the class (e.g., using iteration and lists instead of enumeration). As a general rule, you can ignore parts of the style guide that rely on language features that are not described in the posted version of these notes. |
Function headers
Each named function definition should have a signature and purpose comment preceeding the function definition. We expect these to have the following format:
(function : type)
;; a comment describing the purpose of the function
;; and any assumptions about its input that are not
;; reflected in its type
(define (function ...)
...)
Note that the purpose comment comes after the signature (not before).
Commenting
Comments can be classified into what comments and how comments. The former provides a description of what a variable is used for and what a function does, whereas the latter provides a description of how something is computed.
Purpose comments are an example of "how" comments, as are comments that describe types.
Variable names
A good rule of thumb when choosing a variable or function name is that the length of the name should be propotional to the span of its use. In other words, if the variable is used over most of the program, then its name should be descriptive of its use, whereas a variable that is local to a small function can be short.
You should avoid using names that are the same as commonly used library
functions. In particular, you should never use the names "list
" or
"vector
" for variables.
Indentation and line lengths
Racket code should be indented to show the syntactic structure of the
code. Fortunately, DrRacket’s auto-indent support makes it easy to
keep your code properly indented. You can also get the editor to
re-indent your code by either selected a block of code with the mouse
and hitting the <tab>
key or by using the Racket>Reindent
or
Racket>Reindent All
menu options.
You should limit the length of any line of code (or comment) to no more than 90 characters (some would argue that 80 is a better target for historical reasons, but we relax that limit a bit). DrRacket has a preference option (under the "Editing/General Editing" tab) for displaying a guide at a given width.
Remember that line lengths matter for comments too!
Where you choose to put line breaks will have an impact on the amount of indentation and length of your lines. In general, you should try to place your line breaks to avoid excessive indentation. For example, put a line break immediately following the parameters of a function definition. For example, the layout
(define (f x)
(if (= x 1)
(+ 1 x)
(- x 1)))
instead of
(define (f x) (if (= x 1)
(+ 1 x)
(- x 1)))
When indenting conditionals put a break immeditately after the cond
; i.e.,
(cond
[(= x 1) (+ 1 x)]
[else (- x 1)])
instead of
(cond [(= x 1) (+ 1 x)]
[else (- x 1)])
Likewise for if
expressions, the layout
(if (= x 1)
(+ 1 x)
(- x 1))
is prefered over
(if (= x 1) (+ 1 x)
(- x 1))
or
(if
(= x 1)
(+ 1 x)
(- x 1))
Advanced language features
Typed Racket is a large language that inherits many language mechanisms from Racket. In this course, we use a fairly constrained subset of these features so that we can focus on computational patterns, rather than language mechanisms. You should restrict your programs for this class to those features that are presented in lecture or lab, described in this document, or described in an assignment.
Likewise, we will try to introduce all of the library functions that you might reasonably need for this course, but if you want to use a function that we have not introduced, please ask about it on Piazza.
Common programming mistakes
There are a number of common errors that we see students make in their code. We describe these in this section, so that you can avoid them.
Equality testing
Typed Racket provides several polymorphic equality functions (equal?
, eqv?
, and
eq?
). In general, you should avoid using these and instead use the equality test
that is specific to the type of values you are comparing; e.g., use "=
" for
testing equality of numbers, "string=?
" for comparing strings,
"symbol=?
" for comparing symbols, etc..
If you have to use a polymorphic equality function, then use equal?
, which implements
structural equality on most types.
Comparing booleans
Avoid comparing booleans with #t
and #f
, i.e., instead of
(equal? exp #t) ;; WRONG
just write the expression by itself
exp
and instead of
(equal? exp #f) ;; WRONG
write
(not exp)
Another common pattern to be avoided is boolean constants in the arms of conditionals. For example, do not write
(if exp #t #f) ;; WRONG
instead write
exp
Likewise, using boolean constants as arguments to and
and or
should
be avoided.
Proper use of type membership tests
Type membership tests should be used to descriminate on union types, but should not be used as a substitute for value testing. For example, instead of using a type test
(integer? (/ a b)) ;; WRONG
write
(= (remainder a b) 0)
Use iteration instead of static enumeration
Avoid large conditionals that enumerate many cases when possible. Instead, define tables (lists) and use iteration to do the case analysis. For example, instead of writing
(define (int->dir dir)
(cond
[(= dir 0) 'North]
[(= dir 1) 'NorthEast]
[(= dir 2) 'East]
[(= dir 3) 'SouthEast]
[(= dir 4) 'South]
[(= dir 5) 'SouthWest]
[(= dir 6) 'West]
[else 'NorthWest]))
define a list of symbols the mapping:
(define dir-map
(list 'North 'NorthEast 'East 'SouthEast 'South 'SouthWest 'West 'NorthWest))
and use the list-ref
function to implement the mapping
(list-ref dir-map dir)
Local definitions
Although, Racket does allow including a define
in certain contexts where an
expression is required, we use the local
declaration form to clearly define
the scope of the definitions.
For example, instead of
(: f : Integer -> Integer)
(define (f x)
(define y : Integer (* x x)) ;; WRONG
(- y x))
write
(: f : Integer -> Integer)
(define (f x)
(local
{(define y : Integer (* x x))}
(- y x)))
Efficiency issues
Use local definitions to avoid expensive redundant computations. In particular, you should never have redundant calls to recursive functions.
Be careful in your use of append
to build lists. For example, instead of using
append
to repeatedly add elements to the end of a list, build the result in reverse
order using cons
and then reverse the result. It is the difference between an
$O(n)$ and latexmath[$O(n^2)$] running time.
Do not use length
to test for empty or singleton lists. For example, instead of
(if (= (length xs) 0) ...)
write
(if (empty? xs) ...)
or use pattern matching.