Also see the Commenting Guide

Introduction

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.

Motivation

Here is a summary of why code quality matters: - Programming is a collaborative activity, so it is important to know how to write code that is easily understandable by other humans. - Writing clean code reduces the chance that our code is buggy. - You’ll be able to come back to your code much later and still get an idea of what it does!

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.

Source file structure

Package statements

Do not include package statements in your submissions.

Import statements

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.

Member ordering

  1. Place class constants at the top of your class.
  2. Place the main method before any other methods.
  3. The order of your other methods is up to you, but it helps readability to maintain some kind of logical ordering. One common approach is to order methods in roughly the order they are used in the program. Programmers also often put methods that call each other close togehter so that a viewer of the code can see the related methods at the same time.

Formatting

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.

Indentation

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.

Spacing

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.

  • Class headers: public class SomeClass {
  • Method headers: public static void someMethod(int number1, int number2) {
  • Method calls: someMethod(4, 3);
  • Loops:
    • for (int i = 0; i < 4; i++) {
    • while (someTest) {
    • for (String s : list) {
  • Arrays: int[] someArray = new int[4];
  • Variable initializations: int someNumber = 4;
  • Expressions:
    • 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.

One statement per line

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.

Line length

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.

[OPTIONAL] Curly braces

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");
    }
}

Printing

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:

  • Blank lines should be printed using System.out.println() rather than System.out.println(""). (That is, do not include an empty string.)
  • Multiple lines of output should be produced using multiple System.out.println() statements, rather than by including \n characters.
  • Sequential 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!");    

Class constants

Class constants should be declared with public, static, final modifiers.

  1. public allows a client to directly interact with the constant
  2. static 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)
  3. 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.

Methods

Access specifiers

All methods should include an access specifiers. In CSE 121, almost all methods should be declared as public.

Parameters

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.

Returns

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 List<String> generateNames(List<String> toFill) {
    // add some elements to toFill
    ...
    return toFill;
}
// an improved reduced version
public void generateNames(List<String> toFill) {
    // add some elements to toFill
    ...
}

Unspecified behavior

  • Do not add additional behavior beyond what’s described in the spec. This includes additional error checking - only perform error checking listed in the specification for each method.
  • Do not modify object parameters passed to public methods, unless allowed by the spec. (This is also an implication of the previous rule.) An unsuspecting client of the method may not have wanted their data dismantled while your method ran for a different purpose.

Naming

Descriptiveness

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
String[] a = new String[10];
a isn't super helpful - it really doesn't say anything about what the values in a or a itself represent.
String[] arrayOfStrings = new String[10];
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.
String[] values = new String[10];
Every variable stores a value, so this one is a little bit too vague to be helpful.
String[] chapterTitles = new String[10];
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.

Loop variable names:

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();
}

Casing

  • Variables and methods should be camelCased, where every word after the first begins with capitalization.
  • Class names should be UpperCamelCased. This is camel casing but the first word also begins with capitalization.
  • Class constants should be SCREAMING_CASE, where words are all uppercase and separated by underscores.

Use of types

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.

  • If you’re using an int to keep track of whether it’s one value or another, you likely want to use a boolean instead.
  • If you’re using a double to keep track of a number that is counting things, you probably want to use an int instead.
  • If you’re using a String to keep track of a single letter, you probably want to use a char.
  • If you’re using Integer to keep track of a sum, you can probably just use int.

Logic and redundancy

This section is all about less being more. Generally we want to follow the mantra that more concise code will be eaier 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

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.

Inappropriate if/else

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 {
    ...
}

Factoring

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.

Helper methods

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.

If / else factoring

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.

Loop zen

Loop bounds

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("*");
}

Only repeated tasks

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.

Redundant conditions

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.

Loops

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");
    }
}

Already known values

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) {
    ...
}

Reuse existing code

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.

Miscellaneous

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.

Scope

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.

Convenience methods

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

list.size() == 0
or
stringA.length() == 0
use the more readable method that was made for this purpose:
.isEmpty()

Another common offender is checking

stringA.toLowerCase().equals(stringB.toLowerCase())
Intead, use
stringA.equalsIgnoreCase(stringB)

Forbidden Features

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):

  • Global variables (ex: public static int ...)
  • break or continue
  • return from a void method
  • the System.exit method
  • the switch statement
  • try/catch
  • annotations
  • the var keyword
  • Java 8 functional features (e.g., lambdas, streams, method references)
  • Java 11 features such as local variable type inference
  • the toCharArray, join, matches, and repeat(n) methods of String
  • the StringBuilder, StringBuffer, StringJoiner, StringTokenizer classes
  • the methods Arrays.fill, Arrays.asList, Arrays.copyOf, Arrays.copyOfRange, and Arrays.sort
  • the toArray and clone methods of ArrayList
  • the methods Collections.copy and Collections.sort
  • package declarations
  • 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.)

Acknowledgements

Thanks to Michelle and Michael, who provided several code examples and explanations that are used in this guide.

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 suggetions or clarifications, feel free to email the head TA to suggest a fix (and optionally have your name listed in the acknowledgements).