Jump to content
Mike Torrettinni

Should my record be a class instead?

Recommended Posts

By removing global variables (and other definitions - 100s of them), I also started removing global type definitions, because I believe this also messes up Delphi's Error insight and Code completion (too many global vars and type definitions to handle properly).

 

So, I read about records vs classes, and I think my global records are used properly. I want to show the structure of example of unit with a global record + local methods, and if anybody has any comments on 'sanity' of such cases, please do help me improve it:

 

So, this is the usual way how I structure a unit with global record:

 

To 'expose' only 1 or as minimum as possible globals:

- main record type : TGlobalRec

- and 1 global variable :  GlobalRec

- and as minimum global exposed procedures as possible: PrepareGlobalRec

 

It starts with simple main TGlobalRec, and 

 

unit Test;

interface

type

  // definition used only by TGlobalRec
  TValue = record
    ValName      : string;
    ValInProject : boolean;
  end;

  // Global record holder
  TGlobalRec = record
    Id      : Integer;
    RecName : string;
    Values  : TArray<TValue>;
  end;

  // only public method
  procedure PrepareGlobalRec;

var
  // global variable
  GlobalRec: TGlobalRec;

implementation

procedure PrepareGlobalRec;
begin
  // prepare GlobalRec data
end;

end.

 

 

Then I add local types, vars and procedure that are only used for dealing with GlobalRec data:

 

unit Test1;

interface

type

  // definition used only by TGlobalRec
  TValue = record
    ValName      : string;
    ValInProject : boolean;
  end;

  // Global record
  TGlobalRec = record
    Id      : Integer;
    RecName : string;
    Values  : TArray<TValue>;
  end;

  // only public method
  procedure PrepareGlobalRec;

var
  // global variable
  GlobalRec1: TGlobalRec;


implementation


// local types
type
  TLocalRec = record
    LocalRecStr: string;
    // ...
  end;

// Local methods
procedure LocalProc1; forward;
procedure LocalProc2; forward;

// local vars
var
  LocalStrVar : string;
  LocalRecVar : TLocalRec;


procedure PrepareGlobalRec;
begin
  // prepare GlobalRec data
  LocalStrVar := 'local test';
  LocalRecVar.LocalRecStr := 'test';
  LocalProc1;
  LocalProc2;
end;

procedure LocalProc1;
begin
  // ...
end;

procedure LocalProc2;
begin
  // ...
end;



end.

 

And since records allow Private definitions, I move all local definitions into Private section of a record, and also a public procedure:

 

unit Test2;

interface

type

  // definition used only by TGlobalRec
  TValue = record
    ValName      : string;
    ValInProject : boolean;
  end;

  // Global record
  TGlobalRec = record
    Id      : Integer;
    RecName : string;
    Values  : TArray<TValue>;

    // only public method
    procedure PrepareGlobalRec;

    private
      // ALL PREVIOUS LOCAL DEFINITIONS ARE NOW IN PRIVATE SECTION OF RECORD
      type
        TLocalRec = record
          LocalRecStr: string;
          // ...
        end;

      procedure LocalProc1;
      procedure LocalProc2;

    var
      LocalStrVar : string;
      LocalRecVar : TLocalRec;
  end;


var
  // global variable
  GlobalRec2: TGlobalRec;

implementation


procedure TGlobalRec.PrepareGlobalRec;
begin
  // prepare GlobalRec data
  LocalStrVar := 'local test';
  LocalRecVar.LocalRecStr := 'test';
  LocalProc1;
  LocalProc2;
end;

procedure TGlobalRec.LocalProc1;
begin
  // ...
end;

procedure TGlobalRec.LocalProc2;
begin
  // ...
end;



end.

 

As said above, I like this approach because:

- the GlobaRec data is needed to be global and is only 1! (so, no need for classes...)

- I don't need to deal with .Create and .Free (class)

- I expose only minimum number of elements, so I hope eventually Error insight and Code completion might start working in my projects

 

 

So, does anybody have any comments or experience that this design should be improved?

Is there any benefit to turn this into a class, that my limited experience just can't see, understand?

 

 

Share this post


Link to post

This is an class approach. The benefit is the full encapsulation of the private parts inside the implementation section.

unit uGlobalData;

interface

type
  TGlobalData = class
  type
    TValue = record
      ValName: string;
      ValInProject: boolean;
    end;
  public
    { these should probably be properties instead }
    Id: Integer;
    RecName: string;
    Values: TArray<TValue>;
    procedure PrepareGlobalRec; virtual; abstract;
  end;

function GlobalData: TGlobalData;

implementation

type
  TGlobalDataImpl = class(TGlobalData)
  private type
    TLocalRec = record
      LocalRecStr: string;
      // ...
    end;

    procedure LocalProc1;
    procedure LocalProc2;

  var
    LocalStrVar: string;
    LocalRecVar: TLocalRec;
  public
    procedure PrepareGlobalRec; override;
  end;

procedure TGlobalDataImpl.LocalProc1;
begin

end;

procedure TGlobalDataImpl.LocalProc2;
begin

end;

procedure TGlobalDataImpl.PrepareGlobalRec;
begin
  // prepare GlobalRec data
  LocalStrVar := 'local test';
  LocalRecVar.LocalRecStr := 'test';
  LocalProc1;
  LocalProc2;
end;

var
  GlobalDataInstance: TGlobalData = nil;

function GlobalData: TGlobalData;
begin
  if GlobalDataInstance = nil then begin
    GlobalDataInstance := TGlobalDataImpl.Create;
  end;
  Result := GlobalDataInstance;
end;

initialization

finalization
  GlobalDataInstance.Free;
  GlobalDataInstance := nil;
end.

 

  • Thanks 1

Share this post


Link to post

It's unclear what your point is other than to formulate an excuse not to use classes. I mean, you're going through a lot of gyrations simply to avoid using classes.

 

I think Uwe did you a disservice by simply wrapping your existing mess inside of a class. It would be far simpler to just convert it to a class directly so you could see the difference.

 

Your PrepareGlobalRec method IS your constructor, so claiming you don't need "create" is baseless. 

 

      constructor SomeName;  <-- This is for doing exactly what Preparexxxx is doing! The name is irrelevant

 

You probably will also need a destructor to avoid memory leaks, although if the lifetime of these vars is the same as the application, then they'll be freed when the program quits.

 

I'm not sure why you think you need 100's of global vars other than this is just how you've been doing things for years. This is how VB structures things.

 

There are generally very few global vars needed in properly designed Delphi apps. What you'd probably want to do is put them inside of a DataModule or Form and access them from there.

 

Do you NEED TO? No, of course not. Nor do you need to use records or arrays for anything either, just lots and lots of variables.

 

Either you're going to use OOP principles to build your software or you're not. It's up to you.

 

You don't need to ask anybody here for permission to build something using pre-OOP practices. Sure, you CAN ... but why?

  • Like 1

Share this post


Link to post
5 hours ago, Uwe Raabe said:

This is an class approach. The benefit is the full encapsulation of the private parts inside the implementation section.


unit uGlobalData;

interface

type
  TGlobalData = class
  type
    TValue = record
      ValName: string;
      ValInProject: boolean;
    end;
  public
    { these should probably be properties instead }
    Id: Integer;
    RecName: string;
    Values: TArray<TValue>;
    procedure PrepareGlobalRec; virtual; abstract;
  end;

function GlobalData: TGlobalData;

implementation

type
  TGlobalDataImpl = class(TGlobalData)
  private type
    TLocalRec = record
      LocalRecStr: string;
      // ...
    end;

    procedure LocalProc1;
    procedure LocalProc2;

  var
    LocalStrVar: string;
    LocalRecVar: TLocalRec;
  public
    procedure PrepareGlobalRec; override;
  end;

procedure TGlobalDataImpl.LocalProc1;
begin

end;

procedure TGlobalDataImpl.LocalProc2;
begin

end;

procedure TGlobalDataImpl.PrepareGlobalRec;
begin
  // prepare GlobalRec data
  LocalStrVar := 'local test';
  LocalRecVar.LocalRecStr := 'test';
  LocalProc1;
  LocalProc2;
end;

var
  GlobalDataInstance: TGlobalData = nil;

function GlobalData: TGlobalData;
begin
  if GlobalDataInstance = nil then begin
    GlobalDataInstance := TGlobalDataImpl.Create;
  end;
  Result := GlobalDataInstance;
end;

initialization

finalization
  GlobalDataInstance.Free;
  GlobalDataInstance := nil;
end.

 

Thanks @Uwe Raabe! I will need some time to process this.

Share this post


Link to post
1 hour ago, David Schwartz said:

It's unclear what your point is other than to formulate an excuse not to use classes. I mean, you're going through a lot of gyrations simply to avoid using classes.

 

I think Uwe did you a disservice by simply wrapping your existing mess inside of a class. It would be far simpler to just convert it to a class directly so you could see the difference.

 

Your PrepareGlobalRec method IS your constructor, so claiming you don't need "create" is baseless. 

 

      constructor SomeName;  <-- This is for doing exactly what Preparexxxx is doing! The name is irrelevant

 

You probably will also need a destructor to avoid memory leaks, although if the lifetime of these vars is the same as the application, then they'll be freed when the program quits.

 

I'm not sure why you think you need 100's of global vars other than this is just how you've been doing things for years. This is how VB structures things.

 

There are generally very few global vars needed in properly designed Delphi apps. What you'd probably want to do is put them inside of a DataModule or Form and access them from there.

 

Do you NEED TO? No, of course not. Nor do you need to use records or arrays for anything either, just lots and lots of variables.

 

Either you're going to use OOP principles to build your software or you're not. It's up to you.

 

You don't need to ask anybody here for permission to build something using pre-OOP practices. Sure, you CAN ... but why?

I'm just trying to organize all my mess of 100s of globals into units, records... even if they are still global records, at least they are organized, in 1 place, and hopefully better for Error insight and Code completion.

My current project(s) are slowly going from 'full RAD on steroids' stage (most of the code was in control events) to refactored, better organized, easier maintainable... projects.

 

I have a few simple classes, but nothing like this one in this topic, so any help for 'pre-OOP' developer, is most helpful 🙂

 

So, thank you for pointing out the constructor point, I do understand a bit better, now. I can't be the only one who dislikes creating, freeing classes...

 

Share this post


Link to post
36 minutes ago, Mike Torrettinni said:

 

So, thank you for pointing out the constructor point, I do understand a bit better, now. I can't be the only one who dislikes creating, freeing classes...

 

When you eat something, you need to prepare it (maybe cook it, or order it, or unwrap it), then you need to clean up afterwards.

 

When you get in your car to go somewhere, you need to start it up; when you arrive at your destination, you need to shut it off.

 

When you walk into a room, you may need to open the door and perhaps turn on a light; when you leave, you may need to shut the light off and close the door.

 

Most things in life require some kind of setup, then you do something, then you clean up after yourself.

 

Tell me it really bothers you having to deal with this constantly in your life ... or if you're like most people, you don't ever give any of it a second thought.

 

Objects work the same way.

 

The fallacy in your logic (and people who claim to dislike creating and freeing things) is that just because there's nothing named "constructor" or "destructor" that you're not initializing things first and cleaning up afterwards. Variables often need to be initialized and cleaned-up, regardless of their scope. By default, the compiler clears out globals automatically for you by setting them to zero, but it's risky to depend on default behaviours like that from your environment.

 

Constructors are simply methods used for INITIALIZING an object's state. They're usually just a bunch of assignment statements. They may also create other objects required inside of the object. Simple objects that don't inherit from a parent class with a constructor that needs to be called don't need constructors.

 

Destructors are for cleaning up when you're finished using the object. For simple classes, they're often not necessary. But if you allocate anything from the heap within the class, then they're required if you want to avoid memory leaks. 

 

However, it's a good practice to always have constructors and destructors defined, even if they're empty. And if they ARE empty, the compiler optimizes them out. The nice thing about them is they're called automatically, unlike your existing code that needs to be called explicitly. So don't tell me you dislike creating things! Classes with constructors and destructors require less code than what you're doing now ... guaranteed.

 

Even if you're working with a bunch global variables, any nontrivial application has code that initializes groups of related variables, and breaks them down when you're finished.

 

One of the biggest hurdles functional programmers need to overcome when learning OOP is that all of those global variables need to be collected into closely-related groups, and those groups are called "classes". Then you move the code that's already there that's used to initialize those variables into the class' constructor, and any code that's already there for cleaning up into the class' destructor. Then you want to hide the variables themselves ("encapsulation") by defining properties so if the underlying implementations change, you don't have to change every single place they're referenced.

 

Trust me ... after you've been using OOP principles correctly for a while, it becomes second-nature, and you'll wonder how you managed to keep your sanity dealing with everything splattered all over the place with no organization whatsoever.

 

  • Like 5

Share this post


Link to post
17 minutes ago, David Schwartz said:

When you eat something, you need to prepare it (maybe cook it, or order it, or unwrap it), then you need to clean up afterwards.

One of the best explanations I ever read..... 
Thanks for write this. 

  • Thanks 1

Share this post


Link to post
2 hours ago, David Schwartz said:

Tell me it really bothers you having to deal with this constantly in your life ... or if you're like most people, you don't ever give any of it a second thought. 

 

Objects work the same way.

 

All these things do not really bother me, but I am thinking of installing movement detectors so doors open (and close) automatically, and lights go on (and off again) automatically. 

 

And objects in a program can much more easily be automated than physical objects in a house or building. Add to that that light and door management is often forgotten (doors here are often open and we forget to turn off the light in the kitchen). Likewise, one can also forget to free objects. Computers are much better at this than we are and they don't forget.

 

Manual memory management:

  • is very often done wrong, even by those who think they fully control it; you only really control it if you can code thus that you can be sure that you can't have objects that are freed to early or too late. Not many actually know how to do that (or actually: how to avoid writing code that can do these bad things). Object freed too early: you could access a freed object. Object freed too late: memory leak.
  • is a chore so why not get rid of it? It is not that I can't handle it, it is that I don't want to. I can also handle shift gears and yet I prefer an automatic gear, because that is more relaxed.
  • is the cause of many (most?) errors in programming. It is always nice if you can remove such an important source of errors.

So I am quite sad that they want to roll back ARC. It had its flaws, but more because the libraries were not really designed for it and had sometimes conflicting memory management strategies like the ownership/notification principle in the VCL and FMX than because of how ARC actually works. Now they seem to be thinking of going the lightweight smart pointers (hence the desire for auto-constructors and auto-destructors). I am not sure if that is a solution.

 

I had hoped they would go the way MS proposed, a different kind of (indirect) refcounting, something I have been thinking about for years. It requires that pointers work slightly differently, and don't directly point to an object.

 

Oh well.

 

So again: yes we can certainly do all these things, but we tend to forget them and the fact we must do them repeatedly and often is a nuisance, so something that relieves us of that task can make our code safer and easier to write at the same time. It is like buying a car with automatic gear instead of shift gear.

Edited by Rudy Velthuis

Share this post


Link to post
11 hours ago, Mike Torrettinni said:

By removing global variables (and other definitions - 100s of them), I also started removing global type definitions, because I believe this also messes up Delphi's Error insight and Code completion (too many global vars and type definitions to handle properly).

 

So, I read about records vs classes, and I think my global records are used properly.

 

...

 

