10

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.

1
  • Don't forget, if the rates change and all you have are hard coded values, you could mess up historical record calculations when the rates change (and they will, regardless of what your client is telling you).
    – Andy
    Jul 29, 2015 at 0:34

9 Answers 9

6

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.

3
  • I wouldn't say that a Rails migration creates tables from models... Rails migrations describe changes to tables. Executing a migration changes the database. Model properties matching the table structure are created at run time. Mar 9, 2011 at 17:52
  • This interests me, I hadn't considered automating parts of the initial effort to that level. And having that automation would mean I could set up a full table rather using the CSV to save time. What I'm concerned about is the long-term maintenance of the generated code. By generating it upfront I've made an early assumption that the method of doing the lookup will never change. I guess if that's a possibility I should just take that into consideration as part of cost/benefit, giving the reduced cost of the boilerplate. +1 Mar 9, 2011 at 20:41
  • The question was, "should I hard-code values when my client says they won't change?" and your answer is "no, and in fact you should throw an ORM at the problem." I disagree. There are simpler options that would allow the OP to avoid hard-coding. Making large additions to the architecture to support things explicitly designated unnecessary is unethical. It's unfortunate that many developers are predisposed to do such things. And I must say, I disagree 100% with your suggestion to not "let the feeling, 'wow, this is a pain in the neck' stop you from doing the right thing." Those feelings matter! Jul 28, 2015 at 20:24
10

To key-off of and extend @Thorbjørn Ravn Andersen's answer: Keeping the calculation/lookup in one place is a good start.

Your thought process of defensive vs. YAGNI is a common problem. In this case, I would suggest it be informed by two more things.

Firstly - how was the user requirement presented? Did they specify editability of rates? If not, is the added complexity part of something you can bill for or not? (or if you are on staff, that you can spend your time doing other work instead ?) If so, then definitely go ahead and deliver what was reasonably asked for.

Secondly, and perhaps more importantly, mere editability is unlikely to fulfill an actual requirement in the face of a legislated change. If and when the rates do change, there is likely to be a cut-over date and some before-and-after processing that has to happen. Furthermore if that same interface has to then do any kind of back-dated claims processing, then the correct rate might have to be looked up based on the effective date of the entry, rather than the actual date.

In short, I'm noting that an actual editable requirement may well be quite complex, so unless or until it is fleshed out, the simple is likely better.

Good Luck

3
  • 1
    +1 for your second point. I don't try to outguess what legislative bodies might do in the future. Mar 9, 2011 at 15:15
  • Do it in a library function. It is OK to have only one user. When and if requirements change you have one place to change. (You may need to add a function to do lookups for the value as of a particular date.)
    – BillThor
    Mar 9, 2011 at 16:53
  • Best and most complete answer, +1
    – Andy
    Jul 29, 2015 at 0:34
7

Make the actual lookup a library function. You can then see all the code using that lookup in your source repository, so you know which programs need to be upgraded when the rates change.

9
  • The lookup will only be implemented in a single place (something like a PensionRateLookup class maybe) which is then used globally. I would do that regardless of whether it is stored outside the app or hardcoded, that way only the implementation of the PensionRateLookup class needs to be maintained. My problem is how I've used YAGNI to come to the conclusion that hard-coding the lookup table is acceptable. Mar 9, 2011 at 13:29
  • Thanks for your answer though. Keeping the lookup implementation in one place is definitely good design. Mar 9, 2011 at 13:39
  • YAGNI is only valid IF you can ship new binaries when the rates change. If you cannot, but you can alter the configuration, then make it a configuration value read at start up with the current textual representation as the default.
    – user1249
    Mar 9, 2011 at 13:55
  • I ship a new version usually weekly so actually updating the lookup isn't a problem. Putting it into the client configuration is actually worse as I can't change the client config with a new binary. The alternative as I see it is putting it in the database which means a lot of effort. Mar 9, 2011 at 14:19
  • Then what is the problem? When the rates change, you update the code and ship to all customers?
    – user1249
    Mar 9, 2011 at 14:42
2

