CSE143 Java Style Guide

1 Introduction

This guide is a style guide for CSE 143 and contains a list of rules we expect you to follow when writing code. This document covers the entire quarter, so it is not meant to be something that you read through in one sitting. Rather, it is a reference guide that you will be able to use to look up rules and examples as they come up.

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 style 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 should not collaborate on homework assignments in CSE 142 and 143, 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.

2 Source file structure

2.1 Package statements

Do not include package statements in your homework submissions.

2.2 Import statements

Use wildcard import statements instead of specific imports. e.g. use statements like import java.util.*; over import java.util.ArrayList;

Note: There are some merits in importing classes individually, but for this course use wildcard import statements in your homework submissions.

2.3 Ordering of class contents

  1. Place field declarations at the top of your class.
  2. The order of how you place methods is up to you, but it helps readability to maintain some kind of logical ordering. One common ordering is to include all constructors in the beginning, then all other public methods before private methods.

3 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.

3.1 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.

3.2 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.

The spacing around methods also matters. Remember to leave a blank line before each method comment for readability.

3.3 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.

3.4 Column limit and line wrapping

No line should exceed 100 characters in length.

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

Tip: Extracting some method call(s) into a local variable may solve the problem without the need to line-wrap.

3.5 Exceptions

// This example doesn't check/throw for the exception at the very beginning of the method.
public static int countVowels(String name) {
    int total = 0;
    if (name.length() <= 1) {
        throw new IllegalArgumentException();
    }
    ...
}
// This example attaches an 'else' to an exception check. 
// This leads to indenting all the normal behavior code, which makes it a little bit harder to read.
public static int countVowels(String name) {
    if (name.length() <= 1) {
        throw new IllegalArgumentException();
    } else {
        int total = 0;
        ...
    }
}
// This one is perfect! No else attached with regular behavior and the check for exception is at the top of the method.
public static int countVowels(String name) {
    if (name.length() <= 1) {
        throw new IllegalArgumentException();
    }
    int total = 0;
    ...  
}

Note: else ifs that check for more exceptions are fine. Just don't use else to have a branch for the method's normal behavior code. For example the following code structure would be fine.

if (exceptionCheck) {
     throw exception1
 } else if (exceptionCheck2) {
     throw exception2
 }
 // non-exception behavior code here

3.6 (Optional) Curly Brace Style

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.

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

4 Class Design

This section describes the style specifics of designing Java classes. Unlike the majority of work we do in lecture or section, your homework is largely centered around class design. Every homework assignment in CSE 143 involves writing 1 or 2 of your own "blueprint" classes, so you may find it helpful to review this section weekly and use it as a general checklist.

4.1 Fields

4.2 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 have to be initialized at declaration unlike how fields should be initialized.

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.

4.3 Methods

4.3.1 Access modifiers

Methods (and constructors) to implement that are listed in the spec should all be declared public, so that clients can call these methods. Any additional methods/constuctors you write in the class should be declared private, so that these methods can only be called internally. This will not only prevent clients from calling these methods in ways that might corrupt your fields or otherwise break your program, but also simplify the functionality a client will have to deal with.

4.3.2 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.

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

Note: For the public methods defined in the spec, the parameters and returns listed are required. You should define your method headers to match with the same parameters and returns. For private methods, however, you are in control of the design -- this is where extra parameters and returns (4.3.2 and 4.3.3) come into play.

4.3.4 Unspecified behavior

4.4 Constructors

Note: For the later homework assignments (7 and 8), you are required to design and use your own constructors. For earlier assignments, this isn't relevant just yet.

5 Naming

Note: The names of the public methods and the class must appear exactly how they appear in the spec (case matters). If these names are not exactly correct, the provided main program should fail to compile if you try to.

5.1 Casing

5.2 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/2 word description is usually appropriate.

For the following example, assume we're trying to name a List<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
List<String> l = new ArrayList<String>(); l isn't super helpful - it really doesn't say anything about what the values in l or l itself represent.
List<String> listOfStrings = new ArrayList<String>(); 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 listOfStrings represents.
List<String> values = new ArrayList<String>(); Every variable stores a value, so this one is a little bit too vague to be helpful.
List<String> chapterTitles = new ArrayList<String>(); Sweetness! This one gives some context for what the list 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, like for 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.

6 Use of Types

6.1 Type parameters (generics)

Always include the type parameters when dealing with generic classes like ArrayList, TreeSet, etc.

Bad:   Stack visited = new Stack();
Good: Stack<String> visited = new Stack<String>();

Failing to do so will likely lead to casting that clutter up the code and / or unsafe type operations.

6.2 Interface types

Always declare variables of a specific interface type instead of the class type, where applicable.

Bad:   ArrayList<String> names = new ArrayList<String>();
Good: List<String> names = new ArrayList<String>();

This includes field declarations and parameter and return types in method headers. By working with more general types rather than the specific class type, our code will be more generalized and more flexible. For example, a method that has a parameter of type List<Integer> will be able to accept anything that implements the List interface (LinkedList<Integer>, ArrayList<Integer>, etc.). This is more flexible than a method that defines a parameter of type LinkedList<Integer> that can only accept a LinkedList<Integer>.

