A programming task involves many decisions. We get to choose names for variables, parameters, and methods. We figure out what the patterns are and whether or not we create methods for them. A good programming style produces code that is easy for others to understand. When you work on a particular project, there may be some elements of programming style that are dictated to you. But the overriding principle of programming style is to write code so that a future reader of the code can easily understand it. The future reader may even be you!
Just as with a paper or a poem, your first draft of a program, even the first version that actually works, is rarely the best. Writing the first version will help you figure out what the right abstractions are, how you can modularize the code, etc. Rewriting for readability is also a normal part of the process.
Some general guidelines follow:
- Pick meaningful names for variables and methods.
In principle, variable and method names can be anything, but it's far better if names reflect the role of their corresponding values in the program. For example, our program may draw a star on a background. Then, we may want variables called
backgroundColor
andstarColor
, which need no additional documentation since the names are self-explanatory. It is a Java convention to name things by running words together and capitalizing the start of words except for the very first word. (This convention is called "camelCase" because the up-and-down nature of the letters resembles camel humps.) If you have programmed in other languages before, you may be used to using underscores between words like so,background_color
andstar_color
. We don't care which convention you follow, but you should be consistent within a given program.If we want a method that finds out if a buggle is at a location called
school
, a good name would beisAtSchool()
. The method should evaluate totrue
if the buggle is at school andfalse
otherwise. If we want a method that draws a star, a good name would bestar()
ordrawStar()
.It's very confusing when we use names like
a
,B
, orxyz
. While it may be fun to pick names likebonkers
to represent a method likeisAtSchool()
, our code will be hard to read, since it's hard to remember what the name stood for. That's why we should always give names that are meaningful. Other people should be able to read our names and understand almost instantly what they stand for. - Use methods to give names to meaningful sequences of
events
It may be easy to write code like
if (this.getPosition().equals(new Location(4, 6))) { this.right(); this.right(); this.right(); this.right(); this.setPosition(new Location(1, 1)); }
But no one can understand what is going on unless they read each line of code. Also, it's hard to get the big picture of what is going on. The following code is clearer:
private static Location home = new Location(1, 1); private static Location school = new Location(4, 6); // ... if (this.getPosition().equals(school)) { spin(); goHome(); } // ... public void spin() { this.right(); this.right(); this.right(); this.right(); } public void goHome() { this.setPosition(home); }
Although the second version takes up more lines of code, it is much easier to read. Programming is an art. We don't want to overdo creating methods, but we need to be willing to use them when it will help our understanding of what is going on.
- Use methods to capture general patterns
Let's say we want to draw a picture by having a buggle draw lines in all sorts of directions. For example:
public class MyBuggleWorld extends BuggleWorld { public void run() { Buggle barb = new Buggle(); barb.setPosition(new Location(4, 5)); barb.setHeading(Direction.SOUTH); barb.setColor(Color.cyan); barb.forward(4); barb.setPosition(new Location(2, 1)); barb.setHeading(Direction.NORTH); barb.setColor(Color.green); barb.forward(6); barb.setPosition(new Location(5, 7)); barb.setColor(Color.red); barb.setHeading(Direction.WEST); barb.forward(3); } }
This works. However, it is better to capture the pattern involved in drawing a line into a method and to use that method in our
run()
method. So, our new code will create a methodline()
which we will use.public class MyBuggleWorld extends BuggleWorld { public void run() { // a LineBuggle knows how to draw lines LineBuggle barb = new LineBuggle(); barb.line(new Location(4, 5), Direction.SOUTH, Color.cyan, 4); barb.line(new Location(2, 1), Direction.NORTH, Color.green, 6); barb.line(new Location(5, 7), Direction.WEST, Color.red, 3); } } public class LineBuggle extends Buggle { /* draws a line from specified location, in specified direction * and color by moving buggle forward the specified number of steps */ public void line(Location start, Direction dir, Color col, int length) { this.setPosition(start); this.setHeading(dir); this.setColor(col); this.forward(length); } }
Capturing the pattern in a method allows us to reuse the pattern without having to rewrite the same lines of code over and over again. It also makes it easier for people to understand what our code is doing. Most importantly, if we have to change the way the pattern works, we only have to change it in one place and not in all the places the pattern is actually used. So the code is easier to maintain.
- Use local variables to capture specific instances of
patterns
Let's say we have a
patch()
method that allows us to draw a square in PictureWorld. Now we want to create aredGreenFrame
that looks like a four by four grid with red around the edges and green inside. Let's also assume we have thefourPics()
method available to us (but not thefourSame()
orcorner()
orrotations()
methods). How can we do this? Here's one way:
public Picture redGreenFrame () { return fourPics(fourPics(patch(Color.red), patch(Color.red), patch(Color.red), patch(Color.green)), fourPics(patch(Color.red), patch(Color.red), patch(Color.green), patch(Color.red)), fourPics(patch(Color.red), patch(Color.green), patch(Color.red), patch(Color.red)), fourPics(patch(Color.green), patch(Color.red), patch(Color.red), patch(Color.red))); }
Yes, that works, but what a mess! It doesn't make it obvious that all four quadrants are essentially the same pattern rotated in a different way. We should write the code so that people who read the code can see the patterns without having to work out every line of code in their mind. Here's another way to do this:
public Picture redGreenFrame () { return fourPics(fourPics(patch(Color.red), patch(Color.red), patch(Color.red), patch(Color.green)), flipVertically(fourPics(patch(Color.red), patch(Color.red), patch(Color.red), patch(Color.green))), flipHorizontally(fourPics(patch(Color.red), patch(Color.red), patch(Color.red), patch(Color.green))), flipDiagonally(fourPics(patch(Color.red), patch(Color.red), patch(Color.red), patch(Color.green)))); }
This also works. However,the pattern is still not clear since the reader needs to check if the same colors are being used in the same places. What we need to do is give the pattern a name. Here's another try:
public Picture redGreenFrame () { return fourPics(redGreenCorner(), flipVertically(redGreenCorner()), flipHorizontally(redGreenCorner()), flipDiagonally(redGreenCorner())); } public Picture redGreenCorner () { return fourPics(patch(Color.red), patch(Color.red), patch(Color.red), patch(Color.green)); }
Ok, this works. However, it's really overkill to use a method here. Methods should really be used to define general patterns that you use over and over again. By general, we mean that we should be able to change characteristics of the pattern like color, width, size, etc.
patch()
is a good method because we can use it to create patches of any color. A method that returns a red patch is not a good idea. Instead, we should be using local variables. Here's our final code forredGreenFrame()
:public Picture redGreenFrame () { Picture redPatch = patch(Color.red); Picture greenPatch = patch(Color.green); Picture redGreenCorner = fourPics(redPatch, redPatch, redPatch, greenPatch); return fourPics(redGreenCorner, flipVertically(redGreenCorner), flipHorizontally(redGreenCorner), flipDiagonally(redGreenCorner)); }
Note: What is the difference between
redGreenCorner()
in the next to last sample code andredGreenCorner
in the last sample? The first is a method invocation, the second is referencing a local variable. Don't get confused! If we define methods, even if they don't have parameters, we must put empty parentheses after the method name to use the method. Variables are just names for things and therefore are never used with parentheses. - Use comments to clarify code
Comments should be added to clarify what is going on. A block of comments at the start of a file, class definition, or method definition that describes the purpose, contract, or general approach to the corresponding code can be very helpful for those who read your code.
Add comments to describe obscure code or something not obvious (or better yet, re-write the code to make things obvious!). For example, in the following code, the reader may have forgotten what the buggle
bobby
was up to:
Overcommenting is often worse than undercommenting. Too many comments (eg, comments for every line) distract from the code, and if you need that much explanation, then you should probably re-write the program. Do not write comments that duplicate information that is apparent to anyone who knows the programming language.// finish yellow ring bobby.forward(6); bobby.left(); bobby.forward(6);
This a dreadful comment. First, anyone who knows Java (or almost any other language) knows what is in the comment. But worse, consider what happens if you discover thatint radius = 2.0 * x; // set radius to 2 times x
radius
should really be2.5 * x
. It is common in such cases for the code to get updated but not the comment. Now a future reader has to wonder whether the code or the comment is right. They will be confused and and will waste valuable time figuring out what should be obvious.Use indentation to enhance readability.Indentation can make it easy for the reader to see how your code fits together without having to read the details of what it is doing. Indent enough so that the structure of your code is clear without reading — we have arranged for the DrJava installations on campus to indent 5 spaces. Many editors (like DrJava or Emacs) can automatically indent your code. Use this feature to your advantage. For example, it is a good idea to re-indent our code from time to time (eg, in DrJava, select the whole program and hit the Tab key). This can help you find missing or misplaced braces as well as unintended nesting.
Indent the components of a compound statement. A compound statement is a statement that contains other statements, like an
if
,while
,for
, or a block (a group of statements enclosed in curly braces). For example:
but notif (isOverBagel()) numBagels++;
if (isOverBagel()) numBagels++;
If arguments to a method appear on separate lines, line them up:
return fourPics(fourSame(p3), empty(), empty(), sierpinski(4, 200));
- Use blank lines to enhance readability.
Blank lines are not a precious resource. Use them to divide code into meaningful chunks, possibly with a comment describing the purpose of the block.
- Don't skimp on spaces.
Spaces are not precious either,and squeezing them out(for example,to keep moreitemsonaline)reduces readability. So put spaces after commas in parameter lists; around operators (like
+
); after keywords (likeif
,for
, andwhile
); and before open curly braces. Lining things up on two lines is better than squeezing too much onto one line. When in doubt, spread it out! - Use curly braces in a way that enhances readability.
They seem like such trivial things, but, in conjunction with good indentation, they can show the structure of your code at a glance. Used badly, they can make editing a program error prone and painful.
Close braces go on a line by themselves, possibly followed by a comment that tells us what they are closing (if it may have scrolled off the screen):
notif (isOverBagel()) { numBagels++; }
if (isOverBagel()) { numBagels++;}
This makes it easier to update code and allows the readers' eyes to see the region of the program executed when the
if
test is true. Annotating a close brace can help if a class definition goes over several pages.
Here, the reader knows the close curly brace at the end closes the definition ofclass MyClass { . . . } // class MyClass
MyClass
(or is intended to should a compiler error suggests something else is going on).The exception is for additional syntax that is part of the construct in question. For example,
else
clauses (andelse if
) are written this way:if (isOverBagel()) { numBagels++; } else { numSpaces++; }
try
/catch
is similar.For blocks used as part of a compound statement, the open brace goes on the end of the line of the introduction of the compound statement. That is, for an
if
,else
,while
, the code will look like this:int numBagels = 0; while (!isFacingWall()) { if (isOverBagel()) numBagels++; forward(); } if (isOverBagel()) // Test the last square numBagels++;
For open curly braces that are not starting blocks as part of a compound statement, i.e., for braces that begin a class or method definition, there are two accepted standards (actually, there are more, but these two the main ones). Either is fine, but don't mix them, certainly not in the same program.
- Because classes and methods are different, employ a
different standard,
namely, the open braces go on a line by themselves indented with
the header of the definition they belong to. Thus:
This emphasizes that classes and methods are really different things (declarations rather than compound statements with a block). It also provides some visual space between the header and the body of the method or class. And it works nicely when we have more complex definitions that include exceptions: the extra lines are set off from the code body so they obviously belong to the header:public class Magic8Ball extends Applet { private Image testImage; public void init() { testImage = getImage(getDocumentBase(), "pic1.gif"); } public void paint (Graphics g) { g.drawImage(testImage, 50, 50, this); g.drawString(StringChooser.chooseLine(getCodeBase() + "answers.txt"), 80, 215); } }
public static void copyFile(BufferedReader input, BufferedWriter output) throws IOException { // File manipulation code here }
- The other approach says to treat these cases uniformly with
compound statements with blocks. The logic being that,
though they are different, the containment implied is
similar. Thus:
An advantage of this second style is that it takes fewer lines of vertical space, which makes it possible to see more of the program in a given number of lines in a text editor, on a slide, on a blackboard, etc.public class Magic8Ball extends Applet { private Image testImage; public void init() { testImage = getImage(getDocumentBase(), "pic1.gif"); } public void paint (Graphics g) { g.drawImage(testImage, 50, 50, this); g.drawString(StringChooser.chooseLine(getCodeBase() + "answers.txt"), 80, 215); } }
- Because classes and methods are different, employ a
different standard,
namely, the open braces go on a line by themselves indented with
the header of the definition they belong to. Thus: