Which uses clause to default to? Interface or Implementation?

I was reading an old thread in another forum, and a discussion came up about a topic that to me has been obvious and not really up for debate for years. But by the end of the thread, I was questioning what I’ve believed for years, and no longer sure the answer is totally obvious. So I’m curious to know where everyone else stands.

The question is this: When adding a unit to the uses clause, what is your default option?:

A: Add it to the Implementation uses clause, unless you need to reference something from it in the Interface section.
B: Add it to the Interface section, unless you need to add it to the Implementation uses clause because of circular references.

I’ve been firmly in one of these camps for probably approaching 30 years, and until I read this thread I thought EVERYONE took the same view. I’m now realising it might be less obvious than I thought.

So, Team A or Team B?

Team A. If it’s not A it flies against some basic principles and IMHO would mean you aren’t thinking about your design

2 Likes

Team A.

I’m interested in hearing arguments against that approach.

1 Like

I would be in Team A. Only move it to the Interface section if the compiler complains.

1 Like

It’s been awhile since I’ve used Delphi but I’m definitely in Team A. But I’m curious as to why you would choose B as the default position.

1 Like

Firmly in team A.
Our devs have a massive problem with Delphi our large project - intellisense keeps falling over, cashing, having to restart. Reducing circular references by driving units to implementation is what has helped us keep the Delphi IDE kinda working.
The delphi unit dependency scanner has been invalulable.

1 Like

I’m with Misha – A

Although, I can sometimes do lazy B, as an exception, too.

PS: Just had “fun” with System.Net.HttpClient unit the other day: unless it was in the interface section, the project would not compile, even though nothing in the interface section refers to it. It was creating some conflict with something that I could not put my finger on (one random method in one of the classes there was being highlighted in the implementation section as not matching its definition in the interface section – incorrectly highlighted, because it was in fact matching, every time I would add System.Net.HttpClient anywhere but the 1st position of the interface section’s uses clause), so once I figured that it works with it in the interface section, I just let it be there.

Alex

Well, it seems everyone does take the same view (where “everyone” means people on the ADUG forum on a cold Friday morning who could be arsed to reply). I’m also Team A, probably for the same reasons as you guys (defaulting to lowest visibility first, etc) but I did find Team B’s reasoning interesting.

The B option was being promoted by a surprising number of people, but it included a couple of experienced developers who’s opinions I generally respect. The idea they put forward was to make circular references a conscious and deliberate exception. To ensure they could not happen accidentally.

To achieve this, they have the default position that all required units go in the Interface uses clause. If all unit references are in the Interface uses clause, then the compiler will not allow a circular reference.

The only time they move a unit into the Implementation uses clause is when the compiler complains about a circular reference and they’ve explored refactoring to remove it. Only once they’ve exhausted those options do they put something in the Implementation uses clause. So it ends up being a rarity in their projects.

Perhaps it comes down to which issue you believe carries the worse consequences? Having a unit in the interface section that is only used in the Implementation section vs having unintended circular references.

2 Likes

Sounds like the reason for option B could be eliminated by improving the ide to show when unused units are in the uses clause - probably like in Visual Studio where the include is grayed out.

CnPack has the Uses Cleaner which can also assist with keeping the referenced units minimised.

1 Like

It’s not really about unused Units, more about Units that are only used in the Implementation section, but placed in the Interface section uses clause, and whether that brings other benefits.

I was going to post something similar. Sounds like the reason for going option B is because the IDE/compiler makes it a little harder to “review” circular references. But that is a job for a tool, NOT a reason to choose option B. I reckon those developers have chosen the wrong solution to the problem!

@Malcolm I’ve found circular reference issues a problem with my large code base and it seems like a good time to ask how others handle it.

When I code nowadays I try to aim for these high level concepts:

Less code per unit, and using more of a namespace unit style like System.Users.Passwords.pas instead of putting everything in a single file.

Wanting to keep strong typing

Not wanting to use interfaces because I find them painful to memory manage, slower performance and annoying to debug and difficult to work with within the JS Engine where most of our business logic runs.

Delphi has the concept of partially declaring a class in a unit i.e.

TFoo = class;

TOtherThing = class(Tobject)
end;

TFoo = class(TObject)

I’ve always wished you could do the same across units (files) to allow for proper type security without the mess of circular reference problems. I constantly need to declare one type inside another that also has references to the other type and I don’t want them in the same unit.

Is there any better way to handle this situation?

Well, I guess they are using the tool (ie. the compiler) to make the accidental circular references visible/unignorable. Whether that is worth the trade off is the real issue I think.

Feel free to tell me any issues I’ve missed:

Downsides of Circular references

  • slower compiles
  • can disable inlining of methods
  • complicates class design

Downsides of Implementation units added to Interface section uses clause

  • less clear intent (ie. the uses clause you choose sends a message about how the unit is used in your code.)
  • violates a common design philosophy of choosing the tightest/narrowest visibility possible.
  • risk of unintentionally using “downstream” types in your interface section.

Any others?

Note, I’m not necessarily advocating for Team B. But when my long held beliefs are challenged like this, I try to be open to the fact I might be wrong.

The shifting of uses units does nothing about circular references. Whether you have them or not is a design issue and how you partition your code. Option B just makes it easier to find them. But that can be done just as well, or better, with a specific tool. Using the compiler to detect them (by putting everything into the interface uses clause) is a poor way to do this in 2024 (there are much better ways) and has a poor outcome for readability. So, the choice of option B may have made sense 10-20 years ago, but not now. However, once chosen for a codebase, it is very hard to undo

My overall view is that option B MAY have been a valid choice a while ago, but not now for new projects

As an aside, this comes up regularly for codebases (in any language) older than 10 years (mine included). It then becomes a cost-benefit choice as to whether you act on changed circumstances and migrate how you develop for the current tech. A good assessment of a long-standing developer is whether they can accept that what they are doing has negative consequences now, even though the choice was correct a while ago. I frequently ponder this when I develop, mainly on a codebase started 15 years ago (C#), built on a Delphi threading framework started 25 years ago (now included as .NET 4.8 assemblies). In actual fact I have hived off development into two streams: one computationally and thread intensive on the old framework, and one all new using .NET 8

@ap2021 Do you think your issue could have anything to do with later USED units possibly overshadowing earlier ones?

Maybe the IDE or a tool should point out units that are not orthogonal?
ie if the order could matter, maybe we should know about it.

Team A.

B seems to be the solution for a symptom that is caused by some poor circular unit reference handling of the compiler.

Team A. B’s problems can be dealt with with the build in static code analysis feature - oh wait… never mind.