After my post on accessing game objects by unique ids last week, I was asked whether it was not potentially harmful to allow objects to create their own identifiers.
With this follow-up post I would like to discuss that question in more detail.
Limiting identifier construction
As was pointed out, my solution in last week’s post did not circumvent the problem that any part of the application’s code could create arbitrary identifiers.
That could mean that identifiers may not be unique any more, which could cause all sorts of problem.
There are many different approaches we could take to solving this problem.
For example, it was suggested that we could move our types to a different assembly, and use internal constructors to prevent our main code from creating their own identifiers.
Similarly we could nest the
IdManager inside the
Id type, and have the latter only expose a private constructor.
Unfortunately, in our case there is a small flaw in this approach: C# structures always have a public parameterless default constructor, which will always remain accessible and can be used to create fake identifiers – albeit always with the same internal integer representation.
We could circumvent this by using a class instead, however given the simplicity of our identifier type – as well as its semantics, which fit the intended usage of value types – I will dismiss that possibility. I do not think that it is worth not having a public constructor, if the trade-off is significantly more pressure on the garbage collector.
Next to the above, there is another problem we encounter: What if we are using multiple identifier creating managers?
Multiple managers are bound to create the same identifiers, which could again lead to a world of trouble if abused.
To solve this issue, we could create our identifiers statically, either by only exposing a static creation interface, or by keeping the manager’s backing counter static and thus shared between multiple instances. This solutions creates a number of other problems however. For example it makes it almost impossible to unit test our code, or code that uses it.
Alternatively, we could make our identifier more complicated, by for example having it know the manager that created it. That way we can compare and will never confuse identifiers from different sources. Note that we could use another id for this purpose. However, using a reference to the manager object itself is much easier to implement and also more secure, since references cannot be easily faked.
Unfortunately, if we have access to the identifier manager, we can then still create our own identifiers, giving it the valid manager, but an invalid integer value.
A solution to that would be to keep an object only known to the manager and its identifiers stored inside the manager itself. Doing so would in essence sign identifiers in a manner similar to using a private key.
Designing foolproof systems
So far I have not given a good solution to the original concern.
And in fact, I will not give one at all, but instead I would like to use the above to highlight something I consider much more important:
There are many ways in which we can try and prevent the users of our code – and even ourselves – from abusing it. However, even this choice of words shows what I consider an ironic error in thinking:
If we have access to a programs source code, we can always abuse it – or its parts – for any purpose. There is no way to write code that is foolproof, if we allow the user full access to it and is intend on breaking it.
Do not get me wrong: I love code that can only be used the right way.
I am also very guilty of trying to write such code. And it certainly is not impossible – if we assume the user will not modify it.
However, trying to create a system that cannot be abused under any circumstance usually means that that we end up with a lot of complicated code to cover our flanks, and more often than now we have to make the system very bulky and rigid.
In short, trying to create a foolproof system tends to end up with over-engineered solutions.
There are certainly places where it is important that things be foolproof. However, the general topic of this blog is game development and for the most part, we can approach these issues from a more relaxed standpoint.
Incentivising intended usage
Even if we say – and let me know in the comments whether you agree – that we may want to favour simplicity and elegance over foolproofing our code, we do not have to give up hope entirely.
While we maybe cannot force our users to use our code as we would like them to – and only in that one way – we can still give them incentives to do so anyways.
The biggest incentive of course is that using the code the way it is meant to be used will achieve the desired results. This alone may be enough, but we have to make sure that our users know what the point of our system is.
Only if we communicate accurately and clearly what problem our system solves, can it be used for that purpose reliably.
There is however another incentive that is often forgotten and that I would like to highlight specifically:
If we want our code to be used a certain way, we should make that way the easiest way to use it.
If there are multiple alternatives, we tend to go for the one that is easiest. The one that we can find and understand with the least difficulty.
That is why I am convinced that we should always make an effort to design the interfaces to any system we write to make it as easy as possible to use correctly.
While I did not actually solve – or maybe even answer – the questions raised, I hope that this little exploration provides some value on its own.
I certainly very much enjoyed thinking about this topic, and very much appreciate the question that made me start to ponder it.
As for the topic of unique identifiers itself, there are still a bunch of interesting things I would like to write about – but they will have to wait until next week.
Enjoy the pixels!