So, does anybody have any comments or experience that this design should be improved?

Is there any benefit to turn this into a class, that my limited experience just can't see, understand?

 

 

Hold on turning your records into object until they introduce automatic construction and destruction in Delphi. If then, you still want to turn it into an object, then do so. Otherwise, records may be the more ligthweight choice with easier (stack based, and soon automatic) memory management, especially well suited for short-living objects.

Edited by Rudy Velthuis
Overquoting
  • Thanks 1

Share this post


Link to post

@Rudy Velthuis

 

Quote

All these things do not really bother me, but I am thinking of installing movement detectors so doors open (and close) automatically, and lights go on (and off again) automatically. 

 

While I agree in principle, this really has nothing to do with initializing and tearing down objects. You're still going to need to write code to DO the initialization and destruction, and then put it inside of specific methods (constructors and destructors, presumably) in order for the automated setup and tear-down logic to work, right?

 

Whatever style of programming you use, some run-time variables WILL always need to be initialized at run-time.

 

Whether you put them into a constructor that's called automatically or in some random function that you call to initialize global variables is irrelevant.

 

Just don't say you dislike having to call a method to initialize your variables in one case, because it's required in ALL cases. Some just aren't as convenient.

 

By convention, constructors in Delphi are named Create. In fact, you can name them whatever you want, even "PrepareGlobalRec" or whatever. Just change "procedure PrepareGlobalRec" to "constructor PrepareGlobalRec" and you've got a class constructor! And in many cases, you don't even need to call it. 

 

He has not saved a single line of code by avoiding using a class, but in some cases he has by using classes. The initialization code is required no matter what.

 

But without any constructor at all, there's nothing for the memory manager to call to automatically create anything. 

Edited by David Schwartz

Share this post


Link to post

I think I'm so reluctant to use classes, because I don't really have the typical need for them... all tutorials trying to explain the usage with basic examples of inheritance of TCar class and it's details... I just can't relate to anything I use. Except for encapsulation, but I already do this as shown in my 2nd example above, where everything is in record and it's unit. Not perfect, but the way I understand and know how to use it.

So, I'm not going to ask for good book or tutorial on classes, because they all contain such basic examples that I get lost in trying to relate to what I need... like everybody is either making control components or doing some kind of Car related projects.

@David Schwartz said it best: " I mean, you're going through a lot of gyrations simply to avoid using classes. " I guess I'm stuck in my world 🙂

Share this post


Link to post
1 hour ago, Mike Torrettinni said:

I don't really have the typical need for them

 

Fine for Global vars as long as you don't need to finalize. At that point just change to a class.
Pretty sure you don't actually want Global Instance data.. more like a snack without the preparation and cleanup 🙂

 

//MMWIN:CLASSCOPY
unit _MM_Copy_Buffer_;

interface

type
  TGlobal = record
    class var
      Id      : Integer;
      RecName : string;
      Values  : TArray<TValue>;
      class constructor Create;
  private
      class function GetNewProperty: Integer; static;
      class procedure SetNewProperty(const Value: Integer); static;
  public
      class property NewProperty: Integer read GetNewProperty write SetNewProperty;
  end;


implementation

class constructor TGlobal.Create;
begin
  inherited;
  // TODO -cMM: TGlobal.Create default body inserted
end;

class function TGlobal.GetNewProperty: Integer;
begin
  // TODO -cMM: TGlobal.GetNewProperty default body inserted
  Result := ;
end;

class procedure TGlobal.SetNewProperty(const Value: Integer);
begin
  // TODO -cMM: TGlobal.SetNewProperty default body inserted
end;

end.

 

  • Thanks 1

Share this post


Link to post
2 hours ago, Mike Torrettinni said:

I think I'm so reluctant to use classes, because I don't really have the typical need for them... all tutorials trying to explain the usage with basic examples of inheritance of TCar class and it's details... I just can't relate to anything I use. Except for encapsulation, but I already do this as shown in my 2nd example above, where everything is in record and it's unit. Not perfect, but the way I understand and know how to use it.