Let me see if I got your question right. You have 2 options to implement a feature: either you hardcode a value and your feature is easy to implement (btu you don't like the hardcode part) or you have a very big effort to "redo" a lot of things that are done so you are able to develop you feature in a clean way. Is that correct?

The first thing that comes to my mind is "The most important thing about knowing the good practices is knowing when you are better off without them".

In this case, the effort is very high so you can do it in a clean way, the chances of collateral effect of this change is big and the return you would get is small (as you explained, it seems likely that it won't change).

I would use the hardcode approach (but prepare it to be flexible in the future), and, in case of change of this rate in the future, use the opportunity to refactor all this bad design section of the code. So the time and cost could be estimated correctly and the cost of changing your hardcoded value would be minimal.

This would be my approach :)

3
  • Thanks @Oscar. The technical part of that is def. correct. And I agree with how you would approach the problem, by refactoring only when it is needed. So you're saying that as a professional we can and should pick our design principles, based solely on cost/benefit? Makes sense. Mar 9, 2011 at 14:08
  • @Ben Scott: You are welcome :). Yes, in my point of view, design principles were created/cataloged not because they look beautiful, but because they bring benefits like code "cleanness", flexibility, robustness, etc. But there is always 2 questions: 1- Do I need that? 2- Do my restrictions (time, technical, etc) allow me to implement it? This is usually what leads me to choose my design principle. Over-engineering is also bad ;) ps: if you think it is an interesting answer, please vote it up so other people also read it with a higher probability. Thanks! :)
    – JSBach
    Mar 9, 2011 at 14:14
  • upvoted ;-) Mar 9, 2011 at 14:31
2

This is an item that "won't change" until it does. It's inevitable that it will change, but that time may be a bit far off.

Your justification is correct. At this time, the customer didn't ask for the ability to easily change those rates. As such, YAGNI.

However, what you don't want is the code that accesses your rates and interprets the results scattered throughout the codebase. Good OO design would have you encapsulate the internal representation of your rates in a class, and only expose the one or two methods necessary to use the data.

You are going to need that encapsulation to prevent copy and paste errors, or having to perform a refactoring across all the code that uses the the rates when you need to make a change to the internal representation. When you take that initial precaution, the more complicated approach can be a simple swap and replace for the more featured version.

Additionally, call out the limitation of the current design to the client. Tell them that in the interest of maintaining the schedule you hard coded the values--which requires a coding change to update them. That way, when they become aware of pending rate changes due to new legislation, they can choose to simply update the lookup table or perform the more complicated change at that time. But put that decision in their lap.

5
  • Thanks @berin, I didn't mention SRP in the question but it was in the plan. Good point re giving ownership of the problem back to the client. Mar 9, 2011 at 14:34
  • Assigning blame for when things go wrong in the future doesn't seem professional to me.
    – Andy
    Jul 29, 2015 at 0:37
  • @Andy, what part of that is assigning blame? Presenting the design limitations to the client allows them the opportunity to either prioritize the complicated work now and take other stuff off the table, push back the deadline, or accept the limited design now as other things on the table are more important to them. You are empowering your client with the choice over their product. Making your client aware of the risk/reward of choices you want to make in their interest will make the project run smoother. Jul 29, 2015 at 12:48
  • @BerinLoritsch But this particular design decision is akin using substandard parts saying "I can use plumbers putty to plug this leaky pipe, that's your cheapest option!" The rates WILL change. Its a given, and its irresponsible for a professional to build a system that doesn't allow it. And the savings are probably negligible in the grand scheme of the cost of the project. Put the data in a table; the code that gets the lookup data is slightly different, the logic figuring out which rate to use is the same either way. The only other decision is how to handle historical data after the...
    – Andy
    Jul 29, 2015 at 13:05
  • rates changes, which is usually handled by just copying the rate to the relevant record. No admin screen is needed at this point, the system can then handle when the rates change, and it will be simple script to change them. None of this should be more than a few hours, but will prevent the basement from flooding next year when the putty fails.
    – Andy
    Jul 29, 2015 at 13:07
2

Split the difference and put the rate data in a configuration setting. You can use the CSV format you've already got, you avoid the unnecessary database overhead, and if the change ever is necessary, it'll be a change that the customer should be able to make without having to recompile/reinstall and without messing around in the database - they can just edit the configuration file.

Usually when you're looking at a choice between two extremes (violate YAGNI vs. hard-coding dynamic data), the best answer is somewhere in the middle. Beware of false dichotomies.

