Introduction

This is a style guide for CSE 122 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 (hint: Ctrl/Cmd-F is your friend here!). 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 (and what’s in these boxes?)

Here is a summary of why code quality matters:

  • Programming is a collaborative activity, so it is important to know how to write code easily understandable by other humans.

  • Many programmers will distrust and judge you if you routinely churn out unreadable and ugly code.

  • Writing clean code reduces the chances that our code is buggy.

  • Pragmatically, many people in industry and in open source projects will conduct code reviews on your code and will and block changes that do not match their internal standards. The way we grade assessments mimics this industry best practice.

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

Coming from CSE 121?

While many requirements from CSE 122 carry over to CSE 122, we also introduce some new guidelines that we expect you to follow in this course.

  • As you write increasingly complex programs, you should refactor your code into smaller chunks as necessary for readability and flexibility. See the functional decomposition and trivial methods section for more details and examples.

  • Certain methods that were OK in CSE 121 are forbidden features in CSE 122, as they interfere with demonstration of the concepts we teach. In particular, we do not allow the String methods trim and split nor any methods in the Collections class.

  • You will be working with a wider variety of data structures in CSE 122. The data structure usage section describes guidelines for declaring and initialize data structures.

  • Exceptions and object-oriented programming are new concepts that we introduce in CSE 122. The exceptions section describes formatting requirements for exception throws. The object-oriented programming section describes best practices for writing the state and functionality of an object.

Formatting

Line length

No line should exceed 100 characters in length. A line of code that would otherwise exceed 100 characters should be divided into multiple lines and indented following line-wrapping indentation, as described below.

Indentation

Code blocks between curly braces should move to the next indent level. Each time a curly brace is opened, move to the next line and increase the indent level. When reaching the end of the code block, the closing curly brace should occur on the next line and 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. Keep the number of spaces between each indent level consistent!

if (condition) {
    // Code block on increased indent level
    ...
}
// Return to previous indent level
...

If dividing a line of code into multiple lines (line-wrapping), each line after the first should be indented at least one level.

// The continuation lines should be indented at least 1 level.
public static void method1(String param1, String param2, String param3,
String param4, String param5) {
    System.out.println("This is a really really really really really long " +
    "print statement.");
}
// The second line of the method header is now indented properly
// (by 2 levels).
public static void method1(String param1, String param2, String param3,
        String param4, String param5) {
    ...
}
// Continuation lines can also be indented more than 1 level.
public static void method1(String param1, String param2, String param3,
                           String param4, String param5) {
    ...
}
// Second line of the print statement is now indented 2 levels (but
// could also be indented more).
System.out.println("This is a really really really really really long " +
        "print statement.");

Method spacing

The spacing around methods also matters. Leave a blank line between the end of one method and the start of the next method’s comment for readability.

public static void method1() {
    ...
}

// Notice the blank line above, between this method comment
// and the ending curly brace of method1!
public static void method2() {
    ...
}

Exceptions

Note: This section does not apply until we introduce throwing exceptions later in the quarter!

  1. Throw exceptions as early as possible. Code related to checking for the exception condition may occur before the exception throw, but all other code should be placed after the exception to fail early and avoid unnecessary computation.
  2. Place exception checks in the if branch with no attached else or else if statements. Exception checks should be placed in the if branch. Following an exception check, non-exception code should not be placed in an attached else. Additional exception checks may be placed in an attached else if.
public static void isPrime(int num) {
    if (num >= 0) {
        int factors = 0;
        ...
    } else {
        // Exception code should be the if case, not
        // the else
        throw new IllegalArgumentException();
    }
}
public static void isPrime(int num) {
    if (num < 0) {
        throw new IllegalArgumentException();
    } else {
        // Non-exception code should not be attached to the
        // exception branch
        int factors = 0;
        ...
    }
}
public static void isPrime(int num) {
    int factors = 0;
    // Exception should occur at top of method
    if (num < 0) {
        throw new IllegalArgumentException();
    }
    ...
}
public static void isPrime(int num) {
    // Exceptions occur at top of method
    if (num < 0) {
        throw new IllegalArgumentException("Number should be positive");
    } else if (num > 100) {
        // Additional exceptions can be placed in an else-if branch
        throw new IllegalArgumentException("Number is too large");
    }
    // Non-exception code not attached
    int factors = 0;
    ...
}
Why do we format exceptions this way?

