My previous post on class helpers provoked a passionate response from some quarters who believed that they could be used “safely”. More worrying was an apparent belief that their use was actually endorsed by CodeGear – tacitly or otherwise. A rather odd view given the advice from CodeGear themselves is to not use them.
No-one actually described a safe usage scenario though and those scenarios that were described all contained immediately apparent flaws of their own. However, I have realised that there is, if not a safe way then at least a responsible one, to create class helpers.
Using them is still not entirely safe however.
Scope: The Problem AND The Solution
The problem that using class helpers creates is caused by the fact that the compiler will only recognise one class helper for a given class at a time. If there are multiple helpers then normal scoping rules apply, and the “nearest” helper class is the one that the compiler will accept. Any other helpers do not exist as far as the compiler is concerned.
The potential result is that the simple act of adding a unit to your uses clause can break your code, if that code relies on some helper that is now hidden by the inclusion of that other unit.
Numerous strategies were offered if this should arise, including changing the ancestry of your class helper to extend the other helper, which obviously potentially breaks down if there are more than two helpers involved from different sources.
But the solution lies in the problem itself – scoping.
The Devil In The Implementation Detail
Class helpers are, by definition, an implementation detail of your application code – they modify a class outside of your direct control to fit the needs/desires of your code to be able to use a class in a way not supported by the class author, but they have no useful purpose as far as any declarations in your code are concerned.
There is absolutely no reason to ever expose a class helper in the interface of a unit – except of course in a unit that declares and implements a helper, obviously.
By restricting the scope of your class helpers to the tightest possible and requiring helpers to be deliberately and explicitly brought into scope you eliminate the possibility that your helper might accidentally “collide” with another helper in someone else’s code:
unit MyStreamHelper; interface type TStreamHelper = class helper for TStream : end; implementation
As long as everyone that uses class helpers sticks to this simple convention of placing helpers in their own unit, then the problem of “helper hiding” should be largely mitigated. Think of it as the one time when “one class per unit” is not just a nice-to-have, but an absolute necessity.
Sadly, even then the solution is not perfect.
Certainly you can be sure that class helpers that you create will not pose any risk for anybody else, unless they deliberately use your helper unit themselves in which case one must judge that they know what they are doing. But still, somebody carelessly exposing a class helper in the interface of a unit containing other code that might be more generally useful could still break your code.
Consider for example someone implementing a useful stream class of their own, the implementation of which uses a class helper for TStream itself to aid the implementation of their specialised stream class and which they use in other units in their library or framework which they have shared with you:
unit SpecialisedStream; interface type TStreamHelper = class helper for TStream : end; TSpecialisedStream = class(TMemoryStream) : end;
In this situation someone may wish to use TSpecialisedStream but they cannot do so without also bringing that helper class into scope, with the potential that has to break their code using their own TStream helper (after all, if class helpers are good enough for the creator of TSpecialisedStream, why not everybody else?).
So yes, there are ways to responsibly create and consume class helpers, but everyone has to stick to a safe pattern if they are to avoid creating “unsociable” code.
Unlike with, goto or untyped pointers or any other abusable language feature quoted to justify use of this abusable feature, the unique problem with class helpers remains: that abuse risks breaking code other than your own. Yes, you can write bad and even flat-out incorrect code using with or goto etc, but you can’t write code using these things that will fundamentally break code in an entirely separate unit, as you can with a class helper.
It also remains the case that (so far at least) with the exception of the original, highly specialised purpose for which class helpers were created (VCL / VCL.NET compatability at the framework level) there is no use to which a class helper can be put for which a safer alternative does not exist, and in many cases those alternatives are arguably “better” for reasons other than just being considerate.
I believe (and this is after all a personal blog, reflecting the thoughts and opinions of it’s author which I in no way seek to impose on others) the warning issued with class helpers is justified and a non-class helper approach should always be preferred where such an approach exists.
But if someone is going to insist on using class helpers in code that may be – and most especially if it is intended to be – shared with others, then they should do the community the courtesy of implementing those helpers in the above described fashion, so as to avoid the risk of breaking other people’s code.
It would seem to me to be the responsible and considerate approach.