Friday, July 11, 2014

How to Replace Global Variables by Using a Static Class

One of the first things I used to do when creating a new Delphi application was add a copy of the almighty global.pas unit to the project. I'd immediately go in to this file and start tweaking this and tweaking that so it would work with my new application. And, I made sure to ALWAYS add Globals to the uses clause of every new form I created.

My Delphi buddy Bob Wight, who is Scottish and speaks with a very heavy brogue, called his "Globs". Which stood for globs.pas. I remember him saying, "The programmer's life. Another day, another global." It sounds really cool with his Scottish accent.

Anyway, I thought this was the normal way of doing stuff. I thought every Delphi programmer did this. I never challenged it. Not ever.

Now, as I'm re-learning Delphi all over again for the first time, I hear several Delphi people, in the various Delphi hangouts, say stuff like, "Never use global variables" and "Pretend like global variables don't exist".

I'm thinking to myself... "Okay how the heck do you program if you can't use global variables?"

So, last weekend learned how to replace global variables in my Delphi application by using what is called a static class. It turns out that a static class can simply exist without having to be instantiated. That is pretty cool.

A static class can have constants, variables, functions and procedures. Its like having global variables on steroids. I have replaced my globasl.pas file with a file called ApplicationInfo.pas. I've included a copy of this file below.

This is a fairly new concept for me and it will no doubt go through some changes and refactoring. But so far, I'm liking it.

Here's a couple ways I'm implementing the use of my TAppInfo static class.


The cool part is the Intelisense... I just type TApp and hit [Ctrl] + [Space] and I can pick the item I want. No more "Globs" for this Marine!

ApplicationInfo.pas

Enjoy - Semper Fi,
Gunny Mike
end.