So, I'm not going to ask for good book or tutorial on classes, because they all contain such basic examples that I get lost in trying to relate to what I need... like everybody is either making control components or doing some kind of Car related projects.

@David Schwartz said it best: " I mean, you're going through a lot of gyrations simply to avoid using classes. " I guess I'm stuck in my world 🙂

In another post you showed something that's got some kind of TProject thing. You said it's a rather large structure. 

 

From what you showed, I'm guessing that you're also not allocating your records from the heap using new() because that would require you to call free() or whatever it's called. (I rarely use records, sorry.)

 

So you've got arrays of huge records sitting in the global namespace and you're wondering why various debugging tool sare having trouble working. Have you considered that they're optimized to work with smaller chunks of things allocated from the heap at run-time?

 

What percentage of your records are allocated from the heap?

 

And where are all of these structures being loaded up? From a database? An INI file? Or just stuff collected at run-time? Where does it go when the program shuts down?

 

 

To me, OOP is simply a way of organizing things at the source code level to make everything easier to manage. What you end up with (should be) very clear and organized. (I've seen designs that are far from it, but that's the general goal.)

 

There are some constructs in source code that have zero impact on memory and/or execution time, so source code often appears somewhat bloated compared to non-OOP code, although a lot of it has no impact on anything. (Properties that refer to fields directly rather than via setter/getter methods, for example.)

 

What most people focus on are the handful of things that are invisible at the source code level that DO have an impact on memory and/or execution time. This is penny-wise and pound-foolish most of the time.

 

In your case, you just don't seem to see the value of having a tackle box to keep your tackle in rather than a toolbox, and prefer to put into whatever random bags and boxes you have around. And you put your snacks and first aid stuff into the same random bags and boxes.

 

You're confused because a "tackle box" would seem to be derived from a "box". So what? So you seem to prefer a less organized solution instead.

 

Or maybe a toolbox with woodworking tools, or another one with auto mechanic tools. Or maybe a matchbox with matches in it.

 

You are pretending that these are all the same types of containers, that their contents have no particularly differentiating factors, and it's fine to put them into whatever bag or box or bottle is handy. Just don't put matches or tools into something that may get wet.

 

Do you put gasoline into a container that isn't designed to hold liquids? Or flammable liquids? Wait, those are properties that belong to containers. But you're just using random bags, boxes, and bottles. Without having differentiating properties, any of them will do for any purpose. That's what you're saying. You could use a zip-lock baggie for your tackle and put your soda into a matchbox.

 

What you don't realize is THAT approach means MORE CODE is needed to sift through all of the conflicting properties that should be IMPLICIT in the CONTAINERS they're in.

 

Do you really enjoy writing all of that code? Do you realize how much of it is needed? I imagine not.

 

A tackle box is a box; a matchbox is a box; a toolbox is a box. And you refuse to use any of them because you think the fact that they're all "boxes" and share a few common properties is ... confusing?

 

Would you put a liquid in a box? Well, if it has a property like "can_hold_liquid" that's set to true. 

 

I have only one question for you: if you don't want to organize things in such an intuitive way, how are you managing all of this now? I guarantee you it is NOT invisible! It's just part of the mess that your code has to deal with.

 

You don't put fishing tackle in a matchbox. But if they're each just a "box" then you need code added to each one that is intended to insure that what you put there is acceptable.

 

Polymorphism is only confusing when you THINK you only have one kind of box in this case. 

 

Since everything is just a "box", you've probably got lines and lines of Assert statements or nested if...then...else clauses checking to see if stuff is safe to add and update.

 

The reason is that by using properly designed classes, the COMPILER tells you when you try adding the wrong objects to things.

 

You know the old saying: "when your only tool is a hammer, everything looks like a nail"?

 

When you aren't using classes, your only tool might as well be a hammer, because every piece of code you write needs to check to see if the thing really IS a nail or not.

 

