... | ... | @@ -67,16 +67,23 @@ This rule also pertains to source files, components, and modules. Good software |
|
|
|
|
|
Consider the following code:
|
|
|
|
|
|
```java
|
|
|
public interface Stack {
|
|
|
Object pop() throws EmptyException;
|
|
|
void push(Object o) throws FullException; double percentFull();
|
|
|
class EmptyException extends Exception {}
|
|
|
class FullException extends Exception {} }
|
|
|
Object pop() throws EmptyException;
|
|
|
void push(Object o) throws FullException;
|
|
|
double percentFull();
|
|
|
class EmptyException extends Exception {}
|
|
|
class FullException extends Exception {}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The percentFull function is at the wrong level of abstraction. Although there are many implementations of Stack where the concept of fullness is reasonable, there are other implementations that simply could not know how full they are. So the function would be better placed in a derivative interface such as BoundedStack.
|
|
|
The `percentFull` function is at the wrong level of abstraction. Although there are many implementations of `Stack` where the concept of fullness is reasonable, there are other implementations that simply could not know how full they are. So the function would be better placed in a derivative interface such as `BoundedStack`.
|
|
|
|
|
|
Perhaps you are thinking that the implementation could just return zero if the stack were boundless. The problem with that is that no stack is truly boundless. You cannot really prevent an OutOfMemoryException by checking for
|
|
|
Perhaps you are thinking that the implementation could just return zero if the stack were boundless. The problem with that is that no stack is truly boundless. You cannot really prevent an `OutOfMemoryException` by checking for
|
|
|
|
|
|
```java
|
|
|
stack.percentFull() < 50.0.
|
|
|
```
|
|
|
|
|
|
Implementing the function to return 0 would be telling a lie.
|
|
|
|
... | ... | @@ -294,11 +301,11 @@ Programming is often an exploration. You think you know the right algorithm for |
|
|
|
|
|
There is nothing wrong with this approach. Indeed, often it is the only way to get a function to do what you think it should. However, it is not sufficient to leave the quotation marks around the word "work."
|
|
|
|
|
|
Before you consider yourself to be done with a function, make sure you understand how it works. It is not good enough that it passes all the tests. You must know that the solution is correct.
|
|
|
Before you consider yourself to be done with a function, make sure you understand how it works. It is not good enough that it passes all the tests. __You must know__ that the solution is correct. (There is a difference between knowing how the code works and knowing whether the algorithm will do the job required of it. Being unsure that an algorithm is appropriate is often a fact of life. Being unsure what your code does is just laziness.)
|
|
|
|
|
|
Often the best way to gain this knowledge and understanding is to refactor the func- tion into something that is so clean and expressive that it is obvious how it works.
|
|
|
Often the best way to gain this knowledge and understanding is to refactor the function into something that is so clean and expressive that it is obvious how it works.
|
|
|
|
|
|
G22: Make Logical Dependencies Physical
|
|
|
#### G22: Make Logical Dependencies Physical
|
|
|
|
|
|
If one module depends upon another, that dependency should be physical, not just logical. The dependent module should not make assumptions (in other words, logical dependencies) about the module it depends upon. Rather it should explicitly ask that module for all the information it depends upon.
|
|
|
|
... | ... | @@ -354,14 +361,313 @@ We can physicalize this dependency by creating a new method in `HourlyReportForm |
|
|
|
|
|
|
|
|
|
|
|
#### G23: Prefer Polymorphism to If/Else or Switch/Case
|
|
|
|
|
|
This might seem a strange suggestion given the topic of Chapter 6. After all, in that chapter I make the point that switch statements are probably appropriate in the parts of the system where adding new functions is more likely than adding new types.
|
|
|
|
|
|
First, most people use switch statements because it's the obvious brute force solution, not because it's the right solution for the situation. So this heuristic is here to remind us to consider polymorphism before using a switch.
|
|
|
|
|
|
Second, the cases where functions are more volatile than types are relatively rare. So every switch statement should be suspect.
|
|
|
|
|
|
I use the following "ONE SWITCH" rule: There may be no more than one switch statement for a given type of selection. The cases in that switch statement must create polymorphic objects that take the place of other such switch statements in the rest of the system.
|
|
|
|
|
|
#### G24: Follow Standard Conventions
|
|
|
|
|
|
Every team should follow a coding standard based on common industry norms. This coding standard should specify things like where to declare instance variables; how to name classes, methods, and variables; where to put braces; and so on. The team should not need a document to describe these conventions because their code provides the examples.
|
|
|
|
|
|
Everyone on the team should follow these conventions. This means that each team member must be mature enough to realize that it doesn't matter a whit where you put your braces so long as you all agree on where to put them.
|
|
|
|
|
|
If you would like to know what conventions I follow, you'll see them in the refactored code in Listing B-7 on page 394, through Listing B-14.
|
|
|
|
|
|
#### G25: Replace Magic Numbers with Named Constants
|
|
|
|
|
|
This is probably one of the oldest rules in software development. I remember reading it in the late sixties in introductory COBOL, FORTRAN, and PL/1 manuals. In general it is a bad idea to have raw numbers in your code. You should hide them behind well-named constants.
|
|
|
|
|
|
For example, the number 86,400 should be hidden behind the constant SECONDS_PER_DAY. If you are printing 55 lines per page, then the constant 55 should be hidden behind the constant `LINES_PER_PAGE`.
|
|
|
|
|
|
Some constants are so easy to recognize that they don't always need a named constant to hide behind so long as they are used in conjunction with very self-explanatory code. For example:
|
|
|
|
|
|
```java
|
|
|
double milesWalked = feetWalked/5280.0;
|
|
|
int dailyPay = hourlyRate * 8;
|
|
|
double circumference = radius * Math.PI * 2;
|
|
|
```
|
|
|
|
|
|
Do we really need the constants `FEET_PER_MILE`, `WORK_HOURS_PER_DAY`, and `TWO` in the above examples? Clearly, the last case is absurd. There are some formulae in which constants are simply better written as raw numbers. You might quibble about the `WORK_HOURS_PER_DAY` case because the laws or conventions might change. On the other hand, that formula reads so nicely with the 8 in it that I would be reluctant to add 17 extra characters to the readers' burden. And in the `FEET_PER_MILE` case, the number 5280 is so very well known and so unique a constant that readers would recognize it even if it stood alone on a page with no context surrounding it.
|
|
|
|
|
|
Constants like 3.141592653589793 are also very well known and easily recognizable. However, the chance for error is too great to leave them raw. Every time someone sees 3.1415927535890793, they know that it is Pi, and so they fail to scrutinize it. (Did you catch the single-digit error?) We also don't want people using 3.14, 3.14159, 3.142, and so forth. Therefore, it is a good thing that `Math.PI` has already been defined for us.
|
|
|
|
|
|
The term "Magic Number" does not apply only to numbers. It applies to any token that has a value that is not self-describing. For example:
|
|
|
|
|
|
```java
|
|
|
assertEquals(7777, Employee.find("John Doe").employeeNumber());
|
|
|
```
|
|
|
|
|
|
There are two magic numbers in this assertion. The first is obviously 7777, though what it might mean is not obvious. The second magic number is "`John Doe`," and again the intent is not clear.
|
|
|
|
|
|
It turns out that "`John Doe`" is the name of employee #7777 in a well-known test database created by our team. Everyone in the team knows that when you connect to this
|
|
|
database, it will have several employees already cooked into it with well-known values and attributes. It also turns out that "`John Doe`" represents the sole hourly employee in that test database. So this test should really read:
|
|
|
|
|
|
```java
|
|
|
assertEquals(
|
|
|
HOURLY_EMPLOYEE_ID,
|
|
|
Employee.find(HOURLY_EMPLOYEE_NAME).employeeNumber());
|
|
|
```
|
|
|
|
|
|
#### G26: Be Precise
|
|
|
|
|
|
Expecting the first match to be the only match to a query is probably naive. Using floating point numbers to represent currency is almost criminal. Avoiding locks and/or transaction management because you don't think concurrent update is likely is lazy at best. Declaring a variable to be an `ArrayList` when a `List` will due is overly constraining. Making all variables `protected` by default is not constraining enough.
|
|
|
|
|
|
When you make a decision in your code, make sure you make it precisely. Know why you have made it and how you will deal with any exceptions. Don't be lazy about the precision of your decisions. If you decide to call a function that might return `null`, make sure you check for `null`. If you query for what you think is the only record in the database, make sure your code checks to be sure there aren't others. If you need to deal with currency, use integers (or better yet, a Money class that uses integers) and deal with rounding appropriately. If there is the possibility of concurrent update, make sure you implement some kind of locking mechanism.
|
|
|
|
|
|
Ambiguities and imprecision in code are either a result of disagreements or laziness. In either case they should be eliminated.
|
|
|
|
|
|
#### G27: Structure over Convention
|
|
|
|
|
|
Enforce design decisions with structure over convention. Naming conventions are good, but they are inferior to structures that force compliance. For example, switch/cases with nicely named enumerations are inferior to base classes with abstract methods. No one is forced to implement the `switch/case` statement the same way each time; but the base classes do enforce that concrete classes have all abstract methods implemented.
|
|
|
|
|
|
#### G28: Encapsulate Conditionals
|
|
|
|
|
|
Boolean logic is hard enough to understand without having to see it in the context of an `if` or `while` statement. Extract functions that explain the intent of the conditional.
|
|
|
|
|
|
For example:
|
|
|
|
|
|
```java
|
|
|
if (shouldBeDeleted(timer))
|
|
|
```
|
|
|
|
|
|
is preferable to
|
|
|
|
|
|
```java
|
|
|
if (timer.hasExpired() && !timer.isRecurrent())
|
|
|
```
|
|
|
|
|
|
#### G29: Avoid Negative Conditionals
|
|
|
|
|
|
Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives. For example:
|
|
|
|
|
|
```java
|
|
|
if (buffer.shouldCompact())
|
|
|
```
|
|
|
|
|
|
is preferable to
|
|
|
|
|
|
```java
|
|
|
if (!buffer.shouldNotCompact())
|
|
|
```
|
|
|
|
|
|
|
|
|
#### G30: Functions Should Do One Thing
|
|
|
|
|
|
It is often tempting to create functions that have multiple sections that perform a series of operations. Functions of this kind do more than one thing, and should be converted into many smaller functions, each of which does one thing.
|
|
|
|
|
|
For example:
|
|
|
|
|
|
```java
|
|
|
public void pay() {
|
|
|
for (Employee e : employees) {
|
|
|
if (e.isPayday()) {
|
|
|
Money pay = e.calculatePay();
|
|
|
e.deliverPay(pay);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
This bit of code does three things. It loops over all the employees, checks to see whether each employee ought to be paid, and then pays the employee. This code would be better written as:
|
|
|
|
|
|
```java
|
|
|
public void pay() {
|
|
|
for (Employee e : employees)
|
|
|
payIfNecessary(e);
|
|
|
}
|
|
|
private void payIfNecessary(Employee e) {
|
|
|
if (e.isPayday())
|
|
|
calculateAndDeliverPay(e);
|
|
|
}
|
|
|
private void calculateAndDeliverPay(Employee e) {
|
|
|
Money pay = e.calculatePay();
|
|
|
e.deliverPay(pay);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Each of these functions does one thing. (See "Do One Thing" on page 35.)
|
|
|
|
|
|
#### G31: Hidden Temporal Couplings
|
|
|
|
|
|
Temporal couplings are often necessary, but you should not hide the coupling. Structure the arguments of your functions such that the order in which they should be called is obvious. Consider the following:
|
|
|
|
|
|
```java
|
|
|
public class MoogDiver {
|
|
|
Gradient gradient;
|
|
|
List<Spline> splines;
|
|
|
|
|
|
public void dive(String reason) {
|
|
|
saturateGradient();
|
|
|
reticulateSplines();
|
|
|
diveForMoog(reason);
|
|
|
}
|
|
|
...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The order of the three functions is important. You must saturate the gradient before you can reticulate the splines, and only then can you dive for the moog. Unfortunately, the code does not enforce this temporal coupling. Another programmer could call `reticulateSplines` before `saturateGradient` was called, leading to an `UnsaturatedGradientException`. A better solution is:
|
|
|
|
|
|
```java
|
|
|
public class MoogDiver {
|
|
|
Gradient gradient;
|
|
|
List<Spline> splines;
|
|
|
|
|
|
public void dive(String reason) {
|
|
|
Gradient gradient = saturateGradient();
|
|
|
List<Spline> splines = reticulateSplines(gradient);
|
|
|
diveForMoog(splines, reason);
|
|
|
}
|
|
|
...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
This exposes the temporal coupling by creating a bucket brigade. Each function produces a result that the next function needs, so there is no reasonable way to call them out of order.
|
|
|
|
|
|
You might complain that this increases the complexity of the functions, and you'd be right. But that extra syntactic complexity exposes the true temporal complexity of the situation.
|
|
|
|
|
|
Note that I left the instance variables in place. I presume that they are needed by private methods in the class. Even so, I want the arguments in place to make the temporal coupling explicit.
|
|
|
|
|
|
#### G32: Don't Be Arbitrary
|
|
|
|
|
|
Have a reason for the way you structure your code, and make sure that reason is communicated by the structure of the code. If a structure appears arbitrary, others will feel empowered to change it. If a structure appears consistently throughout the system, others will use it and preserve the convention. For example, I was recently merging changes to FitNesse and discovered that one of our committers had done this:
|
|
|
|
|
|
```java
|
|
|
public class AliasLinkWidget extends ParentWidget {
|
|
|
public static class VariableExpandingWidgetRoot {
|
|
|
...
|
|
|
...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The problem with this was that `VariableExpandingWidgetRoot` had no need to be inside the scope of `AliasLinkWidget`. Moreover, other unrelated classes made use of `AliasLinkWidget.VariableExpandingWidgetRoot`. These classes had no need to know about `AliasLinkWidget`.
|
|
|
|
|
|
Perhaps the programmer had plopped the `VariableExpandingWidgetRoot` into `AliasWidget` as a matter of convenience, or perhaps he thought it really needed to be scoped inside `AliasWidget`. Whatever the reason, the result wound up being arbitrary. Public classes that are not utilities of some other class should not be scoped inside another class. The convention is to make them public at the top level of their package.
|
|
|
|
|
|
#### G33: Encapsulate Boundary Conditions
|
|
|
|
|
|
Boundary conditions are hard to keep track of. Put the processing for them in one place. Don't let them leak all over the code. We don't want swarms of `+1`s and `-1`s scattered hither and yon. Consider this simple example from FIT:
|
|
|
|
|
|
```java
|
|
|
if(level + 1 < tags.length) {
|
|
|
parts = new Parse(body, tags, level + 1, offset + endTag);
|
|
|
body = null;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Notice that `level+1` appears twice. This is a boundary condition that should be encapsulated within a variable named something like nextLevel.
|
|
|
|
|
|
```java
|
|
|
int nextLevel = level + 1;
|
|
|
if(nextLevel < tags.length) {
|
|
|
parts = new Parse(body, tags, nextLevel, offset + endTag);
|
|
|
body = null;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
|
|
|
#### G34: Functions Should Descend Only One Level of Abstraction
|
|
|
|
|
|
The statements within a function should all be written at the same level of abstraction, which should be one level below the operation described by the name of the function. This may be the hardest of these heuristics to interpret and follow. Though the idea is plain enough, humans are just far too good at seamlessly mixing levels of abstraction. Consider, for example, the following code taken from FitNesse:
|
|
|
|
|
|
```java
|
|
|
public String render() throws Exception
|
|
|
{
|
|
|
StringBuffer html = new StringBuffer("<hr");
|
|
|
if(size > 0)
|
|
|
html.append(" size=\"").append(size + 1).append("\"");
|
|
|
html.append(">");
|
|
|
|
|
|
return html.toString();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
A moment's study and you can see what's going on. This function constructs the HTML tag that draws a horizontal rule across the page. The height of that rule is specified in the `size` variable.
|
|
|
|
|
|
Now look again. This method is mixing at least two levels of abstraction. The first is the notion that a horizontal rule has a size. The second is the syntax of the `HR` tag itself. This code comes from the `HruleWidget` module in FitNesse. This module detects a row of four or more dashes and converts it into the appropriate HR tag. The more dashes, the larger the size.
|
|
|
|
|
|
I refactored this bit of code as follows. Note that I changed the name of the `size` field to reflect its true purpose. It held the number of extra dashes.
|
|
|
|
|
|
|
|
|
```java
|
|
|
public String render() throws Exception
|
|
|
{
|
|
|
HtmlTag hr = new HtmlTag("hr"); if (extraDashes > 0)
|
|
|
hr.addAttribute("size", hrSize(extraDashes)); return hr.html();
|
|
|
}
|
|
|
|
|
|
private String hrSize(int height)
|
|
|
{
|
|
|
int hrSize = height + 1;
|
|
|
return String.format("%d", hrSize);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
This change separates the two levels of abstraction nicely. The `render` function simply constructs an HR tag, without having to know anything about the HTML syntax of that tag. The `HtmlTag` module takes care of all the nasty syntax issues.
|
|
|
|
|
|
Indeed, by making this change I caught a subtle error. The original code did not put the closing slash on the HR tag, as the XHTML standard would have it. (In other words, it emitted `<hr>` instead of `<hr/>`.) The `HtmlTag` module had been changed to conform to XHTML long ago.
|
|
|
|
|
|
Separating levels of abstraction is one of the most important functions of refactoring, and it's one of the hardest to do well. As an example, look at the code below. This was my first attempt at separating the abstraction levels in the `HruleWidget.render` method.
|
|
|
|
|
|
```java
|
|
|
public String render() throws Exception
|
|
|
{
|
|
|
HtmlTag hr = new HtmlTag("hr");
|
|
|
if (size > 0) {
|
|
|
hr.addAttribute("size", ""+(size+1));
|
|
|
}
|
|
|
return hr.html();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
My goal, at this point, was to create the necessary separation and get the tests to pass. I accomplished that goal easily, but the result was a function that still had mixed levels of abstraction. In this case the mixed levels were the construction of the HR tag and the
|
|
|
|
|
|
interpretation and formatting of the `size` variable. This points out that when you break a function along lines of abstraction, you often uncover new lines of abstraction that were obscured by the previous structure.
|
|
|
|
|
|
#### G35: Keep Configurable Data at High Levels
|
|
|
|
|
|
If you have a constant such as a default or configuration value that is known and expected at a high level of abstraction, do not bury it in a low-level function. Expose it as an argument to that low-level function called from the high-level function. Consider the following code from FitNesse:
|
|
|
|
|
|
```java
|
|
|
public static void main(String[] args) throws Exception {
|
|
|
Arguments arguments = parseCommandLine(args);
|
|
|
...
|
|
|
}
|
|
|
public class Arguments {
|
|
|
public static final String DEFAULT_PATH = ".";
|
|
|
public static final String DEFAULT_ROOT = "FitNesseRoot";
|
|
|
public static final int DEFAULT_PORT = 80;
|
|
|
public static final int DEFAULT_VERSION_DAYS = 14;
|
|
|
...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The command-line arguments are parsed in the very first executable line of FitNesse. The default values of those arguments are specified at the top of the `Argument` class. You don't have to go looking in low levels of the system for statements like this one:
|
|
|
|
|
|
```java
|
|
|
if (arguments.port == 0) // use 80 by default
|
|
|
```
|
|
|
|
|
|
The configuration constants reside at a very high level and are easy to change. They get passed down to the rest of the application. The lower levels of the application do not own the values of these constants.
|
|
|
|
|
|
#### G36: Avoid Transitive Navigation
|
|
|
|
|
|
In general we don't want a single module to know much about its collaborators. More specifically, if `A` collaborates with `B`, and `B` collaborates with `C`, we don't want modules that use `A` to know about `C`. (For example, we don't want `a.getB().getC().doSomething();`.)
|
|
|
|
|
|
This is sometimes called the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). The Pragmatic Programmers call it "Writing Shy Code." In either case it comes down to making sure that modules know only about their immediate collaborators and do not know the navigation map of the whole system.
|
|
|
|
|
|
If many modules used some form of the statement `a.getB().getC()`, then it would be difficult to change the design and architecture to interpose a `Q` between `B` and `C`. You'd have to find every instance of `a.getB().getC()` and convert it to `a.getB().getQ().getC()`. This is how architectures become rigid. Too many modules know too much about the architecture.
|
|
|
|
|
|
Rather we want our immediate collaborators to offer all the services we need. We should not have to roam through the object graph of the system, hunting for the method we want to call. Rather we should simply be able to say:
|
|
|
|
|
|
```java
|
|
|
myCollaborator.doSomething().
|
|
|
```
|
|
|
|
|
|
|
|
|
|
... | ... | |