DEV Community

david duymelinck
david duymelinck

Posted on

Solutions to minimize strings and numbers in the code base

I'm obsessed by removing as much strings and numbers from the code as possible. It comes from the fear that they are magic, I don't want my code to trick me.
There are five solutions I can think of.

The first is moving the strings and numbers to a variable. This relieves the code from the responsibility to have the correct value.

The second solution is an environment variable. The code is still not responsible for the correct value, but it more specialized than a variable because the value is bound to the OS the application is running on.

For the other solutions the code is responsible for the correct value.

Global constant

const USER_TABLE = 'users';`
Enter fullscreen mode Exit fullscreen mode

Global constants go against the object-oriented practices. But if you think about it environment variables are also a form of global constants, and they are used without any hesitation.

One of the reasons global constants are considered bad is because there is a perception it is hard to group them. I think namespacing is as beneficial for classes as for constants to do the grouping.

The main problem is that, like with functions, there is no mechanism to autoload files that contain constants. So you need to provide one yourself.

function loadConstants(string $path) {
  foreach (glob("$path/*.php") as $file) {
    require_once "$path/$file";
  }
}
Enter fullscreen mode Exit fullscreen mode

Class constant

class Table 
{
   const USER = 'users'
}
Enter fullscreen mode Exit fullscreen mode

Using a class constant is the more accepted solution when doing object-oriented programming.

An object-oriented purist will have a problem with having only constants in a class. Because a class should have state, mutable properties, and/or behaviours, methods.

Enum

enum Table : string
{
  case USER = 'users';
}
Enter fullscreen mode Exit fullscreen mode

This is the object-oriented dream solution. An enum is a specialized class that is meant to contain constant like items. There are two types of enums, basic and backed. The biggest difference is that a backed enum item has a value and a basic enum doesn't.

I choose a backed enum for the example because the syntax is closest to the global constant and class constant syntax.
It does require more code to get the value.

echo TABLE_USER; // global constant
echo Table::USER; // class constant
echo Table::USER->value(); // enum
Enter fullscreen mode Exit fullscreen mode

The benefit of a backed enum are the from and tryFrom methods. These are useful when the value comes from a variable.

var_dump(Table::from('users')); // creates Table::USER instance
Enter fullscreen mode Exit fullscreen mode

The biggest drawback of a backed enum is that the value can only be a string or an integer.
To allow more types you need a basic enum with a method to return the value with the correct type.

enum Table
{
  case USER;

  public method get(): TableName
  {
    return match($this) {
      Table::USER => new TableName('users'),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a lot more code than for the other constants. But because the item definition is separated from the value, this opens more possibilities. And one of them is to return different value types for the same definition.

enum Table
{
  private const USERS = 'users'

  case USER;

  public method getTableName(): TableName
  {
    return match($this) {
      Table::USER => new TableName(Table::USERS),
    };
  }

  public method getString(): string
  {
    return match($this) {
      Table::USER => Table::USERS,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example you see how you can use a class constant in an enum to reduce the use of a string.

Conclusion

Check if it is possible to move the a string or a number out of the code. And if that is not possible use one of the constant types that fits best for your application.

Top comments (2)

Collapse
 
ravavyr profile image
Ravavyr

ok, for a large complicated system, i kinda get this, abstraction and whatnot...

But they're variables...put them in a config file and load them in, this is pure overkill and someone's gonna rip their hair out if you do this to like 50 system variables that someone needs to understand to accomplish minor changes.

For example, i see this done to stuff like "pagination" numbers. Say pagination is 12... and it's a variable loaded like this in 30 different interfaces.
In your new setup changing it for just one interface requires a whole new set of functions when simply defining it in a config file and adding a secondary value for the one interface you want to change would take about 2 seconds.

Build code so future you won't hate you.

Collapse
 
xwero profile image
david duymelinck

Sure a config file is a valid option. That is my first solution, make it a variable. I don't care if the variable comes from a config file, user input, or somewhere else.

It was my intention to show the different possibilities. Constants can be used in a library, for example tax rates. They are not changing often.
It is up to you to assess how changeable the string or number is and use the right way to store it, or not store it if it is highly changeable.