Jump to content
dummzeuch

How do you deal with deprecated symbols in backwards compatible code?

Recommended Posts

TJSONObject.Size was deprecated and replaced by .Count in Delphi XE6, so up to Delphi XE5 you have to use .Size (because .Count doesn't exist) and from Delphi XE6 on you either live with the deprecated warnings (.Size is still available in Delphi 10.3) or replace the calls with .Count. But even if you accept those warnings, sooner or later you will have to address them because Embarcadero decided to finally remove the deprecated code.

 

So, what if you want your code to compile on all versions that support JSON without warnings?

 

E.g. in my u_dzGoogleTranslate unit there is the following code:

  if o.Size <> 3 then
    raise Exception.CreateFmt(_('Parsing error on JSON answer: Root object size is %d not 3.'), [o.Size]);

where o is a TJSONObject.

This compiles fine in all Delphi versions that have JSON support but starts throwing deprecated warnings from Delphi XE6 on.

 

So, what are my options?

  • I could IFDEF all those calls:
{$IFDEF JSONOBJ_HAS_COUNT}
  if o.Count <> 3 then
    raise Exception.CreateFmt(_('Parsing error on JSON answer: Root object size is %d not 3.'), [o.Count]);
{$ELSE}
  if o.Size <> 3 then
    raise Exception.CreateFmt(_('Parsing error on JSON answer: Root object size is %d not 3.'), [o.Size]);
{$ENDIF}

Which would be fine for one single line, but there are dozens of similar lines which renders the code unreadable.

  • I could IFDEF complete functions instead but that would mean to fix any bug in all these functions.
  • I could add a class helper for TJSONObject that adds the missing Count function for earlier versions (I think they all support class helpers, so that would be doable.)
  • I could derive TdzJSONObject from TJSONObject and add the missing Count function but that would not work for RTL code that returns a TJSONObject.
  • I could write an inlined wrapper function JSONObject_size that encapsulates that IFDEF. That would improve readability but but would still be ugly.
  • I could drop support for Delphi XE5 or earlier, but I'd rather not. You never know when you're going to need that.

Can you think of any other options?

Edited by dummzeuch
record helper -> class helper

Share this post


Link to post

Do you have an idea, how many users really need new functions for old versions of Delphi? If there are not many of them, I would suggest to drop support for old versions - you can probably spend your time better than keeping code compatible with old Delphi...

Share this post


Link to post
17 hours ago, Vandrovnik said:

Do you have an idea, how many users really need new functions for old versions of Delphi? If there are not many of them, I would suggest to drop support for old versions - you can probably spend your time better than keeping code compatible with old Delphi...

OK, I should have provided some background:

This library is a hobby project of mine, there are only a very limited number of people that use it:

  • I myself at work
  • my two coworkers (but only in Delphi 2007, XE2 and lately 10.2)
  • I myself (again) in many of my open source projects that support various versions of Delphi (for GExperts that means Delphi 6 and later), but not all units from that library are important for this
  • Even though this library has been open source (MPL) for at least 15 years, I have never heard of anybody else using it (Which I think is a shame, but hey, they just don't know what they are missing out. 😉 )

So, I don't want to drop compatibility if it can be kept without too much hassle, I'm asking for alternatives.

 

Believe it or not: Maintaining backwards compatibility can be fun if it involves creative (mis-)uses of language features. I am programming in Delphi for fun, not just for the paycheck, otherwise I would have dropped Delphi for Visual Studio more than 10 years ago.

Edited by dummzeuch

Share this post


Link to post
9 hours ago, dummzeuch said:

So, what are my options?

  • I could IFDEF all those calls:

{$IFDEF JSONOBJ_HAS_COUNT}
  if o.Count <> 3 then
    raise Exception.CreateFmt(_('Parsing error on JSON answer: Root object size is %d not 3.'), [o.Count]);
{$ELSE}
  if o.Size <> 3 then
    raise Exception.CreateFmt(_('Parsing error on JSON answer: Root object size is %d not 3.'), [o.Size]);
{$ENDIF}

I would rewrite that to something more like this instead.  Don't duplicate code that doesn't need to be duplicated:

var
  LCount: Integer;

LCount := o.{$IFDEF JSONOBJ_HAS_COUNT}Count{$ELSE}Size{$ENDIF};
if LCount <> 3 then
  raise Exception.CreateFmt(_('Parsing error on JSON answer: Root object size is %d not 3.'), [LCount]);
9 hours ago, dummzeuch said:

Which would be fine for one single line, but there are dozens of similar lines which renders the code unreadable.

  • I could IFDEF complete functions instead but that would mean to fix any bug in all these functions.
  • I could add a class helper for TJSONObject that adds the missing Count function for earlier versions (I think they all support class helpers, so that would be doable.)
  • I could derive TdzJSONObject from TJSONObject and add the missing Count function but that would not work for RTL code that returns a TJSONObject.
  • I could write an inlined wrapper function JSONObject_size that encapsulates that IFDEF. That would improve readability but but would still be ugly.
  • I could drop support for Delphi XE5 or earlier, but I'd rather not. You never know when you're going to need that.

I would just write a class helper or separate function, and IFDEF the code inside of it.  Class helpers were first introduced in Delphi 2005, but were buggy and not officially supported until Delphi 2006.  JSON was first introduced in Delphi 2010.

 

  • Like 2
  • Thanks 1

Share this post


Link to post

I usually write class helper for this. They can easily be removed when older versions are dropped.

  • Like 2

Share this post


Link to post

AFAIK there could be only one class helper until some of 10s so adding this you're blocking a user from creating his own ones.

In this very case, you can define a descendant class where Count property would be declared for older compilers (GetSize is protected so there's no problem). Then you can typecast or just use type name as a pseudonym for your derived class:
 

{$IFDEF Old_Compiler}
TCompatJSONObject = class(Json.TJSONObject);
  property Count: Integer read GetSize;
end;

TJSONObject = TCompatJSONObject;
{$ENDIF}

If I get it right, non-published properties are "virtual" (just a syntax sugar) so these classes will even be binary-identical.

 

In general, there's no convenient universal solution, every case should be considered individually.

Share this post


Link to post
1 hour ago, Fr0sT.Brutal said:

AFAIK there could be only one class helper until some of 10s so adding this you're blocking a user from creating his own ones.

You can inherit from a class helper, so it is still possible to extend that one. Alas, that doesn't work for record helpers. 

Edited by Uwe Raabe

Share this post


Link to post
15 hours ago, Uwe Raabe said:

You can inherit from a class helper, so it is still possible to extend that one. Alas, that doesn't work for record helpers. 

Oh, you're right, thanks! Then helpers seem the most convenient way to go.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×