mirror of
https://github.com/glen9527/Clean-Code-zh.git
synced 2025-12-18 03:04:21 +08:00
标题 & 代码高亮
This commit is contained in:
198
docs/ch17.md
198
docs/ch17.md
@@ -1,4 +1,4 @@
|
||||
Smells and Heuristics
|
||||
# 第 17 章 Smells and Heuristics
|
||||
Image
|
||||
|
||||
Image
|
||||
@@ -22,11 +22,11 @@ A comment that has gotten old, irrelevant, and incorrect is obsolete. Comments g
|
||||
|
||||
C3: Redundant Comment
|
||||
A comment is redundant if it describes something that adequately describes itself. For example:
|
||||
|
||||
```java
|
||||
i++; // increment i
|
||||
|
||||
```
|
||||
Another example is a Javadoc that says nothing more than (or even less than) the function signature:
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param sellRequest
|
||||
* @return
|
||||
@@ -34,7 +34,7 @@ Another example is a Javadoc that says nothing more than (or even less than) the
|
||||
*/
|
||||
public SellResponse beginSellItem(SellRequest sellRequest)
|
||||
throws ManagedComponentException
|
||||
|
||||
```
|
||||
Comments should say things that the code cannot say for itself.
|
||||
|
||||
C4: Poorly Written Comment
|
||||
@@ -50,11 +50,11 @@ When you see commented-out code, delete it! Don’t worry, the source code contr
|
||||
ENVIRONMENT
|
||||
E1: Build Requires More Than One Step
|
||||
Building a project should be a single trivial operation. You should not have to check many little pieces out from source code control. You should not need a sequence of arcane commands or context dependent scripts in order to build the individual elements. You should not have to search near and far for all the various little extra JARs, XML files, and other artifacts that the system requires. You should be able to check out the system with one simple command and then issue one other simple command to build it.
|
||||
|
||||
```sh
|
||||
svn get mySystem
|
||||
cd mySystem
|
||||
ant all
|
||||
|
||||
```
|
||||
E2: Tests Require More Than One Step
|
||||
You should be able to run all the unit tests with just one command. In the best case you can run all the tests by clicking on one button in your IDE. In the worst case you should be able to issue a single simple command in a shell. Being able to run all the tests is so fundamental and so important that it should be quick, easy, and obvious to do.
|
||||
|
||||
@@ -82,9 +82,9 @@ Following “The Principle of Least Surprise,”2 any function or class should i
|
||||
|
||||
2. Or “The Principle of Least Astonishment”: http://en.wikipedia.org/wiki/
|
||||
Principle_of_least_astonishment
|
||||
|
||||
```java
|
||||
Day day = DayDate.StringToDay(String dayName);
|
||||
|
||||
```
|
||||
We would expect the string “Monday” to be translated to Day.MONDAY. We would also expect the common abbreviations to be translated, and we would expect the function to ignore case.
|
||||
|
||||
When an obvious behavior is not implemented, readers and users of the code can no longer depend on their intuition about function names. They lose their trust in the original author and must fall back on reading the details of the code.
|
||||
@@ -128,7 +128,7 @@ For example, constants, variables, or utility functions that pertain only to the
|
||||
This rule also pertains to source files, components, and modules. Good software design requires that we separate concepts at different levels and place them in different containers. Sometimes these containers are base classes or derivatives and sometimes they are source files, modules, or components. Whatever the case may be, the separation needs to be complete. We don’t want lower and higher level concepts mixed together.
|
||||
|
||||
Consider the following code:
|
||||
|
||||
```java
|
||||
public interface Stack {
|
||||
Object pop() throws EmptyException;
|
||||
void push(Object o) throws FullException;
|
||||
@@ -137,13 +137,13 @@ Consider the following code:
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
The point is that you cannot lie or fake your way out of a misplaced abstraction. Isolating abstractions is one of the hardest things that software developers do, and there is no quick fix when you get it wrong.
|
||||
@@ -193,7 +193,7 @@ G14: Feature Envy
|
||||
This is one of Martin Fowler’s code smells.6 The methods of a class should be interested in the variables and functions of the class they belong to, and not the variables and functions of other classes. When a method uses accessors and mutators of some other object to manipulate the data within that object, then it envies the scope of the class of that other object. It wishes that it were inside that other class so that it could have direct access to the variables it is manipulating. For example:
|
||||
|
||||
6. [Refactoring].
|
||||
|
||||
```java
|
||||
public class HourlyPayCalculator {
|
||||
public Money calculateWeeklyPay(HourlyEmployee e) {
|
||||
int tenthRate = e.getTenthRate().getPennies();
|
||||
@@ -205,11 +205,11 @@ This is one of Martin Fowler’s code smells.6 The methods of a class should be
|
||||
return new Money(straightPay + overtimePay);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
The calculateWeeklyPay method reaches into the HourlyEmployee object to get the data on which it operates. The calculateWeeklyPay method envies the scope of HourlyEmployee. It “wishes” that it could be inside HourlyEmployee.
|
||||
|
||||
All else being equal, we want to eliminate Feature Envy because it exposes the internals of one class to another. Sometimes, however, Feature Envy is a necessary evil. Consider the following:
|
||||
|
||||
```java
|
||||
public class HourlyEmployeeReport {
|
||||
private HourlyEmployee employee ;
|
||||
|
||||
@@ -225,14 +225,14 @@ All else being equal, we want to eliminate Feature Envy because it exposes the i
|
||||
employee.getTenthsWorked()%10);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
Clearly, the reportHours method envies the HourlyEmployee class. On the other hand, we don’t want HourlyEmployee to have to know about the format of the report. Moving that format string into the HourlyEmployee class would violate several principles of object oriented design.7 It would couple HourlyEmployee to the format of the report, exposing it to changes in that format.
|
||||
|
||||
7. Specifically, the Single Responsibility Principle, the Open Closed Principle, and the Common Closure Principle. See [PPP].
|
||||
|
||||
G15: Selector Arguments
|
||||
There is hardly anything more abominable than a dangling false argument at the end of a function call. What does it mean? What would it change if it were true? Not only is the purpose of a selector argument difficult to remember, each selector argument combines many functions into one. Selector arguments are just a lazy way to avoid splitting a large function into several smaller functions. Consider:
|
||||
|
||||
```java
|
||||
public int calculateWeeklyPay(boolean overtime) {
|
||||
int tenthRate = getTenthRate();
|
||||
int tenthsWorked = getTenthsWorked();
|
||||
@@ -243,9 +243,9 @@ There is hardly anything more abominable than a dangling false argument at the e
|
||||
int overtimePay = (int)Math.round(overTime*overtimeRate);
|
||||
return straightPay + overtimePay;
|
||||
}
|
||||
|
||||
```
|
||||
You call this function with a true if overtime is paid as time and a half, and with a false if overtime is paid as straight time. It’s bad enough that you must remember what calculateWeeklyPay(false) means whenever you happen to stumble across it. But the real shame of a function like this is that the author missed the opportunity to write the following:
|
||||
|
||||
```java
|
||||
public int straightPay() {
|
||||
return getTenthsWorked() * getTenthRate();
|
||||
}
|
||||
@@ -260,19 +260,19 @@ You call this function with a true if overtime is paid as time and a half, and w
|
||||
double bonus = 0.5 * getTenthRate() * overTimeTenths;
|
||||
return (int) Math.round(bonus);
|
||||
}
|
||||
|
||||
```
|
||||
Of course, selectors need not be boolean. They can be enums, integers, or any other type of argument that is used to select the behavior of the function. In general it is better to have many functions than to pass some code into a function to select the behavior.
|
||||
|
||||
G16: Obscured Intent
|
||||
We want code to be as expressive as possible. Run-on expressions, Hungarian notation, and magic numbers all obscure the author’s intent. For example, here is the overTimePay function as it might have appeared:
|
||||
|
||||
```java
|
||||
public int m_otCalc() {
|
||||
return iThsWkd * iThsRte +
|
||||
(int) Math.round(0.5 * iThsRte *
|
||||
Math.max(0, iThsWkd - 400)
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
Small and dense as this might appear, it’s also virtually impenetrable. It is worth taking the time to make the intent of our code visible to our readers.
|
||||
|
||||
G17: Misplaced Responsibility
|
||||
@@ -290,9 +290,9 @@ G18: Inappropriate Static
|
||||
Math.max(double a, double b) is a good static method. It does not operate on a single instance; indeed, it would be silly to have to say new Math().max(a,b) or even a.max(b). All the data that max uses comes from its two arguments, and not from any “owning” object. More to the point, there is almost no chance that we’d want Math.max to be polymorphic.
|
||||
|
||||
Sometimes, however, we write static functions that should not be static. For example, consider:
|
||||
|
||||
```java
|
||||
HourlyPayCalculator.calculatePay(employee, overtimeRate).
|
||||
|
||||
```
|
||||
Again, this seems like a reasonable static function. It doesn’t operate on any particular object and gets all it’s data from it’s arguments. However, there is a reasonable chance that we’ll want this function to be polymorphic. We may wish to implement several different algorithms for calculating hourly pay, for example, OvertimeHourlyPayCalculator and StraightTimeHourlyPayCalculator. So in this case the function should not be static. It should be a nonstatic member function of Employee.
|
||||
|
||||
In general you should prefer nonstatic methods to static methods. When in doubt, make the function nonstatic. If you really want a function to be static, make sure that there is no chance that you’ll want it to behave polymorphically.
|
||||
@@ -305,7 +305,7 @@ Kent Beck wrote about this in his great book Smalltalk Best Practice Patterns8 a
|
||||
9. [Beck07].
|
||||
|
||||
Consider this example from FitNesse:
|
||||
|
||||
```java
|
||||
Matcher match = headerPattern.matcher(line);
|
||||
if(match.find())
|
||||
{
|
||||
@@ -313,7 +313,7 @@ Consider this example from FitNesse:
|
||||
String value = match.group(2);
|
||||
headers.put(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
```
|
||||
The simple use of explanatory variables makes it clear that the first matched group is the key, and the second matched group is the value.
|
||||
|
||||
It is hard to overdo this. More explanatory variables are generally better than fewer. It is remarkable how an opaque module can suddenly become transparent simply by breaking the calculations up into well-named intermediate values.
|
||||
@@ -322,9 +322,9 @@ G20: Function Names Should Say What They Do
|
||||
Image
|
||||
|
||||
Look at this code:
|
||||
|
||||
```java
|
||||
Date newDate = date.add(5);
|
||||
|
||||
```
|
||||
Would you expect this to add five days to the date? Or is it weeks, or hours? Is the date instance changed or does the function just return a new Date without changing the old one? You can’t tell from the call what the function does.
|
||||
|
||||
If the function adds five days to the date and changes the date, then it should be called addDaysTo or increaseByDays. If, on the other hand, the function returns a new date that is five days later but does not change the date instance, it should be called daysLater or daysSince.
|
||||
@@ -351,7 +351,7 @@ For example, imagine that you are writing a function that prints a plain text re
|
||||
|
||||
|
||||
Listing 17-1 HourlyReporter.java
|
||||
|
||||
```java
|
||||
public class HourlyReporter {
|
||||
private HourlyReportFormatter formatter;
|
||||
private List<LineItem> page;
|
||||
@@ -392,7 +392,7 @@ Listing 17-1 HourlyReporter.java
|
||||
public int tenths;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
This code has a logical dependency that has not been physicalized. Can you spot it? It is the constant PAGE_SIZE. Why should the HourlyReporter know the size of the page? Page size should be the responsibility of the HourlyReportFormatter.
|
||||
|
||||
The fact that PAGE_SIZE is declared in HourlyReporter represents a misplaced responsibility [G17] that causes HourlyReporter to assume that it knows what the page size ought to be. Such an assumption is a logical dependency. HourlyReporter depends on the fact that HourlyReportFormatter can deal with page sizes of 55. If some implementation of HourlyReportFormatter could not deal with such sizes, then there would be an error.
|
||||
@@ -423,27 +423,27 @@ This is probably one of the oldest rules in software development. I remember rea
|
||||
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 π, 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.
|
||||
|
||||
@@ -462,27 +462,27 @@ 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()) {
|
||||
@@ -491,9 +491,9 @@ For example:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
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);
|
||||
@@ -508,12 +508,12 @@ This bit of code does three things. It loops over all the employees, checks to s
|
||||
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;
|
||||
@@ -525,9 +525,9 @@ Temporal couplings are often necessary, but you should not hide the coupling. St
|
||||
}
|
||||
…
|
||||
}
|
||||
|
||||
```
|
||||
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 reticulate-Splines before saturateGradient was called, leading to an UnsaturatedGradientException. A better solution is:
|
||||
|
||||
```java
|
||||
public class MoogDiver {
|
||||
Gradient gradient;
|
||||
List<Spline> splines;
|
||||
@@ -539,7 +539,7 @@ The order of the three functions is important. You must saturate the gradient be
|
||||
}
|
||||
…
|
||||
}
|
||||
|
||||
```
|
||||
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.
|
||||
@@ -548,7 +548,7 @@ Note that I left the instance variables in place. I presume that they are needed
|
||||
|
||||
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 {
|
||||
@@ -556,32 +556,32 @@ Have a reason for the way you structure your code, and make sure that reason is
|
||||
|
||||
…
|
||||
}
|
||||
|
||||
```
|
||||
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 +1s and -1s 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”);
|
||||
@@ -591,13 +591,13 @@ The statements within a function should all be written at the same level of abst
|
||||
|
||||
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”);
|
||||
@@ -611,13 +611,13 @@ I refactored this bit of code as follows. Note that I changed the name of the si
|
||||
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”);
|
||||
@@ -626,12 +626,12 @@ Separating levels of abstraction is one of the most important functions of refac
|
||||
}
|
||||
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);
|
||||
@@ -646,11 +646,11 @@ If you have a constant such as a default or configuration value that is known an
|
||||
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
|
||||
@@ -663,15 +663,15 @@ This is sometimes called the Law of Demeter. The Pragmatic Programmers call it
|
||||
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().
|
||||
|
||||
```
|
||||
JAVA
|
||||
J1: Avoid Long Import Lists by Using Wildcards
|
||||
If you use two or more classes from a package, then import the whole package with
|
||||
|
||||
```java
|
||||
import package.*;
|
||||
|
||||
```
|
||||
Long lists of imports are daunting to the reader. We don’t want to clutter up the tops of our modules with 80 lines of imports. Rather we want the imports to be a concise statement about which packages we collaborate with.
|
||||
|
||||
Specific imports are hard dependencies, whereas wildcard imports are not. If you specifically import a class, then that class must exist. But if you import a package with a wildcard, no particular classes need to exist. The import statement simply adds the package to the search path when hunting for names. So no true dependency is created by such imports, and they therefore serve to keep our modules less coupled.
|
||||
@@ -682,7 +682,7 @@ Wildcard imports can sometimes cause name conflicts and ambiguities. Two classes
|
||||
|
||||
J2: Don’t Inherit Constants
|
||||
I have seen this several times and it always makes me grimace. A programmer puts some constants in an interface and then gains access to those constants by inheriting that interface. Take a look at the following code:
|
||||
|
||||
```java
|
||||
public class HourlyEmployee extends Employee {
|
||||
private int tenthsWorked;
|
||||
private double hourlyRate;
|
||||
@@ -696,24 +696,24 @@ I have seen this several times and it always makes me grimace. A programmer puts
|
||||
}
|
||||
…
|
||||
}
|
||||
|
||||
```
|
||||
Where did the constants TENTHS_PER_WEEK and OVERTIME_RATE come from? They might have come from class Employee; so let’s take a look at that:
|
||||
|
||||
```java
|
||||
public abstract class Employee implements PayrollConstants {
|
||||
public abstract boolean isPayday();
|
||||
public abstract Money calculatePay();
|
||||
public abstract void deliverPay(Money pay);
|
||||
}
|
||||
|
||||
```
|
||||
Nope, not there. But then where? Look closely at class Employee. It implements PayrollConstants.
|
||||
|
||||
```java
|
||||
public interface PayrollConstants {
|
||||
public static final int TENTHS_PER_WEEK = 400;
|
||||
public static final double OVERTIME_RATE = 1.5;
|
||||
}
|
||||
|
||||
```
|
||||
This is a hideous practice! The constants are hidden at the top of the inheritance hierarchy. Ick! Don’t use inheritance as a way to cheat the scoping rules of the language. Use a static import instead.
|
||||
|
||||
```java
|
||||
import static PayrollConstants.*;
|
||||
|
||||
public class HourlyEmployee extends Employee {
|
||||
@@ -729,12 +729,12 @@ This is a hideous practice! The constants are hidden at the top of the inheritan
|
||||
}
|
||||
…
|
||||
}
|
||||
|
||||
```
|
||||
J3: Constants versus Enums
|
||||
Now that enums have been added to the language (Java 5), use them! Don’t keep using the old trick of public static final ints. The meaning of ints can get lost. The meaning of enums cannot, because they belong to an enumeration that is named.
|
||||
|
||||
What’s more, study the syntax for enums carefully. They can have methods and fields. This makes them very powerful tools that allow much more expression and flexibility than ints. Consider this variation on the payroll code:
|
||||
|
||||
```java
|
||||
public class HourlyEmployee extends Employee {
|
||||
private int tenthsWorked;
|
||||
HourlyPayGrade grade;
|
||||
@@ -772,7 +772,7 @@ What’s more, study the syntax for enums carefully. They can have methods and f
|
||||
|
||||
public abstract double rate();
|
||||
}
|
||||
|
||||
```
|
||||
NAMES
|
||||
N1: Choose Descriptive Names
|
||||
Don’t be too quick to choose a name. Make sure the name is descriptive. Remember that meanings tend to drift as software evolves, so frequently reevaluate the appropriateness of the names you choose.
|
||||
@@ -780,7 +780,7 @@ Don’t be too quick to choose a name. Make sure the name is descriptive. Rememb
|
||||
This is not just a “feel-good” recommendation. Names in software are 90 percent of what make software readable. You need to take the time to choose them wisely and keep them relevant. Names are too important to treat carelessly.
|
||||
|
||||
Consider the code below. What does it do? If I show you the code with well-chosen names, it will make perfect sense to you, but like this it’s just a hodge-podge of symbols and magic numbers.
|
||||
|
||||
```java
|
||||
public int x() {
|
||||
int q = 0;
|
||||
int z = 0;
|
||||
@@ -801,9 +801,9 @@ Consider the code below. What does it do? If I show you the code with well-chose
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
```
|
||||
Here is the code the way it should be written. This snippet is actually less complete than the one above. Yet you can infer immediately what it is trying to do, and you could very likely write the missing functions based on that inferred meaning. The magic numbers are no longer magic, and the structure of the algorithm is compellingly descriptive.
|
||||
|
||||
```java
|
||||
public int score() {
|
||||
int score = 0;
|
||||
int frame = 0;
|
||||
@@ -821,18 +821,18 @@ Here is the code the way it should be written. This snippet is actually less com
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
```
|
||||
The power of carefully chosen names is that they overload the structure of the code with description. That overloading sets the readers’ expectations about what the other functions in the module do. You can infer the implementation of isStrike() by looking at the code above. When you read the isStrike method, it will be “pretty much what you expected.”13
|
||||
|
||||
13. See Ward Cunningham’s quote on page 11.
|
||||
|
||||
```java
|
||||
private boolean isStrike(int frame) {
|
||||
return rolls[frame] == 10;
|
||||
}
|
||||
|
||||
```
|
||||
N2: Choose Names at the Appropriate Level of Abstraction
|
||||
Don’t pick names that communicate implementation; choose names the reflect the level of abstraction of the class or function you are working in. This is hard to do. Again, people are just too good at mixing levels of abstractions. Each time you make a pass over your code, you will likely find some variable that is named at too low a level. You should take the opportunity to change those names when you find them. Making code readable requires a dedication to continuous improvement. Consider the Modem interface below:
|
||||
|
||||
```java
|
||||
public interface Modem {
|
||||
boolean dial(String phoneNumber);
|
||||
boolean disconnect();
|
||||
@@ -840,9 +840,9 @@ Don’t pick names that communicate implementation; choose names the reflect the
|
||||
char recv();
|
||||
String getConnectedPhoneNumber();
|
||||
}
|
||||
|
||||
```
|
||||
At first this looks fine. The functions all seem appropriate. Indeed, for many applications they are. But now consider an application in which some modems aren’t connected by dialling. Rather they are connected permanently by hard wiring them together (think of the cable modems that provide Internet access to most homes nowadays). Perhaps some are connected by sending a port number to a switch over a USB connection. Clearly the notion of phone numbers is at the wrong level of abstraction. A better naming strategy for this scenario might be:
|
||||
|
||||
```java
|
||||
public interface Modem {
|
||||
boolean connect(String connectionLocator);
|
||||
boolean disconnect();
|
||||
@@ -850,7 +850,7 @@ At first this looks fine. The functions all seem appropriate. Indeed, for many a
|
||||
char recv();
|
||||
String getConnectedLocator();
|
||||
}
|
||||
|
||||
```
|
||||
Now the names don’t make any commitments about phone numbers. They can still be used for phone numbers, or they could be used for any other kind of connection strategy.
|
||||
|
||||
N3: Use Standard Nomenclature Where Possible
|
||||
@@ -864,7 +864,7 @@ Teams will often invent their own standard system of names for a particular proj
|
||||
|
||||
N4: Unambiguous Names
|
||||
Choose names that make the workings of a function or variable unambiguous. Consider this example from FitNesse:
|
||||
|
||||
```java
|
||||
private String doRename() throws Exception
|
||||
{
|
||||
if(refactorReferences)
|
||||
@@ -875,7 +875,7 @@ Choose names that make the workings of a function or variable unambiguous. Consi
|
||||
pathToRename.addNameToEnd(newName);
|
||||
return PathParser.render(pathToRename);
|
||||
}
|
||||
|
||||
```
|
||||
The name of this function does not say what the function does except in broad and vague terms. This is emphasized by the fact that there is a function named renamePage inside the function named doRename! What do the names tell you about the difference between the two functions? Nothing.
|
||||
|
||||
A better name for that function is renamePageAndOptionallyAllReferences. This may seem long, and it is, but it’s only called from one place in the module, so it’s explanatory value outweighs the length.
|
||||
@@ -884,13 +884,13 @@ N5: Use Long Names for Long Scopes
|
||||
The length of a name should be related to the length of the scope. You can use very short variable names for tiny scopes, but for big scopes you should use longer names.
|
||||
|
||||
Variable names like i and j are just fine if their scope is five lines long. Consider this snippet from the old standard “Bowling Game”:
|
||||
|
||||
```java
|
||||
private void rollMany(int n, int pins)
|
||||
{
|
||||
for (int i=0; i<n; i++)
|
||||
g.roll(pins);
|
||||
}
|
||||
|
||||
```
|
||||
This is perfectly clear and would be obfuscated if the variable i were replaced with something annoying like rollCount. On the other hand, variables and functions with short names lose their meaning over long distances. So the longer the scope of the name, the longer and more precise the name should be.
|
||||
|
||||
N6: Avoid Encodings
|
||||
@@ -898,14 +898,14 @@ Names should not be encoded with type or scope information. Prefixes such as m_
|
||||
|
||||
N7: Names Should Describe Side-Effects
|
||||
Names should describe everything that a function, variable, or class is or does. Don’t hide side effects with a name. Don’t use a simple verb to describe a function that does more than just that simple action. For example, consider this code from TestNG:
|
||||
|
||||
```java
|
||||
public ObjectOutputStream getOos() throws IOException {
|
||||
if (m_oos == null) {
|
||||
m_oos = new ObjectOutputStream(m_socket.getOutputStream());
|
||||
}
|
||||
return m_oos;
|
||||
}
|
||||
|
||||
```
|
||||
This function does a bit more than get an “oos”; it creates the “oos” if it hasn’t been created already. Thus, a better name might be createOrReturnOos.
|
||||
|
||||
TESTS
|
||||
|
||||
Reference in New Issue
Block a user