Jump to content

Recommended Posts

Hi

 

I'm refactoring a lot of my code and trying to consolidate, group a lot of utility methods. Now I got the point where I have, for example, 20 units using 5 different set of grouped methods in separate units, Utilities1.. Utilities5. Changing this is a big task, so I would like to ask if anybody has experience how different designs work better or worse in regarding Delphi 10.2, 10.3 error insight, compiling... if anybody has any comments, I would like any advice before I decide to any of the below designs:

 

Example A:

In all 20 units I use all 5 Utilities units. So, when I add another Utilities unit, I need to add it to all that use the methods in them.

 

Unit1 uses Utilities1, Utilities2, Utilities3, Utilities4, Utilities5

Unit2 uses Utilities1, Utilities2, Utilities3, Utilities4, Utilities5

...

 

-> This example is annoying that I need to add every single Utilities1-5 unit to be used in all units. I have a feeling that error insight might have a problem with cross referencing all these methods...

 

Example B:

 

I make one Main Utilities unit that contains all Utilities methods, so 20 Units will use only 1 looong unit.

 

MainUtilities contains all methods from Utilities1, Utilities2, Utilities3, Utilities4, Utilities5 - so becomes 1 looong unit

Unit1 uses MainUtilities

Unit2 uses MainUtilities

...

 

-> This example is hard to maintain since all utilities methods are in 1 long unit, instead of grouped nicely in separate units.

 

Example 😄

 

I make one Main Utilities that only have calls to other 5 Utilities, like a 'bridge' calls to methods from Utilities1-5 units

 

MainUtilities uses Utilities1, Utilities2, Utilities3, Utilities4, Utilities5

MainUtilities methods call all methods from Utilities1-5, like this:

   procedure CalculateXFactors(params): integer:

   begin

      Result := Utilities1.CalculateXFactors(params);  -> the real code is still in all Utilities1-5 units, grouped and managed

   end;

 

Unit1 uses MainUtilities (and calls CalculateXFactors method)

Unit2 uses MainUtilities (and calls other methods)

...

 

-> This example seems good, because I have all methods grouped and maintained in separate units, and I avoid the need to use all Utilities1-5 in all units. But!, I need to replicate all method calls in this MainUtilities.

 

 

So, this is simple example, I have a lot more than 20 units, so maintenance is becoming a hassle. Any comments on this?

 

Thanks!

 

 

Share this post


Link to post

You aren't really naming the units with numeric suffices, surely. If you actually are doing that, put them all in the same unit. 

Share this post


Link to post

No, these are not real names. The methods are grouped and named by the purpose, like calculations, math methods, then one unit is all about graphics, drawing, visual stuff, then another unit is exporting, reporting unit... not all units use all of these utilities units, but a portion of them do use all of them. So I want to design a good units design that will work best for maintenance and perhaps less confusing for error insight... to test each concept would mean so much extra work, that I would like to avoid it for now, especially if there is advice from experienced developers.

Share this post


Link to post

Out of the three options, the first two are fine, the last one is a terrible idea. Don't do it. You just create loads of extra work for no benefit. 

  • Like 2

Share this post


Link to post

I would go with Example A. But exclude not used utility units from uses clauses of your units.

 

Error Insight does not like conditionally compiled blocks, especially in the definitions of types and classes.

Share this post


Link to post

What's wrong with adding many units in the uses clauses?

 

I've got a big number of units that are very small in general. For example, the interface of one class is declared to a different unit than the implementation.

 

The number of units may be big but the code is totally manageable.

 

 

Share this post


Link to post

It got to be annoying adding all newly-refactored units to all units that used to use one or two big units, and was thinking if maybe it's not supposed to be like that... but now I see it works good for me.

Also, after using unit uses cleaner, it removed a lot of unused units and now adding new units seems better than before when it was already using a lot of units, and adding new uses just looked strangely complex for the error insight (and me).

Now I'm also removing circular references, but this will take some time, it's manual work.

 

So, to answer your question again: it was a just a thought in the process of refactoring the projects and wanted to check with you guys that I'm not going completely off the road.


Thanks for the feedback!

 

Share this post


Link to post

Maybe Uwe's MMX-Explorer might be a helpful tool clearing your units up.

(be aware, I'm not sure if its already 100% Rio-ready yet, better check before)

Share this post


Link to post
33 minutes ago, Rollo62 said:

Maybe Uwe's MMX-Explorer might be a helpful tool clearing your units up.

(be aware, I'm not sure if its already 100% Rio-ready yet, better check before)

There is a beta for Rio as announced in this thread: https://en.delphipraxis.net/topic/287-mmx-for-delphi-103-rio/

 

Although there are still some minor glitches (e.g. using the v13 registry key than the v14 one), it seems to work quite reliable. When I find some time during the next days I will provide an official release.

 

BTW, the Use Unit dialog of MMX allows to add predefined modules, which are just groups of unit names. Click the small wizard symbol to the right from the edit field. This is quite helpful when you have to add several units that somehow belong together and are often needed in full.

  • Like 1

Share this post


Link to post

As the speekers before, Example A is the better joice.

As an addition, i personally prefer a more "namespace" like notation:

 

Example:

 

uses utils.math,utils.graphics,utils.ui

Share this post


Link to post
25 minutes ago, Markus Kinzler said:

And the header of the file.

Well, I assumed that changing the filename is done via Save As, which already takes care of that. The same is true for renaming a unit from inside Project Manager.

Share this post


Link to post

I use your Example A method and I put ALL utility units into a project by themselves.

 

This means I have a Project 'my_UtilityGenerator.dpr' that 'owns' all my utility units that are common use routines (not application specific), 'my_APIUtilities.pas', 'my_DateUtilities.pas', 'my_MathUtilities.pas', etc. When I build 'my_UtilityGenerator' ALL utilities get built. There is a DUNITX testrunner included in the project directory as well.

 

Any application project that needs the given utility set just names it in the uses clause - you need to coordinate the U-G output and the Application search path, of course (for all release/targets).

 

Notice that if your application project has a LOT of utility units, you could do the same sort of thing, but your U-G could be remain inside your application project file structure and you could just load it into the project manager as part of a group.

 

Occaisionally, I need to add functionality to a utility set or debug it further for the purposes of a particular application under development. Often when this happens, I add the utility to the application DPR, work on it there, and when finished, just go back to the UtilityGenerator.dpr and rebuild it there (alternative load the U-G project into the Project Manager too).

 

Just remember to rebuild the utilities for each release and target you are supporting.

Edited by KeithLatham
clarification

Share this post


Link to post

@KeithLatham So you have that project 'my_UtilityGenerator.dpr' just for compiling common units?

 

I have a folder structure something like this:

 

\Projects\

\Projects\Commons\

\Projects\A\

\Projects\B\

\Projects\C\

 

and all projects include units like this: Utilities1in '..\Commons\Utilities1.pas'  in each project.dpr and all units that use this unit. 

So, every time I compile any of the projects will compile with all used common units.

 

What's the difference with your setup and mine, I mean I don't understand the advantage of putting all in separate project... units get compiled when changed and each project compiles with these common units used.

 

 

Share this post


Link to post

Yes.

 

I have a structure like this:

\Projects\

\Projects\Project One\                              <- contains structure for stuff for project one ONLY

\Projects\Project Two\                              <- contains structure for stuff for project two ONLY

\Projects\Project Utilities\                        <- contains dpr, dproj, etc for my Utility Generator

\Projects\Project Utilities\source\            <- contains pas (and DFM) files for Utility Generator.

\Projects\Project Utilities\DUNITX\          <- contains DUnitX test runners, etc

 

In general my application dpr files will NOT specifiy the path to the utilities.

An application units merely USES the appropriate unit that has already been compiled.

 

I DO NOT want to recompile the utility unit every time - that is part of what makes it a general purpose utility.

 

Only when I need a quick fix to utility unit will I SOMETIMES include the utility source in my dpr while I work out just what I need to do.

Then when I have it right, I leave my application project, load up my utility generator, build ALL utilities for each target, and run regression DUnitX tests as appropriate.

 

The advantage is that you have ALL your units in ONE consistent place, ALL your projects know exactly where to look, and you don't have to guess which version of the utility was compiled last time you built your application.

 

I came to this setup after MANY years of upgrading from Turbo Pascal right up to RAD 10.2 (I haven't gone to 10.3 yet - probably in Feb for me) and noticing that I had dcu's all over the darn place littering up the drive. It really does pay to study the release/target search structures to figure out a consistent way of handling this stuff. You could end up with DCU's compiled for 32 bit all mixed up with DCU's compiled for 64 bit and resultant head scratching over what the heck is going on.

 

What I haven't done yet is organized all my utilities into a namespace, but really its almost there. Some time back, before namespaces, I started naming my units consistently something like 'myCompany_APIUtilities', 'myCompany_PrintUtilities', etc. So I just need to change the '_' to a '.' - I think (and in only about a million places).

Share this post


Link to post

Aha, makes sense. Thanks for the details.

Right now I still compile/build full project every release. I don't use DCUs only, all pas files. I only release for 32 bit.

Share this post


Link to post

Yes, that can be a good strategy if you need to maintain multiple versions of an application. But then you need to keep the the appropriate version of the utilities in the same 'domain' as the correct version of your application. That way if (when) you need to recompile to implement a bug fix, you have all the right sources in one place.

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

×