Magic Numbers – How to remove them from your Codebase. - Peter's Software House

Magic Numbers – How to remove them from your Codebase.

I am not a fan Magic Numbers at all.  But most code bases I have seen and/or worked on have them too varying degrees.  Using simple OO techniques can help you avoid them.  In this post I am going to discuss how to avoid Magic Numbers. 

Let’s start by creating a simple class of say a Person.

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public char Status { get; set; }
}

Nothing special, just a firstname, lastname and status.  Let’s write a test to use it (Yea I know, should have written the test first!)

[TestMethod]
public void TestMethod1()
{
    Person p = new Person();
    p.Status = 'C';
    Assert.AreEqual(p.Status, 'C');
}

Ok, this works, but WTF does ‘C’ mean?  Closed? Created? (The test Name doesn’t help)

Let’s make a change to the Test to make it clear:

 

[TestMethod]
public void Person_Status_Closed()
{
    Person p = new Person();
    p.Status = Person.Closed;
    Assert.AreEqual(p.Status, Person.Closed);
}

So we changed the ‘C’ to a constant called Closed, which we have added to the Person class. This is cool for now, when I read this code it is clear that the status I am assigning to the Person class is Closed. A funny status to pick when you have just created the object, so let’s say the default status for a person is Active. Let’s write a test for that first:

[TestMethod]
public void PersonStatus_Default_Active()
{
    Person p = new Person();
    Assert.AreEqual(p.Status, Person.Active);
}

And let’s update the person class:

public class Person
{
    public const char Active = 'A';
    public const char Closed = 'C';

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public char Status { get; set; }

    public Person()
    {
        Status = Active;
    }
}

All going pretty well so far.  But this does not stop us from using Magic Numbers, i.e. I can still check for ‘A’ or ‘C’ or I can assign any char value I like to status.  This is especially likely to happen when you are working on a large codebase and an inexperienced developer has to make a change in code and their first response is to use ‘A’ instead of Person.Active.  Sure this should be picked up during code reviews, but why not make it impossible to do this in the first place?  How?  Very easily, replace the Person.Status with a Status class.

Let’s create the test first:

[TestMethod]
public void PersonStatus_Default_Active()
{
    Person p = new Person();
    Assert.AreEqual(p.Status, PersonStatus.Active);
}

A simple change, can you spot it?

Let’s have a look at the implementation:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public PersonStatus Status { get; set; }

    public Person()
    {
        Status = PersonStatus.Active;
    }
}
public class PersonStatus
{
    public static PersonStatus Active = new PersonStatus('A', "Active");
    public static PersonStatus Closed = new PersonStatus('C', "Closed");

    public char Code { get; set; }
    public string Description { get; set; }

    public PersonStatus(char code, string description)
    {
        Code = code;
        Description = description;
    }
}

A huge improvement, you now have to use a PersonStatus object to indicate the status of Person.  But you can still create your own version PersonStatus, i.e. you can set status = new PersonStatus(‘X’, ‘Invalid Status’) and it will work.  Let’s add some validation. 

Test first:

[TestMethod]
[ExpectedException(typeof(InvalidStatusException))]
public void PersonStatus_InvalidStatus_ExpectException()
{
    Person p = new Person();
    p.Status = new PersonStatus('X', "Invalid Status");
    Assert.Fail("Expected an InvalidStatusException");
}

And the Implementation:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    private PersonStatus _status = PersonStatus.Active;
    public PersonStatus Status 
    {
        get
        {
            return _status;
        }
        set
        {
            if (!PersonStatus.IsValid(value))
                throw new InvalidStatusException(string.Format("'{0}' - {1} is an invalid status", value.Code, value.Description));
            _status = value;
        }
    }
}
public class PersonStatus
{
    public static PersonStatus Active = new PersonStatus('A', "Active");
    public static PersonStatus Closed = new PersonStatus('C', "Closed");

    private static IList<PersonStatus> _validStatusList = new List<PersonStatus>();

    public char Code { get; set; }
    public string Description { get; set; }

    static PersonStatus()
    {
        _validStatusList.Add(Active);
        _validStatusList.Add(Closed);
    }

    public static bool IsValid(PersonStatus status)
    {
        return _validStatusList.Contains(status);
    }

    public PersonStatus(char code, string description)
    {
        Code = code;
        Description = description;
    }
}
[Serializable]
public class InvalidStatusException : Exception
{
    public InvalidStatusException() { }
    public InvalidStatusException(string message) : base(message) { }
    public InvalidStatusException(string message, Exception inner) : base(message, inner) { }
    protected InvalidStatusException(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
}
In my next post I will show how you can use this same technique with an ORM tool.
Published Sunday, August 30, 2009 12:09 PM by Pieter
Filed under: ,

Comments

# re: Magic Numbers – How to remove them from your Codebase.

Hello Pieter,

You lost me with this article.  Let me explain what confused me:

1. My understanding of Magic Numbers is the same as explained in the Wikipedia article you quoted. But this very much in contrast with that of a property on a class .  Magic numbers are very useful to identify the possible format for a given binary format - for example serving as file identifiers (The hex CAFEBABE for Java class files being a beautiful example). I guess one can argue 'C' is a random value but not a 'magic' number/value in my opinion.

2. That said I understand the problem you trying to solve.  Using a single character 'C' to describe a state is not self-describing.  Won't the use of a enumerated type solve the same problem and be much simpler?

Sunday, August 30, 2009 1:01 PM by Philip

# re: Magic Numbers – How to remove them from your Codebase.

Nice. I'm looking forward to the ORM post.

Monday, August 31, 2009 7:18 AM by CalmYourself

# re: Magic Numbers – How to remove them from your Codebase.

Hello Pieter,

Seems I wasted my time to respond to this blog post before.  

That is sad.  One spends time to read, understand and respond.  Yet when engaging in the post and not deemed favourable by the author it is simply ignored.

I won't waste any further time on this blog.

At least save the next traveller some time and make your comments read-only...

Friday, September 04, 2009 6:33 AM by Philip

# re: Magic Numbers – How to remove them from your Codebase.

@Philip - Get over yourselve!

I post for my own pleasure and to share what I find.  The fact that I don't have the time to respond to every comment placed on my blog doesn't seem to concern you.  And the fact that spammers create thousands of comments, making it damn near impossible for me to make the time to wade through all the comments trying to find the legitimate comments.

I don't edit or moderate any ligitimate comment that I do find... So yea, get a life!

Thursday, October 01, 2009 1:31 AM by Pieter

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above: