That's a very vague title but I couldn't think of a better way to word it. But, just as an example, think about the direction that a character in a game is moving in. It just feels kind of wrong to be using a string and then doing things like if(character.direction == "left")
. It seems to me that it leaves too much room for silly errors, like accidentally using Left
or l
or whatever instead of left
. Are my suspicions correct? If so, what's the preferred way of achieving something like this?
7 Answers
If the language you are using supports the use of enums, I'd use them. It allows you to limit the number of options available for a given type. e.g. in Java:
public enum Direction {
LEFT,
RIGHT,
UP,
DOWN
}
-
2This answer does not contribute to understanding OP problem; I see no reasoning here, only an opinion limited in scope to languages with classy
enum
s like java. Many languages (e.g. VBA) haveenum
, but it may not be the best option as it lacks the same future-proofing. See my answer for some discussion of whyenum
alone is an incomplete, often brittle, and language-specific solution.– sqyklyDec 22, 2016 at 6:00
It's terrible practice to use literal strings (or magic numbers) in code.
Enums are good, or at very least use constants (enums of course are just a wrapper type for one or more related constants).
const string LEFT = "left"
if (someThing == LEFT) { doStuff(); }
This simple switch has so many advantages I wouldn't even know where to start to explain them (at least not without some more coffee). Read up on magic numbers, it's the same exact concept, only applied to a number value instead of a string value.
Here's one quick ref, I'm sure there's hundreds more: https://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad
-
1The problem I have with this approach is that you get typo-bugs like --const string LEFT = "letf"-- whereas if you work with enums that gets caught at compile time.– Pieter BDec 16, 2016 at 13:03
-
@PieterB - I thought OPs question was broad enough, and the example for "left" just an arbitrary example - where all cases wouldn't match to an enum. I agree that for a set of directions specifically, an enum is the best choice, but my answer was meant to be more generic in that "if you put it into a literal, at least make it a constant" (enums, of course, being a type of constant)– jleachDec 16, 2016 at 13:10
-
3I've had the pleasure to work with an application that assigned the days of the weekend as those days which name starts with an "s". Much surprised they were about the "fact" that when they internationalized the application that France only had a one-day weekend.– Pieter BDec 16, 2016 at 13:41
-
4Call it microoptimization, but some languages might interpret this as a value comparison and waste a lot of time checking each character in the string. Even more likely if you do as the asker did and compare with the hardcoded string "left." If there's no need for the constant value to be a string (that is, you don't print it), it would be better to use a const int instead.– DevsmanDec 16, 2016 at 18:37
Short answer: Yes, strings are not ideal for any task other than storing and accessing a sequence of textual characters, and even if the underlying bits of an abstraction are strings, there are benefits to referencing them as variables or constants.
Long answer: most languages offer types that are closer to the domain of your problem, and even if they don't, they probably have some method by which you can define left
as an entity that is different from right
(etc etc). Turning it into a string by using "left"
is simply forfeiture of the language and IDE's helpful functionality such as error checking. Even if you have to use
left = "left";
or something equivalent, it has advantages to using the string when you refer to it across your code. For example,
if (player.direction == Left)
would be detected as a compile-time error (best case, java and such) or be flagged by any IDE worth its bits as wrong (worst case, basic and such).
In addition, having the notion of left
as a referenceable entity will help you accommodate whatever direction you decide to go with the direction type. By using "left"
, direction is committed to being a String
. If you find or create a better abstraction for the type, you need to go hunting through your entire code body changing every instance of "left"
. A constant or variable will not require any changes if the type is elaborated.
The type you really want is particular to your domain. What is your code planning to do with your left
? Perhaps you will want to mirror the x axis of a texture or sprite that is facing left or moves forward; if so, you might want to make left
an object with a property or method that reflects that behavior, with your right
object having the opposite non-mirrored result. You could do that logic in an object that represents the sprites or textures, in which case again you will suffer for using "left"
instead of left
for reasons stated above.
In Java (and probably other languages I don't yet know), enum
is a good alternative because in Java, enum
is as useful a final class
with a finite set of instances. You can define behaviors if you need them, and you can switch to an open class without any hassles outside of the file that declares the enum
, but many languages see an enum
as a primitive type or require special syntax to use. That leaks the enum
hood to code that has no business with it and may need to be corrected later. Make sure you understand your language's concept of an enum
before you consider the option.
You didn't specify your language, but to give a generic answer, you should use strings when you actually need the text, not to represent something. The example you gave for example would simply not be acceptable in production software.
Every language has various basic data types and you should use the one most appropriate for the task. To use your example, you could use numeric constants to represent different states. So left could have a value of zero and right would be one. Besides the reason you mentioned, numerical values take up less space (memory) and it's always faster to compare numbers than it is to compare multiple character strings.
As it's been suggested, use enumerations if your language allows. In your specific example, it's clearly the better choice.
-
Speed and memory are unlikely to matter. That said, prefer enums or constants. Strings make more sense if the data is coming from a text format, e.g. XML, but even then be consistent with all caps, or all uncaps. Or, always convert, e g.
if (uppercase(foo) == "LEFT")
Dec 16, 2016 at 1:44 -
2@user949300 Converting strings to all uppercase or all lowercase isn't reliable either. There's the possibility that someone might decide to visit another locale (or expand the business there), like Turkey, and break naive upper casing (or lower casing) string comparisons.– 8bittreeDec 16, 2016 at 20:16
-
Caring about speed and memory is always good practice, they're not infinite resources. Although it's ok to consciously sacrifice it for the sake of readability or some other trade-off, which is not the case here. But not caring about them can easily lead to sluggish or wasteful applications.– NagevDec 17, 2016 at 14:44
-
Comparing strings is likely to be 50 times slower than comparing enums. Most of the time it doesn't matter. But if your code is already a bit on the slow side, that difference may make it unacceptable. More important of course is the fact that the compiler can't help you with any spelling mistakes if you use strings. Jan 25, 2017 at 19:10
Just use the data type that makes sense in your situation.
The usage of the string
type as an underlying inter/intra-application communication isn't inherently a problem. In fact, sometimes it's preferable to use strings: If you have a public API, it can be desirable for the values going in and out of the API to be human-readable. It's makes it easier for folks using your API to learn and debug.
The real problem with your code lies in repeating the value.
Don't do this:
if (direction == 'left') {
goLeft();
}
If you made a typo in one of these conditionals or other uses of "left" somewhere, you might be unable to effectively perform a codebase-wide search for it, because you've mistyped it!
Instead, code against an enumeration or a constant -- depending on which supports the data type you need in the language(s) you're using.
That means LEFT
should be assigned-to once and only once in your application. And, the rest of your code should leverage those enums or constants:
const string LEFT = "left";
if (direction == LEFT) {
goLeft();
}
This also allows you to more easily change the data type you use for your directions later, if you so choose.
Is it bad practice?
Probably yes, but it depends on exactly what you are doing, and also on the programming language you are using.
In your example, we can infer that there is a small and fixed-for-ever set of values that a direction could take. In this case, there are no advantages in using a string, and some definite disadvantages. For this example:
- An
enum
type would be preferable, if your programming language supports this. - Otherwise, named integer constants are the way to go, with a single definition for the constants.
But the answer may be different if the set of values is dynamically modifiable, or needs to evolve over time. In most languages, changing an enum
type (e.g. to add or remove a value) requires a complete recompile. (For example, removing an enum value in Java breaks binary compatibility.) And the same applies for named integer constants.
In scenarios like this another solution may be required, and using strings is one of the options. (And you could combine string literals and named constants ... if the scenario entails a fixed core with dynamic extensions.)
If you are implementing in Java, character.direction == "left"
is bad practice for another reason. You should not use ==
to compare strings, because it is testing object identities rather than string equality. (It would work if you could guarantee that all instances of the "left"
string have been interned, but that requires whole-of-application analysis.)
-
1What seems like fixed for ever often turns out as not so fixed at all. Four direction can for example become eight.– Jimmy T.Jan 20, 2017 at 10:31
-
@JimmyT. - That depends on the context. For example, in a game, changing the number of directions that a character can move from 4 to 8 would entail a complete overhaul of the game rules, and the code that implements the rules. Using
String
to represent the directions would make close to zero difference to the amount of work involved to make the changes. A more sensible approach is to assume that the number of directions won't change, and recognize that you would have a lot of work to do either way if the game rules changed so fundamentally. Jan 21, 2017 at 0:35 -
@JimmyT - Totally agree Ive seen this happen many times, you also get the developers taking the short cut of redefining the string so for example, product = "Option" becomes product = "Option_or_Future" totally debasing the meaning of the code. Jan 26, 2017 at 9:30
As suggested, you should use Enums. However, just using the Enum as a replacement for the Strigns is not sufficient IMHO. In your particualr example, the player velocity should actually be a vector by means of physics:
class Vector {
final double x;
final double y;
}
This vector should then be used to update the player position each tick.
You can then combine that with an enum for higher readability:
enum Direction {
UP(new Vector(0, 1)),
RIGHT(new Vector(1, 0)),
DOWN(new Vector(0, -1)),
LEFT(new Vector(-1, 0));
private Vector direction;
Direction(Vector v) {
this.direction = v;
}
public Vector getVector() { return this.direction; }
}
-
4
bash
.