Both has advantages and disadvantages.
GetItem("HolySwordOfAwesomness")
is more readable. But did you notice I misspelled "Awesomeness"? The compiler can't catch that error, so it will result in a runtime error which will need to be debugged.
GetItem(1337)
won't throw a runtime error. It will create the wrong item instead. This bug might be even harder to catch than an explicit error. But the advantage is that this passes just 4 byte to the function instead of a 21 character string. Also, a lookup by integer can be implemented much faster than a lookup by string. So this method has performance advantages. But it is completely unreadable unless you have your whole item database in your head (you won't).
But you get the best of both worlds when you use integers, but also define a constant for each item: GetItem(ITEMID_HOLY_SWORD_OF_AWESOMNESS)
. This is readable, can be caught by the compiler because you are referring to an undefined identifier and it might offer you auto-complete help from your IDE. But the drawback is that you now need to define a constant for each item you create, which slightly increases the maintenance overhead for creating new items.
You can of course also go the object-oriented route and only refer to items by reference: player.GiveItem(new HolySwordOfAwesomeness())
. But this only works as long as your game is completely contained within your program. References can not be passed via network, can not be read from a config file and can not be written to a savegame files. This means you will need some serialization/deserialization scheme for your items. Yes, some programming languages can serialize/deserialize objects to strings out-of-the-box, but most of these schemes tend to break after the slightest change to the class definition. IDs (no matter if integers or strings) make this much more convenient, robust and often far more compact. Also, the referencing approach can be difficult or impossible when you want to use a scripting engine (depending on the API for the engine).