34

I'm fairly new to software engineering, and so as a learning exercise I wrote a chess game. My friend had a look at it and pointed out that my code looked like

for (int i = 0; i < 8; i++){
    for (int j = 0; j < 8; j++){

while he insisted that it should instead be

for (int i = 0; i < CHESS_CONST; i++){
    for (int j = 0; j < CHESS_CONST; j++){

with some better symbol name that I can't be bothered to think of right now.

Now of course I do know to generally avoid using magic numbers, but I feel like since

  1. this number will never change;
  2. the name couldn't be that descriptive anyway since the number is used in so many places throughout the code; and
  3. anyone going through source code for a chess program should know enough about chess as to know what the 8 is for,

there really is no need for a symbolic constant.

So what do you guys think? Is this overkill, or should I just go with convention and use a symbol?

3 Answers3

101

IMHO your friend is right in using a symbolic name, though I think the name should definitely be more descriptive (like BOARD_WIDTH instead of CHESS_CONST).

Even when the number will never change through the lifetime of the program, there may be other places in your program where the number 8 will occur with a different meaning. Replacing "8" by BOARD_WIDTH wherever the board width is meant, and using another symbolic name when a different thing is meant makes these different meanings explicit, obvious and your overall program more readable and maintainable. It enables you also to do a global search over your program (or a reverse symbol search, if your environment provides it) in case you need quickly to identify all places in the code which are dependent on the board width.

See also this former SE.SE post for a discussion how to (or how not to) pick names for numbers.

As a side note, since it was discussed here in the comments: if, in your real program's code, it matters if the variable i refers to rows and j to columns of the board, or vice versa, then it is recommendable to pick variable names which make the distinction clear, like row and col. The benefit of such names is, they make wrong code look wrong.

Doc Brown
  • 218,378
11

Ok, here are a few comments I have:

Getting rid of magic numbers is a great idea. There is a concept known as DRY, which is often misrepresented, but the idea is that you don't duplicate the knowledge of the concepts in your project. So if you have a class called ChessBoard, you could keep a constant called BOARD_SIZE or ChessBoard.SIZE attached to it. This way there is one sole source for this information. Also, this helps readability later:

for (int i = 0; i < ChessBoard.SIZE; i++){
  for (int j = 0; j < ChessBoard.SIZE; j++){

Even if the number never changes, your program is arguably better. Any person reading it knows more information about what the code is doing.

A bad name is worse than no name, but that doesn't mean that something shouldn't be named. Just change the name. Don't throw out the baby with the bath water. :p The name can be descriptive as long as you understand well what it is describing. Then, that concept can be used for multiple different things.

Kaz
  • 3,692
unflores
  • 402
-7

What you really want is to eliminate ad nauseum references to constants, whether they be named or bare:

for_each_chess_square (row, col) {
  /*...*/
}

If you're actually going to proliferate the constant by repeating such loops and whatnot, it's best to stick with 8.

8 is self-describing; it's not a macro that stands for something else.

You Ain't Never Gonna (TM) turn it into a 9x9 chess program and if you ever do, the proliferation of 8 will not be the major difficulty.

We can search a 150,000 line code base for the token 8, and classify which occurrences mean what in seconds.

Far more important is to modularize the code so that the chess knowledge is concentrated in as few places as possible. It's better to have one, two, maybe three chess-specific modules in which a literal 8 occurs, than thirty-seven modules laced with chess-specific responsibility, referring to 8 through a symbolic name.

When or if this 8 constant becomes a source of tension in your program, you can easily fix it at that time. Fix real problems that are happening now. If you don't feel that you're hampered by that particular 8, go with that instinct.

Suppose that in the future you want to support alternative board dimensions. In that case, those loops will have to change whether they use a named constant or 8, because the dimensions will be retrieved by some expression like board.width and board.height. If you have BOARD_SIZE instead of 8, these places will be easier to find. So that is less effort. However, you must not forget about the effort of replacing 8 with BOARD_SIZE in the first place. The overall effort is not lower. Making one pass over the code to change 8 to BOARD_SIZE, and then another to support alternative dimensions, is not cheaper than just going from 8 to alternative dimension support.

We can also look at this from a purely cold, objective risk/benefit analysis. The program has bare constants in it now. If these are replaced by a constant, there is no benefit; the program is identical. With any change, there is a nonzero risk. In this case, it is small. Still, no risk should be taken without a benefit. To "sell" the change in the face of this reasoning, we have to hypothesize a benefit: a future benefit which will help with a different program: a future version of the program that doesn't exist now. If such a program is being planned, this hypothesis and its associated reasoning are bona fide and should be taken seriously.

For instance, if you're days away from adding more code that will further proliferate these constants, you might want to do away with them. If those instances of the constants are approximately all the instances that will ever exist then why bother.

If you ever work on commercial software, ROI arguments will also apply. If a program isn't selling, and changing some hard-coded numbers to constants won't improve the sales, you will not be compensated for the effort. The change has zero return on the investment of time. ROI arguments generalize beyond money. You wrote a program, investing time and effort, and got something out of it: that's your return, your "R". If by making that change alone, you get more of that "R", whatever it is, then by all means. If you have some plan for further development, and that change improves your "R", ditto. If the change has no immediate or forseeable "R" for you, forget it.

Kaz
  • 3,692