1

I'm writing a fairly large piece of logic, during which there are 6 points where things could go wrong and execution should stop after logging the error. The error is also stored in an object.

However, up to now I have been using a public static final String for this error string. As it is only used in a single place (and almost certainly won't be used anywhere else), does it make more sense to remove this global variable declaration from the class and instead hardcode the error reason?

3
  • 1
    Does this answer your question? Usage of magic strings/numbers
    – gnat
    Feb 3, 2020 at 16:24
  • Constant values (public static final) are still hardcoded.
    – user253751
    Feb 3, 2020 at 17:11
  • Pain Drive Development - hard code everything you not sure about, then when you see that you need to change all occurrences for the same reason - then you will introduce some centralised place to keep values.
    – Fabio
    Feb 3, 2020 at 22:56

2 Answers 2

3

Declaring it separately means you can reference it in unit (or integration) tests. If you end up needing to change the contents of the string, you then only have to change it once (where it's defined), rather than making the same change in the other instances of the string.

There can be other benefits to centralization - for example, if you end up needing to add localization (other-language translated versions of the user-facing content), it's significantly easier to hand off a few files with all of the strings to a translator, rather than an ad-hoc bunch of spots throughout the code.

If you're not writing test code, you probably want to look into doing that - it's generally considered a best practice. If it's a quick personal project, it's OK to take the shortcut of hardcoding - but it's definitely not something that's generally recommended.

Edit: On rereading this, I realize that I'd missed a specific piece of your description: that the strings are currently defined publicly in the class. I had had the impression that they were defined externally, in a different file. My recommendation would be to move them out to a separate "Error Messages" class that contains all of them (which is what I'd thought you were describing from the start).

9
  • 4
    "Declaring it separately means you can reference it in unit (or integration) tests." Such a value should be private and thus unaccessible in the tests. Remember that your tests are not testing implementations, but behaviour! Feb 3, 2020 at 16:31
  • 1
    That's definitely a valid approach, but in my view this depends on context and how the team approaches testing. I've known teams that treat the string itself as an implementation detail that shouldn't be considered a part of the class itself. (From an SRP standpoint, the idea would be "it's not the validator class's responsibility to spell the error text correctly, it should only be either adding the error or not").
    – autophage
    Feb 3, 2020 at 16:51
  • 1
    @VincentSavard Maybe you want to test that it returns a particular error message, but without duplicating the error message. That is behaviour.
    – user253751
    Feb 3, 2020 at 17:11
  • 1
    @user253751 Yes and no. You are correct that it might be behaviour you want to test, but I disagree that sharing the constant is testing the behaviour. You're exposing internal details of the class for the sole reason of testing, which is generally a code smell. As a client of your code under test, your test class is asserting that its observable actions are behaving as expected, without knowing how it is actually implemented. By sharing a constant, you couple your client with the how instead of the what. You want your test to fail if you change the constant, because that is a breaking change! Feb 3, 2020 at 17:30
  • @VincentSavard Observable behaviour: when the user enters a filename that doesn't exist, they get a FILE_NOT_FOUND error. When the user deletes a read-only item, they get a ITEM_READONLY error. I suppose you could put the error messages into another class entirely (which is no different), or you could return an error code, test for that, and translate it to a string later (more complexity).
    – user253751
    Feb 3, 2020 at 17:48
1

A little late response here, but I'll summarise some of the other comments on autophage's answer and combine them with my own experience. Personally, I would not usually put these in a static constant field, although it largely depends on context. The main reasons for this are outlined as follows:

Testing

If you use the static final field in both your class and your tests, you're never actually testing the content of the field. The following test would pass despite containing multiple typing errors:

public class Greeter {
  public static final String WELCOME_MESSAGE = "Welcmoe to my websiet";

  public String getWelcomeMessage() {
    return WELCOME_MESSAGE;
  }
}
class GreeterTest {

  @Test
  void welcomeMessage_Always_AsExpected() {
    // Passes and doesn't really test much
    assertEquals(Greeter.WELCOME_MESSAGE, new Greeter().getWelcomeMessage());
  }
}

Instead, it might be advisory to check the contents of the string in our test, rather than relying on internal implementation details:

// This highlights the typing mistakes
assertEquals("Welcome to my website", new Foo().getWelcomeMessage());

Readability

Sometimes I'm reading through some code and I get to something like this:

if (complexConditionIsMet()) {
  throw new IllegalArgumentException(ERROR_MESSAGE);
}

What does that mean? I then have to scroll to find where ERROR_MESSAGE is defined to decipher what this means. (Granted, a lot of tooling will do this for you on hover, but there are still plenty of tools like GitHub where this is still an issue.) If this read as:

if (complexConditionIsMet()) {
  throw new IllegalArgumentException("User email is invalid");
}

that's suddenly a lot easier to digest. While this example could be resolved somewhat by choosing better named variables, bear in mind that this is a trivial example, and differentiating similar exception messages stored in constants might be difficult while trying to be concise.

Where I really hate to see this is in template strings:

private static final String MESSAGE_TEMPLATE =
    "User %s does not have access to %s. Requires permission %s";

// ...

String.format(MESSAGE_TEMPLATE, username, resource, permission);

If I'm reviewing this code, for example, how can I tell if the correct number of arguments are being supplied? Are they in the correct order? Again, this breaks the flow of reading as I try to find the template and compare it with the provided arguments.

That being said, there are certain examples that do make sense to be stored in static final fields. Take the following example:

public class User {
  private String username;

  //...

  public Optional<String> getUsername() {
    return Optional.ofNullable(username);
  }
}
public class Greeter {

  public String getWelcomeMessage(User user) {
    return "Welcome to my website, " + user.getUsername().orElse("anon");
  }
}

It's not clear here why "anon" is used. You can probably guess that it's a default value, but this could be cleared up like this:

public class Greeter {

  private static final String DEFAULT_NAME = "anon";

  public String getWelcomeMessage(User user) {
    return "Welcome to my website, "
        + user.getUsername().orElse(DEFAULT_NAME);
  }
}

Note that here I'm using a mix of both a "magic" string, and a static constant, but I think this is a good compromise. Instead, this could have been written as:

return WELCOME_MESSAGE
    + user.getUsername().orElse(DEFAULT_NAME);

Doing something like this would be pretty horrible to read as I can't tell if the username is being concatenated in a way that makes sense. Is the welcome message "Welcome!"? If so, this won't read properly, but I can't tell without checking the value of the constant.

Conclusion

User your own judgement. I'd recommend not relying on the fields in the test class, but whether you store the value in a static field in the class itself should depend purely on what you think is the most readable solution.

Other thoughts

In your question, you use a public static final String field. If it's not being used elsewhere, you may as well change the access modifier to private to further hide the implementation details. If I tried to auto-complete using your class, I'd see that string as something I can retrieve and interact with. Do I need this? What's it for? I probably shouldn't see it to save myself this confusion.

3
  • In your MESSAGE_TEMPLATE example: You supply static strings for parameter less messages; you could provide a static function with three arguments to return the message with three parameters, this makes it possible to handle different languages, different order of parameters, plurals etc in one isolated place.
    – gnasher729
    Jul 9, 2021 at 20:21
  • "What does that mean? I then have to scroll to find where ERROR_MESSAGE is defined to decipher what this means" >> In lot of mature applications the translations are user-editable. Which means each customer of a product can add his own company specific our country specific translations. So these are then always variables to be resolved at the translation layer which usually sits outside the business layer.
    – edelwater
    Jul 13, 2021 at 16:33
  • To both the comments above - yes, if you're using translations, plurals etc. it's fine, although generally translations are probably stored in a database somewhere rather than in a set of static strings (especially if the messages are customisable). However I sometimes see this in places it's really not needed (e.g. untranslated error messages). If there's a particular reason to do it, go for it - but doing it just because "magic strings are bad" as a one-size fits all rule doesn't really help anything as it can (perhaps counterintuitively) detract from the readability.
    – Ekos IV
    Jul 14, 2021 at 10:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.