Readability! Exceptions act as a preliminary check on the state of our program before we execute the rest of our code, so it’s natural to place our exception code in the if branch rather than the else. Furthermore, exception code is fundamentally different from regular method behavior code, and shouldn’t be connected to regular behavior with one long if/else structure. Doing so almost implies the if/else branches are on the same level of importance or that they are related.

Naming

Casing

  • Local variables, methods, and fields should be camelCase (each word after the first begins with capitalization).
  • Class names should be UpperCamelCase (each word begins with capitalization).
  • Class constants (variables declared with public static final) should be SCREAMING_CASE (each word uppercase and separated by underscores).
public class MyClassName {
    private int myFieldName;
    public static final int MY_CLASS_CONSTANT = 1;

    public void myMethodName(int myParamName) {
        int myLocalVariable = 1;
        ...
    }
}

Descriptiveness

Use descriptive names that will help out another reader of your code. The name and type of a variable or method should be sufficient to communicate their purpose in the program.

For example, suppose we’re trying to name a List<String> that will keep track of the names of each chapter in a book. We want to avoid names that simply restate the type of the variable (e.g. listOfStrings) or are overly vague (e.g. values). Instead, we might use chapterNames: this name gives some context for what the list will store and what it represents at a high-level.

Loop variable names

When naming for loop variables, apply one of the following naming conventions to the loop (do not mix them!) and remain consistent.

  1. i-j-k: outer loops use i, once-nested loops use j, and twice-nested loops use k. When moving to another method, reset the naming convention (i.e., start at i again). Make sure that any method parameters that take in loop variables are descriptively named!
  2. Descriptive: choose variable names that follow the descriptiveness criteria above.
public static void method1() {
    for (int i = 0; i < 10; i++) {
        method2(i);
        for (int j = 0; j < 5; j++) {
            for (int k = 0; k < 3; k++) {
                System.out.print("*");
            }
        }
        // Notice this loop is also j since
        // it is also once-nested!
        for (int j = 0; j < 6; j++) {
            System.out.print("*");
        }
    }
}

// Notice that the parameter is not named i
// (parameter names should always be descriptive)!
public static void method2(int startNum) {
    // Notice this loop is also i since
    // we have moved to another method!
    for (int i = startNum; i < 5; i++) {
        System.out.println("*");
    }
}

Methods

Parameters

Avoid unnecessary parameters. Watch out for unused parameters or parameters whose values can be inferred by other parameters.

Returns

Avoid unnecessary returns. When a reference is passed in as a parameter, it is redundant to return that same reference to the caller.

public List<String> generateNames(List<String> toFill) {
    for (int i = 0; i < 5; i++) {
        toFill.add(i);
    }
    // This is unnecessary: the caller already
    // has this reference.
    return toFill;
}
public void generateNames(List<String> toFill) {
    for (int i = 0; i < 5; i++) {
        toFill.add(i);
    }
}

Access modifiers

Methods (and constructors) should explicitly be declared either public or private. Choose the access modifier accordingly based on if clients should be able to call the method (public) or not (private).

Why would I write a method that clients can’t use?

There are many different reasons why we might want to declare a method to be private. When we make a method private, we prevent clients from calling it in ways that might corrupt our fields or otherwise break the program functionality. Furthermore, private methods reduce and simplify the functionality a client has to deal with since those methods can only be called internally.

Functional decomposition and trivial methods

Break down complex programs into methods that each perform meaningful steps. Typically, we want to follow the guidelines below:

  1. Functional decomposition should make our program easier to read and comprehend. Methods that do too much can make it difficult to debug, revise, or understand our program. In particular, the main method should be a clear and concise summary of the entire program at a high level. It should call other methods that each carry out a primary task of our program’s functionality.

  2. Methods should represent a significant task. At minimum, each method should consist of at least 2 meaningful logical steps; otherwise, they may be a trivial method, or a method that does too little. Trivial methods often make it harder for a reader to follow our code. If replacing a method call with the code inside would make the program appear cleaner, or if the code inside is replaceable by any single statement, the method may be trivial.

  3. Significant tasks that occur multiple times throughout the program should be factored into their own method. If we find ourselves rewriting the same piece of code or reimplementing an existing functionality, we should write a separate method to carry out that task and call that method to reduce redundancy throughout our program.

Consider the example program below. Its functionality can be summarized as simulating a walk from an initial position, prompting the user to move forward or backward until they reach position 0. Refer to the comments in the code to get an idea of how this program was functionally decomposed!