Simple question for you: how much of your source code is doing nothing but checking to be sure the parameters being passed are of the correct type and/or within a legal range?

 

If you switch to an OOP design, 99% of that code will disappear because the compiler will catch it. 

 

And I promise you that all of that extra code and complexity is increasing your maintenance overhead and technical debt considerably.

 

 

  • Thanks 1

Share this post


Link to post
6 hours ago, Mike Torrettinni said:

I think I'm so reluctant to use classes, because I don't really have the typical need for them... all tutorials trying to explain the usage with basic examples of inheritance of TCar class and it's details... I just can't relate to anything I use. Except for encapsulation, but I already do this as shown in my 2nd example above, where everything is in record and it's unit. Not perfect, but the way I understand and know how to use it.

So, I'm not going to ask for good book or tutorial on classes, because they all contain such basic examples that I get lost in trying to relate to what I need... like everybody is either making control components or doing some kind of Car related projects.

@David Schwartz said it best: " I mean, you're going through a lot of gyrations simply to avoid using classes. " I guess I'm stuck in my world 🙂

Using classes just for the sake of using classes can be nice exercise in futility.

 

I have to admit that I haven't paid close attention to this post and I have been reading it rather superficially, so it is possible that I missed some piece. But from your original post I don't see any compelling reason why you should switch to classes. 

 

Especially if you don't feel comfortable with them and don't really see the point.  Main advantage of classes over records is polymorphism. If you don't have the use case for it, then records will do.

 

Also, one small note - you are holding your records in dynamic array, and those are allocated on the heap, not on stack. Only the few bytes of your global record holder would sit on stack. And overall, class based solution would use more memory than record based one.

 

That does not mean that you should not learn more about the classes, but trying to learn on wrong use case will make things harder not easier. You should learn about classes with examples where they really show their advantage and then you will be able to understand them better.

  • Like 1
  • Thanks 2

Share this post


Link to post
14 hours ago, David Schwartz said:

Simple question for you: how much of your source code is doing nothing but checking to be sure the parameters being passed are of the correct type and/or within a legal range? 

 

 

Hm, not sure what you are asking here, but I looked into some of my utility methods, that get called lot... very few checking, mostly to do with when 0 means no-value or 0 is valid value. I do not use Assert, I do know that could be a flaw, but nothing really too much in a sense of verifying if proper parameters are being passed. And when index to another array is passed, I check the validity (>=0 and <=High(array)).
 

Share this post


Link to post
10 hours ago, Dalija Prasnikar said:

Using classes just for the sake of using classes can be nice exercise in futility. 

I guess in this case this is exactly what I'm doing, trying to fit this into a class but probably I don't need to. But all the suggestions above gave me some value of what to do, what not to do and when I create next class, how to structure it. Much better than those Car classes in tutorials.

Share this post


Link to post
19 hours ago, Rudy Velthuis said:

Hold on turning your records into object until they introduce automatic construction and destruction in Delphi. If then, you still want to turn it into an object, then do so. Otherwise, records may be the more ligthweight choice with easier (stack based, and soon automatic) memory management, especially well suited for short-living objects.

Good to know, thanks!

Share this post


Link to post
22 hours ago, David Schwartz said:

@Rudy Velthuis

 

Whatever style of programming you use, some run-time variables WILL always need to be initialized at run-time. 

Yes, certainly. That is the same in programming environments with automatic lifetime management. 

 

But with automatic lifetime management, you don't have to take care of freeing them, i.e. you don't have to think about when you can free them and when not, and you can't forget to free them, nor will you free them too early.

 

That is not only safer, it is also less of a bother. It takes a tedious and boring task off my hands.

  • Like 2

Share this post


Link to post
13 hours ago, Dalija Prasnikar said:

Using classes just for the sake of using classes can be nice exercise in futility.

 

Exactly.

Share this post


Link to post
On 2/1/2019 at 1:13 AM, David Schwartz said:

