1

I'm trying to get a good understanding as to whether there is a best practice or standard regarding keeping values within your code libraries or referencing them from another config file. I don't have a formal background in computer science so apologies if I'm not using appropriate language here. It's probably best for me to give an example.

Let's say I have a base class called StringInList.

class StringInList:
    def __init__(self, value_to_check):
        self._value = value_to_check

    def list_of_values(self):
        return []

    @property
    def valid(self):
        return self._value in self.list_of_values()

The class simply checks to see if the value_to_check is in the list_of_values. It's very simple and not very useful but this is just an example.

Now I create another class that inherits the StringInList class.

class TypesOfFruit(StringInList):
    def __init__(self, fruit):
        super().__init__(fruit)

    def list_of_values(self):
        return [
            'Apple',
            'Pear',
            'Banana'
        ]

I basically override the list_of_values with a list of fruit, now I can check to see if any string I pass in is a fruit.

t = TypesOfFruit("Apple")
print(t.valid)
>>> True

This is a very simple example, but imagine I wanted to have dozens of different classes that inherit the StringInList class. Let's say I want to have YogurtBrands, FootballTeams, CandyBars, etc.

Is it better to have a unique object for the list_of_values function for each child class? What if I wanted to have hundreds of fruit in my TypesOfFruit class? Should I have a separate file written in YAML or JSON that contains the list of fruit, and then have a reference to that in the list_of_values function?

The argument for having the values "hard-coded" is that you see the values right there in the code. The argument for having values referenced in a separate config file is that you can separate the code from the values, so if anyone wanted to add a fruit then they just need to append a line to fruit.yml. I lean towards having the values in the code itself, but is there any standards for making decisions like this? Would it make sense to have the base class(es) in its own file, then have any child classes in their own file?

I'm looking for some general guidance, rules of thumb, etc.

3
  • 2
    It depends on how code and values may change over time. If the values often change without other code changing, it makes sense to separate it in a config file. If code changes as often (or as rarely) as the values, it is better to have the values in the code since it keeps the logic cohesive. So basically you have to make a prediction about how the logic will change in the future.
    – JacquesB
    May 25, 2021 at 9:16
  • There is a third option - the lists are stored in some sort of external datastore (EG database) and populated as required.
    – Peter M
    May 25, 2021 at 14:28
  • The language and environment are likely to be the most significant characteristics that determine the best approach. In Python the Pydantic Settings model delivers a great compromise as close as you can get to the best of both worlds. The Pydantic Settings model can be instantiated from hard coded defaults, which can be over-ridden through multiple vectors including environment and config files, and can even be selectively over-ridden, for instance, a single element of a large array. It also centralises the data, so it's not spread all over your code.
    – NeilG
    Mar 8, 2023 at 0:35

3 Answers 3

5

There is no best practice or general rule. It is situational and depends on how often this data needs to change, how difficult it is to change, and team preferences.

Keeping this information in config files comes at a hidden cost. You must parse the files and create the objects at application startup or when handling a request, in the case of a web application.

Your code example is in Python, so we can make a few generalizations. First, if this is a web application, these config files will likely get parsed and objects will get created with each HTTP request. You can use lazy initialization to help reduce this work, since not every request will likely need all the configuration data. But as you said, adding values is easy.

Hard-coding these values also comes at a hidden cost. It needs to be stored some place, and from the sound of it, this data will be scattered all over a bunch of files. I would define the data and class in the same file, although since Python syntax is terse, you could stuff all the data and classes in a single file.

Whichever you choose, consult the team, because they must maintain this code and data as well.

2

Is it likely to change without a feature change? (Within my lifetime we have seen new types of fruit, and I have also encountered fruit while traveling that does not appear to exist here) Config file. It is set in stone? (A week is 7 days, period) Hardcode.

As the years go by I find myself putting more and more stuff in a config file instead of the code.

1

I suspect you may realize that there is no one-size-fits-all answer here. I'm going to do my best to address the related concerns so that you get an idea on how to judge this on a case-by-case basis.

1. List/array?

A lot of languages have an enum construct which specifically allows you to contain a closed list of values. This replaces general "string matching" logic (and therefore your StringInList).

To your credit, you did abstract this string matching logic away in your TypesOfFruit class, but it is still a string matching approach.

For example:

public enum CardSuit { Clubs, Diamonds, Hearts, Spades }
public enum CardValue { Ace, Two, Three, Four, ..., Queen, King }

