Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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.

share|improve this question
add comment

9 Answers 9

up vote 2 down vote accepted

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.

share|improve this answer
    
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. –  kevin cline Mar 9 '11 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 –  Ben Scott Mar 9 '11 at 20:41
add comment

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.

share|improve this answer
    
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. –  Ben Scott Mar 9 '11 at 13:29
    
Thanks for your answer though. Keeping the lookup implementation in one place is definitely good design. –  Ben Scott Mar 9 '11 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 '11 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. –  Ben Scott Mar 9 '11 at 14:19
    
Then what is the problem? When the rates change, you update the code and ship to all customers? –  user1249 Mar 9 '11 at 14:42
show 3 more comments

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

share|improve this answer
1  
+1 for your second point. I don't try to outguess what legislative bodies might do in the future. –  David Thornley Mar 9 '11 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 '11 at 16:53
    
+1 for the circumstances of the change :) –  Matthieu M. Mar 9 '11 at 18:04
add comment

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.

share|improve this answer
    
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. –  Ben Scott Mar 9 '11 at 14:34
add comment

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.

share|improve this answer
    
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. –  Ben Scott Mar 9 '11 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 '11 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. –  Ben Scott Mar 9 '11 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 '11 at 14:54
    
@e.z hart LOL true. –  Ben Scott Mar 9 '11 at 15:03
show 2 more comments

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 :)

share|improve this answer
    
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. –  Ben Scott Mar 9 '11 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! :) –  Oscar Mar 9 '11 at 14:14
    
upvoted ;-) –  Ben Scott Mar 9 '11 at 14:31
    
@Ben Scott thanks! :) –  Oscar Mar 9 '11 at 14:32
add comment

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 :)

share|improve this answer
    
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. –  Ben Scott Mar 9 '11 at 15:14
add comment

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
share|improve this answer
add comment

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.

share|improve this answer
    
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. –  Ben Scott Mar 10 '11 at 1:17
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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