DEV Community

loading...

Avoiding Hard Coding and Magic Strings in code and databases

zakwillis profile image zakwillis ・3 min read

Hard Coding and Magic Strings

Hi, a fairly small article. Am just putting this here to instil some good practice in developers. I won't detail full code alternatives to doing this, so forgive the article for not being complete.

About me
I am currently working on a property platform startup (findigl) and building associated products for my Limited Company. This means I am not working, but starting to look for contracts and client work.

Info Rhino Limited
My Blog
Property Platform - WIP

Hard Coding and Magic Strings

Magic strings in code

We see it all the time.

var customerName = "Fred Smith";

It should be easy to understand why the above is bad. If Fred Smith changes to Freda Smith, we have to recompile the application. We know it could be dangerous to do a global find and replace.

Magic strings in SQL

We see the same thing in SQL. I can't mention the client, but I joined a project very late on and did an export of the codebase, and ran the following regular regex search.

(?is-mnx:('([a-z0-9\s]+)'))

This finds most instances of hard coded strings. Take it a little further, and...

(?is-mnx:(\=((\s|\t|\n)?)'([a-z0-9\s]+)'))

Now we can find most where clauses. On this particular project. I found 8000 hard coded strings, and 600 instances of 'USD'.

What really upset me, was that 'USD' can have different contexts attached to it. For example;

  • Settlement Currency.
  • Domicile Currency.
  • Reporting Currency.
  • Leg 1 Curency.
  • Leg 2 Currency.

And so on... The code base was awful. No thought process, just developers working in isolation.

What the team thought when I presented my findings

They were mystified as to why this was a problem. I am shaking my head as I read it. They hated me for a good while. Finally they came around.

To get around this, it is better to have a table configured with these kinds of settings and to pass in the name of the set as a parameter.

create table conf.ReportSetting
(
SettingKey nvarchar(100) 
,Setting nvarchar(100)
);

In a function or SPROC

create procedure rpt.GetCurrencyValues
@ReportingCurrencyKey nvarchar(100) = 'ReportingCurrency'
AS
SELECT
etc...
FROM 
rpt.profits prft
CROSS APPLY conf.GetReportSetting(@ReportingCurrencyKey , prft.Currency)
rptCcyFilter

People may say, what about row locking etc. Well there are ways to get around this.

We can create a table variable of the conf.ReportSetting table and pass that into the sproc. You don't get row locking on table variables.
The SPROC becomes

create procedure rpt.GetCurrencyValues
@ReportingCurrencyKey nvarchar(100) = 'ReportingCurrency'
,@settings conf.typSettings
AS
SELECT
etc...
FROM 
rpt.profits prft
inner join 
@settings stng
ON 
stng.Setting =  prft.Currency 
WHERE 
stng.SettingKey = @ReportingCurrencyKey 

See how we have suddenly made the intention of the SQL code far clearer? Well, it is the same in C#

Avoiding hard coding and magic strings in .Net

A Magic String is where some component expects a key or property name to get passed in. The best example is this...

ConfigurationManager.ConnectionStrings["ExecutorDB"].ConnectionString

Well, this can be solved with the relatively new and amazing nameof operator.

ConfigurationManager.ConnectionStrings[nameof(ExecutorDB)].ConnectionString

This avoids using Reflection or more complicated approaches to get property names.

A useful way to take advantage of nameof and string interpolation for strongly typed formatting in C

What you quickly start to find is, variable names get used in combination with nameof all the time. I guess we could write an extension method or expression function to really make the code neater.

So whilst this may seem heavy/boiler platey, it means our configuration is all strongly typed.

        public void Create(string FolderPath)
        {
            if (checkFolder.ConfirmFolderExists(FolderPath))
            {
                logger.Info($"{nameof(CreateFolderIfNotExisting)} {FolderPath} already exists - not creating it");
                return;
            }

            logger.Info($"{nameof(CreateFolderIfNotExisting)} {FolderPath} about to create it.");
            this.createFolder.Create(FolderPath);
            logger.Info($"{nameof(CreateFolderIfNotExisting)} {FolderPath} should have created it.");
        }

If we change any parameter names, we will get build/compile errors on strings.

Conclusion on avoiding magic strings

I am on the downhill phase of my year long project now. There are several things a lone developer should not do when working on a startup;

  1. Spend another 6 months putting in loads of unit tests.
  2. Creating perfect code won't cut it either.
  3. Always follow the Single Responsibility Principle, although we can make objects as small as possible.

What we can do is;

  • Avoid hard coding.
  • Make applications small in scope, so they are replaceable with other applications if required in the future.
  • Avoid Magic Strings.
  • Use Interfaces as much as possible.
  • Move transitive dependencies to separate classes.

Hopefully, you got some use from this article.

Written with StackEdit.

Discussion (1)

pic
Editor guide
Collapse
zakwillis profile image
zakwillis Author • Edited

Had a thought. People might say, what about stats on table variables and cardinality? Well, they default to 1 row. We shouldn't be using massively large table variables.