Programming Java with Panache
A C++ Style Manual
David B. Sher
sherd@sunynassau.edu
Mat/Sta/CMP Dept.,
Style or panache is one of the ways programmers separate
the professionals from the incompetents.
Good style in your programs will improve your job prospects. Also good style in programs makes bugs
easier to find and remove. Often using good style allows you to avoid
bugs like mismatched ()’s in the first place.
Stylish programs will seem slower to write but actually they will be
faster since the style will force you to understand what you are doing.
A program with panache is entirely obvious. Each word of the program should have an
obvious meaning to the programmer and the reader of the program. The reason and necessity (or at least
convenience) of each element of the program should be obvious. Style should make every word of the program
so necessary that a reader could not even think of another way of writing the
same program. Of course, this is an
unachievable ideal but an attempt at an ideal leads to greatness.
In this document I will include two
programs called poor.java and GoodStyle.java.
poor.java is a program in the worst style that
I could make myself write. GoodStyle.java
has the best style I could easily write.
The program with poor style compiles with no warnings or erros but does
not work and has a variety of bugs. The good style program took about 1 hour to
get working.
poor.java
/* Extremely
*poor styled simple
program */
package Style;
public class poor {
static char
[] m38z3;
public
static
void
main(String[] z){if(m38z3[0]== // I don't
know sdjw
z[0].charAt(0));int oxo=m38z3
.length;while(--oxo>0)System.out.
print(m38z3[oxo]==z[0].charAt(oxo)?
'=':'!');}}
GoodStyle.java
/*
* class
designed with good style
* Copyright David B. Sher 2003
*/
package Style; // part collection of
programs demonstrating style points
import java.io.*; // allows interaction
with user
public class GoodStyle
{
// Used to get characters from user
private InputStreamReader ISR = new
InputStreamReader(System.in);
// Used to get lines from user
private BufferedReader fromUser = new BufferedReader(ISR,
1);
//
collection of characters for comparison
private char[] characters;
/**
initializes characters in class from user */
public GoodStyle()
{
try // handle any input problems
{
int size; // number of characters in GoodStyle
System.out.print("How many characters? ");
size = readInt(); // size of array from user
//
create an array of characters of the specified size
characters = new char[size];
//
initialize the characters in the array
int index=0; // used to keep track of which character to
enter into
//
keep getting characters from user till string is full
for(;;)
{ System.out.print("Enter
"+(characters.length-index)+" characters: ");
String
lineFromUser=fromUser.readLine(); // get line of chars
from user
//
transfer characters from line to array
for(int
lineIndex=0;lineIndex<lineFromUser.length();lineIndex+=1)
{ characters[index]=lineFromUser.charAt(lineIndex);
index+=1; // one more character in array
//
check if array full
if(index>=size) return;
}
// end for
}
// end for
}
catch(IOException error)
{ // output error that occured
System.err.println("Error
initializing: "+error.toString());
characters = new
char[0];
} // end catch
} //
end constructor
/**
compares array to string parameter and outputs = or !
* to indicate which
characters are equal
*/
public void compareTo(String comparison)
{
//
traverse first argument of program and compare to characters
for(int
index=0;(index<characters.length)&&(index<comparison.length());index+=1)
//
if the characters are the same then output '='
if(characters[index]==comparison.charAt(index))
System.out.print('=');
else System.out.print('!'); // otherwise output '!'
}
/**
executable code goes here */
public static void main(String[] args)
{
if(args==null || args.length<=0) // no argument can't run
{
System.err.println("Syntax is java Style.GoodStyle
word");
return;
}
else
{
GoodStyle thing= new GoodStyle(); // gets an initialized GoodStyle object
thing.compareTo(args[0]); // compares the string to the argument
}
} //
end main
/**Read an int
value from the keyboard*/
private int readInt() throws IOException
{
return Integer.parseInt(fromUser.readLine());
}
} // end GoodStyle
Note that GoodStyle.java is much larger than
poor.java. That is because of poor style
and design much was left out of poor.java such as any initialization of the
class record. Since I chose to
initialize the variable with user input in Java the result was a much larger
program but using panache, the resulting program is fairly clear anyway.
Indenting and alignment is required if the program will
work. I’ve seen many a student program
and when the indenting is wrong they don’t work, usually they don’t
compile. The first rule is that the
lines of a program should be on a single line (if they fit):
|
Wrong |
final
int m = M; float x[m]; int p = 2; |
|
Right |
float
sum = 0; // sum of the numbers in the
circularArray for calculating average float sumSquares = 0; // the sum of the squares of the
#'s in circularArray for calculating variance |
There is no reason for the type
float to be on the same line as the const m and on a different line from x[m]. The exceptions
to this rule are:
·
Lines
that are too long can be broken if:
·
You
break them so that it is clear that they are incomplete.
|
Wrong |
resultVariable = exoticMethod(x,municipalResult(y) |
|
Right |
resultVariable =exoticMethod(x,municipalResult(y)
+ |
·
You
indent the rest of the line to indicate that it is part of the previous line.
|
Wrong |
resultVariable = exoticMethod(x,municipalResult(y)
+ |
|
Right |
resultVariable = exoticMethod(x,municipalResult(y)
+ |
Every {,(, or [ must be aligned
with the corresponding },), or ] either horizontally or vertically. Examples of
horizontal alignment are sin(x),
array[index], or
for(index=0; index < size; index++) {
sum += array[index]; cout << array[index] << endl;}
Vertical alignment is used for
larger or more complex expressions like:
for (
index=0, activeValuesStart
= 3;
index < size;
index++, activeValuesStart
+= 2
)
{
sum += array[index];
cout << array[index]
<< endl;
}
Avoid code where the brackets don’t line up, even if you
were copying your code from a professional source like a text book or the help
documents with your compiler. A common
version of unaligned brackets is:
private void
DoesNothing(){ // this method
does nothing with an unimportant pointer
}
One
of the first signs of code that doesn’t work is when the code inside a method
or loop is not indented. The body of a
loop must be indented from the loop to show what lines are in the loop. The
body of a function is also indicated through indenting. If a long line is broken it should be
indented too. The lines inside an if statement or switch statement should also be
indented. The case statements will not
be indented but the lines in each case should be. Lines within a block must be aligned. Some examples of correct and wrong code are
shown below.
|
Wrong |
public static int
obscure(int y) { cout
<< “Doing something obscure to ” << y << endl; y /= y*y+1; ¬should be indented if(y>5)return
(y+1)/2; if(y<5)
return y; ¬should have same indent return 0; } |
|
Right |
public static int
obscure(int y) { cout
<< “Doing something obscure to ” << y << endl; y /=
y*y+1; if(y>5)return
(y+1)/2; if(y<5)
return y; return 0; } |
|
Wrong |
do { cin
>> x[i // I really like this
line ¬should be indented ]; s = s + x[
/* this line adds s to x[i] and puts the result in s */ i]; s -= x[(i+m-1)%m] ss +=
x[i]*x[i], ss =
ss - x[(i-1+m)%m]*x[i+m-1-m*((i+m-1)/m)]; cout
<< "Avg is: " << s/m ¬should be indented
<< "\tStdev is: " << ss/m -(s/m)*(s/m) <<
endl; } while(ss); |
|
Right |
do { cerr <<
"Enter New Number: "; cin >>
usersNumber; // read number from user sum -=
circularArray[position]; // remove
effect of current # on sum … cout <<
"After entering " << usersNumber << " the average
is " << sum
/ circularArraySize; // the
variance is the average of the squares - the square of the average cout <<
" the variance is " << (sumSquares/circularArraySize - } while (sumSquares > 0 ); // true only when array is cleared |
|
Right |
for(iterator=0;iterator<size;iterator++) //
print and increment all elements of array { cout << array[iterator] << endl; } |
|
Wrong |
switch(userInput[0]) |
|
Right |
switch(userInput[0]) |
|
Wrong |
// here we define a
typical class it will describe a student class student { protected: // this will contain the data. char
first[256],last[256],mi; // name
inforation … // constructors // first version
everything supplied student(char *f, char *l, char m,long id) { init((const char *) f, (const char *) l, m, id ) ; } … { return first; } … |
|
Right |
// here we define a
typical class it will describe a student class student protected: //
this will contain the data. char first[256],last[256],mi; // name inforation … // constructors // first version everything supplied student(char *f, char *l, char m,long id) { init((const char *) f, (const char *) l, m, id ) ;
} … { return first; } … |
Comments tell you what a program is about, the code tells
you what it does. Comments are about why
a line is in your code. Only obscure or confusing code
require a comment to tell you what the code does. For example:
// the %
makes the array circulate back to 0 when pos is circularArraySize -1
return (pos + 1)%circularArraySize;
is an example of obscure code that
needs to be explained. Usually code is
clearer than the above and what it does needs no further explanation.
In general you should not wait till you have completed the
code before you comment it. Writing a
comment on code makes you stop and think about what you are doing. The thinking allows you to avoid bugs before
you write them so the coding would go faster.
Also if you want to show your code to the instructor or to a friend,
having comments will mean that you don’t have to explain each line.
Every class file should begin with a large comment
(delimited with /** and */) that discusses the purpose of the code in that
file. If there are multiple files in the
program it should discuss how this file relates to other files in the
program. It should name the author of
the program, often with a copyright notice. If this is the file containing the
main function then the initial comment should discuss the behavior of the
program. An example of a good initial
comment is:
/**
This class
is meant to demonstrate how a well written program can
make a
complex idea clear.
Copyright David B. Sher 1998
*/
When introducing (declaring) a variable into a method it is
necessary to explain why you need this variable. If you need more than one line to describe a
variable then your variable is doing too much.
A variable should have a simple easily explained purpose. You should explain this purpose when you
write the variable declaration. If you
can not write a simple comment explaining the variable then you need to think
more before you write more code. Global
variable are particularly important to comment since they are used throughout
the program.
An example of variable commenting
is:
|
Wrong |
const int m = M; float ¬every variable and constant should have its own
line x[m]; int p = 2; ¬every variable should have a comment |
|
Right |
float
sum = 0; // sum of the numbers in the circularArray
for calculating average float
sumSquares = 0; // the sum of the squares of the
#'s in circularArray for calculating variance int position = 2; // starting position in
circular array |
When declaring a class variable it is doubly important to
specify why it is there. A class
variable usually will be accessed from many of the methods of the class. Such a variable also persists as long as the
object containing them persists or if they are static they persist as long as
the program exists. In any of these
cases a class variable must have a comment explaining why a persistent
primitive type variable or persistent reference to an object is
required.
Every method should have a comment that explains what a method
does and how it uses its parameters. Of
course, the comments must make sense unlike:
// rubber
baby bunker mice
int main()
Beyond that minimal level a comment should be sufficient so
that a stranger can call your function without having to read the code, just
the comment. Focus on the purpose of
the function rather than how it works, for example:
// This method
squares a number to help calculate variance
public static float square(float number) { return number*number; }
Loops are used in programs either to traverse or search a
data structure (like an array) or to interact with the user, usually. If you are traversing or searching a data
structure you must explain which data structure is being searched or
traversed. You must also explain why you
are doing this. If it is a user interaction
loop a shorter comment is usually sufficient since such loops tend to be self
explanatory. If the loop is used for a
more exotic purpose you should give a more extensive explanation since such a
use of a loop will be surprising for experienced programmers.
Some examples of comments on loops
are:
// Copies
first N elements from array1 to array2
for(index=0;index<N;index++) array1[index]=array2[index];
// copies
elements from linked list into an array
for(linkPointer = list->next, index=0 ; linkPointer != NULL ;
linkPointer=linkPoint->next,index++)
array[index]=linkPointer->data;
Conditionals are if
and switch statements. Often the actual condition in an if statement does
not perfectly clarify why you are testing the conditional. For example:
if(list->next != null) // then
the list has no elements to act on
doSomething();
else // there
are elements in the list
It is usually necessary to examine each case in a switch
statement and what it means. This comes
up particularly often when parsing input:
//
determine the command from the first letter in the word
switch(command[0])
{
case ‘a’:
case ‘A’: // the
activate command
Activate();
break;
…
default: // can’t figure out which
command you want to execute
cerr
<< “I do not understand “ << command << endl;
}
Many other lines require comments to clarify them. Any line that is not a simple print statement
or equally obvious should have a comment explaining why the statement is
necessary in the program. If you don’t
know why then you are programming too quickly.
Unless the line is quite unclear you need only express why the line is
there and not what it does.
|
Wrong |
Array[x] /= 2; ¬this line needs a comment cout << “Lets boogie\n” //
prints a line which says lets boogie ¬this line doesn’t x++; //
this line adds 1 to x ¬this comment doesn’t explain why |
|
Right |
size++;
// after inserting the element increase the
size of the list cout <<
“Copyright David B. Sher 1998” |
Methods, variables and types need names that (in order of
importance):
1.
indicate
the purpose of the item,
2.
are
easily remembered,
3.
and are unique.
If a local variable is meant to index the array you can
call it index. If there are several
array parameters to the function – inputArray, outputArray - for example, you
can use indexInputArray as a variable name.
Variables names can contain several words which are separated by
capitalization or by _’s (user_name).
More than two words in a variable name can make it too difficult to
remember. Avoid words that are difficult
to spell, like receive. There is a
custom of using one letter variable names for indices, like array[i]. Such names are common but should be avoided
unless you are implementing mathematical formulai. Similarly unless you are referring to a
position on the X coordinate avoid using the name x for a variable.
Primitive type variables should be singular nouns since
they represent a single measure count or character. Arrays usually should be plural nouns. Methods should be verbs since they represent
an action their object can perform.
Classes and interfaces should be category nouns representing the idea
that a class or interface
represents.
The names of all class variables and methods should start
with a lowercase letter. Classes and interfaces should
start with capitol letters. This allows
one to instantly recognize whether a name is a type or part of an object.
Packages start with small letters also.
The general rule is: variable names should be words or word
combinations containing ordinary vowels and consonants. This allows you to talk about your program
with professors, bosses, and colleagues.
It is difficult to discuss how the variable mxtplztz’s value changes in each iteration of a loop.
Avoid:
·
Giving
any variable the same name as a type.
For example: float
float; struct link link;
·
Giving
a local variable the same name as a parameter (will cause
’s for sure). For example:
void deleteElement(list
input, int pos)
{ float
pos; // this is a
because the parameter pos can not be used in
the program now.
·
Giving
a local variable the same name as a global variable.
·
Giving
two local variables the same name unless they do exactly the same thing.
C, C++ and Java were designed to be programmed by experts,
code written in these languages could not avoid being obscure. Many features of this language were designed
to encourage efficiencies no longer important in modern machines and
programs. A good programmer can avoid
using obsolete and confusing language features.
Some language features offer advantages the designers did not
imagine. You show panache by taking
advantage of features that improve and avoiding the rest.
There are several obscure uses of punctuation marks in Java
that can confuse novice or distracted programmers. In particular, the ,
should be avoided except as a parameter separator. Other legitimate uses for
,’s are separating declarations and using inside a for loop where ;’s are not allowed.
|
Wrong |
if(x>3,x<7) … // pointless test of x against 3 |
|
Right |
max(x,y) //
, separates parameters |
|
Right |
for(int first=0,last=size-1; first<size; first+=1,last-=1) |
Another obscure piece of C syntax is the ?: combination. This is used as an if statement when you can not use the if
statement. Avoid using this syntax since it is obscure and often
confusing.
The single & and |
punctuation marks are used for bit manipulation. Unless you are masking bits avoid using this syntax.
If you don’t know what masking bits are then you shouldn’t use these
operators. Also >> and <<
should only be applied between integers when you actually are doing bit
manipulations. Avoid using >> and << for
integer multiplication and division.
Usually you can rely on your compiler’s optimizer for such simple
tricks.
Whenever ++ or --
appear in a program it causes a side effect which can result in
unexpected program behavior. For example
temp=array[index++]; mysteriously changes the value of index while it also accesses an
array. Combining many operations on one
line might be a fun game but the pain of unscrambling the confusing code that
can result is simply not worth it. Most
of what we use ++
for can be done as easily with +=1. Hence, temp=array[index++]; is the same as temp=array[index]; index+=1; but less obvious; and we must all
strive to be obvious!
Clear programming requires organization of the code. In particular people have limited attention
spans and can best focus on small items.
Java allows us to break up complex ideas into smaller structures. Focus on how one can make the items in your
program smaller and simpler. Avoid or
break up methods that take more than 15 lines to write.
Methods that do several tasks should call methods that do
simple tasks. Often simple methods can
be used in many parts of the program. Such
methods should be public and, if appropriate, static. Some methods should be dedicated to the user
interface or interfacing with the file system.
There should be a main method that calls these when necessary. The other methods should avoid any file
system or user interaction. This allows
these methods to be used many different user interfaces. Even error messages can be handled with
throws.
If you organize your program this way you will have a more
organized program and you will be able to reuse your carefully written classes.
If a method never uses any of the class variables or
non-static methods of its class it should always be static. However if most of the methods you are writing
are static perhaps there is something wrong with the class organization. The purpose of a class is to hold and
manipulate persistent data which is encoded as class variables. There may be a few classes in a project like
the Math class in java.lang whose purpose is collect static objects and
packages. However usually use of a lot
of static data and methods shows that the programmer is not thinking in an
object oriented manner.
Classes whose purpose is related such as classes for sound
output or classes for special effects should be gathered into packages. Each package should have a name indicating what
kinds of services the package provides.
All the packages designed for a project should be subfolders of the
project folder. Having packages within
packages such as java.awt.events just makes the job of the programmer
harder.
Interfaces allow classes from a variety of different
packages to fit into the same variable.
An interface describes what methods and variables a class requires but
not how it implements the methods.
Interfaces are used the same as classes and are very useful for
collection types. Generally when you
need to use an object of a different type than your class it is better to use
an interface since a wide variety of classes can fit that description.
Classes in Java allow you to design types for advanced data
structures. It gives you to features to
make these types operate almost like built in types. This power can result in beautifully clear
code or incredibly confusing code. If
you design well your classes will be clear and easy to use.
All data members (members that are not functions) should be
protected. Avoid
private members since that makes derived
members behave differently than members of the type. Member functions that can only be called from
members should also be protected.
Class variables are members of a class that contain data
rather than methods to manipulate the data.
The data members should be protected or private to
prevent inconsistent data in the class. If
a local variable suffices do not create a data member. The data members should hold data that
persists and is manipulated by several member functions.
No one is perfect.
No one writes perfectly clear programs.
Even the example above has flaws.
If you are a perfectionist, think twice about writing a program, they
are intolerably imperfect. Do not let
this stop you from writing the best program you can. Do not let poor examples that come with your
compiler or your text book dismay you. Writing with panache will result in programs
that are fast to write and easy to read.
You will be able to use repeatedly reuse your code and save a lot of
time.