public class PlayingCard
{
    public CardSuit Suit { get; set; }
    public CardValue Value { get; set; }
}

var card = new PlayingCard() { Suit = CardSuit.Spades, Value = CardValue.Ace };

if(card.Suit == CardSuit.Hearts && card.Value == CardValue.Queen)
    Console.WriteLine("Paint those roses red!");

This is just a basic usage example. You can see here that you can enshrine specific values and reference them directly. This enables intellisense, so you can get a quick overview of all available values, and immediate feedback if you pick a value that doesn't exist (whether a typo or something you never created).

This is much better than your string-based approach, as this is more liable to needing you to doublecheck spelling or making trivial mistakes in capitalization.

If your language of choice has no enum-like construct, I would still consider a static-property style approach. This is very close to rolling your own enum:

public static class CardSuit
{
    public const string Hearts = "ANY_UNIQUE_STRING_VALUE";
    public const string Spades = "ANOTHER_UNIQUE_STRING_VALUE";
    public const string Diamonds = "A_THIRD_UNIQUE_STRING_VALUE";
    public const string Clubs = "YET_ANOTHER_UNIQUE_STRING_VALUE";
}

public static class CardValue
{
    // Omitted for brevity's sake
}

I used string here as the underlying type but you could use any data type you want, e.g. integers or guids.
Also notice that the specific string values do not matter because we specifically intend to never directly use them. All that matters is that all of these string values are unique.

This still allows you to mostly write code without using string literals. The only thing that changes is that you don't get a nice type for your value, and you have to use string instead, but the rest of the code is untouched:

public class PlayingCard
{
    public string Suit { get; set; }
    public string Value { get; set; }
}

var card = new PlayingCard() { Suit = CardSuit.Spades, Value = CardValue.Ace };

if(card.Suit == CardSuit.Hearts && card.Value == CardValue.Queen)
    Console.WriteLine("Paint those roses red!");

This is not as great as using enums out of the box, but it's still better than having to handle string literals.

2. Hardcoded or config file?

Enums (or static class properties) are still a form of hardcoding, just like your approach. So should we take this route, or should we dynamically load the data from a config file (or similar external data source)?

Well, it all depends on whether you want to be able to adjust this list of values without redeploying the application.

In my playing card example, it is reasonable to assume that the card suits and values are set in stone, and are not going to change. If tomorrow we add a fifth suit and remove the King, then it's going to be nigh impossible to not have to dramatically change our codebase anyway, so we're not going to benefit from being able to dynamically change this data set.

However, if your fruit example is for a fruit vendor point-of-sale application, then you are most likely going to want to be able to add/remove possible fruit from the list without needing to adjust/redeploy the application. Here, using a config file is much more desirable.

Do note that the dynamically loaded approach entails needing to use a dynamic reference value, meaning you cannot benefit from both the enum and config file benefits at the same time. However, in most cases, when the data set is dynamic, your code tends to never have hardcoded expectations of specific values, so this is usually not a big deal.

However, I would generally urge you to take either option 1 or option 2, and avoid having a hardcoded list of strings. Either option is better (in their specific approach).

3. How many values?

Maybe surprisingly, this doesn't really factor into the decision all that much. How we source our data is a decision based on structure, not content.

Pick any arbitrary amount of fruit types, and the reasoning for options 1 and 2 remains the same.

However, there is of course an upper limit on reasonability. If we are talking millions of entries, this is well beyond what any human can manage in a text-based file (whether code file or config file). This is where you start using a database, but your codebase will also dramatically change by querying the database selectively instead of just loading the entire list in memory.

If we stick to the reasonable upper limit that we're only talking about an amount of data entries that you're happy to load into memory all at once, then the specific amount of entries does not factor into the decision on how to source the data.


Would it make sense to have the base class(es) in its own file, then have any child classes in their own file?

The general advice is to stick one thing (class, interface, struct) in one file, so that the filename matches the class/interface/struct name.

That being said, I'll admit that I sometimes break this approach when I specifically want to keep things together.

  • In a CQRS-style approach, I tend to keep the query and query handler in the same code file, because when I'm looking at one, I almost always want to look at the other as well.
  • I often have a Enums.cs file which contains all my one-liner enums
  • If a class returns (trivial) custom DTO objects which are not used by any other class in that layer, then I nest the dto classes in the only class that uses them, which also means they remain in the same file.

But when working professionally, I stick to the "one thing per file" approach more so than I do in my personal projects, because not everyone sees eye to eye on how I prefer to structure my code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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