public class Walker {
    // The main method reflects the summary of the entire
    // program and calls the moveForward and moveBackward
    // methods to execute those actions.
    public static void main(String[] args) {
        Scanner console = new Scanner(System.in);
        System.out.print("Initial position? ");
        int pos = console.nextInt();

        while (pos > 0) {
            System.out.print("(F)orward or (B)ackward? ");
            String choice = console.next();
            if (choice.equals("F")) {
                pos = moveForward(console, pos);
            } else {
                pos = moveBackward(console, pos);
            }
        }
        System.out.println("Reached position 0.");
    }

    public static int moveForward(Scanner console, int pos) {
        System.out.print("How many steps forward? ");
        // Calling the getNumSteps method, just like moveBackward
        // does.
        int steps = getNumSteps(console);
        int newPos = pos + steps;
        System.out.println("You are taking " + steps + " steps forward to " +
                           "position " + newPos + ".");
        return newPos;
    }

    public static int moveBackward(Scanner console, int pos) {
        System.out.print("How many steps backward? ");
        int steps = getNumSteps(console);
        // Calling the getNumSteps method, just like moveForward
        // does.
        int newPos = pos - steps;
        System.out.println("You are taking " + steps + " steps backward to " +
                           "position " + newPos + ".");
        return newPos;
    }

    // Both moveForward and moveBackward call this method
    // since they share the functionality of re-prompting
    // the user for the number of steps until the user
    // enters a non-negative number. 
    public static int getNumSteps(Scanner console) {
        int steps = console.nextInt();
        while (steps < 0) {
            System.out.println("Number of steps must be non-negative!");
            System.out.print("Please re-enter the number of steps: ");
            steps = console.nextInt();
        }
        return steps;
    }
}

Note that the move... methods are not further functionally decomposed as it would be difficult for them to consist of two meaningful steps!

How do I know for sure if my method is doing too much or too little?

The guidelines we provide above are just that: guidelines. There are no cut-and-dried rules we can provide to determine whether a method is poorly functionally decomposed because there are so many possibilities for how to write a “good” program. Over time, we can gain a stronger intuition on how best to divide up our code into logical, readable parts, and the feedback we offer in this class is intended to guide you in the right direction!

Logic and Redundancy

Redundant conditionals

Avoid writing conditional logic that can be omitted without changing program functionality. There are typically 2 reasons why a conditional might be unnecessary:

  1. The program’s code flow guarantees that the conditional is unnecessary. In the example below, if condition is true, we will return a value and exit the method, meaning that condition is guaranteed to be false if we reach the second if check. As a result, we can omit the second if check entirely.
if (condition) {
    return false;
}
if (!condition) {
    ...
}
  1. The program’s specification guarantees that the conditional is unnecessary. In the example, suppose the specification guarantees that size represents the number of elements in the array nums. The check i < size is thus equivalent to the check for i < nums.length, so we should retain only one of them.
for (int i = 0; i < size && i < nums.length; i++) {
    ...
}

Improper conditional structure

Avoid the following cases of improper conditional structure that can inhibit program logic and readability.

  1. Empty conditional branches: a conditional branch containing no code within.
if (max < 0) {
    // Instead of including an empty branch,
    // flip the conditional to have if (max >= 0)
    // and no else.
} else {
    System.out.println(max);
}
  1. Combinable conditional code: two or more conditional branches containing exactly the same code.
if (max < result) {
    return max;
}
if (max == 0) {
    // Instead of having two branches with the same code,
    // combine the two conditions with ||.
    return max;
}
  1. Illogical conditional structure: conditional branches that do not clearly reflect code logic.
if (condition) {
    System.out.println("hello!");
} else if (!condition) {
    // Instead of making an additional check to
    // condition, replace the else if with an else
    // branch.
    System.out.println("goodbye");
}
if (num == 122) {
    System.out.println("This is great number");
}
if (num == 123) {
    // Instead of making a disconnected if branch,
    // attach an else if branch to the first check.
    System.out.println("This is a pretty good number");
}

Factorable conditional code

Factor out duplicate lines of code in a conditional structure so that they are only written to happen once, unconditionally.

// Note that some lines of code happen unconditionally.
if (x % 2 == 0) {
    System.out.println("The number is " + x + ".");
    System.out.println("I love even numbers too.");
    System.out.println("See you later!");
} else {
    System.out.println("The number is " + x + ".");
    System.out.println("I don't like even numbers either.");
    System.out.println("See you later!");
}
System.out.println("The number is " + x + ".");
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!");