6.3 Casting

Avoid casting to object types.

Bad: ((List<String>) data).get(0);

Casting to object types is essentially disregarding the safety net of the type system that Java provides. It may achieve the desired behavior, but it is usually a sign that we should change the logic of our program elsewhere. Casting to object types should only be done if truly necessary.

Casting to primitive types to actually convert values is fine, however.

int truncated = (int) (Math.random() * 5);

6.4 Prefer simple variable 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.

7 Efficiency

Note: Not all issues of efficiency that students might lose points for 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 homework 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 homework.

7.1 Objects

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 ArrayList<String>(); just to discard it in the next line. When we set words = generateWords() in the second line, the previous value of words is lost (unless it's saved elsewhere). The second example doesn't throw away any newly made objects.

List<String> words = new ArrayList<String>();
words = generateWords();
List<String> words = generateWords();

Note: Initially, it can be confusing to understand the difference between declaring variables of object types and actually making new objects. This is a discussion that won't really be touched on here, but will happen during the quarter. It should be noted, however, that you can declare variables of object types for use without setting them to new objects. Setting List<String> words = null; or List<String> words; is totally fine (these statements do not make new objects). This may come in to play for some types of factoring.

7.2 Methods and expressions

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.

There are some methods that are fast enough that storing the value in a variable for the future doesn't save us anything. For example, the size() method for any data structure we use in this course will have the same instant access whether we store it or call the method. In such a case, efficiency is not an issue we have to worry about.

7.3 Material beyond CSE143

Don't worry about having to use advanced material (language features and concepts not covered in this course or 142) to optimize your code. The efficiency of your programs will not be tested on things we don't expect you to know or haven't been exposed to.

Note: Material not yet discussed in class is usually forbidden. All the homeworks can be completed with knowledge from the course so far and information from the spec/homeworks page. See the General Style Deductions for more information.

7.4 Tradeoffs

For any task, there will be multiple implementations that can achieve the desired behavior. These different solutions may have different benefits and it may not be immediately obvious which one is the best. In the end, writing and designing code comes with many decisions, and you should try your best to choose an appropriate solution that matches what the spec emphasizes and requires. This isn't normally a huge focus in the style you should be focusing on, but it may come up and is worth thinking about.

If you're still worried about choosing "the wrong" implementation for something you did ponder over, make sure that there are true benefits (follows other mantras of style, other things listed in this guide) to your implementation that you can argue. If you can convince yourself, then you can have some confidence in your solution.

Tip: If such a decision is not well defined in the spec, try writing out both implementations and looking at them side-by-side. List out the pros and cons of each and see how closely those things align with what is written in the spec and what we've talked about in lecture/section. It should also be noted that if all the options seem poor, don't eliminate the possibility of other solutions existing.

8 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.

8.1 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.

8.2 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 after revising 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 {
    ...
}

8.3 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.

8.3.1 Private 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 private helper method. Putting the code into one place instead of several will make making changes easier, as well as make our code more readable.

8.3.2 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.

8.4 Loop Zen

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

8.4.2 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!");

8.5 Redundant conditional logic

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.

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

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

8.6 Re-implementing methods

Avoid re-implementing methods. The classes you will use in this course (String, ArrayList, TreeMap, 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.

8.7 Recursion zen

Avoid adding extra if/else cases to recursive methods when there are more generalized conditions to use.

public static int sum(int[] list) {
    return sum(list, 0);
}

// This private method has an extra base case for index == list.length - 1. 
// If we omit this and just use the first base case, the behavior will actually 
// still generalize and be correct, but our code will be more concise 
// and easier to reason about. One way of thinking about this code 
// is that it's "looking ahead" too much and not believing in the full power of recursion. 
// This author wanted to stop so early that they wrote this extra base case 
// to stop instead of recursing one level deeper, which is not great. In general
// with recursive programming, a good mantra is to write "lazy" code (AKA fewer if/else cases) 
// to make our code more flexible.

private static int sum(int[] list, int index) {
    if (index == list.length) {
        return 0;
    } else if (index == list.length - 1) {
        return list[index];
    } else {
        return list[index] + sum(list, index + 1);
    }
}

And here's the fixed version with simpler cases.

private static int sum(int[] list, int index) {
    if (index == list.length) {
        return 0;
    } else {
        return list[index] + sum(list, index + 1);
    }
}


Here's one more example: this one has extra cases that also have the same lack of recursion zen - they both "look ahead" instead of letting the recursion generalize. Here the public method redundantly does some of the work that the private method could already do. The better version of this code is also the green example above.

public static int sum(int[] list) {
    if (list.length < 1) {
        return 0;
    } else {
        return list[0] + sum(list, 1);
    }
}

private static int sum(int[] list, int index) {
    if (index == list.length) {
        return 0;
    } else {
        return list[index] + sum(list, index + 1);
    }
}  

9 Acknowledgements

Thanks to Google for providing the template that this guide was created with.

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