Global variables are generally considered to be bad. This is especially the case where state variables are concerned. However, as my focus has been, and continues to be, on legacy projects, global variables are a reality to be managed.
Maintaining a project which has many global variables is a challenge. Depending on the names which have been used, one of those problems may be in discovering all the writes to these variables. Searching is troublesome, if the project is large.
Create a class to contain global variables. Create a private field variable for each of the variables, and provide access only through properties. This is a bit of work, but one advantage gained is that you can set a breakpoint in the setter to discover when it is being written. You will also have had to modify the client modules which accessed the variables, adding the class instance name to reference them in the new class.
Consider this very simple class:type
TMyGlobals = class
private
FWindowsDocumentsDir: string;
function GetWindowsDocumentsDir: string;
public
property WindowsDocumentsDir: string read GetWindowsDocumentsDir;
end;var
MyGlobals: TMyGlobals;
As the Windows temporary directory is not likely to be changed during the life of your program instance, you can collect it during the initialization of the class, and wherever the program needs to use it, simply reference it so: ...
SaveToFile(MyGlobals.WindowsTempDir + MyFileName);
Your getter for that variable might look like this:
function TMyGlobals.GetWindowsDocumentsDir: string;
var
FilePath: array [0..MAX_PATH] of char;
begin
if FWindowsDocumentsDir.IsEmpty then
begin
ShGetSpecialFolderPath(0, FilePath, CSIDL_Personal, False);
FWindowsDocumentsDir := IncludeTrailingPathDelimiter(FilePath);
end;
Result := FWindowsDocumentsDir;
end;
Note that in such a class, when you store a pathname, you will want to ensure that it ends with a path delimiter. Doing so will make it unnecessary to litter your application code with calls to IncludeTrailingPathDelimiter.
This example is too small to be useful, but in a legacy project, you are likely to find dozens of globals, perhaps more. Encapsulating them will facilitate further cleanup.
Your globals class module will have to be added into uses clauses in other modules. However, you may then also be able to remove references to modules which previously contained the global variables. In turn, you may then gain the benefit of fewer unit dependency cycles in your project. A variable defined in a large and complex module which creates dependency cycles multiplies the problem of cycles each time it is referenced. You will keep your globals class very clean, and free from unit dependency cycles, so when it can be referenced instead of a more complex module, you enjoy the benefit.
You may also find that your project uses typed constants in numerous places. This practice is deprecated, and their constant nature depends on a compiler setting. If {$J+} or {$WRITEABLECONST ON} is used, then they are not constants, but can be written. However, members of your class can behave as constants, as in this example, by exposing only read access. The setting of the variable can be done as in this example, or through the class constructor, or an explicit initialization procedure in the class. I find this a better implementation when a nominal constant is needed.
Encapsulation is a core feature of OOP, and as suggested here, is also an effective tool in refactoring legacy projects.
Can’t you also use Delphi’s “data breakpoints” with global variables?
https://docwiki.embarcadero.com/RADStudio/Sydney/en/Setting_and_Modifying_Breakpoints#To_set_a_data_breakpoint