CSE143 Java Style Guide
1 Introduction
This 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 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 General Style Deductions and 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 style 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 homework 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 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
- Place field declarations at the top of your class.
- 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. Programmers also commonly choose to put related public/private pair methods together so that another viewer of the code can see the pair at the same time.
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.
- 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.
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
Exception code should come at the top of the method. Everything related to checking for and throwing the exception (computing a value to be used in the condition, the if statement itself) counts as exception code. Any code unrelated to the exception conditions / throwing should be placed lower than exception code. This is both an attempt to fail early to avoid unnecessary computation and a readability rule.
If an exception check cannot be checked at the beginning of the method (e.g. requires significant computation / additional statements), check for it when it can be more easily detected as the method continues.
// 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(); } ... }
Do not attach else statements onto exception checks to connect regular behavior code. Following an exception check, non-exception code should not be placed in an attached else. This is another readability rule (mostly done by convention. Another reason: why indent all of the method's regular behavior code a whole level if the structure is still clear without indenting?) as well as a logic rule. Exception code is fundamentally different than 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.
// 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 if
s 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.
- 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"); } }
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.1.1 Initialize fields inside the constructor
Do not initialize fields at declaration - this is both a flexibility and readability rule. It's possible that you want to initialize a field to different values in different constructors. Initializing a field at declaration and again in a constructor is both redundant and distracting.
4.1.2 Fields should always be declared with the private
access modifier.
This modifier ensures clients of the class cannot modify the internal data without using an allowed (public
) method.
Note: There is one exception to this rule. For node classes (ListNode
, AssassinNode
, IntTreeNode
, QuestionNode
, HuffmanNode
) that you will work with in lecture and for homework, it is acceptable (and encouraged) to use public
fields so that the client (you again) can directly manipulate the field references to other nodes without using setter methods.
Why? As you'll see when inner classes are introduced, ideally these classes would be private
inner classes, as clients should be abstracted away from the nodes themselves. Being an inner class also means that the field access modifiers wouldn't matter. However, we're writing them as separate classes, so public field access is the way to allow the nodes to be manipulated in the natural / conventional way.
4.1.3 Avoid extra fields
The more fields your class has, the more difficult it becomes to maintain and reason about your code. With more fields, each instance of the class will also take up more memory.
Tip: When revising your code, watch out for fields that are only used in one public method, and/or that are recomputed / cleared every call to a specific method. These can likely be simplified to local variables within the scope of that public method / its private helper methods. Also watch out for redundant fields that can be inferred from the properties of other fields.
Note: Designing a class always comes with its own set of tradeoffs, and deciding on what fields to include can be particularly tricky. In some situations, there may be debate about whether or not to make something a field to improve efficiency. Suppose you were writing a class that needed to keep track of a String
field. To also store the .length()
of the String
as a field might seem "more efficient" (not actually more efficient), because then the code of the class won't have to call .length()
more times, but this is a case that .length()
is fast enough (and accessible by accessing the String
itself) that storing this field isn't significantly beneficial and should be omitted. You might also run into cases where it does make sense to include another field (that could be omitted) because it makes code significantly easier to write or it relieves a bunch of recomputation. In any case, you should always review what fields you have and consider any tradeoffs, pros/cons, etc. to your current design and the other possibilities. See the section on efficiency for some more thoughts.
4.2 Class Constants
Class constants should be declared with public
, static
, final
modifiers.
public
allows a client to directly interact with the constantstatic
means the constant is associated with the class rather than a specific instance of the class. (e.g. each instance of a class should have its own independent fields from other instances, but it's fine if they all share the same class constant value)final
prevents the constant from being assigned a new value. i.e. the initialized value is final.
Create and use class constants when directed by the spec and where appropriate. A good creation/use of a constant constitutes replacing a raw value that is used multiple times in your program with a constant that stores the value, but with more descriptive and readable name. This also provides more flexibility if the raw value ever needs to change.
Note: Class constants 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
- Do not add additional behavior beyond what's described in the spec. This includes additional exceptions - only include exceptions 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.
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.
- 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). - Clients of a class should never have to set fields of an object immediately after construction - there should be a constructor included for this situation. The general behavior of a constructor is to set up the state of an object correctly, and this should encompass setting fields.
- Clients of a class should never have to pass in dummy values to a constructor to set up the state they want - instead, there should be a constructor included so that only the relevant parameters need to be passed.
- Only implement constructors that are used somewhere.
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
- Fields, local variables, and methods should be
camelCased
, where every word after the first begins with capitalization. - Class names and constructors 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.
Note: Private helper methods that aide a constructor are still methods (have explicit return types and aren't used with new
) and should be camelCased unlike the constructor itself.
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.
- If you're using an
int
to keep track of whether it's one value or another, you likely want to use aboolean
instead. - If you're using a
String
to keep track of a single letter, you probably want to use achar
. - If you're using
Integer
because you want to keep track of a sum, you probably can just useint
.
6.5 Funky arrays or other data structures
The point of a data structure is to store related values. If your data structure just has one value in it, it probably should just be a variable of a simpler type like the String or int it is. Another example of funky usage is to store unrelated values in the same data structure. For example, one way to store all your variables would be to use a Map<String, Object>
where the keys are the names of the original variables and the objects are just the value of the variable. This would would be a bad usage of a map and data structures in general.
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.
Tip: Watch out for unnecessary object computation to do with fields.
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. Using true
or false
literally in your code isn't inherently redundant, but it's something to double check.
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!");
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.
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 Reuse existing code and methods
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 value) { for (int i = 0; i < size; i++) { if (elementData[i] == value) { return i; } } return -1; } public boolean contains(int value) { for (int i = 0; i < size; i++) { if (elementData[i] == value) { return true; } } return false; }
public boolean contains(int value) { return indexOf(value) >= 0; }
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 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 CSE143, but are always good to consider.
9.1 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.
9.2 Convenience methods
This is a section about reimplementing methods that has to do with smaller-scale redundancies than Reimplementing Methods (Section 8.6). Try to take advantage of already-implemeted methods (that aren't from Java 8 or later) 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())
- use stringA.equalsIgnoreCase(stringB)
instead.
10 Acknowledgements and Notes
Thanks to Google for providing the template that this guide was created with.
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).