TDDC74 Programming: Abstraction and Modelling Programming Style (updated jan 2015)

advertisement
TDDC74 Programming: Abstraction and Modelling
Programming Style (updated jan 2015)
Rules, Conventions, & Guidelines
1
Introduction
In this course we will emphasize aspect of good programming style. This means both a) aspects
that are specific to Scheme, and b) good programming in general. We also have some course-rules
intended to help programmers learn some good habits (or help more experienced programmers
unlearn some poor ones).
Below are some brief guidelines to help you write readable and well-organized Scheme code. The
guidelines are divided into four sections: PRAM Rules, Coding Restrictions, Scheme Conventions,
and Style Guidelines.
2
THE IDEAL
It shouldn’t take a lot of effort (for a reasonably knowledgeable) reader to ”decode”
your code. The interpreter can handle valid code, however ugly or unwieldy. You don’t
write programs only for the interpreter. You write it for other programmers.
3
PRAM RULES
The following rules and restrictions apply for PRAM Lab Assignments and Quizzes:
• No procedures or variables named “X”1 . Many bugs “magically vanish” when coders
go through and replace too-short abbreviations with meaningful names. Not only that, but
single-letter names make code extremely difficult for other people to read.
WARNING! Students should expect that code may be returned, or quiz credits deducted, for
this even if their code is otherwise correct.
• Don’t overcomplicate things. We might remove points if it is clear that a student is trying
to immediately ”optimize” the code rather than first implementing a correct algorithm.
• Don’t write any Scheme code that relies on case distinctions. You can technically write code
where eg the symbols myVar and MyVar refer to different values. But you shouldn’t.
• Follow the ideal set out above.
4
CODING RESTRICTIONS
DrRacket supports several non-Racket languages, and there are several flavours of Racket (eg Typed
Racket, Lazy Racket). In general, students should use the standard Racket language in this course.
Write #lang racket.
Unless otherwise noted in writing on a lab assignment or a quiz, students may only use:
1
Unless it can be very well justified.
1
• Racket primitives allowed for that lab or quiz, plus all Scheme primitives introduced previous
to the current lab or quiz. Labs will contain lists of these allowed primitives.
• Any procedures the students build themselves from those primitives
5
SCHEME CONVENTIONS
In addition to specific PRAM rules, there are some Scheme conventions that students should follow:
Predicates (ie, “recognizers”) Programmers expect any Scheme procedure-name that ends in
a question mark to be a recognizer – that is, to return true (#t) or false (#f). Thus, number? is a
recognizer. Recognizers are also known more commonly as predicates.
Do not use question marks to end the names of procedures that return other kinds of values! Do
not create predicates that do other things when run (change data etc).
Thus, even though this is a perfectly useful procedure, it is poorly named:
(define (how-many-elements? lst)
<code>)
(how-many-elements? ’(a b c)) -> 3
A better name would be something like length or get-length.
Note that >, <, and = are also recognizers, but Scheme follows the historical convention and does
not add the question mark to them.2
Highlight Global Variables
Global variables are accessible to all procedures, which means one procedure can modify them in
ways that cause problems for other procedures. In order to remind ourselves to minimize the use
of global variables, and to be careful when we do use them, it is a common convention to include
an asterisk (“splat”) at each end of the name.
(define *pi* (/ 22 7))
Commands Racket commands – that is, procedures that change the values bound to names –
usually end with an exclamation-mark (ie, “bang”): set!, add-item-to-database!, etc. (Note
that define is a command, although its name does not end with a bang.)3
Note that the return value for a command is unspecified – and, in general, most the applications
of most Scheme commands do not display returned values. If you write a command of your own,
don’t write code that relies on its return value.
6
STYLE GUIDELINES
Here are some notes about good programming style. Unless otherwise indicated, we will not be
obsessive about the guidelines. But we will return labs for completion (or remove points on a quiz)
if we feel that code violates good style too much.
Short Procedures Short procedures are easier to read, write, test, debug, and understand –
especially when each procedure provides a single, well-defined operation. If procedures start to do
several things, break them up into separate procedures. A good rule of thumb is that a procedure
shouldn’t be longer than a screen full of code.
2
It would probably be more obvious if they had the following names, greater-than?, less-than?, equal-to?.
We will not introduce commands until SICP 3 – and unless otherwise noted, students may not use them until
then.
3
2
Good test: will someone need to spend time “deciphering” what a procedure is meant to do? No
one – not other programmers and not you, yourself, six months after you write the code – should
need to work hard to understand what a procedure is supposed to do.)
Page-width is 80 characters A related rule is that code should not be wider than 80 characters.
As a rule of thumb, hard-copy printouts of code should be well-formatted and easy to read on the
printed page. On the screen, expect that the person reading your code may do it on a small screen.
Indentation & Whitespace Within the Lisp/Scheme community there is a particular style associated with the use of parentheses, indentation and white-space; namely, to reflect the structure
of the code. In particular, please do not use the “hanging parenthesis style” (typical of Java) for
Scheme code in this course.
GOOD:
(define (foo x y)
(let ((z (+ x y 10)))
(* z z)))
BAD:
(define (foo x y) (let ((z (+ x y 10)))(* z z)))
(define (foo x
y)
(let ( (z (+ x y 10)
(* z z)
)
)
)
)
Abstract common patterns/code into new procedures It is often the case that certain
patterns appear in the process of developing different parts of an application. It improves the
overall readability, reliability, and, yes, elegance of a program to abstract these patterns, thereby
reducing the amount of code and increasing the visibility of the program’s structure.
No Variables/Procedures Named X Do we repeat ourselves? :-)
Use descriptive variable and function names. In most cases, it should be clear from a procedure’s
name what the procedure is meant to do – and each procedure should only do one well-defined
thing.
(define *pi* (/ 22 7))
(define *pi-approx* (/ 22 7))
Of course, a little common sense is important: it is not necessary (or usually a good idea) to create
extremely long names – so don’t create absurdly-long “self-documenting” names in order to avoid
commenting the code (gasp!).
(define *it-is-overkill-to-create-such-a-long-variable-name-for-pi* (/ 22 7))
(define *pi-approximation-correct-to-the-tenth-decimal-maybe* (/ 22 7))
Note that Scheme will allow you to use semi-colons for comments within programs.
When commenting code, you can assume that the person reading your code knows Scheme –
but does not know (or remember) what this exact code does. If the code is complex, it is much
more helpful to have information about what the code is supposed to accomplish, rather than a
detailed description of how the code works. Thus, comments should not be “text versions” of the
code/algorithm itself.
Example of clear, concise, helpful comment:
(define (gcd num1 num2)
;; This returns the greatest common divisor of 2 numbers
(...))
3
Example of long, unhelpful commenting:
(define (gcd num1 num2)
;; this program first determines whether num1 divided by num2 is
;; zero; if so, then it returns num1
;; if not, then it ...
;; blah blah blah
(...))
Use Helper Procedures Programmers should use helper-procedures to abstract away implementation details – and to create appropriate abstract interfaces to different levels of programs.
For example, if a program is going to be working with records of the form (GIVEN-NAME FAMILY-NAME
DATE-OF-BIRTH), it is very difficult to read and debug code if it all consists of long compositions
of car and cdr. Instead, create and make use of helper-procedures:
(define (get-given recrd)
(car recrd))
(define (get-family recrd)
(cadr recrd))
(define (get-dob recrd)
(caddr recrd))
Creating abstractions such as this takes no time at all – and dramatically reduces the number of
bugs while also increasing the speed of implementation.
Create Good Test-Cases A very important part of programming involves the development and
use of suitable test-cases. You will get some automatic tests for free for some labs. During development, test your code on more/other values than the illustrative examples included in the lab
document. Try to think of different kinds of corner cases that might be tricky, in order to convince
yourself that your implementation is correct.
Local Variables Are Also Your Friends Use local variables in preference to global variables
whenever possible. Don’t use global variables as a substitute for parameter passing. Your programs
should be as modular as possible.
In keeping with old style, use let for local names, except in the cases of local procedure or, possibly,
port definitions.
Don’t overuse local variables, but note that they are sometimes useful for the sake of readability,
or to save computations (instead of running a calculation twice, save the result temporarily).
Use Appropriate Paradigmand don’t over-use assignment Use functional and imperative
techniques as appropriate. In this course when we want procedures to “remember” certain values
between different procedure-calls, we will often use assignment. Otherwise, the general philosophy of
the course is to keep referential transparency by using basic recursion. Choose wisely. An example
of inappropriate: using assignment and looping to update the value of a variable-binding when the
same result can be achieved with recursion, unless asked to.
Similarly, it is often a sign that there are problems if all of a student’s code starts to look “blocklike:”
(define (some-assignment-based-procedure foo)
(set! foo some-val)
(set! foo (+ foo some-val))
(set! bar something-else)
(set! bar (+ foo bar))
(do-something-with foo bar))
It is ok to use let if it makes the code more readable.4
4
The example block above can, however, easily be described as (define (some-assignment-based-procedure
foo) (do-something-with (* 2 some-val) (+ some-val something-else)))
4
Download