views:

121

answers:

4

As a thought experiment on a hobby project, I've been thinking of a way to ensure that this sort of subtle bug/typo doesn’t happen:

public void MyMethod(int useCaseId)
{
    // Do something with the useCaseId
}

public void SomeOtherMethod()
{
    int userId = 12;
    int useCaseId = 15;
    MyMethod(userId); // Ooops! Used the wrong value!
}

This bug would be hard to find because there’s no compile-time error, and you wouldn’t necessarily even get an exception at run-time. You’d just get "unexpected results".

To resolve this in a simple way, I’ve experimented with using empty enum definitions. Effectively making a user id a data type (without going quite as far as a class or a struct):

public enum UseCaseId { // Empty… }

public enum UserId { // Empty… }

public void MyMethod(UseCaseId useCaseId)
{
   // Do something with the useCaseId
}

public void SomeOtherMethod()
{
   UserId userId = (UserId)12;
   UseCaseId useCaseId = (UseCaseId)15;
   MyMethod(userId); // Compile error!!
}

What d’you think?

+1  A: 

I personally think it is unnecessary to be honest.
It is down to the developer to implement the logic properly and you can not rely on compile time errors for such bugs.

J Angwenyi
Even when the compiler can check this part of the logic, and even when this is sort of the point of strong typing? Almost -1 .
Joey Adams
... which makes sense if you're using a dynamic language. If you've got the headache out of a statically-typed language, why not let it catch errors for you?
kyoryu
+2  A: 

I'd rather prefer to validate the argument in MyMethod and raise appropriate exception in case of error-condition.

public void MyMethod(int useCaseId)
{
    if(!IsValidUseCaseId(useCaseId))
    {
         throw new ArgumentException("Invalid useCaseId.");
    }
    // Do something with the useCaseId
}

public bool IsValidUseCaseId(int useCaseId)
{
    //perform validation here, and return True or False based on your condition.
}

public void SomeOtherMethod()
{
    int userId = 12;
    int useCaseId = 15;
    MyMethod(userId); // Ooops! Used the wrong value!
}
this. __curious_geek
Of course, however how to validate that a UserID was passed in instead of a UseCaseID, when they're both `int`? I.e. what would your `IsValidUseCaseId()` look like?
Neil Barnwell
read my comment on your question.
this. __curious_geek
Why not use a type, and catch the error at compile-time rather than runtime? Isn't that one of the major benefits of strongly-typed languages?
kyoryu
+5  A: 

If you're going to the trouble of creating new types to hold UserId and UseCaseId, you could almost as easily make them into simple classes and use an implicit conversion operator from int to give you the syntax you want:

public class UserId
{
    public int Id { get; private set; }

    public UserId(int id)
    {
       id_ = id;
    }

    public static implicit operator UserId(int id)
    {
        return new UserId(id);
    }

    public static void Main()
    {
        UserId id1 = new UserId(1);
        UserId id2 = 2;
    }
}

That way, you get the type safety without having to litter your code with casts.

Niall C.
+1. This solution makes more sense to me via usage than the OP code. While the OP code is very short, it will be confusing for a user of the class taking those parameters.
Merlyn Morgan-Graham
I think the implicit conversion defeats the idea of letting the compiler check the code.
jdv
I don't recommend a whole new type for just validating the arguments. In your solution, If it could take 2, it could take any value. Are we addressing the right problem here ?
this. __curious_geek
@Niall C: You could make this a "TypedInteger" base class, and each time you want a new int type, make an empty derived class. If you felt like it, you could event make it a template, and provide this for any class...
Merlyn Morgan-Graham
@jdv, Niall C: Yes I agree. If you can make the implicit conversion go the other way, I think you'll have a winner.
Merlyn Morgan-Graham
@this: It prevents you from passing a `UserId` to a method expecting a `UseCaseId`, which is what the OP wanted.
Niall C.
@this: I say, name it `ExplicitlyTyped<T>`, and change the operator to `public static implicit operator T(ExplicitlyTyped<T> value)`. Make `UserId` and `UseCaseId` derived classes, with only a constructor (to forward to the base constructor). This will solve the implicit conversion calling problem (the compiler accepting `MyMethod(5)`) , and the cross-compatible values problem.
Merlyn Morgan-Graham
That's too much of engineering. There's no need for all this. Check my comments on the question to see why.
this. __curious_geek
Oops, I thought the @this was a special marker for everyone on the question... :) Anyhow, another advantage of taking the class hit is that it would leave you a hole to shoehorn more code into later. While I wouldn't say that everyone should use these everywhere, I can see where it would be useful. It may be over engineering for some problems, but it may well be a clever, useful, and painless solution in other cases.
Merlyn Morgan-Graham
@Merylyn: I agree with you. I only commented within the context of the question and the discussions.
this. __curious_geek
It probably makes sense to do the implicit cast back to int. But either way, I'd recommend not doing both.
kyoryu
Since the language in question seems to be C#, why not use a value type? The semantics seem a better fit.
Daniel Pryden
@Merlyn: I think I've done similar, with a ConstrainedValue<T> type.
kyoryu
@kyoryu: Is that what you named a class that you created, or is there a ConstrainedValue<T> type that I should check out? I love finding .NET framework classes I didn't know about :)
Merlyn Morgan-Graham
@Merlyn: That was just a class I created. I'm considering writing some classes like that (possibly one for each primitive type) and CodePlexing them.
kyoryu
+1  A: 

If it were Haskell and I wanted to do this, I might do it like:

data UserId    = UserId    Int
data UseCaseId = UseCaseId Int

This way, functions will accept a UserId instead of an Int, and creating a UserId is always explicit, something like:

doSomething (UserId 12) (UseCaseId 15)

This is similar to Niall C.'s solution of creating a type to wrap around an Int. However, it'd be nice if it didn't take 10 lines to implement per type.

Joey Adams