If significant sections of code are repeated throughout the whole program, consider functionally decomposing that code into a separate method.

Complex code

Avoid recomputing complex expressions and method calls. Instead, store the resulting value into a variable that can be reused.

For example, suppose we have the following computationally expensive method that appends * 1000 times to a given string:

public static String method1(String s) {
    for (int i = 0; i < 1000; i++) {
        s += "*";
    }
    return s;
}

Rather than call method1 multiple times, we can call it once, save the result, and reuse that variable as much as we want.

public static void main(String[] args) {
    String s1 = "hello";
    System.out.println("Our result is " + method1(s1));
    System.out.println("The length is " + method1(s1).length());
}
public static void main(String[] args) {
    String s1 = "hello";
    String res = method1(s1);
    System.out.println("Our result is " + res);
    System.out.println("The length is " + res.length());
}
More on inefficient computation…

Our program has to do a lot of work each time it carries out a computationally expensive function, just to give us the same result every time. At a larger scale, this repeated computation can slow down our program execution by a lot. On the other hand, storing the result in a variable and using that variable will give us instant access to the expression’s value.

Some methods, like those to get the number of elements in a data structure, are fast enough that storing the value in a variable for the future doesn’t save us much time at all. In these cases, we don’t have to worry about efficiency. Over time, we can build up intuition about which methods are or are not “expensive” to use!

Data Structure Usage

Extra object creation

Avoid making unnecessary objects. This can be tricky to spot, but in particular, look out for any statements that initialize objects with the new keyword (or call other methods that do so). We can walk through the logic of our program to determine whether there are any cases in which an initialized object would remain unused.

It can be especially easy to accidentally introduce objects that are used in some, but not all, logical paths through are program. We can often reorganize our code to ensure that we only create an object when it is absolutely needed.

// nums is unnecessary if our program enters
// the if case.
int[] nums = new int[5];
if (condition) {
    System.out.println("Not using the array here!");
} else {
    for (int i = 0; i < nums.length; i++) {
        nums[i] = i;
    }
}
if (condition) {
    System.out.println("Not using the array here!");
} else {
    int[] nums = new int[5];
    for (int i = 0; i < nums.length; i++) {
        nums[i] = i;
    }
}

Similarly, if our method might terminate early (e.g. through a return statement or an exception throw) without using an initialized object, we want to reorganize our code to avoid creating an object and neglecting to use it.

public static void increasingArray(int length) {
    // res goes to waste if we throw an
    // exception immediately!
    int[] res = new int[length];
    if (length <= 1) {
        throw new IllegalArgumentException();
    }
    ...
}
public static void increasingArray(int length) {
    if (length <= 1) {
        throw new IllegalArgumentException();
    }
    int[] res = new int[length];
    ...
}

In this course, the program specification will often explicitly mention what objects we are allowed or required to create—make sure to adhere to these requirements! When not specified, however, we should consider whether adding an additional object to our implementation is a valuable tradeoff for functionality. For instance, consider the two implementations below that both accomplish the task of counting the number of tokens in a text file.

// This example creates additional Scanners over each line
// to count the tokens per line individually.
Scanner fileScan = new Scanner(new File("file.txt"));
int numTokens = 0;
while (fileScan.hasNextLine()) {
    Scanner lineScan = new Scanner(fileScan.nextLine());
    while (lineScan.hasNext()) {
        numTokens++;
        lineScan.next();
    }
}
System.out.println(numTokens + " tokens found in file");
// This example only uses one Scanner to accomplish the same
// functionality as the "bad example" above.
Scanner fileScan = new Scanner(new File("file.txt"));
int numTokens = 0;
while (fileScan.hasNext()) {
    numTokens++;
    fileScan.next();
}

Introducing an additional Scanner per line in the first implementation does not provide an advantage over the second implementation, so we should consider using the latter.

Type parameters

Include type parameters when dealing with classes like List, Set, etc. that can be initialized to hold different data types. The type must be specified in the <> operator on the left-hand side (when declaring the variable), but the right-hand side (when initializing the variable) can leave the operator empty.

List names = new ArrayList();
List<String> names = new ArrayList<String>();
List<String> names = new ArrayList<>();

Interface types

Declare variables using their interface type rather than the implementation type when applicable. For instance, we should declare a variable with List rather than ArrayList on the left-hand side.

Isn’t it better to be specific?

By working with more general types rather than the specific implementation 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>.

Use of data structures

Note: This section will become more relevant as we introduce more data structures in lecture!

Choose the most suitable data structure for the situation. Unless the program specification requires a certain data type, we should take advantage of any data structure properties that best fit our desired functionality.

For instance, if we have a collection of names that are guaranteed to be unique, we might want to use a Set rather than a List to reflect this property. If we want to access those unique names in alphabetical order, we should consider using a TreeSet over any other Set implementation.

Object-Oriented Programming

Note: This section does not apply until we introduce object-oriented programming later in the quarter!

Fields

Always declare fields with the private access modifier to prevent clients from accessing or modifying internal data through uncontrolled means.

Initialize fields in the constructor rather than at declaration to keep our class flexible and allow different initializations in different constructors.

Avoid unnecessary fields. Fields that are unused, easily derivable from other fields, or used only in a local scope are all considered unnecessary.

What makes a field “derivable”?

There is no hard-and-fast rule for determining whether a field is derivable, but consider the following situation: suppose we have a List<String> field and another int field representing the number of elements in that list. Whenever we add or remove an element from our list, we would have to update our size field accordingly, and neglecting to do so (an easy mistake to make) could result in tricky bugs. In situations like these, it is simpler to store only the List<String> and make a quick call to the size() method when we need the number of elements.

Constructor

Initialize fields in the constructor. The purpose of the constructor is to set up the state of the object. If we have no fields to initialize, we should omit the constructor entirely rather than include an empty constructor.

When applicable, reduce redundancy by using the this() keyword to call another constructor in the same class. Generally, the constructors that assume more defaults (fewer parameters) call the more general constructors (more parameters).

Forbidden Features

Why do we have forbidden features?

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 122. 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. Furthermore, many of these features circumvent the concepts we seek to teach in CSE 122 and can prevent coursework from demonstrating full understanding of said concepts.

The following features should NEVER be used in any graded CSE 122 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
  • the var keyword and local variable type interface
  • lambdas, streams, method references (Java 8)
  • the toCharArray, join, trim, split, repeat(n), and matches methods of String
  • the StringBuilder, StringBuffer, StringJoiner, StringTokenizer classes
  • the methods Arrays.fill, Arrays.copyOf, Arrays.copyOfRange, and Arrays.sort
  • the toArray and clone methods of ArrayList
  • the Collections class and its methods
  • package declarations
  • System.console
  • LinkedHashMap

(Don’t worry if you are not familiar with some 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.)

Additional Preferences

A few more common style practices…

Spacing

Java is not a whitespace-sensitive language, but maintaining good spacing throughout the program improves its readability. 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);
    

One statement per line

Each time you finish a statement with a semicolon, you should use a new line to begin the next statement. The exception to this rule is that the for loop header should have its 3 parts all on one line.

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 \n characters.
  • Consecutive System.out.print() statements, or a System.out.print() statement followed by a System.out.println() statement, should be combined whenever possible.

Curly braces

he 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 the body of a method, constructor, or class.

Magic numbers

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 program specification might have more information.

Boolean zen

Make use of booleans to simplify if/else structures and boolean expressions. For instance, rather than checking if (condition == true), we can write if (condition). The opposite also applies: if(!condition) is equivalent and shorter than if (condition == false). It is almost always possible to remove the usage of the literal true and false values in expressions and conditional structures without changing or complicating the code’s functionality.

Improper loop bounds

Avoid pulling out loop iterations unnecessarily. The loop should handle as much of the code as possible to make the program more flexible and less complicated.

// This is a bad example because we can incorporate the below statement
// into the for loop!
System.out.print("*");
for (int i = 0; i < 4; i++) {
    System.out.print("*");
}
// This is a good example because the loop handles as much
// as it can.
for (int i = 0; i < 5; i++) {
    System.out.print("*");
}

Acknowledgements

Thanks to:

  • Ava Doan
  • Cady Xia
  • Colin Lim
  • Connor Sun
  • Dani Ballisteros
  • Diya Yaga
  • Elba Garza
  • Hanna Pan
  • Ivy Wang
  • Nicole Ham
  • Rohan Boinpally
  • Wesley Li
  • Yang Wu

and the rest of the CSE 12X team, who all provided feedback that shaped this guide!

Finally, thanks to all the students reading. Surely you’ll now 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).