Graphic by Keith Ohlfs |
|
Reading on Style
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 which is easy for others to understand. Some general guidelines follow: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. Instead, the following code is much clearer and easy to understandif (this.getPosition().equals(new Point(4,6))) { this.right(); this.right(); this.right(); this.right(); this.setPosition(new Point(1,1)); }
Although the second programming style takes up a lot 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 greatly help our understanding of what is going on.Point home = new Point(1,1); Point school = new Point(4,6); if (this.getPosition().equals(school)) { dance(); goHome(); } public void dance() { // spin in a circle this.right(); this.right(); this.right(); this.right(); } public void goHome() { this.setPosition(home); }
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 method line which we will use.public class MyBuggleWorld extends BuggleWorld { public void run() { Buggle barb = new Buggle(); barb.setPosition(new Point(4,5)); barb.setHeading(Direction.SOUTH); barb.setColor(Color.cyan); barb.forward(4); barb.setPosition(new Point(2,1)); barb.setHeading(Direction.NORTH); barb.setColor(Color.green); barb.forward(6); barb.setPosition(new Point(5,7)); barb.setColor(Color.red); barb.setHeading(Direction.WEST); barb.forward(3); } }
By capturing the pattern in a method, it allows us to easily create the pattern over and over again without having to write lines of code. 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.public class MyBuggleWorld extends BuggleWorld { public void run() { LineBuggle barb = new LineBuggle(); // a LineBuggle knows how to draw lines barb.line(new Point(4,5), Direction.SOUTH, Color.cyan, 4); barb.line(new Point(2,1), Direction.NORTH, Color.green, 6); barb.line(new Point(5,7), Direction.WEST, Color.red, 3); } } public class LineBuggle extends Buggle { // draws a line from the specified point, in the specified direction // and color by moving a buggle forward in the specified steps public void line(Point lineStart, Direction lineDirection, Color lineColor, int lineLength) { this.setPosition(lineStart); this.setHeading(lineDirection); this.setColor(lineColor); this.forward(lineLength); } }
Yes, that works, but what a mess! It also doesn't make 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)), 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))); }
This also works. However,the pattern is still not clear since people need 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(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)))); }
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 for how to create our redGreenFrame showing good programming style: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)); }
Note: What is the difference between redGreenCorner() in the next to last sample code and redGreenCorner 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 parenthesis after the method name to use the method. Variables are just names for things and therefore are never used with parenthesis.public Picture redGreenFrame () { Picture redPatch = patch(Color.red); // local variable to name the red patch since it is used multiple times Picture greenPatch = patch(Color.green); // optional use of local variable since the green patch is used only once, // but it makes the code easier to read and understand Picture redGreenCorner = fourPics(redPatch,redPatch,redPatch,greenPatch); // local variable to name the pattern return fourPics(redGreenCorner, flipVertically(redGreenCorner), flipHorizontally(redGreenCorner), flipDiagonally(redGreenCorner)); }
Reading on Invariants
When we write methods, we need to define for ourself and others how we expect our environment to be before and after the method. If the method takes an action that affects objects in Object Land, the actions must be clearly defined. On the other hand, if the method is used to ask questions about objects in Object Land, it should not change the state of the environment. We say, in this case, that the method meets an invariant.
As an example, let's write a method that will determine if there is a wall to our buggle's right. Well,
we could do the following:
So, what's wrong? Well, nothing initially, the method does give us the answer we want. However, things get confusing if we try to use it in a program. After we've asked the question, our buggle is no longer facing its original direction. Instead, it's facing the direction that's to the right of its original direction. Ok, no problem. Let's do the same thing for figuring out if there is a wall to our buggle's left:public boolean isWallToRight () { this.right(); // turn right return isFacingWall(); // return the result from asking if we're facing a wall }
Again, this works. However, if you ask the buggle if there is a wall to its left, it will end up facing the direction left of its original direction. Where is the problem? Let's say we want to figure out whether or not our buggle is in a hallway. Our buggle is in a hallway if there is a wall to the left and to the right of the buggle. So, let's define a new method called isInHallway like so:public boolean isWallToLeft () { this.left(); // turn left return isFacingWall(); // return the result from asking if we're facing a wall }
Does this work? No. Why not?public boolean isInHallway () { return (isWallToLeft() && isWallToRight()); // buggle is in hallway if there is a wall to the left and to the right }
With the redefined methods above, our isInHallway method will now work correctly.public boolean isWallToLeft () { this.left(); // turn left boolean result = isFacingWall(); // store result from inquiry this.right(); // return to the original position; undo the this.left() return result; } public boolean isWallToRight () { this.right(); // turn right boolean result = isFacingWall(); // store result from inquiry this.left(); // return to the original position; undo the this.right() return result; }
Moral of the story: Methods that answer questions about the state of the environment should not change the state of the environment!