简体   繁体   中英

Would These Be Considered Magic Numbers?

I've just completed writing a program for a programming class, and I want to avoid use of magic numbers, so here's my question:

In the function below, would my array indexers be considered magic numbers?

Code:

string CalcGrade(int s1, int s2, int s3, double median)
{
const int SIZE = 23;
const int LETTER_GRADE_BARRIERS[SIZE] = { 400, 381, 380, 361, 360, 341, 340, 321, 320, 301, 300, 281, 280, 261, 260, 241, 240, 221, 220, 201, 200, 181, 180 }; 
double finalGrade;
string letterGrade;

finalGrade = s1 + s2 + s3 + median;

if (finalGrade >= LETTER_GRADE_BARRIERS[1] && finalGrade <= LETTER_GRADE_BARRIERS[0])
{
    letterGrade = "A";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[3] && finalGrade <= LETTER_GRADE_BARRIERS[2])
{
    letterGrade = "A-";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[5] && finalGrade <= LETTER_GRADE_BARRIERS[4])
{
    letterGrade = "B+";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[7] && finalGrade <= LETTER_GRADE_BARRIERS[6])
{
    letterGrade = "B";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[9] && finalGrade <= LETTER_GRADE_BARRIERS[8])
{
    letterGrade = "B-";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[11] && finalGrade <= LETTER_GRADE_BARRIERS[10])
{
    letterGrade = "C+";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[13] && finalGrade <= LETTER_GRADE_BARRIERS[12])
{
    letterGrade = "C";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[15] && finalGrade <= LETTER_GRADE_BARRIERS[14])
{
    letterGrade = "C-";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[17] && finalGrade <= LETTER_GRADE_BARRIERS[16])
{
    letterGrade = "D+";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[19] && finalGrade <= LETTER_GRADE_BARRIERS[18])
{
    letterGrade = "D";
}
else if (finalGrade >= LETTER_GRADE_BARRIERS[21] && finalGrade <= LETTER_GRADE_BARRIERS[20])
{
    letterGrade = "D-";
}
else if (finalGrade <= LETTER_GRADE_BARRIERS[22])
{
    letterGrade = "Fail";
}

return letterGrade;
}

Thanks!

Yes, any number other than -1,0 or 1 is probably a magic number.

Unless you're a real guru, then you're probably allowed to use powers of two freely as well :-)

As an aside, you could probably refactor that code to be a little more understandable, something like:

string CalcGrade (int s1, int s2, int s3, double median) {
    // Grade lookup arrays. If grade is >= limit[n], string is grades[n].
    // Anything below D- is a fail.
    static const int Limits[] = {400, 380, 360, 340,320, 300, 280,260, 240, 220,200,180 }; 
    static const int Grades[] = {"A+","A","A-","B+","B","B-","C+","C","C-","D+","D","D-"};

    double finalGrade = s1 + s2 + s3 + median;

    // Check each element of the array and, if the final grade is greater
    //   than or equal to, return the grade string.
    for (int i = 0; i < sizeof(Limits) / sizeof(*Limits); i++)
        if (finalGrade >= Limits[i])
            return Grades[i];

    // Otherwise, failed.
    return "Fail";
}

This removes the magic numbers spread all over the code to an area where it's immediately obvious how they work (assuming you align them nicely).

It also removes a problem with your original solution as to what we do with someone that achieved a score of 380.5 - it's not really fair to fail those bods :-) Or to assign a grade to "" to those above 400 (since there doesn't appear to be a way to return "A+" ).

In the fashion you are doing things , I would say they are not magic numbers. What would you rename them? I can't think of any useful answer ( static const int One = 1; is useless.)

The 400, 381, etc. line is more confusing to me at first. I would put something like // GPA times 100 above it to clarify.

In fact, while your question (array indexes) isn't too magical, the 400... line should probably be replaced with static const int A = 400; static const int AMinus = 381; static const int A = 400; static const int AMinus = 381; then ...BARRIERS[] = {A, AMinus,} and so on. Those are definitely meaningful constants

There are alternate (cleaner) methods that would need numbers that should definitely be turned into named constants. (The same ones suggested above)

Yes. You need to recompile to change the numbers; that's where the problem lies.

Any configuration things like that should be, well, configurable, and not require a recompliation. Of course, you may still have numbers in your config, but in your case, it all seems like legitimate data for a configuration table.

How about how not to do it for a bit of humour?

string CalcGrade (int s1, int s2, int s3, double median) {
    int grade = median + s1 + s2 + s3;
    grade = (grade>400)?400:((grade<180)?179:grade);
    return
        "Fail\0D-\0\0\0D\0\0\0\0D+\0\0\0C-\0\0\0C\0\0\0\0"C+\0\0\0"
        "B-\0\0\0B\0\0\0\0B+\0\0\0A-\0\0\0A\0\0\0\0A+"[((grade-160)/20)*5];
}

The definition of LETTER_GRADE_BARRIERS is disjoint from what they actually represent, so yes. If it was an array of structs of an int and a char* (the mark) then no.

Yes, but they are properly represented using constants, so no problems there.

I would, however, consider assigning the letter grades to another array and aligning them with the barriers.

And I would definitely use a loop and not write out each of the 12 cases seperately.

it can look a lot simpler, for example using std::lower_bound to find which bracket score belongs to and array of letters , eg letter_grade[]= { "A", ... }; to convert bracket to a letter grade

Yes, they are most definitely magic numbers. The way you are going about it doesn't help either. All these numbers are spaced 20 steps apart (with an extra +1 buffer before each) but that is not apparent from the code. A much better implementation would be something like this:

string CalcGrade(int s1, int s2, int s3, double median) {
  const int MAXIMUM_GRADE = 400;
  const int MINIMUM_GRADE = 180;
  const int GRADE_STEP = 20;
  const char* GRADES[] = { "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-" };

  double finalGrade = s1 + s2 + s3 + median;

  if (finalGrade >= MAXIMUM_GRADE) {
    return "A+";
  } else if (finalGrade <= MINIMUM_GRADE) {
    return "Fail";
  } else {
    return GRADES[(size_t)((MAXIMUM_GRADE - finalGrade) / GRADE_STEP)];
  }
}

Yes. The indices into the array have no semantic meaning whatsoever. This makes them 'magic.'

paxdiablo's response is a pretty good way of doing it, though I'd be tempted to combine the limit and grade name into a single class/struct.

Even keeping the code structure, consider these two fragments:

// original
else if (finalGrade >= LETTER_GRADE_BARRIERS[13] && finalGrade <= LETTER_GRADE_BARRIERS[12]) 
{ 
    letterGrade = "C"; 
} 

// compared to
else if (finalGrade >= MIN_C_GRADE && finalGrade < MIN_C_PLUS_GRADE)
{
    letterGrade = "C";
}

The second sample attaches more semantic meaning to the code, rather than relying upon what '13' and '14' represent.

Storing them in an array buys you little, as you're not actually iterating over the array.

A good check for magic numbers is to describe the solution to the problem to someone. If the numbers don't show up in your verbal description, they're almost certainly magic.

If you're talking about the numbers that make up the contents of the LETTER_GRADE_BARRIERS array, I'd probably consider those numbers as data , not necessarily numbers that deserve unique names.

I'd guess that ideally they would come from a data file rather than embedded in the program, but your assignment/requirements might dictate otherwise.

However, the numbers that are used to index the array might well deserve names.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM