[Estimated Reading Time: 5 minutes]

Class helpers (introduced in Delphi 2007 2006 2005 – thanks to Chris and Bruce for the corrections) seem to be cropping up more and more frequently in suggested work-arounds or implementation approaches.  I find this worrying given that this language feature has always come with the admonition from CodeGear that it isn’t advisable to use it!

So why do people seem so keen on using them, and why shouldn’t they?

Class helpers are to be considered “bad” when used in an application because they were never designed for general purpose use. As I understand it, they were devised to workaround a problem (or problems) in the VCL that existed for the language (or more accurately, the VCL) implementors, not those who implement code that merely uses the VCL.

The biggest problem with class helpers, from the point of view of using them in your own applications, is the fact that only one class helper for a given class may be referenced at any time.

That is, if you have two helpers for a class nominally reachable in your code (e.g. in two separate units both of which are in your uses list), only one of them will actually be “seen” by the compiler. You won’t get any warnings or even hints about any other helpers that exist or may be hidden.

So you can be happily coding away with your lovingly crafted helper, and then you add some other unit to your uses list that unbeknownst to you contains another helper for the same class and suddenly your code simply stops compiling.

Since helpers are used – as designed – within the VCL, adding your own helpers for VCL classes is likely to lead to this problem if you also write code that (perhaps unknown to you) relies on the VCL helpers for those classes.

A Sticky Situation

You may be able to fudge your way out of this predicament by fiddling with the order of your units in the uses list (once you’ve figured out what’s going on, which is likely to come after perhaps your 2nd or 3rd restart of the IDE and an experimental reboot or two before you accept that it isn’t just the compiler having “a moment”).

If you can’t fix it with the uses list – if more than 2 helpers are involved – you might perhaps succeed in moving one or other helper up or down the ancestry of the helped class, assuming you are in control of the source for that helper and hoping that that does not in turn create a conflict with some other helper out there, perhaps in some other area of your code.

But if you can’t wriggle your way out of the situation then you are well and truly stuck, because you can’t even use qualification to forcibly reference the “hidden” helper.

  TMyHelper(obj).MyMethod;

Won’t compile if TMyHelper is “hidden” by some other helper for the class of obj involved. In fact, this code won’t compile anyway because TMyHelper does not exist as a “proper” type, so you can’t even use this style to aid clarity if you wish (i.e. to make it clear that your code is reliant on a helper).

But Without Class Helpers, What Can We Do?

Fortunately there is an alternative, which is not only immune from all these problems but is also in my opinion actually more flexible/powerful (not least because it works in any Delphi version).

That is, to use explicit helper-style pseudo sub-classes, the “old fashioned” way, where you adhere to the rules for helpers (only accessing public methods, not adding instance data etc etc) but use explicit hard-casting to “add” your methods to an instance.

  TMyHelper = class(TForm)
    procedure MyMethod;
  end;

  TMyHelper(form).MyMethod;

This is “better” to my mind because:

a) it is explicit. No magic. No wondering where these undocumented methods came from (for the uninitiated, unaware of the existence of your “helper” class in some obscure unit somewhere).

b) it is unbreakable. As long as you stick to the rules for implementing the helper itself, nobody else’s helper can “hide” yours or interfere with how it works.

c) it is more powerful and more flexible – the “helper” subclass is able to access protected as well as public members.

There are three downsides – two of which are arguably in fact a further benefit, in some situations at least.

1) it is necessary to explicitly type-cast in order to invoke your helper functionality.

2) the compiler will not prevent you from violating some of the rules of class helping (not overriding inherited methods).  It still isn’t possible to access private members, for example.

3) protected members are made visible in “consumer” code.

It is the first and last of these that I could be considered further benefits.

In the case of the first, it is made clear that an instance is being treated as something that, strictly speaking, it isn’t.  To my mind the real basis for this complaint is that it involves a little more typing than using a formal helper.

In the case of the third, gaining access to protected members is quite often the very reason that such a class might have been used in the first place, since some aspects of frameworks – particularly in the case of the VCL – keep some members protected that would most usefully be made public.

For example, TControl introduces the Color property but only as a protected member, so if you have only a TControl reference to some control whose Color you wish to change (or inspect) you need some way to bring that protected member into visibility. You could have a complicated type checking and type casting sequence (hoping you catch all the relevant sub-classes), or you could use a “helper”:

  if ctrl is TEdit then
    TEdit(ctrl).Color := aColor
  else if ctrl is TListbox then
    TListbox(ctrl).Color := aColor
  else....

versus:

  TControlHelper = class(TControl);

  TControlHelper(ctrl).Color := aColor;

Note that this is not even possible with a “proper” class helper, so if you are using a “proper” class helper you will still need this “dirty” alternative to solve this particular problem.

Why not simply consistently use one approach that meets all needs?

But It’s Not Type Safe! TWorld.IsEnding!

Type safety is a potential concern of course, with the risk that you may cast to a helper class inappropriate to the object being cast, but this is not a problem unique to class helpers and you can easily code safely with this technique if you prefer:

  TControlHelper(obj as TControl).Color := aColor;

Personally I feel the slight risk (which it is in my power to mitigate and avoid) is worth the comfort of knowing that some-one else’s helper (or perhaps even an ill considered additional helper of my own) is not one day going to break my code.

So Why Bother WIth Class Helpers At All?

Good question. It is rather curious that the VCL problem was not solved using this alternate approach, for example. Why spend time implementing a seemingly flawed language feature to solve an problem that can be easily solved some other way?

I suspect that class helpers might be earmarked for future things… they bear an uncanny resemblance to extension methods in C#, for example, which are key to the syntactic candy floss and lexical gymnastics required to deliver LINQ.

But until their purpose is fully realised, I personally think it wise to take heed of CodeGear’s advice and steer well clear.

21 thoughts on “Class Helpers On Trial”

  1. Class helpers are not near as dangerous as you make them out, but I do agree that they can be easily overused. In my opinion, they should only be used when there is no other option. You have provided some great alternatives though.

    They really are not a “flawed language feature”. They enable mapping of the VCL over the .NET Framework’s object hierarchy. They actually predate C#’s extension methods too.

    Maybe I will do a future podcast about Class Helpers. . . .

  2. I use class helpers a lot, like this extension to the TStream hierachy:

      TStreamHelper         = CLASS HELPER FOR TStream
                              PUBLIC
                                FUNCTION    WriteSignature(CONST SIG : ShortString) : ShortString;
                                FUNCTION    WriteBoolean(B : BOOLEAN) : BOOLEAN;
                                FUNCTION    WriteByte(B : BYTE) : BYTE;
                                FUNCTION    WriteChar(C : CHAR) : CHAR;
                                FUNCTION    WriteWideChar(WC : WideChar) : WideChar;
                                FUNCTION    WritePChar(PC : PChar) : PChar;
                                FUNCTION    WriteWideString(CONST WS : WideString) : WideString;
                                FUNCTION    WriteAnsiString(CONST S : AnsiString) : AnsiString;
                                FUNCTION    WriteShortString(CONST S : ShortString) : ShortString;
                                FUNCTION    WriteSmallWord(SW : SmallWord) : SmallWord;
                                FUNCTION    WriteSmallInt(SI : SmallInt) : SmallInt;
                                FUNCTION    WriteLongInt(LI : LONGINT) : LONGINT;
                                FUNCTION    WriteLongWord(LW : LongWord) : LongWord;
                                FUNCTION    WriteDate(D : TDate) : TDate;
                                FUNCTION    WriteTime(T : TTime) : TTime;
                                FUNCTION    WriteDateTime(DT : TDateTime) : TDateTime;
                                FUNCTION    WriteSingle(S : Single) : Single;
                                FUNCTION    WriteDouble(D : Double) : Double;
                                FUNCTION    WriteExtended(E : Extended) : Extended;
                              PUBLIC
                                FUNCTION    ReadSignature : ShortString;
                                FUNCTION    ReadBoolean : BOOLEAN;
                                FUNCTION    ReadByte : BYTE;
                                FUNCTION    ReadChar : CHAR;
                                FUNCTION    ReadWideChar : WideChar;
                                FUNCTION    ReadPChar(PC : PChar ; MaxLen : CpuWord) : PChar;
                                FUNCTION    ReadWideString : WideString;
                                FUNCTION    ReadAnsiString : AnsiString;
                                FUNCTION    ReadShortString : ShortString;
                                FUNCTION    ReadSmallWord : SmallWord;
                                FUNCTION    ReadSmallInt : SmallInt;
                                FUNCTION    ReadLongInt : LONGINT;
                                FUNCTION    ReadLongWord : LongWord;
                                FUNCTION    ReadDate : TDate;
                                FUNCTION    ReadTime : TTime;
                                FUNCTION    ReadDateTime : TDateTime;
                                FUNCTION    ReadSingle : Single;
                                FUNCTION    ReadDouble : Double;
                                FUNCTION    ReadExtended : Extended;
                              END;
    

    It makes using TStreams (and [i]any[/i] descendant thereof!) much easier.

  3. I still can’t see how you preferred hack is fundamentally any different to class helpers – if you have a fundamental objection to one (in particular, that it’s not *really* adding to the class) then you should have a fundamental objection to the other – but at least with the class helper syntax there’s a chance CodeGear might make the implementation less hacky one day. Adding the fact that your way is the well-established way to access protected properties in a base class is a red herring, IMO, since that’s not a matter of adding functionality. (Also, a properly working
    implementation of class helpers for the native side was first introduced in D2006, not D2007, the syntax having been formally available for native code since D2005, I think.)

  4. @Jim – well I didn’t think I painted them as dangerous, as such, just explored what I saw as the reasons behind the advice to not use them.

    Why introduce a language feature which you then have to warn people to not use? If it warrants such a warning – for any reason – you have to consider it flawed.

    Even “with” doesn’t come with an official warning!

    😉

    @Keld – yes, your helper does seem to make working with streams much easier.

    Assuming that ALL streams contain data that is read/written using those helper methods.

    Assuming that you never use some code from someone else that also has their own TStream helper with methods that do not exactly coincide with your own.

    Assuming that where another helper does exist with the same method signatures, that the implementation in that other helper is functionally identical to your own.

    As is often the case with “quick fixes”, it is almost certainly better to stop and think through the requirement fully before embarking on an implementation.

    The warning attendant with this feature should be sufficient to make you stop and think.

    Stream reader/writer helpers are a very good example.

    Even something as apparently simple as reading an integer is not something that can be handled generically for all possible TStream’s.

    Consider the difference between a string/text stream and a binary stream, for example. I wonder… what does your ReadInteger code look like in order to handle the difference between:

    00000100 (binary Integer = 4 (four))
    and
    313030 (“ASCII” Integer = “100” = 100 (one hundred))

    Even a binary stream isn’t necessarily universal – you may need to consider the endianess of the data in that stream.

    How can a universal ReadInteger for ALL TStream derived classes possibly hope to correctly read an integer from an arbitrary TStream?

    These concerns are best captured in a reader class implemented independently of the stream class itself.

    e.g.

    reader := TTextStreamReader.Create(stream);

    reader := TBinStreamReader.Create(stream);

    Those reader classes would extend a (possibly abstract) base reader class.

    All those methods in your helper can then be implemented by your reader (and/or writer – you might have separate classes or combine them in one) as appropriate to the type (as in format, not class) of stream they are intended to be used with, and likely will be able to raise an exception if/when they determine that they are being used inappropriately.

  5. @CR – the real and important difference is that my “preferred hack” cannot be broken – or break anything – by the simple addition of a unit to a uses list.

    Class helpers can break /other/ peoples code (if they too take the view that it’s “OK” to use class helpers and so are doing so themselves).

    If nobody but CodeGear uses class helpers then they are perfectly safe. The whole point of my post is that they seem rapidly to be becoming accepted as a legitimate tool, and THAT is precisely what will lead to problems.

    Given the warning – from the outset – to not use class helpers, I’d suggest that there is as much chance – if not more – that they will be removed from the language completely, perhaps in favour of a more completely reliable alternative.

    Thanks for the correction w.r.t the Delphi version in which they were introduced.

  6. Re: TStream helper class

    I never use TStreams for anything other than binary files (for text files, I usually use TStrings, which I have another class helper for :-)).

    And I never use it on anything else than Liitle-Endian files, so there’s no portability problem there.

    As long as you are aware of the limitations, the TStream helper I have shown here makes my code so much easier to write and so much easier to understand that it (for me) far outweighs the possible back-sides.

    Also, it *is* possible to have two class helpers active for a given class at the same time – you just declare the second one a descendant of the first one, and voilà – you have two different class helpers (in different source files) available.

    I use this as well. I have a VclHelpers unit that implements class helpers for VCL controls, and a VclHelpersDB that then extends these helpers with functions that are good to have for database-driven programs. In programs that don’t use database functions, I only use VclHelpers, and in programs that uses database functions, I use both VclHelpers and VclHelpersDB.

    It is so much nicer to be able to write

    ComboBox.ItemObject:=OBJ
    or
    ComboBox.ItemValue:=StringVar

    than

    ComboBox.ItemIndex:=ComboBox.Items.IndexOfObject(OBJ)
    or
    ComboBox.ItemIndex:=ComboBox.Items.IndexOf(StringVar)

    Both of these two helpers are properties, of course, so I can both set and get the object and string directly in a ComboBox with style csDropDownList.

    Most of my class helper classes are merely shortcuts/nicer syntax for VCL classes. Things I think Borland/CodeGear should have incorporated from the beginning (they are free to get the source code for my helpers and incorporate them into the VCL if they wish :-)).

    Another place I use Class Helpers is when I need to make extensions to machine-generated classes (I have made a class generator that creates classes for use in my database-driven programs so that I can access a record in the database using properties which then reads/writes from an SQL store). That way my modifications aren’t lost when I re-generate the class hiearchy (and before you ask: No, I can’t just declare a descendant of the class and place them there, as some of the classes in the generated source code creates internally and returns some of the classes that I need to extend, so although I could do it with class types, it would be much harder to do this than simply extending the class with a class helper).

  7. I think you are missing the point Keld.

    If you use code that somebody else has written and *they* also use their own TStream helper then *their* helper potentially will break your code that relies on *your* helper. Their helper won’t be considerate enough to extend your helper because it simply isn’t aware that it exists.

    You will perhaps not even be aware that the code you have chosen to use incorporates a helper. Any helper in someone elses code is likely to be one that *will* conflict since helpers are of most “use” with classes outside the authors direct control, i.e. classes that are likely to be common and yet also outside the control of either of you – i.e. in the case of TStream, a VCL class.

    And an awful lot of people have their own, very different idea of what constitutes a “useful set of helper methods” for a number of VCL classes.

    Now, as long as you never use anybody else’s code, or only use code from people that don’t themselves use class helpers then you will be fine.

    But the whole point is that more and more people are taking your view, so the risk is increasing that people will be creating code that, if shared, will have unintended – and difficult to diagnose – *breaking* consequences.

    The bottom line is, class Helpers are NOT DESIGNED to be used outside the VCL. CodeGear say as much. This is not a conclusion I have reached on my own and trying to shove down anyones throat. All I’ve done is explored perhaps *why* the *creator* of this feature recommends that you do not use it.

    As I pointed out in the case of your reader/writer extensions to a TStream, a helper is more often than not the wrong way to go about implementing something in the first place.

    It works for you, right up to the point that you share your code with, or use code shared by, someone else that also uses a TStream helper that works perfectly *for* *them*. At that point you will wish you had gone about things differently.

    That day might never come of course and if you can be entirely sure of that now and for the life of your code, then all power to your elbow – this post and CodeGear’s warning perhaps aren’t relevant to you.

    As Jim says, class helpers are for when there is *no*alternative*.

    But imho there is *always* an alternative unless you are CodeGear (with the unique VCL / VCL.NET problem that they felt had to be solved this way).

    As you point out, those alternatives sometimes take a little more effort, but building something that will last usually does.

  8. I *do* understand your point, but if i ever run into this problem, I’ll simply make my base class helper extend their class helper, thereby re-creating the entire class help hierachy. Any naming conflict will then be solved on a case-by-case basis – just like it would if I used inheritance (you won’t claim that inheritance is bad as well, will you? It has the same problems vis-a-vis naming conflicts).

    Also, I never use libraries where I don’t have the source so that *if* I run into problems, I can fix them *myself*. Therefore this issue is a non-issue as far as I am concerned, as I will always be able to circumvent any problems that might arise.

    Just because a tool/technology *can* cause problems doesn’t mean that one shouldn’t use it – as long as the user is aware of the possible pitfalls. Just like pointers, WITH statements, GOTO statements 🙂 and many more.

    (Not that I have ever used GOTO statements, but I frequently use EXIT statements, which by some is considered a glorified GOTO).

    *IF* Borland had not intended to allow us to use class helpers, why did they document them? Why didn’t they solve it by compiler magic instead (f.ex. only allowing CLASS HELPER to extend specificic, named classes and/or only in specific named source files)?

    The mere fact that I *am* able to write Class Helpers tells me that it a tool that is supposed to be used, and although it is a tool that may bring along some problems, as long as I am aware of these, then I should (and will) be free to use them as I see fit…

    Just like WITH has its uses, so does Class Helpers. Yes, you can do without them, but then again, you can also do without classes, as they are only a glorified way to interpret

    PROCEDURE TObject.Test(CONST S : STRING);

    as

    PROCEDURE TObjectTest(Self : TObject ; CONST S : STRING);

    and even virtual functions and method pointers can be done without the use of classes, but it’s so much easier to code, read and maintain when using classes, that this TOOL is used whenever possible.

    Just as Class Helpers is a TOOL that can be used to make code more readable and maintainable, if used properly…

  9. “Good question. It is rather curious that the VCL problem was not solved using this alternate approach, for example. Why spend time implementing a seemingly flawed language feature to solve an problem that can be easily solved some other way?”

    Class helpers are much more than simple method injection. Unlike “extension methods” in C#, class helpers allow for introduction of virtual methods that are actually overridable in descendant classes. You can also implement interfaces on a class helper as well. It is for this reason that class helpers have the rule of “only one helper.” The intent of their introduction was to provide functional equivalence between the Win32 VCL and the base .NET FCL.

    It is, however, remarkable that C# introduced “extension methods” to solve a very similar problem. MS was faced with an interesting dilemma; They wanted to add the LINQ functionality to the .NET framework, yet they could not easily just embed this new functionality into the existing .NET 2.0 framework without introducing yet another version of the base framework, which runs the risk of breaking existing applications and makes framework testing a nearly untenable prospect. So they use extension methods to “mix-in” this new functionality without modifying the core framework. This also meant that unless you use the new functionality, there is no need to even deploy these extensions. And to think that Anders H. once scoffed at the notion of class helpers when Chuck J. and I discussed them with him back before the initial release of Delphi/.NET ;-).

    “But until their purpose is fully realised, I personally think it wise to take heed of CodeGear’s advice and steer well clear.”

    Overall, your analysis of class helpers is very accurate and the fact that you question their existence means you clearly see the inherent dangers and pifalls of using them extensively.

    Allen.

  10. Just a small correction. Class helpers were introduced in Delphi 8 and available (unofficially?) in Delphi 2005.

    http://blogs.teamb.com/rudyvelthuis/2005/05/11/4270

    I know Danny Thorpe used to mutter “don’t use class helpers” whenever he spoke about them at conferences. He either thought the feature was unwise or was managing expectations for the inevitable case where someone misused them.

  11. “I’ll simply make my base class helper extend their class helper”

    This is using inheritance to work around a problem, not express an actual inheritance relationship – a hack in other words, and not even a reliable or safe one.

    What happens when you find you have TWO additional helpers in addition to your own? Last time I looked Delphi did not support multiple inheritance.

    To fix this you will have to change one of those 3rd party helers to extend the other and then derive from that one. If you’re lucky the two 3rd party helpers won’t have identical method signatures – if they do then it won’t be a simple job of changing the base helper, you’ll also have to modify one or other helper (and all the 3rd party code that relies on that helper) to resolve those collisions.

    In other words, the problem hasn’t been solved, you’ve just found a way to defer the impact but the potential impact is now even greater than it was before!

    You ask why shouldn’t you use them if CodeGear provided them. I would have thought CodeGear’s own warning was sufficient to answer that question. I suspect that CodeGear documented them to explain their presence in the VCL code and as a means of describing why they should NOT be used in your code.

    If they weren’t documented there would have been nowhere to provide the explanation of their limitations and to advise against their use.

    And yes, pointers, “with” and “goto” can all be used badly, but doing so will not break some-one elses code – all you can do with those is create problems for yourself, and so on your head be it.

    But by using class helpers you risk breaking other peoples code and you are setting up your code to be broken by others too.

    That is the key difference.

    Everyone that recommends their use does so with the advice that they should be “used with care”, or similar. This fails to acknowledge that no matter how careful *you* might be, you can still fall foul of someone else also using them, even if they also are being “careful”.

    Finally you make the common mistake of believing that reducing the amount of characters/typing in a piece of code makes it more readable.

    When:

    someObj.SomeMethod;

    involves a SomeMethod that is not actually part of the class that someObj is an instance of, using a class helper makes it easier to read (fewer characters, less syntax, whatever) but it is harder to understand – how it is possible to invoke a method that is not actually provided by someObj? When you consult the declaration of the class of which someObj is an instance you will not find SomeMethod, nor will you find it in any ancestor.

    The very nature of class helpers is to be a silent agent of change – I’d go so far as to say to obscure the details of their implementation.

    That absolutely does NOT make that code easier to maintain – it demonstrably makes it harder, because you have made it harder to understand AND introduced the risk of dependencies and breakages arising from code other than your own.

    But I doubt I will change you mind, and I don’t intend to try, other than to point out where any perceived “solution” to the problem is itself flawed.

    You are, as you say, free to code as you wish. All I can do is explain why a particular technique might be considered dangerous – whether you agree is then entirely up to you.

    🙂

  12. @Joylon:

    You wrote:

    When:

    someObj.SomeMethod;

    involves a SomeMethod that is not actually part of the class that someObj is an instance of, using a class helper makes it easier to read (fewer characters, less syntax, whatever) but it is harder to understand – how it is possible to invoke a method that is not actually provided by someObj? When you consult the declaration of the class of which someObj is an instance you will not find SomeMethod, nor will you find it in any ancestor.

    My reply:

    That’s what “Find declaration” (or whatever it’s called) is for. Why go looking for a method implementation, when you can ask the compiler to tell you exactly where the code is?

    You wrote:

    Finally you make the common mistake of believing that reducing the amount of characters/typing in a piece of code makes it more readable.

    My reply:

    No – it has nothing whatsoever to do with the amount of characters, but the fact that

    ComboBox.ItemObject:=OBJ
    or
    ComboBox.ItemValue:=StringVar

    is much easier understood (as to what the purpose of the statement is), than

    ComboBox.ItemIndex:=ComboBox.Items.IndexOfObject(OBJ)
    or
    ComboBox.ItemIndex:=ComboBox.Items.IndexOf(StringVar)

    and the syntax

    Stream.WriteAnsiString(S)

    is much easier to read and understand than

    StrLen:=LENGTH(S);
    Stream.Write(StrLen,SizeOf(LongWord));
    IF StrLen>0 THEN Stream.Write(S[1],StrLen)

    especially if you have a lot of them and intermixed with other values (of differing types) in a long stream (pun intended) of code…

    Class Helpers – like WITH et. al. – has its place. When used properly, it can greatly enhance readability and maintainability of code, but like all tools, one must be aware of the risks and pitfalls involved. As long as one is, I see no problems whatsoever in their use, and will even encourage their use for the purpose I outline here – to implement what should have been implemented in the first place by Borland/CodeGear in the base classes.

    But as you say – we probably won’t agree (and I have no problem with that). But your insistence that Class Helpers is inherently bad in EVERY case is an opinion that I object against. Yes, they CAN be cause of problems, but as long as you are aware of these, I am all for it…

  13. “That’s what “Find declaration” (or whatever it’s called) is for”

    When Code Insight isn’t working or you are reading the code in some tool other than the IDE (your preferred diff/merge tool for example) “Find Declaration” is going to come to your rescue.

    And you seem to have interpreted “not using class helpers” as meaning “go deliberately out of your way to make your life harder” in order to justify using class helpers.

    SelectItemWithObject( ComboBox, aObject);

    Is perfectly readable. And in fact I’d argue it is clearer even than your helper method. 😉

    procedure SelectObject(aCombo: TComboBox; aObject: TObject); // overload if necessary/desired
    begin
    aCombo.ItemIndex := aCombo.Items.IndexOfObject(aObject);
    end;

    I’m guessing your helper method contains almost identical code. The difference of course, yet again, is that the above routine cannot be inadvertently broken by some other routine or class creeping into scope.

    With your stream example you have conveniently ignored my lengthy explanation of a proper separation of concerns that would leave you with a TStreamReader that was just as intuitive and convenient to use as your stream helper without exposing yourself to the risk that helpers entail.

    I think the problem is the use of the word “helper” in the name for these things. It sounds warm and fuzzy and full of goodness. What can possibly be harmful about a “helper”? It’s there to “help” us after all.

  14. Once again you appear to be missing the point…

    Why go back to coding the way we did *before* objects and classes (where we explicitly pass the Self parameter) instead of incorporating it directly into the object/class syntax?

    I, for one, find the statement

    ComboBox.ItemObject:=OBJ

    much more readable (and thus maintainable) than

    SelectItemWithObject( ComboBox, aObject);

    As you are going outside the established syntax of setting an item in a combobox, you can’t from the statement see if there’s any unwanted side efftects with the procedure statement, whereas if you keep to the same standard syntax as the ItemIndex setting, then (if properly implemented) you can assume that you know precisely what it is doing.

    You may prefer the second syntax – I most definately prefer the first. It’s perfectly clear what it does from the statement itself (if you know the ComboBox class in the first place) – no need to look it up (provided the implementor has followed the same style as Borland/CodeGear).

    Another good example is f.ex. that Borland/CodeGear has implemented an AddItem(STRING,OBJECT) to the ComboBox class, but not an Add(STRING) not to mention that AddItem is a PROCEDURE and not a FUNCTION returning the index as I would expect. By using Class Helpers, I can fix this inconsistence across the board with one simple declaration.

    Class Helpers most certainly (in my view) has its place and its uses. It also has the possibility for misuse and/or overuse, but to declare it inherently bad in all cases is taking too simplistic and narrow-minded a view of the reality out there.

    Class Helpers shouldn’t be used to implement advanced new things, but when used as syntax enhancers and “fixing” the things that Borland has left out of the base classes, they are very useful (and very helpful) – even with the side effects that can happen.

    In my case, I have used Class Helpers ever since Delphi 2005 (where the implementation was a bit shaky – a lot of “Internal Compiler Error” when compiling), and I have absolutely no intention of stopping my use. I am aware that there could be problems ahead, but I am also aware of the solutions to these problems, so that if – and I say IF – I should encounter these, I am convinced that I can program my way around it – just as I need to program my way around any modifications that CodeGear makes to the base classes from which I have used class inheritance in order to extend their use.

    The great benefit for Class Helpers is that it allows me to extend the capabilities of a class AND ALL THE DESCENDANT CLASSES, and the fact that I can extend the method list for classes that are properties/fields within other classes (such as when I extend TStrings, it’ll also extend TComboBox.Items) which I cannot do any other way.

    So what Class Helpers give me is the ability to modify the VCL into what I would have liked Borland had done in the first place by implementing properties and methods directly into the object hierachy without compromising compatibility with previous projects (which then simply doesn’t include the class helper, and thus has no potential for compatibility problems).

    In other words – it allows me to work with the same VCL in different “versions”, in various projects, whereas a change directly in the VCL source would impact all the old projects as well.

    For me, these benefits far outweigh the risks involved. You may have a different opinion, but that in no way nullifies mine…

  15. By applying a class helper to a class you fix it to your code.

    You render your code incompatible with other code that creates a different class helper.

    The OO way to modify a class is to extend it through inheritance or some other pattern – a visitor for example.

    It is not valid to claim that class helpers are OO simply because they result in syntax that /looks/ OO.

    The thing you should not lose sight of is that it is not *me* saying you shouldn’t use them. CODEGEAR tell you that. I’m simply showing you why. Your justifications for using them don’t stack up, and you clearly do not see that there are some situations where you CANNOT have a (practical) solution to ALL the problems that class helpers can lead to.

    But you’re perfectly at liberty to continue to code any way you wish.

    Just be aware that it risks limiting your ability to share code with other people who share your opinions.

  16. I do not see what is the fuss about using class helpers to code like this:

    ComboBox.ItemObject:=OBJ
    or
    ComboBox.ItemValue:=StringVar

    Why not just create your TComboBox class and implement these?

  17. @Geza:

    If you have a hiearachy with multiple levels (like a TCustomComboBox that has several descendants, f.ex. TComboBox and TJvComboBox and TColorComboBox etc.), then I’d have to implement the inheritance for ALL descendant classes, whereas a Class Helper injected at the TCustomComboBox level will automatically inject the helper functions in ALL descendant classes.

    @Joylon:

    I don’t think we’ll ever agree. You are obsessed with the notion that Class Helpers are bad in ALL cases, whereas I can see certain areas where they have a definite advantage.

    So let’s get back to the subject matter…

    You are obviously the prosecutor in this trial, and I am willing to be defense. So let me challenge you:

    Come up with a package of source files where you demonstrate a problem caused by Class Helpers that you think can’t be solved, and I’ll try to solve it for you. Only requirement is that I get full source (like I said, I never use other people’s code without having access to source, so I will never end up in a situation where there’s a problem and I don’t have the source).

    Let’s *really* put Class Helpers on trial on “People’s Court” :-).

  18. @Karl

    For one last time I’ll walk you through a potential nightmare scenario – bear in mind this would play out over a period of weeks, months or even years, so the issues and techniques to resolve them won’t necessarily be fresh in your mind as they occur.

    Unit A – your unit contains a TStream helper and a derived stream class
    Unit B – 3rd party unit also contains a TStream helper and a derived class
    Unit C – “4th party” unit also contains a TStream helper and a derived class

    Unit A initially created in isolation – contains stream code, obviously, and is used by other units in the application to provide the helper class throughout the app.

    Unit A modified and now uses Unit B. Unit A code reliant on it’s own helper no longer compiles.

    Relocate Unit B to interface section to ensure it is hidden by the Unit A helper (hmmm, location sensitive code – that’s a bad sign). If you wish to use the methods of the Unit B helper you will have to modify the Unit A helper accordingly.

    Either way, Unit A now compiles once again (after having to be refactored simply to accomodate “using” Unit B – there has already been a cost to your project)

    Unit A is further modified and now uses Unit C. Rinse and repeat as for Unit B.

    If you have not used Unit B helper methods you can refactor (again) and hide the Unit C helper behind Unit A and B (more location sensitive code, mmmm, lovely – that’s a future compilation failure waiting to re-occur in my experience). If you want to use Unit C helper methods in addition to the Unit A and Unit B helpers you have more work to do of course. More cost to your project simply to use Unit C.

    If you choose to extend one or other helper, and if Unit A, B or C helpers contain methods with identical signatures (each might have their own idea of a “useful” ReadInteger function, for example) then you will have to rename one or more methods.

    More refactoring just to “use” a unit.

    If you have to modify methods in Unit B or Unit C then you will also have to modify all affected source in the libraries that Unit B and/or Unit C are a part of. If those libraries are then updated by their respective authors you will have to repeat these changes in any updates in order to continue using them in your project.

    NONE of these issues arise if each unit simply provided helper *methods*, without the fine dusting of syntactic sugar that class helpers provide.

    In the comment reply where I first outlined this scenario I did not say that these issues were impossible to resolve, only that the impact is vastly increased.

    Everything that you wish to achieve using a class helper can be achieved more safely and more responsibly, but just as usefully, by other means.

    Why court risk and flirt with potentially deadline busting impact if you don’t have to?

    The only difference between class helpers and the alternatives is in the notation required to invoke these alternate approaches, which doesn’t have the entirely false and cosmetic appearance of OO’ness that you see as so desirable.

    It’s not a question of being strictly and technically /impossible/ to work around these issues, but a question of being practical, justifiable and desirable to have to.

    Maybe your working conditions are different to mine but I have deadlines and goals, and anything that introduces an unnecessary risk to meeting those is avoided as a matter of course.

    All along I’ve said you are free to code as you wish – of course you are. But I might suggest that you’re the one obsessed here. Indeed, to the point of discounting the advice from CodeGear themselves.

    But if you feel you can make such a strong case for class helpers, take it to CodeGear and suggest that they remove the advice from their documentation.

    Who knows, maybe they just didn’t understand what they had created? You could be the one to show them their error.

    😉

  19. @Keld: FWIW, I would actually have been completely misled by your ItemObject and ItemValue property setters. Without knowing your implementation I would – without the trace of a doubt! – have expected them to be equivalent to the following rather than what you implemented:

    ComboBox.Objects[ComboBox.ItemIndex] := OBJ;

    ComboBox.Items[ComboBox.ItemIndex] := StringVar;

    …assuming that the readers for these properties were equivalent to this (which I guess they probably are, even in your implementation):

    Result := ComboBox.Objects[ComboBox.ItemIndex];

    Result := ComboBox.Items[ComboBox.ItemIndex];

    So yes, I do find your approach more readable but I do not find your implementation at all intuitive. And I guess Jolyon chose a suboptimal counter-example when he presented a standalone procedure as a replacement rather than using the approach promoted in his original article. Consider the following:

    type
    TComboHelper = class(TComboBox)
    public
    procedure SelectItemByObject(const AObject: TObject);
    procedure SelectItemByValue(const AValue: String);
    end;


    TComboHelper(ComboBox).SelectItemByObject(OBJ);

    Note that as long as you do not add new instance data in the helper the above calling code does not require ComboBox to be instantiated as TComboHelper but can be applied directly to an instance of TComboBox!

  20. Contain the type safety concerns like this:

    TMyHelper = class(TForm)
    class function Upcast(Form: TForm): TMyHelper;
    procedure MyMethod;
    end;

    class function TMyHelper.Upcast(Form: TForm): TMyHelper;
    begin
    Result := TMyHelper(Form);
    end;

    Then you have the cast in one place only.

Comments are closed.