Solved

Discussion: Unit naming conventions and other stuff

Posted on 2004-10-21
17
239 Views
Last Modified: 2010-04-05
As my projects get larger I feel an urge to formalize my unit handling. Actually I put related units in a subdirectory with the name of the module (or package, ... whatever), let's say "foo". In this directory there's a file "foo.pas" that I want to be used by other modules. I often have a file "foo_types.pas" that holds (surprise) some type definitions, constants and tool functions ("foo_typesconststools.pas" would be more appropriate). However I don't distinguish between internal types (only used by the foo module) and external definitions (something that other modules need if they want to use the classes in "foo.pas"). What's your approach to handle this? I also consider abstract classes, frameworks, patterns (MVC, factories,...).
0
Comment
Question by:__alex
  • 9
  • 6
  • 2
17 Comments
 
LVL 17

Accepted Solution

by:
Wim ten Brink earned 400 total points
ID: 12368290
I use the following convention: ("Whatever" indicates more information about the unit.)

For simple units: untWhatever.pas
For forms: frmWhatever.pas
For frames: fraWhatever.pas
For datamodules: dmWhatever.pas
For webmodules: wmWhatever.pas
For service units: serWhatever.pas

So, in general, the first 2/3 characters indicate what kind of unit it is and the rest is just a description.

Second of all, I try to keep related information in the same unit. Thus a Foo.pas and Foo_Types.pas would be combined by me into untFoo.pas which makes life more easier. If I have some useful tool functions, I add them in a unit that I call untTools.pas or perhaps some other useful name. I also try to avoid using lots of units in my own unit. If I can stick to just the Windows unit then it's perfect. If I have to add SysUtils then it's okay. If I have to add a dozen or so units then I get annoyed, trying to find ways to limit the number of required units.

And the latter is done quite easily:

unit untAAA;
interface
type AClass=Class(TObject);
var AValue: integer;
end;

unit untBBB;
interface
uses untAAA;
type AClass=untAAA.AClass;
var AValue: integer absolute untAAA.AValue;
end;

If you look at above example, you'll see how I'll forward types and variables from one unit to another. Thus if I need this type in unit untCCC then all I need to use is untBBB, not untAAA. Avoiding too many units in my uses clause this way. :-)
It's a bit difficult for global functions, though. Which is why I try to avoid them too! Often when I create a tool with useful functions, I group these functions in a single class and then just use this class to run my functions. (Especially class functions can be very useful!)

About abstract classes, I try to avoid them. I do have to use abstract methods in some situations though, where there are multiple inheritance levels, but if possible I try to use interfaces instead of abstract classes. Interfaces are much more flexible.

For units, I also try to keep the interface part as clean as possible. This is best done by just defining an interface and a function that creates an interface object. Something like this:

unit untFoo;
interface
type
  IBar = interface
    procedure Whatever;
  end;
function NewBar: IBar;
implementation
type
  TBar=class(TInterfacedObject, IBar);
  private
  protected
    procedure Whatever;
  public
  end;
function NewBar: IBar;
begin
  Result := TBar.Create;
end;
procedure TBar.Whatever;
begin
end;
end.

Above code works quite well and only exposes the information to the user of this unit what he really needs. All he needs is a way to create and use an object, and above code does just that. It does take a bit to get used to but once you're familiar with this technique it is extremely powerful and keeps the number of possible bugs to a minimum. Even funnier, in most of my units I don't even have classes in my interface sections anymore, just interfaces...

And I rely a lot on over two decades of development experience. :-)
0
 
LVL 2

Author Comment

by:__alex
ID: 12368864
@Workshop_Alex
Quite useful stuff, thanks.
I like to split my source in little units because it seems easier to debug. If I made changes to "foo_types.pas" and the unit test fails I just have to search in this unit and not in "bigFooWithAllStuffInIt.pas". However my goal is that the part of the application which wants to get a foo object just has to include one unit.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12369271
Actually, dividing sources up in smaller units makes it just harder to debug since you're dividing code over more units. Your Foo source could end up being divided over 5 different units and while one unit shows the error, the actual error might just be in some onter unit.
I tend to consider units as building blocks. They have to be completely self-contained or build on top of another unit. And yes, it could well be that for a project I need a dozen or so of different classes. Well, you could put each class in it's own unit or combine a few classes in the same unit, but try to avoid class dependencies if possible. If you need inheritance, then create one unit for the base class, with perhaps a few abstract methods. Then you can build other classes on top of this single unit, as long as they're depending on a minimum number of units.
And if you need two different units to share data with one another, consider adding a third base unit with an interface definition in it for the classes in those two units. (Or abstract classes but I dislike them.) Then both units can use this base unit and thus communicate with one another without the need of them referring to one another. The base unit doesn't use any of these objects, of course.

Consider building libraries of units like building a tree. You have a strong base and you add branches to it, and smaller branches to the branches. But every branch has only one route that it should use to get to the base. The same for units, keep them linear to make your code more readable.

This is not a requirement, of course. Other solutions are often easier to create but hey... If possible, try it. I have the same problem with people who use break, exit and/or goto in their Delphi sources. You NEVER need these functions anyway and should be kicked in the ass if you do use them. There are simple ways to avoid using them, but most people just don't bother to think about those solutions anyway.

Once you start building an application, you can just use whatever you need. But developing unit libraries and developing applications are two completely different things.

For you Foo stuff, why not put everything in the same unit? And if that happens to be too big then put everything your Foo unit needs in an BaseFoo unit so your main application still has to use only the Foo unit. The BaseFoo would only be used by your Foo unit, providing only things Foo needs.
0
 