@Rudy Velthuis

 

He has not saved a single line of code by avoiding using a class, but in some cases he has by using classes. The initialization code is required no matter what.

He has saved a lot of things:

  • time: records don't need complicated allocation, objects do; nor do records need complicated deallocation;
  • space: records are generally smaller than objects;
  • hassle: usually no need to take care of their lifetime management;
  • more hassle: he already has records; there is no need to rewrite things.

 

So I don't see why on earth he should use objects, if he can use records. Yes, they also need initialization. So what?

 

I don't get your objections WRT naming of constructors, or why you think that constructors should be automatic. Note that default constructors, as they were envisioned for 10.3 Rio, together with default destructors, copy constructors, conversion constructors and overloaded assignment operators have totally different purposes. They allow you to get rid of the (for the user invisible, but rather slow) initialization, finalization, etc. which is generally done by relatively slow runtime functions like _InitializeRecord, _FinalizeRecord, CopyRecord, etc. and which use RTTI (at runtime!) to search their way through a record and initialize, finalize or copy the managed fields they find. These special constructors and destructors are meant to give you the opportunity to replace these slow but automatic runtime routines by your own code, i.e. you tell the compiler: I know what I am doing, I know which members need special handling, so there is no need to call these slow routines. If you don't write these special methods, the automatic behaviour will still take place, i.e. managed fields will be initialized to 0 (or whatever the type equivalent is), refcounts will be handled, etc.

Edited by Rudy Velthuis
  • Thanks 1

Share this post


Link to post
6 hours ago, Mike Torrettinni said:

how much of your source code is doing nothing but checking to be sure the parameters being passed are of the correct type and/or within a legal range? 

Actually, I hardly ever have to check for the type.

 

And having parameters within range: that depends. I usually have an unchecked and a checked version. Internally, I generally use the unchecked code (no range checks, I trust myself to pass arguments in the proper range). For code to be called by others, e.g. in the public interface of a component or library, I do checks. But even then, I usually have a way for the user to tell me if (s)he wants range and/or overflow checks, or if (s)he is confident. The user could set the options to "checking" in debug and "no checking" in release mode.

Share this post


Link to post
On 2/1/2019 at 7:12 PM, Rudy Velthuis said:

Actually, I hardly ever have to check for the type.

 

When you pass everything as a string, as this guy seemed to be doing, you probably do. Or you need lots of exception handling code to deal with conversion errors.

Share this post


Link to post
48 minutes ago, David Schwartz said:

When you pass everything as a string, as this guy seemed to be doing, you probably do. Or you need lots of exception handling code to deal with conversion errors.

Well, I try to name the methods and arguments as meaningful as possible, also there are comments, so it's kind of hard to really mess it up in what is supposed to be passed as parameter. For example function GetProjectName(aProjId: integer): string; doesn't really need a lot of checking or exception handling. Not sure how different anybody else is doing, but I never had problems with method arguments or treating them any special way, if I need different arguments, I overload methods.

Share this post


Link to post

You claim that these global variables "need to be global". Why is that? It would not surprise me if it turned out not to be the case. 

Share this post


Link to post
On 2/1/2019 at 11:21 PM, Rudy Velthuis said:

Yes, certainly. That is the same in programming environments with automatic lifetime management. 

 

But with automatic lifetime management, you don't have to take care of freeing them, i.e. you don't have to think about when you can free them and when not, and you can't forget to free them, nor will you free them too early.

 

That is not only safer, it is also less of a bother. It takes a tedious and boring task off my hands.

Only safe if it works...and just to make sure, this is still Delphi we are talking about, correct? So this feature might work reliably two versions after the introduction, if it gets used enough for a decent flow of error tickets from the get go. At the current rate for RIO, people (including me) seem to be holding off the upgrade until RIO.1 is out - the more people do that, the less likely that .1 will make sense. Sorry for that OT interjection there...carry on.

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

×