23 comments:

  1. Wow!!! this is a real useful technique.

    Now I will be able to exchange data between my different modules easily!

    Thanks for this!

    ReplyDelete
    Replies
    1. Do not use this trick, please.

      Static vars are IMHO just as bad as global variables.
      See http://blog.synopse.info/post/2014/07/12/staticsareglobals

      Delete
    2. Could not have said this any better, Arnaud. Thanks for your intervention.
      It doesn't matter how its called or technically done. The point is that global state is bad, be it a global variable, as static class or a function that returns a singleton. I suggest watching this video to fully understand why: https://www.youtube.com/watch?v=-FRm3VPhseI

      Delete
    3. Every programmer who had to maintain some huge code base with globals or statics knows what we are talking about.
      You are perfectly right: it is not a matter of dogmatism or coding-by-the-book. It is about being pragmatic and efficient.

      IoC/DI are great tools for the lazy programmer!
      :)

      Delete
    4. @Stefan - Thank you for the link to the clean code video. I understand "the why" much better. I was hoping to move forward and make progress on my application this weekend. I was no not intending to go back to the drawing board and work on fundamentals. Oh well.

      Delete
  2. You don't need a static class for this although it is nice. Delphi 7 pulls this off by simply extantiating something like TGlobal from initialization and freeing it finalization of your Globals unit. Not as slick as a static class but for a few lines essentially the same thing.

    I haven't tried this but you could also do a helper class object on TApplication.

    ReplyDelete
  3. Thank you you can declare (as I found out) the set function as private nd the init code still will execute

    TAppInfo = class
    private
    class function SetAppInfoPath(CSIDL:integer): string; static;
    public
    const
    AppInfoWebsiteName : string = 'www.creditcardmath.com';
    AppInfoWebsiteURL : string = 'http://www.zilchworks.com/buynow.asp';
    AppInfoCopyright : string = 'Copyright 1994-2014 © by Michael J. Riley. All rights reserved.';
    AppInfoName : string = 'Credit Card Math';
    class var
    AppInfoPathData : string;
    AppInfoPathLocal : string;

    class procedure GoToAppInfoWebsiteURL; static;
    end;

    ReplyDelete
  4. Well done! And now replace the initialization section with a class constructor.

    ReplyDelete
    Replies
    1. @Uwe - Done thanks for your help and encouragement.

      Delete
  5. Using such static declaration is just another way of creating a global variable.
    This is just a global variable in disguise.
    In fact, the generated asm will be just like a global variable!

    It encapsulates the global declaration within a class name space, but it is still IMHO a very wrong design.
    I've seen so many C# or Java code which used such a pattern (there is no global variable in those languages), and it has the same disadvantages as global variables.
    Code is just not re-entrant nor thread-safe.
    Nightmare to debug and let evolve.

    Imagine one day you would like to re-use the code of your app, then run your business logic code on a server.
    You would like to re-use your existing code.
    But since all your client instances would have to share the same global data, stored in static variables, you would be stuck to make the running instances un-coupled.

    This just breaks the SOLID principles.
    What should be done instead of such deprecated globals is to use true classes (or even better interfaces), then use Inversion Of Control / Dependency Injection.
    See http://blog.synopse.info/post/2012/10/14/Interfaces-in-practice%3A-dependency-injection,-stubs-and-mocks or Nick Hodges' book - https://leanpub.com/codingindelphi

    ReplyDelete
    Replies
    1. @Arnaud - I appreciate your comments. I'm currently trying to upgrade a simple, single-user application that has not been through a major re-write for eight years. I'm a self-taught part-time guy trying to elevate my game.

      I am struggling with the answer to this question... "Will it help me ship my product?"

      I do not have time to fully learn everything and get a product built. I bought Nicks book and I'm stuck between pages 12-18. I purchased the had printed version of his book. I need to go purchase the PDF version so I can cut and past stuff I don't understand and ask about it in his google= community.
      https://plus.google.com/u/0/communities/110978417023349293804

      Delete
    2. Like the use of the goto statement, to use or not to use global variables should *not* be a dogmatic decision as is implied in some of these replies. The decision should be made on a case by case basis. The writer appears to be the sole author, he has full control over the code, he may not be interested in reuse and most importantly he has to ship! His approach seems perfectly reasonable. Of course some will come back and comment that he may want to reuse the in the future. Trying to make software future proof and to anticipate future usage can be a bad thing, it results in over engineered and in the long term more complex code to maintain code. The way he handles global variables I think is elegant, essentially putting them into their own namespace.

      Delete
  6. Why the bashing? Given the code shown there is nothing wrong with it using a static class. That doesn't mean that static classes are the solution to every problem. Most of the time a pragmatic approach is better than a dogmatic one which might require a full redesign of all applications. Even the use of IOC and DI has to be learned before it can be used properly (and not overused). One cannot expect everyone to program at the sharp end. In addition, there is always room for improvement. No one is born as a master.

    ReplyDelete
    Replies
    1. "No one is born as a master."
      @Uwe True. And that is why I think that more knowledgeable people should share their knowledge so others can learn. If everyone would say "yay cool" nobody would think about why certain things might not be good. I hardly consider constructive criticism (even if strongly worded sometimes) bashing. Bashing is just talking down something without offering alternatives.

      I am not saying you should use this or that pattern or principle no matter what. Rather the opposite. Sometimes you just have to be pragmatic but over time your way of thinking and coding will change into a direction that will result in better code overall.

      Delete
  7. Gunny, my next step was to make this self-persisting, so the class handles loading and saving it's properties to an ini / registry / file. If you base that on RTTI, you are then able to add new properties and they will be loaded/saved automatically.

    I used to hate creating the ini code for each app, now it's one of the first building blocks in all my apps.

    ReplyDelete
    Replies
    1. Eamonn, nice idea. Thanks for your comment. I purchased Raize components (finally) and I will be using the TRzRegIniFile, TRzFormState and TRzPropertyStore for all my ini stuff.

      Delete
  8. Thanks for sharing Michael:)

    I just wanted to add that what you did is bascially adding another scope to your already scoped code. You could write as well:

    Global.AppInfoCopyright;
    Global.AppInfoWebsiteName;

    Also, try avoid redundancy like this:

    TAppInfo.Copyright;
    TAppInfo.WebsiteName;

    Good luck with your application :)

    ReplyDelete
  9. I think what Arnaud and Stefan are trying to say is that making your class var PUBLIC is a big no no.
    Your should rather scope the class var as STRICT PRIVATE then declare a PUBLIC PROPERTY with a getter function.
    You then have the option to initialize the class var in the property getter or the class constructor.

    Also, personally I would have called the class 'AppInfo' instead of 'TAppInfo', and dispensed with the AppInfo prefix for class constants, class methods and class properties.
    The net result usage is:
    str := AppInfo.WebSite;

    Another neat trick is to make a 'super' static class to gather all the global library functions and constants into one place, I call it RTL which looks like this:

    unit MyRTL;
    ...
    uses MyAppInfo;
    ...
    RTL = class
    public
    class function AppInfo: AppInfoClass;
    end;

    class function RTL.AppInfo: AppInfoClass;
    begin
    Result := AppInfo;
    end;

    in a separate unit:

    unit MyAppInfo;
    ...
    AppInfoClass = class of AppInfo;
    AppInfo = class
    ...
    end;

    Cool things about this pattern:

    1. Usage and origin is clear, eg: str := RTL.Appinfo.WebSite;

    2. You can have hundreds of static classes in separate units but you only ever have to delcare MyRTL (.pas) in your uses clauses.

    3. Having your library functions grouped into many separate units makes them easier to test and manage.

    4. If you ever need to go cross platform it's so much easier by using compiler directives on the uses clause of MyRTL.pas

    ReplyDelete
  10. .. yes, but this is just another way to disguise global variables...

    ReplyDelete
    Replies
    1. ... yes, but now it's obviously a global variable (or constant), not disguised thanks to the forced declaration of it's namespace, and as expected one is forced to be careful enough to not let the global variable have an unpredictable state by protecting it with a getter.

      Delete
    2. correction:
      now it's obviously a global variable (or constant), not disguised, thanks to the forced declaration of it's namespace

      Delete