Design Principles – Removing Hard-Coded Values vs YAGNI

design

First a bit of background. I'm coding a lookup from Age -> Rate. There are 7 age brackets so the lookup table is 3 columns (From|To|Rate) with 7 rows. The values rarely change – they are legislated rates (first and third columns) that have stayed the same for 3 years. I figured that the easiest way to store this table without hard-coding it is in the database in a global configuration table, as a single text value containing a CSV (so "65,69,0.05,70,74,0.06" is how the 65-69 and 70-74 tiers would be stored). Relatively easy to parse then use.

Then I realised that to implement this I would have to create a new table, a repository to wrap around it, data layer tests for the repo, unit tests around the code that unflattens the CSV into the table, and tests around the lookup itself. The only benefit of all this work is avoiding hard-coding the lookup table.

When talking to the users (who currently use the lookup table directly – by looking at a hard copy) the opinion is pretty much that "the rates never change." Obviously that isn't actually correct – the rates were only created three years ago and in the past things that "never change" have had a habit of changing – so for me to defensively program this I definitely shouldn't store the lookup table in the application.

Except when I think YAGNI. The feature I am implementing doesn't specify that the rates will change. If the rates do change, they will still change so rarely that maintenance isn't even a consideration, and the feature isn't actually critical enough that anything would be affected if there was a delay between the rate change and the updated application.

I've pretty much decided that nothing of value will be lost if I hard-code the lookup, and I'm not too concerned about my approach to this particular feature. My question is, as a professional have I properly justified that decision? Hard-coding values is bad design, but going to the trouble of removing the values from the application seems to violate the YAGNI principle.

EDIT To clarify the question, I'm not concerned about the actual implementation. I'm concerned that I can either do a quick, bad thing, and justify it by saying YAGNI, or I can take a more defensive, high-effort approach, that even in the best case ultimately has low benefits. As a professional programmer does my decision to implement a design that I know is flawed simply come down to a cost/benefit analysis?

EDIT While all the answers were very interesting as I think this comes down to an individual's design choices, I think the best answers were @Corbin's and @E.Z. Hart's as they bring up things that I hadn't considered in the question:

  • the false dichotomy of 'correctly removing hard-coded values' by moving it to the database vs 'efficiently applying YAGNI' by using hard-coding. There was a third option of putting the lookup table into the app configuration, which doesn't incur the overhead of the correct way, and without the efficiency of YAGNI. We generally aren't limited to either/or decisions, and it then comes down to a cost/benefit decision.
  • code generation can reduce the overhead of moving the hard-coded values to the database, and in a way that also removes my over-engineered decision to process a CSV into the table. Potentially this also adds a long-term maintenance issue with the generated code if the basic requirements change for the lookup method. This all just affects the cost/benefit analysis, and it is likely that if I had had that automation available I wouldn't have even considered hard-coding something like this.

I'm marking @Corbin's answer as correct because it changes my assumptions of development cost, and I'll probably add some code generation tools to my arsenal in the near future.

Best Answer

You found a flaw in your development process. When it's a pain to do the right thing (create the table, repo, repo tests, flatten tests...), developers will find a way around it. This usually involves doing the wrong thing. In this case, it's tempting you to treat application data as application logic. Don't do it. Instead, add useful automations to your development process. We use CodeSmith to generate the boring, boilerplate code no one wants to write. After we create a table, we run CodeSmith and it generates the DAOs, DTOs, and stubs out unit tests for each.

Depending on the technologies you're using, you should have similar options. Many ORM tools will generate models from an existing schema. Rails migrations work in the opposite direction - tables from models. Meta-programming in dynamic languages is particularly strong at eliminating boilerplate code. You'll have to work a little harder to generate everything you need if you have a complex multi-tier application, but it's worth it. Don't let the feeling, "wow, this is a pain in the neck" stop you from doing the right thing.

Oh, and don't store your data in a format that requires additional processing (CSV). That just adds extra steps that require your attention and testing.

Related Topic