Commenting Guide
This is a style guide for CSE 121 and contains a list of guidelines we expect you to follow when writing code. This document covers the entire quarter, so it is not meant to be something that you memorize and understand entirely through one read-through. Rather, it is a reference guide that you will be able to use to look up rules and examples. Using this in combination with the the spec is a great way to be careful with your internal code details.
Note that this guide is not 100% comprehensive. It cannot replace feedback your TAs give or specific instructions from the spec. This guide lists the general rules to follow, but your TAs will be able to give you specific feedback on the code you write and how they fit into issues of style.
Here is a summary of why code quality matters:
Many people believe that when writing code, the only thing that matters is if your code is working or not. This is false - while it’s important to get your code working, it is just as important to make sure your code is understandable to other people.
Programming is a highly collaborative activity - it’s fairly common to have to work together with other people, sometimes even hundreds of them at a time! As a result, it’s very important to write code that is easy to work with to allow others to integrate seamlessly with your work. Another benefit is that since clean code is easier to understand, you’ll work much faster and introduce fewer bugs. While you will not work in groups on assignments in CSE 121 unless otherwise specified, it’s still important to practice programming with good style - it’s the core difference between whether a program is pleasant or impossible to work with.
In general, focusing on writing clean code forces you to acquire an eye for detail. Programming, and writing correct (externally and internally) programs requires careful attention, so it’s better to acquire that habit sooner, rather then later. After all, code is now a part of everyday life - we use code to control cars, control medical equipment, moderate our infrastructure, perform legal analysis, hold private data, and so forth. It’s possible you’ll go on to help create the software that ends up becoming part of others’ lives. If that’s the case, we have a responsibility to write as clean and well-designed code as possible to minimize the chances of bugs and accidents that might sneak in due to carelessness. As a result, the sooner you can get into the habit of caring about detail and correctness, the better.
Note that there is not just one way to write code. A code quality guide outlines a common set of standards that a community of programmers would like everyone to use. Different communities may use completely different rules than one another, and that is totally fine! Having a common set of standards for code quality allows for those in the community to better edit and read that code. So just because there is something in our code quality guide doesn’t make it the “one right way to write” something. But you will still need to follow our guidelines, just like if you were to go work at a tech company, you would have to follow theirs.
Do not include package statements in your submissions.
Use wildcard import statements instead of specific imports. e.g. use statements like import java.util.*;
over import java.util.Scanner;
Note
There are some merits in importing classes individually, but for this course use wildcard import statements in your submissions. This is just one example of different groups might have different rules about what is “good code quality.” In our context, we ask that you use wildcard imports since it is helpful for our context (learning and testing your code). Different groups might feel differently about this rule though, but what matters is being consistent with your peers.
The following section describes rules about how your code should be organized visually. It should be noted that the rules listed below aren’t inarguably the best rules; there are plenty of reasonable alternatives out there. It is important, however, to adhere to the relevant style of the work environment. For other readers, code that follows the style guidelines is easier to understand than code that doesn’t. Being able to anticipate conventional formatting of code eliminates time wasted on deciphering the meaning of any unfamiliar structure and formatting.
Each time a curly brace is opened, increase the indent level. When the closing curly brace occurs and ends the code block, the indent should return to the previous indent level. The indent level applies to both code and comments throughout the block defined by the curly braces.
Choose 3 or 4 spaces to be your indent level and stay consistent.
Java is not a whitespace sensitive language, but maintaining good spacing throughout your program helps your code be more readable for human eyes. Most Java programmers follow the below spacing conventions for their programs.
public class SomeClass {
public static void someMethod(int number1, int number2) {
someMethod(4, 3);
for (int i = 0; i < 4; i++) {
while (someTest) {
for (String s : list) {
int[] someArray = new int[4];
int someNumber = 4;
int x = 4 * (3 + 2) - 1;
boolean result = test1 && test2 || (x != 3);
The spacing around methods also matters. Remember to leave a blank line before each method comment for readability.
Each time you finish a statement with a semicolon, you should use a new line to begin the next statement.
Note
The exception to this rule is that the for loop header should have its 3 parts all on one line.
No line should exceed 100 characters in length.
Info
When code that might otherwise legally occupy a single line is divided into multiple lines, this activity is called line-wrapping. Use line-wrapping to break up long lines that would otherwise exceed 100 characters, as described below.
When line-wrapping, each line after the first (each continuation line) should be indented at least one level. Common choices for how much to indent include 2 levels of indentation or lining up elements from the previous line of the long line.
// It's good that this line is wrapped to maintain the 100 char line limit,
// but the continued line should be indented at least 1 indentation level.
public static void method1(String param1, String param2, String param3,
String param4, String param5) {
...
}
// This method header is properly indented and uses 2 levels of indentation
// for the broken up line.
public static void method1(String param1, String param2, String param3,
String param4, String param5) {
...
}
// This method header is also properly indented, and lines up elements from
// the broken up line.
public static void method1(String param1, String param2, String param3,
String param4, String param5) {
...
}
Sometimes, we need to print very long lines to output. In this case, it’s best to wrap long print-outs into multiple lines using the string concatenation operator.
// This is a similar example with a println statement, still using 2 levels of indentation
// for the broken up line, but using string concatenation to break up the string.
System.out.println("CSE 121 is an amazing course. Thank you to everyone who put in "
+ "work to redesign the UW introductory computer science course sequence!");
}
Tip
Extracting some method call(s) into a local variable may solve the problem without the need to line-wrap.
The rules mentioned below document the style that you will likely see your TAs and lecturers use, but is optional if you want to use a reasonable (readable) alternative instead. The most important thing is consistency. - No line break before the opening brace. - Line break after the opening brace. - Line break before the closing brace. - Line break after the closing brace, only if that brace terminates a statement or terminates the body of a method, constructor, or class. For example, there is no line break after the brace if it is followed by else
.
Example:
public static void method1(int times) {
for (int i = 0; i < times; i++) {
if (i % 2 == 0) {
System.out.print(i);
} else {
System.out.print("x");
}
System.out.println(" hello");
}
}
As much as possible, System.out.print
and System.out.println()
statements should be written so that the intended output and format is as clear as possible from the code. In particular:
System.out.println()
rather than System.out.println("")
. (That is, do not include an empty string.)System.out.println()
statements, rather than by including \n
characters."Hi World"
), rather than escape characters such as \t
(e.g. "Hi\tWorld"
).System.out.print()
statements should be combined whenever possible.Here are a few incorrect and correct examples:
System.out.print("Hello, "); // Combine!
System.out.print("World!"); // Combine!
System.out.println();
System.out.println();
System.out.println("I love Java!");
System.out.println("Hello, World!");
System.out.println(""); // No empty strings, please.
System.out.println("I love Java!");
System.out.println("Hello, World!\n\nI love Java!"); // Use println() rather than
// newline escape sequences.
System.out.println("Hello, World!");
System.out.println();
System.out.println("I love Java!");
Note that the earlier section on line length (and the subsection long lines and printing) still applies. For example, the following block does not follow the code quality guidelines on combinable print statements.
System.out.print("CSE 121 is an amazing course. Thank you to everyone who put in work ");
System.out.println("to redesign the UW introductory computer science course sequence!");
However, to fix this it should not be combined into one long line. Instead, use string concatenation:
System.out.println("CSE 121 is an amazing course. Thank you to everyone who put in work "
+ "to redesign the UW introductory computer science course sequence!");
Class constants should be declared with public
, static
, final
modifiers.
For example:
public static final int RANGE = 10;
public
allows a client to directly interact with the constantstatic
means the constant is associated with the class rather than a specific instance of the class. (e.g. each instance of a class should have its own independent fields from other instances, but it’s fine if they all share the same class constant value)final
prevents the constant from being assigned a new value. i.e. the initialized value is final.Create and use class constants when directed by the spec and where appropriate. A good creation/use of a constant constitutes replacing a raw value that is used multiple times in your program with a constant that stores the value, but with more descriptive and readable name. This also provides more flexibility if the raw value ever needs to change.
Note
Class constants must be initialized at declaration.
Tip
Watch out for using raw numbers aka “magic values” in your code. These values might confuse another reader of your code, so you should avoid them and consider other options. There may be a useful method or way to access the value that is more logical, or the spec might have more information.
All methods should include an access specifiers. In CSE 121, almost all methods should be declared as public
.
Avoid extra parameters. For the same reasons to cut down on fields, simplifying the number of parameters can simplify the code. Watch out for unused parameters or parameters whose values can be inferred by other parameters.
Avoid unnecessary returns. If you pass in a reference to an object as a parameter, there’s no reason to define your method to return that same reference. Wherever called the method already has a reference to the object, as it was able to pass it in the first place, so returning the same value back is redundant. In fact, defining a method to have a redundant return may imply that you made a copy of the object passed in, and may confuse a reader of your code.
// This method unnecessarily defines a return that always returns
// the reference to the original object.
// Because whoever called this method has a reference to the List object,
// we can change this method's return type to void to simplify the method.
public int[] incrementNumbers(int[] nums) {
// increments the number at each index in nums by 1
...
return nums;
}
// an improved reduced version
public void incrementNumbers(int[] nums) {
// increments the number at each index in nums by 1
...
// no need to return here since the caller
// still has access to the nums array's reference!
}
Use descriptive names that will help out another reader of your code. Giving some kind of context for what the value(s) is or giving a high-level 1 or 2 word description is usually appropriate.
For the following example, assume we’re trying to name a String[]
that will keep track of the names of each chapter in a book. Since we have some context for what we want to use this variable for, we can try and come up with a descriptive name.
Example | Discussion |
---|---|
| a isn't super helpful - it really doesn't say anything about what the values in a or a itself represent. |
| Restating the type may seem better, but it doesn't actually help a reader understand the code any more easily. A reader still doesn't have any context or high level understanding what arrayOfStrings represents. |
| Every variable stores a value, so this one is a little bit too vague to be helpful. |
| Sweetness! This one gives some context for what the array will store in it / represent in high-level. |
Note
One-letter names may be easy to type, but they aren’t too helpful as far as descriptiveness goes. Other readers of your code will likely ponder over their meaning if they need to understand what your code is doing. There are some appropriate uses, however, such as for
loop variable names. They’re also used to describe values that are temporary or so general that there isn’t really any context to describe. But for the most part, try to use more helpful names when you can think of them. Other readers will likely appreciate it.
We use i-j-k
for loop variable names because it is general programmer convention for for loops. i
should be used for outer-loops, j
for once-nested loops, k
for twice nested loops, etc. Generally, if we use i-j-k
convention for loop variable names, we don’t want to mix them with descriptive loop variable names. Descriptive loop variable names are okay, as is using i-j-k
convention, but we want be consistent with one or the other. i-j-k
convention should be used like this:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < i; j++) {
for (int k = i; k > j; k++) {
System.out.print("*");
}
System.out.print("+");
}
System.out.println();
for (int j = 0; j < i; j++) { // Notice this loop is also j since it is once nested
System.out.print("#");
}
System.out.println();
}
camelCased
, where every word after the first begins with capitalization.UpperCamelCased
. This is camel casing but the first word also begins with capitalization.SCREAMING_CASE
, where words are all uppercase and separated by underscores.Sometimes we have options for how we want to represent the data and either option(s) will be able to produce correct behavior. Instead of using a more complicated representation that can do everything we want and more, we should try use the representation that fits the specific problem the best. The intent and logic of your code will be simpler to another reader. Some examples are listed below.
int
to keep track of whether it’s one value or another, you likely want to use a boolean
instead.double
to keep track of a number that is counting things, you probably want to use an int
instead.String
to keep track of a single letter, you probably want to use a char
.Integer
to keep track of a sum, you can probably just use int
.Note
Not all issues of efficiency that we care about in 121 can be addressed in this section. There are concrete examples of common efficiency issues listed below, but there are too many possibilities to cover every single one. However, we can still talk about some general guidance for reviewing code for efficiency.
A lot of what we define to be important for efficiency boils down to “don’t do anything unnecessary”. Knowing what’s necessary and what isn’t can be difficult, but following the way the spec phrases certain implementation details will lead you in the right direction. Being familiar with the kind of code we write in lecture and section will also give you a ballpark for what we expect the assessment code to be. If you find yourself having to look up tons of things online or write some code that seems noticeably more complicated than code we’ve written in section or lecture, you might want to take a step back and rethink the current approach. Unfortunately, there’s no hard and fast rule for finding every inefficiency. To find them, you will have to dedicate some time to reason about your code.
Above all refer to the spec for guidelines on how to write the code in a reasonable manner. The spec is the true guide for what we expect for that specific assessment.
Avoid making objects that you don’t need. This can be tricky to spot, but in particular you should review your code statements that instantiate new
objects or call other methods that do so. Making sure that every object you instantiate is necessary (no other available methods can provide the same or reasonable behavior) and logical will give some sense of security here.
The first example makes a new Random();
and new Scanner();
, even though we never use either. The second example omits the objects that we don’t use.
public static void main(String[] args) {
Random randy = new Random();
Scanner console = new Scanner(System.in);
System.out.println("Hello, world!");
}
public static void main(String[] args) {
System.out.println("Hello, world!");
}
Avoid recomputing complex expressions and method calls. If you have to use the value of a method call or a complicated expression more than once, you should store it in a well named variable. Using the value stored in this variable will give you instant access instead of recomputing the value by evaluating the method call or expression again. This will also clear up the clutter of your code by replacing generic symbols and expressions with a name to describe some context.
This section is all about less being more. Generally we want to follow the mantra that more concise code will be easier to read and reason about. As a general rule, aim to revise your code to have fewer complicated expressions, ifs, elses, loops, etc., where reasonably possible.
Boolean zen is all about using boolean values efficiently and concisely, and is best described through a few examples.
The following code shows a tempting way to write an if statement based on boolean value.
if (test == true) {
// do some stuff
}
Note that test
itself is a boolean
value. When it is true
, we are asking whether true == true
. true == true
will evaluate to true
, but remember that the if
branch will execute as long as what is in its parentheses evaluates to true. So we can actually use test
directly:
if (test) {
// do some stuff
}
Note
If you want to check for the opposite of test
, don’t check for test == false
. Use !test
to check that the opposite of test
evaluates to true
.
Here’s an example that uses what we learned in the previous section about simplifying boolean expressions.
if (test) {
return true;
} else {
return false;
}
There is actually a much more concise way to do this. If we want to return true when test is true and false when test is false, then we actually just want to return whatever test
is.
return test;
In general, make your use of booleans to simplify if/elses and boolean expressions. This is something to look out for whenever you have conditions or boolean values.
Tip
If you ever have to write true
or false
literally in your code, double check that there’s no way to simplify your logic. Using true
or false
literally in your code isn’t inherently redundant, but it’s something to double check.
if (someTest) {
System.out.println("hello!");
}
if (!someTest) {
// Because this code always wants to do one or the other, (and
// doesn't involve a return or exception) we want to express this
// code more simply as an if/else
System.out.println("I'm redundant");
}
if (max < result) {
return max;
}
if (max == 0) {
// Note that the behavior inside this if block is exactly the same behavior as in
// the other if block
// Instead of rewriting the same code twice, we can combine the two if conditions
// with || and just write the behavior once.
return max;
}
if (max < 0) {
// It doesn't matter if you think of conditions/cases or their negated versions, but
// afterrevising your code don't include empty condition blocks with no line of code
// inside. Instead, just flip the condition to have if (max >= 0), and no else.
} else {
...
}
If you have lines of code that appear in multiple places in your program, you should consider trying to cut down on redundancy with some kind of factoring.
If you have lines of repeated or very similar code that need to be executed in different places, group the code of the task into a helper method. Putting the code into one place instead of several will make making changes easier, as well as make our code more readable.
Code inside if-else
structure should represent different cases where the code is inherently different. This means duplicate lines of code or logic in an if-else
structure should be factored out to come before or after, so that they are only written to happen once, unconditionally.
// Note that there are repeated lines of logic that actually always happen, instead of
// conditionally like how our structure is set up. We can factor these out to simplify and
// clean our code.
if (x % 2 == 0) {
System.out.println("Hello!");
System.out.println("I love even numbers too.");
System.out.println("See you later!");
} else {
System.out.println("Hello!");
System.out.println("I don't like even numbers either.");
System.out.println("See you later!");
}
System.out.println("Hello!");
if (x % 2 == 0) {
System.out.println("I love even numbers too.");
} else {
System.out.println("I don't like even numbers either.");
}
System.out.println("See you later!");
Tip
Review any if-else
structures you write and make sure there are no duplicate lines of code or logic at the beginning or the end.
When writing loops, choose loop bounds or loop conditions that help generalize code the best. For example, the code before this for
loop is unnecessary.
System.out.print("*");
for (int i = 0; i < 4; i++) {
System.out.print("*");
}
Instead, why not just run the loop one extra time? This way we avoid writing duplicate behavior.
for (int i = 0; i < 5; i++) {
System.out.print("*");
}
If you have something that only happens once (maybe at the end of a bunch of repeated tasks), then don’t put code for it inside of your loop.
for (int i = 0; i < 4; i++) {
if (i != 3) {
System.out.println("working hard!");
} else {
System.out.println("hardly working!");
}
}
for (int i = 0; i < 3; i++) {
System.out.println("working hard!");
}
System.out.println("hardly working!");
Similarly, a loop that always runs one time is also a bad use of a loop. When reviewing the logic of your code, it might be helpful to check if a loop can be reduced to an if or just deleting the loop entirely.
This section details issues with writing unnecessary if-else
logic that can be omitted. We will refer to any control flow like if
, else
, etc. as conditional logic.
Good bounds / loop conditions will already deal with certain edge case behavior. Avoid writing additional conditional logic that loop bounds already generalize.
// The 'if' here is redundant. The loop won't produce any output (or even run)
// if n were 0 or negative in the first place, so we should remove the check.
if (n > 0) {
for (int i = 0; i < n; i++) {
System.out.println("I love loops <3");
}
}
By using control flow (if
/else
/return
) properly, you can guarantee some implicit facts about values and state without explicitly checking for them. The examples listed below add in redundant conditional logic that should be removed.
if (someTest) {
...
} else if (!someTest ...) {
// Because someTest was tested for alone in the first if,
// future branches of the if/else structure know someTest must be
// false, so the check for !someTest is redundant and can be omitted.
}
if (someTest) {
throw exception/return
}
// At this point, we know if someTest were true, we would have thrown
// an exception or returned from the method. So for all code
// from here on, someTest must be false and it is redundant to check again.
if (!someTest) {
...
}
Avoid re-implementing the functionality of already existing methods, by just calling those existing methods.
In the following example, the two methods are almost identical, except indexOf
is more powerful/flexible than contains
. So, contains
can actually just use a call to indexOf
to reuse already written behavior.
public int indexOf(int[] numbers, int value) {
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == value) {
return i;
}
}
return -1;
}
public boolean contains(int[] numbers, int value) {
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == value) {
return true;
}
}
return false;
}
public boolean contains(int[] numbers, int value) {
return indexOf(numbers, value) >= 0;
}
The classes you will use in this course (String
, Scanner
, etc.) will provide many useful methods, and you should aim to take advantage of them by using them. Rewriting existing behavior is less flexible, redundant, and by writing more lines of code you raise the risk of encountering more bugs.
Tip
Refer to section handouts and the spec for reminders about helpful methods.
The following sections detail information about style that doesn’t quite fit into any of the previous categories. These aren’t a huge focus in CSE 121, but are always good to consider.
It is generally good practice to keep variables in the most local scope possible. Remember that the scope of a variable is the closest pair of curly braces that surround it. In the following example, the variable nextScore
has been declared in the scope of the entire method, but note that declaring it in such a wide scope is actually not necessary.
public static void printTenScores(Scanner console) {
int nextScore;
for (int i = 1; i <= 10; i++) {
System.out.print("next score? ");
nextScore = console.nextInt();
System.out.println("score " + i + " = " + nextScore);
}
}
Notice that nextScore
is only ever accessed inside of the for loop, so we can actually localize its scope to just the loop itself rather than the whole method.
public static void printTenScores(Scanner console) {
for (int i = 1; i <= 10; i++) {
System.out.print("next score? ");
int nextScore = console.nextInt();
System.out.println("score " + i + " = " + nextScore);
}
}
By localizing scope, our code becomes simpler to reason about, because we’ve minimized the number of different variables floating around at any given location in our code. Note that eliminating unnecessary fields is often just an example of localizing variables. Also note that “re-declaring” the variable nextScore
inside the loop is just fine efficiency-wise because of how the compiler will optimize our code. In this course, you shouldn’t ever have to worry about micro-optimizations like the number of variable declarations.
This is a section about reimplementing methods that has to do with smaller-scale redundancies. Try to take advantage of already-implemeted methods if they exist for a particular task.
For example, instead of checking for
stringA.length() == 0
use the more readable method that was made for this purpose:
stringA.isEmpty()
Another common offender is checking
stringA.toLowerCase().equals(stringB.toLowerCase())
Instead, use
stringA.equalsIgnoreCase(stringB)
In general, once a class has been discussed, it is available for use by students. (For example, once the String class is introduced, you may assume that you can call any of the available String methods in future programming assignments without asking permission). Unless otherwise specified, language features should not be used before they have been introduced in class (e.g. you should not use if statements in any assignment until you have seen them in class). Students should realize, however, that saying that you are not forbidden from using a certain construct is not the same thing as saying that it is a good idea to use a certain construct. We generally don’t give advice to students about which constructs to use; you should use your best judgment to decide. (Though if a particular assignment states that you are required to use a construct, you should do so.)
In addition, there are some constructs that you are NEVER allowed to use, as described below. Java has grown to be a complex language with many features, and we don’t have time to teach all of these features in CSE 121. We have a general rule that students should not use “advanced” material that we have not covered in class. In addition, we have identified several Java features that we do not want students to use at all. It is not bad style to use these features, but we want to have a level playing field for all students. Since we don’t teach these features in the class, we do not allow them to be used to avoid giving an advantage to students who have learned about the features on their own.
The following features should NEVER be used in any graded CSE 121 work (unless otherwise specified by the assignment or problem):
public static int ...
)break
or continue
return
from a void
methodSystem.exit
methodswitch
statementtry/catch
var
keywordformat
, join
, matches
, repeat(n)
, and toCharArray
methods of String
StringBuilder
, StringBuffer
, StringJoiner
, StringTokenizer
classesArrays.fill
, Arrays.asList
, Arrays.copyOf
, Arrays.copyOfRange
, and Arrays.sort
toArray
and clone
methods of ArrayList
Collections.copy
and Collections.sort
System.console
LinkedHashMap
(Don’t worry if you are not familiar with one of the features listed above. You can simply think of these as Java features we have chosen not to teach in this class so that we can focus on other important concepts instead.)
Thanks to Michelle and Michael, who provided several code examples and explanations that are used in this guide. Thanks to Kelsey and Jasmine for calling out the intersection of line length and printing.
Additional thanks to proofreaders who help find typos and suggest rewordings. Finally, thanks to the students reading this guide. Surely after reading this guide, you’ll contribute to writing stylish code that exists in the world.
Note
If you find any typos or otherwise have suggestions or clarifications, feel free to email the head TA to suggest a fix (and optionally have your name listed in the acknowledgements).