cs115 Style Guide
cs115 Style Guide
Spring 2022
Last Updated: 7 April 2022
1 Introduction
The code you submit for assignments, as with all code you write, can be made more
readable and useful by paying attention to style. This includes the placement of com-
ments, choice of variable and function names, whitespace and indentation. None of
these things affect the execution of a program, but they affect its readability and
extensibility. As in writing English prose, the goal is communication, and you must
keep your reader in mind. This is especially important when the reader is assigning
you a grade.
The examples shown in lessons are often condensed to fit them into a few lines; you
should not imitate their condensed style for assignments, because you do not have
the same space restrictions.
DrRacket(.rkt) files can store rich content that can include images, extended for-
matting, comment boxes, and special symbols. Using this rich content may make
your assignment unmarkable. Unfortunately, some of the content in the Interac-
tions window may be “rich” (e.g., rational numbers), and so you should avoid copy-
and-pasting from your interactions window into your definitions window. In ad-
dition, code that appears in a .pdf document (e.g., presentation slides and assign-
ments) may contain hidden or unusual symbols, so you should not copy-and-paste
from those sources either.
1
For your assignments, save in plain text format. In DrRacket you can ensure your
.rkt file is saved using plain text format by using the menu items: File > Save
Other > Save Definitions as Text.
2 Assignment Formatting
2.1 Header
Your file should start with a header to identify yourself, the term, the assignment
and the question. There is no specifically required format, but it should be clear and
assist the reader. The following is a good example in Racket.
;;
;; ***************************************************
;; Juliette Andromeda Mao ( 12345678 )
;; CS 115 Spring 2022
;; Assignment 03, Question 4
;; ***************************************************
;;
2.2 Comments
Comments should be used for documentation purposes and to explain why code does
what it does.
2
Use in-line comments sparingly. If you are using standard design recipes and tem-
plates, and following the rest of the guidelines here, you should not need many ad-
ditional comments. Any such comment can either be put on its own line, or tacked
onto the end of a line of code, providing it fits.
2.3 Identifiers
Identifiers (names) should be meaningful, but not awkwardly long nor cryptically
short. The first priority should be to choose a meaningful name. Names like salary
or remittance would be appropriate in a program that calculates taxes.
Sometimes a function will consume values that don’t have a meaning attached, for
example, a function that calculates the maximum of two numbers. In that case, chose
names that reflect the structure of the data. That is, n is for numbers, i for integers,
lst for a list or lon for a list of numbers, and so on. Names that are proper nouns
like Newton should always be capitalized. Otherwise, use the Racket convention:
2.3.2 Constants
• To improve flexibility and allow easier updating of special values. If the value
of hst changes, it is much easier to make one change to the definition of the
constant than to search through an entire program for the value 0.13. When
3
this value is found, it may not be obvious if it refers to the tax rate, or whether
it serves a different purpose and should not be changed.
• To define values for testing and examples. As values used in testing and ex-
amples become more complicated (e.g., lists, structures, lists of structures), it
can be very helpful to define named constants to be used in multiple tests and
examples.
Whitespace should be used to make it easier to read code. This means not having too
much or too little whitespace in your code. In what follows, we give an example of
what our whitespacing will look like in our model solutions though you can deviate
from this slightly as long as the code is still readable.
In DrRacket, if you have many functions and/or large function blocks, you may want
to insert a row of symbols (such as the *’s used in the file header above) to separate
your functions.
If the question asks you to write more than one function, the file should contain them
in the order specified by the assignment. Helper functions are typically placed above
the assignment function(s) they are helping. Remember that the goal is to make it
easier for the reader to determine where your functions are located.
4
a few spaces. However, DrRacket will never break up a line for you, and you can over-
ride its indentation simply by putting in more spaces or erasing them. DrRacket also
provides a menu item for reindenting a selected block of code (Racket > Reindent)
and even a keyboard shortcut reindenting an entire file (Ctrl+I in Windows; Cmd-I
on a Mac).
When to start a new line (hit enter or return) is a matter of judgment. Try not to
let your lines get longer than about 70 characters, and definitely no longer than 80
characters. You do not want your code to look too horizontal nor too vertical.
In DrRacket there is a setting you can change to show you when you’ve exceeded
the common style guide’s recommended line length:
Edit -> Preferences -> Editing tab -> General Editing sub-tab
-> Maximum character width guide.
Occasionally, your examples and tests may exceed the line length guidelines when
there is no obvious place to break the line. You should still strive to keep the lines
within the suggested limits though.
Style marks may be deducted if you exceed 80 characters on any line of your
assignment.
Indentation examples:
;; GOOD
( define ( distance posn1 posn2 )
(sqrt (+ (sqr (- ( posn-x posn2 ) ( posn-x posn1 )))
(sqr (- ( posn-y posn2 ) ( posn-y posn1 ))))))
;; GOOD
( define ( distance posn1 posn2 )
(sqrt (+ (sqr (- ( posn-x posn2 )
( posn-x posn1 )))
(sqr (- ( posn-y posn2 )
( posn-y posn1 ))))))
;; BAD (too horizontal )
( define ( distance posn1 posn2 )
(sqrt (+ (sqr (- ( posn-x posn2 ) ( posn-x posn1 ))) (sqr (- ( posn-y posn2 ) ( posn-y po
;; BAD (too vertical )
( define ( distance posn1 posn2 )
(sqrt
(+
(sqr
(-
5
( posn-x posn2 )
( posn-x posn1 )))
(sqr
(-
( posn-y posn2 )
( posn-y posn1 ))))))
For conditional expressions, placing the keyword cond on a line by itself, and align-
ing not only the questions but the answers as well can improve readability (pro-
vided that they are short). However, this recommendation is not strictly required.
Each question must appear on a separate line, and long questions/answers should be
placed on separate lines. Marks may be deducted if a cond is too dense or confusing.
cond examples:
;; Looks good for short cond questions and answers
(cond
[(< bar 0) ( neg-foo bar )]
[else (foo bar )])
;; Acceptable
(cond [(< bar 0) ( neg-foo n)]
[else (foo n)])
;; BAD: ( place each question on a separate line)
(cond [(< bar 0) ( neg-foo n)] [else (foo n)])
6
2.5 Summary
• Use two comment symbols for full-line comments and use one comment sym-
bol for in-line comments, and use them sparingly inside the body of functions.
• Provide a file header for your assignments.
• Make it clear where function blocks begin and end
• Order your functions appropriately.
• Avoid overly horizontal or vertical code layout.
• Use reasonable line lengths.
• Choose meaningful identifier names and follow our naming conventions.
• Avoid use of “magic numbers”.
Style marks may be deducted if you have poor headers, identifier names,
whitespace, indentation or layout.
We hope you will use the design recipe as part of the process of working out your so-
lutions to assignment questions. If you hand in only code, even if it works perfectly,
you will earn only a fraction of the marks available. Elements of the design recipe
help us to understand your code. But the big reason to use the design recipe is that
the process will help you write working code more quickly.
Not everything in this section will make sense on first reading, and some of the fol-
lowing details will only appear at the end of the course. We suggest that you review
it before each assignment.
7
3.1 Design Recipe Sample
In your final version, the content including and after ;<--- should be removed and
is included here to highlight the different components of the design recipe.
;; ( sum-of-squares p1 p2) produces the sum of ; <--- Purpose
;; the squares of p1 and p2
;; sum-of-squares : Num Num -> Num ; <--- Contract
;; Examples : ; <--- Examples
( check-expect ( sum-of-squares 3 4) 25)
( check-expect ( sum-of-squares 0 2.5) 6.25)
( define ( sum-of-squares p1 p2) ; <--- Function Header
(+ (* p1 p1) ; <--- Function Body
(* p2 p2 )))
;; Tests : ; <--- Tests
( check-expect ( sum-of-squares 0 0) 0)
( check-expect ( sum-of-squares -2 7) 53)
3.2 Purpose
The purpose statement has two parts: an illustration of how the function is applied,
and a brief description of what the function does. The description does not have to
explain how the computation is done; the code itself addresses that question.
• The purpose starts with an example of how the function is applied, which uses
the same parameter names used in the function header.
• Do not write the word “purpose”.
• The description must include the names of the parameters in the purpose to
make it clear what they mean and how they relate to what the function does
(choosing meaningful parameter names helps also). Do not include parame-
ter types and requirements in your purpose statement — the contract already
contains that information.
• If the description requires more than one line, “indent” the next line of the
purpose 2 or 3 spaces.
8
• If you find the purpose of one of your helper functions is too long or too com-
plicated, you might want to reconsider your approach by using a different
helper function or perhaps using more than one helper function.
• It’s often helpful to use the word ‘produces’ to highlight what the function
creates as an output.
3.3 Contract
The contract contains the name of the function, the types of the arguments it con-
sumes, and the type of the value it produces. The contract is analogous to functions
defined mathematically that map from a domain to a co-domain (or more loosely to
the range of the function).
3.4 Examples
;; Examples :
( check-expect ( sum-of-squares 3 4) 25)
( check-expect ( sum-of-squares 0 2.5) 6.25)
The examples should be chosen to illustrate “typical” uses of the function and to
illuminate some of the difficulties to be faced in writing it. Examples should cover
each case described in the data definition for the type consumed by the function.
The examples do not have to cover all the cases that the code addresses; that is the
job of the tests, which are designed after the code is written. It is very useful to write
your examples before you start writing your code. These examples will help you
to organize your thoughts about what exactly you expect your function to do. You
might be surprised by how much of a difference this makes.
For recursive data, your examples must include each base case and at least one recur-
sive case. Examples should cover edge cases whenever possible (for example, empty
lists).
In DrRacket, examples serve both as examples and as tests for your function.
9
3.4.1 Types in Racket
The following is a list of types that are valid in Racket. Note that types are introduced
throughout the course as needed, so some of these may not be familiar upon first
reading.
If there are important constraints on the parameters that are not fully described in
the contract, add an additional requires section after the contract. For example, this
can be used to indicate a Int must be in a specific range, a Str must be of a specific
length, or that a (listof ...) cannot be empty. Single requirements can be on one
10
line. Multiple requirements should start on a separate line and each line should be
indented several spaces. For an example in Racket:
;; quot: Nat Nat -> Nat
;; Requires :
;; n1 >= 0
;; n2 > 0
( define (quot n1 n2) ...)
3.5 Tests
It is extremely important that you submit your code and check your basic tests
to make sure the format of your submission is readable by the autograder!
;; Tests :
( check-expect ( sum-of-squares 0 0) 0)
( check-expect ( sum-of-squares -2 7) 53)
Make sure that your tests are actually testing every part of the code. For example,
if a conditional expression has three possible outcomes, you have tests that check
each of the possible outcomes. Furthermore, your tests should be directed: each one
should aim at a particular case, or section of code. Some people write tests that use
a large amount of data; this is not necessarily the best idea, because if they fail, it is
difficult to figure out why. Others write lots of tests, but have several tests in a row
that do essentially the same thing. It’s not a question of quantity, but of quality. You
should design a small, comprehensive test suite.
In DrRacket, examples serve both as examples and as tests for your function. Notice
that in other languages, this is not necessarily true!
Never figure out the answers to your tests by running your own code. Work out
the correct answers independently (e.g., by hand).
11
3.5.1 Testing Tips
Do not use the word “helper” in your function name: use a descriptive function name.
In this course, tests are not required for helper functions, but they should still have
a purpose, a contract, and examples. (You are still welcome to include tests, which
can help you ensure your helper functions work correctly.)
In the past, we have seen students avoid writing helper functions because they did
not want to provide documentation for them. This is a bad habit that we strongly dis-
courage. Writing good helper functions is an essential skill in software development
and having to write a purpose and contract should not discourage you from writing a
helper function. Marks may be deducted if you are not using helper functions when
appropriate.
Helper functions should be placed before the required function(s) in your submis-
sion.
12
Functions we ask you to write require a full design recipe.
When using a small wrapper function that invokes a more complex helper function to
do the real work, only one of the functions requires tests. If the wrapper function is
required, then include the tests with it. Otherwise, use your judgement to choose the
function where tests seem most appropriate, but by default stick with the wrapper.
As always, both functions should include examples. In the following example, the
tests are included with the wrapper function remove-from.
;; ( remove-from-list loc c) produces a new list, like loc, but with
;; all occurrences of c removed
;; remove-from-list : ( listof Char) Char -> ( listof Char)
;; Examples :
( check-expect ( remove-from-list empty #\z) empty )
( check-expect
( remove-from-list (cons #\a (cons #\z (cons #\b empty ))) #\z)
(cons #\a (cons #\b empty )))
( define ( remove-from-list loc c)
(cond [( empty? loc) empty ]
[( char=? ( first loc) c) ( remove-from-list (rest loc) c)]
[else (cons ( first loc) ( remove-from-list (rest loc) c ))]))
13
( check-expect ( remove-from "A" #\y) "A")
( check-expect ( remove-from " Waterloo " #\o) " Waterl ")
( check-expect ( remove-from " OOOOO " #\O) "")
For functions defined with a local expression, no tests or examples are necessary.
Include the other design recipe elements as illustrated in the following code. Add a
blank line after each local function definition. Add a blank line after each block of
local constant definitions, as you would for non-local constant definitions.
;; ( remove-short los len) produces the list of strings in los
;; which are longer than len.
;; remove-short : ( listof Str) Nat -> ( listof Str)
;; Examples :
( check-expect ( remove-short empty 4) empty )
( check-expect ( remove-short (list "ab" "1234" "hello " "bye") 3)
(list "1234" " hello "))
( define ( remove-short los len)
( local
[;; ( long? s) produces true if s has more than len characters
;; long? : Str -> Bool
( define ( long? s)
(> ( string-length s) len ))]
( filter long? los )))
;; Tests
( check-expect ( remove-short (list "abc") 4) empty )
( check-expect ( remove-short (list " abcdef ") 2) (list " abcdef "))
( check-expect ( remove-short (list "ab" "1234" "hello " "bye") 1)
(list "ab" "1234" "hello " "bye"))
( check-expect ( remove-short (list "ab" "1234" "hello " "bye") 20)
empty )
14
3.6.4 Manual Constructors and Selectors
Relevance: Modules 05–09.
In Modules 05–09, we will sometimes use lists to define compound data types, an
approach that foreshadows structures in Module 10. In such cases, we define very
simple functions called constructors and selectors to support the new type. For these
functions, no design recipe elements are required apart from definitions, though a
few comments (and maybe contracts) might be useful as documentation. Here are
constructors and selectors defined in support of an AssociationList type:
;; A Pair is a (list Str Num)
;; Constructor
( define ( make-pair key val) (list key val ))
;; Selectors
( define ( pair-key p) ( first p))
( define ( pair-val p) ( second p))
;; An AssociationList (AL) is a ( listof Pair)
If we ask you to write two mutually recursive functions, only one of the functions
requires tests. In the following example, the tests are included with the function
is-odd?.
;; ( is-even? x) produces true if x is even, and false otherwise
;; is-even? : Nat -> Bool
;; Example :
( check-expect ( is-even? 2) true)
( define ( is-even? x)
(cond [(= x 0) true]
[else ( is-odd? (sub1 x))]))
15
;; Examples :
( check-expect ( is-odd? 0) false )
( check-expect ( is-odd? 45) true)
( define ( is-odd? x)
(cond [(= x 0) false ]
[else ( is-even? (sub1 x ))]))
;; Tests
( check-expect ( is-odd? 1) true)
( check-expect ( is-odd? 10) false )
3.7 Summary
• Purposes should be brief and describe everything your function is doing.
• Parameters names must be in the purpose and explained.
• Contracts should use appropriate types
• Requirements should be used to explain restrictions on input not able to be
captured by a contract.
• Examples for code should cover edge/base cases.
• Tests should be a small comprehensive suite covering both edge and typical
cases.
• Helper functions do not need tests but require at least one example and all
other design recipe elements.
• With the exception of the Racket built-in types (e.g., Posn), every user-defined
type used in your assignment must be defined at the top of your file (unless an
assignment instructs you otherwise).
16
• User-defined types that appear in contracts and other type definitions should
be capitalized (use MyType or My-Type, not my-type).
A descriptive data definition can be used to define a new user-defined type to im-
prove the readability of contracts and other type definitions
;; A StudentID is a Nat
;; Requires : the value is between 1000000 and 99999999
;; A Direction is an ( anyof 'up 'down 'left 'right )
4.2 Templates
Relevance: Module 05 and beyond.
For any non-trivial user-defined type, there is a corresponding template for a func-
tion that consumes the new type. Since a template is a general framework for a func-
tion that consumes the new type, we also always write a contract as part of the
template. For each new data definition, writing a template is recommended, but not
required unless otherwise specified.
For mixed user-defined types, there is a corresponding cond question for each pos-
sible type:
17
;; A CampusID is one of:
;; * a StudentID
;; * a StaffID
;; * a FacultyID
;; * 'guest
;; campusid-template : CampusID -> Any
( define ( campusid-template cid)
(cond
[( studentid? cid) ...]
[( staffid? cid) ...]
[( facultyid? cid) ...]
[( symbol=? 'guest cid) ...]))
As you combine user-defined types, their templates can also be combined. See the
lessons for more examples.
4.3 Structures
Relevance: Module 10 and beyond.
When you define a structure, it should be followed by a data definition, which spec-
ifies the type for each of the fields. For example:
( define-struct date (year month day ))
;; A Date is a ( make-date Nat Nat Nat)
If there are any additional requirements on the fields not specified in the type defi-
nition, a requires section can be added. For example:
( define-struct date (year month day ))
;; A Date is a ( make-date Nat Nat Nat)
;; Requires :
;; fields correspond to a valid Gregorian Calendar date
;; year >= 1900
;; 1 <= month <= 12
;; 1 <= day <= 31
The template for a function that consumes a defined structure includes a placeholder
for each field:
( define-struct date (year month day ))
18
;; A Date is a ( make-date Nat Nat Nat)
;; date-template : Date -> Any
( define ( date-template d)
( ... ( date-year d) ...
... ( date-month d) ...
... ( date-day d) ...))
4.4 Summary
• Structures should be followed by a type definition specifying the types of each
field.
• Descriptive definitions can be used to define a new user-defined type to im-
prove readability.
• We also always write a contract as part of the template.
5 A Sample Submission
Problem: Write a Racket function earlier? that consumes two times and will pro-
duce true if the first time occurs earlier in the day than the second time, and false
otherwise.
Note how the named constants makes the examples and testing easier, and how the
introduction of the time->seconds helper function makes the implementation of
earlier? much more straightforward.
;;
;; **********************************************
;; Juliette Andromeda Mao ( 12345678 )
;; CS 115 Spring 2022
;; Assignment 03, Question 1
;; **********************************************
;;
( define-struct time (hour minute second ))
;; A Time is a ( make-time Nat Nat Nat)
;; Requires : 0 <= hour < 24
;; 0 <= minute, second < 60
19
;; time-template : Time -> Any
( define ( time-template t)
( ... ( time-hour t) ...
... ( time-minute t) ...
... ( time-second t) ... ))
;; Useful converters
( define seconds-per-minute 60)
( define minutes-per-hour 60)
( define seconds-per-hour (* seconds-per-minute minutes-per-hour ))
20