My answer is a stark contrast to NGLN's answer. However, I suggest you seriously consider my reasoning. Then, even if you do still wish to use initialization
, and least your eyes will be open to the potential pitfalls and suggested precautions.
Is it a good idea to use initialization sections for module registration?
Unfortunately NGLN's argument in favour is a bit like arguing whether you should do drugs on the basis of whether your favourite rockstar did so.
An argument should rather be based on how use of the feature affects code maintainability.
- On the plus side you add functionality to your application simply by including a unit. (Nice examples are exception handlers, logging frameworks.)
- On the minus side you add functionality to your application simply by including a unit. (Whether you intended to or not.)
A couple of real-world examples why the "plus" point can also be considered a "minus" point:
We had a unit that was included in some projects via search path. This unit performed self-registration in the initialization
section. A bit of refactoring was done, rearranging some unit dependencies. Next thing the unit was no longer being included in one of our applications, breaking one of its features.
We wanted to change our third-party exception handler. Sounds easy enough: take the old handler's units out of the project file, and add the new handler's units in. The problem was that we had a few units that had their own direct reference to some of the old handler's units.
Which exception handler do you think registered it's exception hooks first? Which registered correctly?
However, there is a far more serious maintainability issue. And that is the predictability of the order in which units are initialised. Even though there are rules that will rigorously determine the sequence in which units initialise (and finalise), it is very difficult for you as a programmer to accurately predict this beyond the first few units.
This obviously has grave ramifications for any initialization
sections that are dependent on other units' initialisation. Consider for example what would happen if you have an error in one of your initialization
sections, but it happens to be called before your exception handler/logger has initialised... Your application will fail to start up, and you'll be hamstrung as to figuring out why.
Is it guaranteed that my registration units (in this case Unit2s) initialization section is always run first?
This is one of many cases in which Delphi's documentation is simply wrong.
For units in the interface uses list, the initialization sections of the units used by a client are executed in the order in which the units appear in the client's uses clause.
Consider the the following two units:
unit UnitY;
interface
uses UnitA, UnitB;
...
unit UnitX;
interface
uses UnitB, UnitA;
...
So if both units are in the same project, then (according to the documentation): UnitA
initialises before UnitB
AND UnitB
initialises before UnitA
. This is quite obviously impossible. So the actual initialisation sequence may also depend on other factors: Other units that use A or B. The order in which X and Y initialise.
So the best case argument in favour of the documentation is that: in an effort to keep the explanation simple, some essential details have been omitted. The effect however is that in a real-world situation it's simply wrong.
Yes you "can" theoretically fine-tune your uses
clauses to guarantee a particular initialisation sequence. However, the reality is that on a large project with thousands of units this is humanly impractical to do and far too easy to break.
There are other arguments against initialization
sections:
- Typically the need for initialisation is only because you have a globally shared entity. There's plenty of material explaining why global data is a bad idea.
- Errors in initialisation can be tricky to debug. Even more so on a clients machine where an application can fail to start at all. When you explicitly control initialisation, you can at least first ensure your application is in a state where you'll be able to tell the user what went wrong if something does fail.
- Initialisation sections hamper testability because simply including a unit in a test project now includes a side-effect. And if you have test cases against this unit, they'll probably be tightly coupled because each test almost certainly "leaks" global changes into other tests.
Conclusion
I understand your desire to avoid the "god-unit" that pulls in all dependencies. However, isn't the application itself something that defines all dependencies, pulls them together and makes them cooperate according to the requirements? I don't see any harm in dedicating a specific unit to that purpose. As an added bonus, it is much easier to debug a startup sequence if it's all done from a single entry point.
If however, you do still want to make use of initialization
, I suggest you follow these guidelines:
- Make certain these units are explicitly included in your project. You don't want to accidentally break features due to changes in unit dependencies.
- There must be absolutely no order dependency in your
initialization
sections. (Unfortunately your question implies failure at this point.)
- There must also be no order dependency in your
finalization
sections. (Delphi itself has some problems in this regard. One example is ComObj
. If it finalises too soon, it may uninitialise COM support and cause your application to fail during shutdown.)
- Determine the things that you consider absolutely essential to the running and debugging of your application, and verify their initialisation sequence from the top of your DPR file.
- Ensure that for testability you are able to "turn off" or better yet entirely disable the initialisation.