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.
// #BAD!
private ArrayList<Object> container = new ArrayList<>();
// #GOOD!
private ArrayList<Object> container;
public SomeClass() {
container = new ArrayList<>();
}
public SomeClass(int initialCapactity) {
container = new ArrayList<>(initialCapactity);
}
This modifier ensures clients of the class cannot modify the internal data
without using an allowed (public
)
method.
ListNode,
DoubleListNode, TreeNode, BSTNode
), it is acceptable (and encouraged)
to use public
fields so that the client (you) can directly manipulate the field references
to other nodes without using setter methods.Why? As you’ll see when nested and inner classes are introduced, ideally these classes
would be private
nested or inner
classes, as clients should be abstracted away from the nodes themselves. Being
a private nested or inner class also means that the field access modifiers wouldn’t matter.
However, if we write them as separate classes, public field access is the
way to allow the nodes to be manipulated in the natural/conventional way.
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. This is a case of the overarching guidelines of minimize the scope of variables to the smallest necessary.
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.
5.1.4 Accessing fields
Prefer consistency when accessing fields. Assuming a getter exists for a field (which is not always the case) you can access the field in several ways
public class Point {
private int x;
public int getX() {
return x;
// other code not shown
}
We have the following options for accessing the field x in instance methods of the class:
public void accessXExamples() {
int temp1 = x; // direct, this. implicit
int temp2 = this.x; // explicit this.
int temp3 = getX();
}
Assuming there are no efficiency concerns (the getter method is O(1)), pick one style and use it consistently throughout your code.
If you prefer the direct, implicit this. approach (as most of the instructional staff does) it is acceptable to use the explicit this. when there are multiple objects of the same type as the calling object in scope. This is not required, but it is acceptable if it helps your understanding of which objects are being dealt with.
// #ACCEPTABLE This is an example method from the Point class.
// pre: other != null
// post: return the distance between this and other
public double distance(Point other) {
temp result = Math.pow(this.x - other.x, 2); // this.x could
be replaced with simply x, but this use is acceptable
temp += Math.pow(this.y - other.y, 2);
return Math.sqrt(temp);
}
5.1.5 Maintain Encapsulation.
Just as 5.1.2 requires the vast majority of fields (instance variables) to be private, it is important to maintain encapsulation. If you have a field that is a mutable object, such as an ArrayList, do not have a getter method providing a reference to that mutable object to a client. Doing so can lead to logic errors as the client may alter the object is some way.
If feel you must provide an instance variable that is a mutable object to a client, then make a copy of it to provide the client. This should likely be avoided as well though, because making the copy requires using both time and space. Ask yourself, why does the client need this data? Can I instead write a method that provides the client the answer they are looking for?
5.2.1 Prefer class constants
A static field belongs to the class as a whole. All objects of the class type share it as opposed to fields / instance variables. Every object of the class type has its own copy of fields / instance variables. Early programmers often misuse class variables as a form of global data, leading to code that is hard to understand, hard to debug, hard to maintain, and often leads to incorrect behavior.
While class variables can be useful in some rare occasions, prefer (in CS314)
class constants, declared final
.
public
allows a
client to directly interact with the constant.
private
means the
constant is only accessible inside the class itself. Decide if clients need
access to the class constant or whether you are using it to improve internal
readability of the class.
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.
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. You must determine if a constant should be local to a method only or a class constant available to the whole class. If the constant is used in only one method it should be local to the method. If it is used in 2 or more methods prefer making it a class constant.
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/constructors 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.
Avoid unnecessary 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. Likewise, don't declare a variable until it is needed.
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.
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;
}
public void generateNames(List<String> toFill) {
// add some elements to toFill
...
}
Do not use returns for empty statements in void methods.
// #BAD!
public void fooBAD(int x, int y) {
if (y == 0) {
return; // avoid
} else {
System.out.print(x / y);
}
}
// #ACCEPTABLE!
public void fooGOOD(int x, int y) {
if (y != 0) {
System.out.print(x / y);
}
}
Prefer to have a single return for non-void methods. However, it is acceptable to use multiple returns if the code is succinct and it is easier to simply return when we have found our answer. Likewise, in recursive methods multiple returns can lead to easier to understand code when we have a return for base case(s) and recursive cases, successful and failures.
// #ACCEPTABLE!
// pre: data != null
// post: return the first index of tgt in data or -1 if tgt is not present
public void indexOf(int[] data, int tgt) {
for (int i = 0; i < data.length; i++) {
if (data[i] == tgt) {
return i;
}
}
// never found tgt in data, never returned, tgt not present
in data
return -1;
}
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. An unsuspecting client of the method may not have wanted their data dismantled while your method ran for a different purpose.
Methods should do one thing. A method should have one job. Methods that try to do to many things shall be broken up into smaller methods to provide structure.
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).Minimize the scope of variables to the smallest necessary. 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.
Check method preconditions if possible and throw an appropriate exception if the precondition is not met.
Ensure the messages included with exceptions are meaningful. An exception without a message describing the failure provides little value over simply terminating the program, while one with a message describing the reason the exception was thrown can save a substantial amount of debugging time.
//#BAD!
// pre: data.length >= 7
public void printMovingAverage(int[] data) {
if (data.length < 7) {
throw new IllegalArgumentException();
// Not enough information.
//#GOOD!
// pre: data.length >= 7
public void printMovingAverage(int[] data) {
if (data.length < 7) {
throw new IllegalArgumentException("The length of the array data must be >= 7. "
"length of parameter data = " + data.length);
Consider the program you turn in your final draft. Like the final draft of an English paper or Chemistry lab report, include only what is necessary.
For you solution code especially, remove any and all debugging code, notes to yourself, TODO's, and previous versions of your solution, unused imports, and other comments and / or code that is no longer necessary for your solution.
You can of course save another version of your program with that debugging code and early attempts at solutions, but the code your turn in shall be pristine and without any unnecessary cruft. Take some well-deserved pride in what you have created.