| The Do's and Don'ts of Java Programming By Rob Signorelli |
|
Programming is art. Pretend that you're doing a
painting of a house. Now you can throw some colors
together and effectively depict a house with little
effort... a block, a triangle, some color here and
there. Also, I'm sure you'd agree with me that the
house you paint and a house say, Van Gogh, would paint
would look very differently. Both paintings are
effectively houses, but only one of yours is probably
good enough to sell for millions of dollars (no
offense to those of you who are artistically talented,
but I don't think you'll be outselling Van Gogh any
time soon). "Is there a point to this, Rob?" you may be asking yourself. Yes there is... programming works much the same way. I can produce a number of equally functional solutions to the same problem. That does not mean, however, that all solutions are equally as good. The point of this document is to show you some of the common techniques that we can use to arrive at clean, elegant solutions more often. Let's take the following example: Write a method that takes an integer and returns a boolean value describing whether or not the number is odd. Here are 2 ways to go about this:
/**
* This is a good, elegant way, similar to how you
* would hope to answer this in an assignment or in
* the real world
*/
public boolean isOdd(int number)
{
return (number % 2) != 0;
}
/**
* This way, though still correct, uses some techniques
* that make the code confusing and unnecessarily long
*/
public boolean isOdd(int number)
{
boolean result;
int i;
result = true; // default value;
for( i = 0; i < number; i++ )
{
// every other number is odd, so when we finish
// the loop at 'number' we'll have our answer
if( result == true )
result = false;
else
result = true;
}
return result;
}Aside from being an extremely silly way to go about answering the problem, the second way utilizes a lot of unnecessary code to accomplish things that can be done with much less code. Now lets get into what these specific things are. |
| Conservative, Smart Use of Variables |
Let's start simple. We use variables. We always
will. That being the case, let's use them in a fashion
that does not waste space (lines of code space) that
we don't have to. Take a look at the following 3
examples. They all are syntactically correct, and
more importantly, accomplish the same task (declaring
and initializing some variables). Notice that the latter
ones get a little less annoying to read since they waste
less space in our code:// 1) This is the unnecessarily long way (uses 6 lines) String myString; String someOtherString; String jamesKPolk; myString = "hello"; someOtherString = "there"; jamesKPolk = "James K Polk"; // 2) This way is a little better (uses 4 lines) String myString, someOtherString, jamesKPolk; myString = "hello"; someOtherString = "there"; jamesKPolk = "James K Polk"; // 3) This way is even better (uses just 3 lines) String myString = "hello"; String someOtherString = "there"; String jamesKPolk = "James K Polk"; This note on coding will not change the world, nor will it make or break your ability to code solutions to problems. It WILL, however make your programs much easier to read if you declare and initalize your varialbes like we did in the 3rd example. When you first start programming, your biggest concern is making your code work, plain and simple. Oftentimes, that concern makes us ignore the fact that we created a few variables in our code that we really didn't need to. The result is extra code that doesn't add to our program's functionality, but rather just clutters up its overall readability. Let's see a basic example:
/**
* This method unnecessarily creates a boolean variable
* since we could just immediately return the result
*/
public int add(int a, int b)
{
int sum = a + b;
return sum;
}
/**
* Let's avoid using unnecessary variables, just return it
*/
public int add(int a, int b)
{
return (a + b);
}Some of you may feel that this is extremely picky... well, it is. BUT, why make people have to read extra lines of code that we don't have to. Besides, it's always nice to have clean, short code that is right to the point (your ability to get high homework grades depends on it). Now let's take a look at a much hairier example. This one takes using unnecessary variables to the extreme. Hopefully after seeing these two (functionally identical) methods, you'll get a better appreciation for conservative variable usage.
/**
* Use some wasteful variables to return
* a string adjective based on the age
*/
public String translateAge(int age)
{
String result;
String child = "child";
String teen = "teen";
String adult = "adult";
String middleAged = "middle aged";
String old = "old";
if( age < 13 )
result = child;
else if( age < 20 )
result = teen;
else if( age < 40 )
result = adult;
else if( age < 65 )
result = middleAged;
else
result = old;
return result;
}Now let's look at the same exact method, only written in a fashion that minimizes the number of variables we use.
/**
* Don't use any variables while giving a
* verbal description of the entered age
*/
public String translateAge(int age)
{
if( age < 13 )
return "child";
else if( age < 20 )
return "teen";
else if( age < 40 )
return"adult";
else if( age < 65 )
return "middle aged";
else
return "old";
}
Again, the code accomplishes the exact same thing, but we didn't waste a lot of space declaring, initializing and using variables that we didn't have to. The end result is that the code is a lot shorter making it much less intimidating to look at and easier to understand. |
| Programming with Booleans |
|
Ah booleans. There is a lot of power at your
fingertips by having the ability to have true/false
values. Let's not abuse this power with ugly (but
sadly still functional) code. Remember, with great
power comes great responsibility. There are a number of basic ideas that you should keep in mind when programming with boolean values. These are things that may not be intuitively obvious, but once you see them for the first time, hopefully they will make sense so you can use these techniques in your own code. Toggling a boolean's value There will be a number of times in your programming career when you will want to turn a true into a false and a false into a true. At first glance, we can accomplish this with a simple if/else statement: if( myBoolean )
myBoolean = false;
else
myBoolean = true;In a nutshell... don't ever ever ever ever ever ever do this... ever. We can accomplish the same task by using the exclaimation point (!). Remember that in programming, ! means "not" or "opposite" of a boolean expression. Using this, we can accomplish the same feat as before, but with a single line of code: myBoolean = !myBoolean; To give some final detail to this: (!true == false) and equally (!false == true). So when we ran the last code segment, we just flip the value of myBoolean to the opposite value. Pretty nifty, eh? Returning boolean values It's not too uncommon to write methods that return a boolean value. Something else that is rather common is the unnecessary use of if/else statements for return either true or false. Take this example. It uses an if/else to determine which boolean value to ultimately return: public boolean isPositive(int n)
{
if( n > 0 )
return true;
else
return false;
}This looks perfectly legit, but here's a subtle nugget that allows us to shrink these 4 lines of code to 1. (n > 0) already is a boolean value! So why should we do this extra work via the if/else to return some value we already have? Here's the same method that (gracefully) exploits this distinction: public boolean isPositive(int n)
{
return (n > 0);
}
Because (n > 0) is a boolean value in and of itself, the expression will automatically return true if n happens to be 1, 67, 120121 (or any other positive number) or false if it is -4, -324, or 0. This can also be applied to more complex boolean expressions that have || && ! in them as well. Again, notice how we don't have to use if/else statements in order to return the appropriate boolean value because simple usage of our test case holds all of the return info we need: /**
* Return whether or not the string is a primary color
*/
public boolean isPrimaryColor(String color)
{
String lowerColor = color.toLowerCase();
return (lowerColor.equals("red") || lowerColor.equals("blue") ||
lowerColor.equals("yellow"));
}
/**
* Return if the bowler "marked" in a given frame (hit all 10 pins)
*/
public boolean marked(int roll1, int roll2)
{
return ((roll1 == 10) || (roll1 + roll2 == 10));
}
/**
* Return whether or not a number is a negative number (for this we'll
* allow 0 to be a negative number)
*/
public boolean isNegative(int n)
{
return !isPositive(n);
}Using booleans in conditional statements In Java, we have a number of constructs that expect a boolean expression. For instance we have the following definitions: if( <boolean expr> ) { // do stuff } while( <boolean expr> ) { // do stuff } Each of these definitions require us to give some sort of boolean test case where the "do stuff" only happens when the test is true. With that knowledge, it is quite easy to write the following snippets of code: if( (x > 12323) == true )
{
System.println(x + " is a pretty big number.");
}
// or
while( (x <= 10) == true )
{
System.out.println("Counting up to number " + x);
x++;
}
I'm sure that by now, you're starting to see the pattern in this document. Don't write code this way. We rationalize this with the same reason that we didn't use if/else to return boolean value. In the test: (<expression>) == true, the <expression> is already the boolean value that you want! The == true is just redundant code. Thus, the same snippets should be written as follows: if( x > 12323 )
{
System.println(x + " is a pretty big number.");
}
// or
while( x <= 10 )
{
System.out.println("Counting up to number " + x);
x++;
}
There you have it. All you ever wanted to know about programming with booleans. Remember, just because it works, that doesn't make it good code. Keep these considerations in mind, and you will greatly simplify the readability of your code with even less effort! |
| De-Uglifying if/else Blocks |
|
The last section of conventional nuggets I have for
you today deals with making your if/else if/else
statements more readable. You might think that at
first glance, an if is an if and and else is an else,
no big deal, I'll use them and it will be
great. Wrong. We need if/else if/else statements, but
using them unnecessarily and improperly can lead to
ugly, hard to read code. We'll break this down into 2
areas: extra elses, and nested conditionals. Mastering
these 2 conventions will make your code much more
pleasant to read (and easier to understand). Extraneous 'Else' Remember this, folks... just because you have an if, you don't absolutely need an else. In may cases you do, but consider the following: public void printGreeting(String name)
{
if( name.equals("") )
{
return; // Don't do anything
}
else
{
System.out.println("Hello, " + name);
System.out.println("But lose that first letter, " +
name.substring(1));
}
}"What's so bad about that?" you might be asking yourself. Well, what are you gaining by having an else after this particular if? The answer is, nothing. Why? Look at what is going on in the body of the if... we're returning. Once we go in there, there is no coming back to do anything else in printGreeting() so we might as well just write the code like such: public void printGreeting(String name)
{
if( name.equals("") )
{
return; // Don't do anything
}
System.out.println("Hello, " + name);
System.out.println("But lose that first letter, " +
name.substring(1));
}Alright, so you don't get drastically different code, BUT it is still less which means less reading and comprehension we need to accomplish in order to know what this method does. The else is implied by the nature of what we did in the if of this particular instance, so we don't need to code it. That is not to say that you can just stop using elses all together. That's not what I'm saying at all. Just look for these special cases where you can handle an implied else without using an actual else. Here's one last example where we do that using something other than return: for( int i = 0; i < 100; i++ )
{
if( i % 7 == 0 )
{
continue; // skip rest of loop body and jump back to the top
}
// notice we don't need an else, because it is implied by
// the continue and what will happen when we do/don't enter
// the if block
System.out.println("We are on number " + i);
}Nested Ifs A lot of times, an if/else just won't cut it when trying to figure out what code you want to run. It's these cases that lead to some of the most horrible code you might ever read. Take this example: if( x < 100 )
{
System.out.println("x is pretty small");
}
else
{
if( x < 200 )
{
if( x < 150 )
{
System.out.println("x is moderately small");
}
else
{
System.out.println("x is moderately sized");
}
}
else
{
System.out.println("x is HUGE");
}
}
This code just doesn't "flow" well. It's really hard to follow because you have to jump to so manay areas of code to figure out what is going on. Look at the following example which makes use of "else if" to make the code much much prettier. Everything is on the same "level" of nesting, making it easier to understand logically: if( x < 100 )
{
System.out.println("x is pretty small");
}
else if( x < 150 )
{
System.out.println("x is moderately small");
}
else if( x < 200 )
{
System.out.println("x is moderately sized");
}
else
{
System.out.println("x is HUGE");
}
You should be getting used to me saying this by now, but this code does the exact same thing as the previous example. But because we aren't nesting ifs within ifs within ifs, we are able to make the code more logical and easy to read. One BIG note about this. This does not mean that you should never nest if/else if/else statements. I only avoided nesting these statements because all of the checks dealt with the value of x. Since every if check we wanted were on the same variable, they belong on the same level. BUT lets say we had a number of checks that had to be made on a couple of different variables, say x, y, and z. Nesting is actually preferred. In this case, look at how we handle the nesting: depending on what the value for x is, we either perform more checks on y or z. To logically separate this, we prefer to nest our if/elses: if( x < 100 )
{
// nest our if because we're running test cases on y, not x
if( y == 12 )
{
System.out.println("You win!");
}
else
{
System.out.println("You lose!");
}
}
else
{
// nest our if inside of this else rather than just using "else if"
// because these checks deal with z instead of x
if( z.charAt(0) == 'q' )
{
System.out.println("I always knew you were a quitter.");
}
else
{
System.out.println("That's the spirit!");
}
}
Again, we actually want to nest our if/elses here because we aren't just checking x in a number of different ways. Some of them deal with y and z as well. So keep this in mind: When you have a bunch
of if checks on the same variable, try not to nest
them. Just make a bunch of 'else if' clauses to go
along with your logic. Ignore the previous statement when dealing with checks on a number of *different* variables. Nest them as they most logically belong. |