This assumes all your configuration is in files; if it's somewhere difficult like the registry, you should probably disregard this advice :)

2
  • I did consider having it in a configuration setting but put it aside because the users aren't very technical as far as editing a CSV string, so I would be the one updating the config for N users (as I do all the IT support in the org as well), in terms of effort it would be easier to do the upfront work to get it into the database which I can administer from one place. I suppose if that wasn't a consideration (if the users were capable of managing their individual configuration) I wouldn't have this problem. So a very good point, thank you. Mar 9, 2011 at 15:14
  • If you combined this with the other suggestions of centralised logic then it's a great answer. It is pure evil to really hardcore the lookup rather than put in a way that allows it to be changed via config. Whenever any other developer comes across it they will hate you for it. Even a one man development shop should consider what happens if they get hit by a bus and should make it easy for the next dev to change the values without a full software release. Only if you hate the client and hate all other developers should you hardcore it. So basically never.
    – simbo1905
    Jul 28, 2015 at 22:14
2

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.

Just created a DB Table. (You where thinking of storing a whole table in one filed, have you gone mad?)

The table needs 2 fields NOT 3. Age and Rate. This next row contains the upper value! You are de-normalizing without even knowing it!

Here is the Sql to get the rate for someone of age who is 67.

Select * from RateTable where Age in (Select max(age) from RateTable where age <=67) 

Don't bother to make a maintenance screen as it's out of scope. If they ask for it later, issue a change request and do it.

EDIT: As others have said keep the code the gets the rate centralized, in case the whole Rate structure chages.

7
  • Hi @Morons, thanks for your answer. The table would actually need 3 columns. It is a single rate per range of ages, min age to max age (65 to 69 year olds are on 5%). And I am only storing a small amount of data for a limited purpose, so what's wrong with making an assumption re the structure and going for a CSV instead of an entire dedicated table? I would probably add a row delimiter, and split by row then column and pull the columns into the required fields. This is always going to be read much more than written to so I'm not too worried about a full table. Mar 9, 2011 at 14:28
  • The next Row Contains the upper value of the range... This is duplication of data. Saying <69 and >7 are the same thing. The only reason to have both values is if the range can have Holes. ... I am saying to use a table because that is easier (and a better design). I don't understand why you think storing a csv in a table will save you any time or effort, You still need a DB call just to get that string, then you have to parse it. As I showed you above you can get the rate with a single DB call.
    – Morons
    Mar 9, 2011 at 14:37
  • Ah true. I was keeping the table itself similar to the source material... and I missed that the age is the upper limit in your answer because I had the sads that I gave you the sads. Mar 9, 2011 at 14:43
  • 1
    "You are demoralizing without even knowing it!" - at first I thought this was a typo and that you meant 'denormalizing', but the more I thought about it the more it seemed you might be right either way :)
    – E.Z. Hart
    Mar 9, 2011 at 14:54
  • @e.z hart LOL true. Mar 9, 2011 at 15:03
1

Hard code in a function.

  • When values change, you can bill the customer again
  • When the values change, chances are the table format will have to change, doing CSV will waste time
  • Easier to implement, higher chances of being on budget with current contract
  • Can find easily when it will need to be updated
1
  • Let me install this substandard value so when it most certainly breaks in a year I get repeat business. That sounds shady, because it is.
    – Andy
    Jul 29, 2015 at 0:40
1

I agree with most of the answers given. I'd also add that consistency with the rest of the application is important. If this is the only place in the code that has hard-coded values it will probably surprise maintainers. This is especially true if it's a large, stable code base. If it's a small program maintained by you the decision is less important.

I have a distant memory of reading a well known agile/OOP guy (like Dave Thomas or Kent Beck or someone) saying that their rule of thumb for code duplication was twice and only twice: don't refactor the first time you duplicate something since you might only ever write it twice in your life. But the third time...

That doesn't exactly address the question, since you're questioning YAGNI but I think it speaks to the general flexibility of the agile rules. The point of agile is to adapt to the situation and move forward.

1
  • Thanks @Dave, that's helpful. I am the only maintainer, from inception, but it is a large, relatively stable code base (thousands of files, over 100 tables, etc) and I am still constantly surprised (and dismayed). Consistency is definitely one of my goals going forward. Mar 10, 2011 at 1:17

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.