Gotcha #2: Magic Numbers
Magic numbers, in the sense used here, are raw numeric literals used in contexts where named constants should be used instead:
class Portfolio {
// . . .
Contract *contracts_[10];
char id_[10];
};
The main problem with magic numbers is that they have no semantic content to speak of; they are what they are. A 10 is a 10, not a maximum number of contracts or the length of an identifier. Therefore we're obliged, when reading or maintaining code that employs magic numbers, to determine the intended meaning of each raw literal. That's work, and it's unnecessary and often inaccurate work.
For example, our poorly designed portfolio above can manage a maximum of ten contracts. That's not a lot of contracts, so we may decide to increase it to 32. (If we had any concern about safety and correctness, we'd use a standard vector.) The trouble is that we're now obliged to examine every source file that uses Portfolio for each occurrence of the literal 10, to decide if that 10 means maximum number of contracts.
Actually, the situation can be worse. In large and long-lived projects, sometimes word gets out that the maximum number of contracts is ten, and this knowledge becomes embedded in code that doesn't even indirectly include the Portfolio header file:
for( int i = 0; i < 10; ++i )
// . . .
Does this literal 10 refer to the maximum number of contracts? The length of an identifier? Something unrelated?
The chance confluence of raw literals can sometimes bring out the worst coding tendencies in programmers:
if( Portfolio *p = getPortfolio() )
for( int i = 0; i < 10; ++i )
p->contracts_[i] = 0, p->id_[i] = '\0';
Now the maintainer has to somehow tease apart the initializations of the different components of a Portfolio that would not have been combined but for the chance coincidence of the values of two distinct concepts. There is really no excuse for provoking all this complexity when the solution is so simple:
class Portfolio {
// . . .
enum { maxContracts = 10, idlen = 10 };
Contract *contracts_[maxContracts];
char id_[idlen];
};
Enumerators consume no space and cost nothing in runtime while providing clear, properly scoped names of the concepts for which they stand.
Less obvious disadvantages of magic numbers include the potential for imprecision in their types and the lack of associated storage. The type of the literal 40000, for instance, is platform dependent. If the value 40000 can fit into an integer, its type is int. Otherwise, it's a long. If we don't want to leave ourselves open to obscure problems (like overload resolution ambiguities) when porting from platform to platform, it's probably best to say precisely what we mean rather than letting the compiler/platform combination decide for us:
const long patienceLimit = 40000;
Another potential problem with literals is that they have no address. This is not a common problem, but it is nevertheless occasionally useful to be able to point to or bind a reference to a constant:
const long *p1 = &40000; // error!
const long *p2 = &patienceLimit; // OK.
const long &r1 = 40000; // legal, but see Gotcha #44
const long &r2 = patienceLimit; // OK.
Magic numbers offer no advantage and many disadvantages. Use enumerators or initialized constants instead.
|