LVL 14

Expert Comment

by:DragonSlayer
ID: 12372812
> I have the same problem with people who use break, exit and/or goto in their Delphi sources ... kicked in the ass if you do use them

Agree with the goto part... and the exit part too, but for break, well, I still find them useful to break out of a loop that does searching using a for loop, for example

for i := 0 to SomeMaxLimit - 1 do
  if SomeList[i].Value = ValueIAmLookingFor then
  begin
    DoSomeProcessing(SomeList[i]);
    Break; // so that the iteration need not continue
  end;

What's a better alternative?
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12373090
I := 0;
while (I < SomeMaxLimit) and not (SomeList[I].Value = ValueIAmLookingFor) do Inc(I);
if (I < SomeMaxLimit) then DoSomeProcessing(SomeList[I]);

Less lines of code even... :-P
0
 
LVL 14

Expert Comment

by:DragonSlayer
ID: 12373143
the for loop still looks more structured, IMHO :-p
0
 
LVL 2

Author Comment

by:__alex
ID: 12378885
I like Breaks, Exits and even Gotos (to exit nested for loops). I know this is kinda off topic but I'm curious. This snippet is from my actual project: It gets the index of an item in a list dependant on tag. To avoid searching the list twice if you want to get the same item in succession I store the last access index. How would you implement it?

function TLoiList.GetIndex(const tag: integer): integer;
var
  i: integer;
begin
  if ((0 <= lastIdx) and (list.Count > lastIdx)) then begin
    if (tag = GetItem(lastIdx).Tag) then begin
      Result := lastIdx;
      Exit;
    end;
  end;
  Result := -1;
  for i := 0 to Pred(list.Count) do begin
    if (tag = GetItem(i).Tag) then begin
      Result := i;
      lastIdx := i;
      Break;
    end;
  end;
  if (-1 = Result) then
    DoRaise(TAGNOTEXISTANT, [tag]);
end;
0
 
LVL 2

Author Comment

by:__alex
ID: 12378896
Extra points if I like it!
0
Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12378922
unction TLoiList.GetIndex(const tag: integer): integer;
var
  i: integer;
begin
  if ((0 <= lastIdx) and (list.Count > lastIdx)) and (tag = GetItem(lastIdx).Tag) then begin
    Result := lastIdx;
  end
  else begin
    I := 0;
    while (I < List.Count) and not (tag = GetItem(i).Tag) do Inc(I);
    if (I < List.Count) then begin
      Result := i;
      lastIdx := i;
    end
    else begin
      DoRaise(TAGNOTEXISTANT, [tag]);
    end;
  end;
end;

Not too difficult, is it? :-P
As I keep saying, once you know how, it is quite easy to avoid the use of breaks, exits and goto's. It just requires a different way of thinking. (And the iron will to never use those commands!)
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12378930
Oh, if DoRaise doesn't create an exception then just set Result to -1 right after calling this method... :-)
Now, do you like it?
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12378945
It can also be done shorter, by going in the opposite direction!

 unction TLoiList.GetIndex(const tag: integer): integer;
begin
  if ((0 <= lastIdx) and (list.Count > lastIdx)) and (tag = GetItem(lastIdx).Tag) then begin
    Result := lastIdx;
  end
  else begin
    Result := Pred(List.Count);
    while (Result >= 0) and not (tag = GetItem(Result).Tag) do Dec(Result);
    if (Result >= 0) then begin
      lastIdx := Result;
    end
    else begin
      DoRaise(TAGNOTEXISTANT, [tag]);
    end;
  end;
end;

This has as advantage that Result will be set to -1 once the loop ends. It also gets rid of one little variable.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12378960
Oops... Result will be set to -1 if it doesn't find anything... :)

Darn, 4 posts just for a simple answer... :-D
0
 
LVL 2

Author Comment

by:__alex
ID: 12379007
> if ((0 <= lastIdx) and (list.Count > lastIdx)) and (tag = GetItem(lastIdx).Tag) then begin

If list.Count has decreased you may get a list index out of bounds error (I know there are compiler settings to avoid the complete evaluation of booleans...). That's why I use two if. So I still don't like it.
0
 
LVL 17

Assisted Solution

by:Wim ten Brink
Wim ten Brink earned 400 total points
ID: 12379245
function TLoiList.GetIndex(const tag: integer): integer;
begin
  Result := -1;
  if ((0 <= lastIdx) and (list.Count > lastIdx)) then begin
    if (tag = GetItem(lastIdx).Tag) then Result := lastIdx;
  end;
  if (Result < 0) then begin
    Result := Pred(List.Count);
    while (Result >= 0) and not (tag = GetItem(Result).Tag) do Dec(Result);
    if (Result >= 0) then begin
      lastIdx := Result;
    end
    else begin
      DoRaise(TAGNOTEXISTANT, [tag]);
    end;
  end;
end;

Happy now? :-)
Besides, I never use full boolean evaluation. There's no need for this anyway.
0
 
LVL 2

Author Comment

by:__alex
ID: 12379385
I surrender (and live happily with my sloppy habits). I increased points, enjoy them. Thanks for the input!
0
 
LVL 2

Author Comment

by:__alex
ID: 12379765
Full boolean evaluation necessary (otherwise no new item created)

if (userWantsMessage and not Assigned(CreateNewItem(owner))) then
  ShowError;
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12379812
Nope, not needed... Create the object FIRST! :-P


if ((not Assigned(CreateNewItem(owner)) and userWantsMessage )) then
  ShowError;

Now, it will create the object and if that fails, checks if an error must be displayed. It's all a matter of putting things in the right order.
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now