When a global variable is justified

I use few global vars in my app. The Code Analyzer suggests to get rid of them, but this would involve--in my view--more complicated coding. I'd like to learn the best programming practice in this regard and maybe a fix is simpler than I think.
I use global vars for two main reasons.
First, I need a debug flag in all my functions. This is to spit out debug info if app is run with a debug flag (on the command line). To fix this, I would need to intorduce an additional input variable in all definitions and calls. To me, this is more ugly compared to a line of global definition inside each function. But in general this is easy to fix (introducing addional input argument).
Second, this is more difficult, I think. One function logit() writes log info in both GUI text box and in the file on disk. Thus, it needs FID of the file, and a text box handler, plus another flag which tells if the app runs in batch mode (w/o GUI) or not. For the latter, probably there is a way to find out batch or not via a GUI ask, as well as it shall be possible to find the text box handler via a GUI ask, i.e. w/o explicit handler in a varable. So, I guess it is possible to fix my logit(), where it would have more logic to find out: batch or not, find the needed handler for the text box, and have the file FID in a persistent local variable, maybe (if other funcs do not write to this file).
Thanks

5 commentaires

Stephen23
Stephen23 le 4 Juil 2023
Modifié(e) : Stephen23 le 4 Juil 2023
Rules are there for breaking... once you understand why that rule exists :)
Debug mode and logging meta-data seem like reasonable uses of globals: you have justified why they should be used, and presumably this fits into your overall design requirements, etc. Unreasonable uses would be passing actual data around, etc. Some people would suggest that you write a small function or class to hold such global meta-parameters, but that really boils down to much the same thing, although it has the benefit that perhaps you can perhaps more easily trace who calls or changes it.
But you mention that you are using an app: why not just use the app properties? That might be a suitable solution if most of the functions are called from the app itself. Not sure how that would work in batch mode w/o GUI though...
Otherwise write a comment in the code saying why globals are used, then right-click and hide that Mlint message.
Rik
Rik le 4 Juil 2023
In cases like this, I am generally sharing a data structure anyway, so it is fairly trivial to add a field to that struct. No complicated coding required.
Once you are talking about an app, that implies a GUI. That means you can use guidata or AppDesigner properties. Again, no globals required. For a CLI-mode the solution is a bit more tricky, but what implementation would work best, depends on what you can assume to be available.
Can you assume there will only ever be a single instance of either the GUI or the CLI? In that case I would suggest using a persistent variable. You can load the persistent variable and use the type to deal with GUI vs CLI: if the persistent is a struct, return it (or store it), if the persistent is a (ui)figure handle, load and return the guidata.
Valtar
Valtar le 4 Juil 2023
Thanks, Rik.
I'm not fully getting your 1st paragraph. In my functions, the argumnets are functional--they are of differnt types, and I have none which would carry the global settings. So, i cannot really add the debug flag into a structure for something else. I do have structs, but they are data driven. What I understood from you is that I would to add an extra argument, which would be a structure of those global app vars, for instance with the debug flag, and else. This is not terrible, since I have only 25 funcs, and maybe 50 calls; those that need to check the flag.
Valtar
Valtar le 4 Juil 2023
@Stephen23, I think I have confused all by using "app" as equivalent to "software" ;) My software (eventually a compiled Matlab porgram) is runing in batch mode (w/o GUI) as well as in GUI mode. And if i can work on logit() internal logic to be able to find which mode I'm runing and then query for the text box handle, the issue with the debug flag stays. I struggle to understand the need to refactor it as an additonal input variable :) it's very benign. if something wrong happens to it, nobody will get hurt)
Stephen23
Stephen23 le 4 Juil 2023
" if something wrong happens to it, nobody will get hurt"
The important question is: if something goes wrong with the global, can it in any way (silently) affect the data being processed? If yes, then using globals is a risk. Otherwise... you could consider the global benign.

Connectez-vous pour commenter.

Réponses (3)

Matt J
Matt J le 4 Juil 2023
Modifié(e) : Matt J le 4 Juil 2023

1 vote

To fix this, I would need to intorduce an additional input variable in all definitions and calls.
No, you could add the debug flag as an app property. Then, it would get passed around to all the callbacks and class methods as part of the app object that gets passed to them automatically.

9 commentaires

Valtar
Valtar le 4 Juil 2023
Not getting it. How do I set this app property?
I'm like the user be able to swith it on/off, on the command line (the bedug flag is not set at the compilation time, but rather decided on the app start). How to set this property and how to query it in all the funcs (i'm not using OOP, but i think it does not matter in this context). Thanks!
Matt J
Matt J le 4 Juil 2023
Are you using appdesigner? If you're using appdesigner, you're using OOP. You can add a property in the Code Browser, which is available in the Code View.
Valtar
Valtar le 4 Juil 2023
No. I don't use the AppDesiner. I have all done programmatically. I find it gives better control.
Valtar
Valtar le 4 Juil 2023
Modifié(e) : Valtar le 4 Juil 2023
you are basically saying that a debug flag can be passed via a class private property. Indeed, in OOP, we can pass many variables implicitly (as a class property). yeah... but this is unfortunatley not my case, since i don't really do OOP
Matt J
Matt J le 4 Juil 2023
Modifié(e) : Matt J le 4 Juil 2023
Then how are you giving each of your callbacks access to the elements of the GUI? Are you passing around a structure of uicontrol handles? If so, just add the debug flag as an additional field of the structure.
Valtar
Valtar le 4 Juil 2023
Well, I have all the callbacks as nested funcs :) My app is rather simple. I can have them all as nested. And if those need to do more calc, I pass the data as explicit arguments to other, lower-level funcs. But the GUI sort of stays in one main func. And I have one function logit() who needs the GUI handlers (some of the warnings it will show up as a pop-up if the app is in GUI mode; otherwise just spit to the console). And, again, this logit() I call a lot from many places, it is like a fprintf() replacement, a wrapper which decides what to do depending on the exec mode (CLI aka no GUI vs GUI). So, naturally, I don't want to give an additional argument to logit() in which I could pass a structure with all the needed (e.g. debug flag, GUI flag, maybe a text box handler for writing in GUI).
My logit() has one input param--the message string. All the rest is taken from global vars ;)
I probably can get away figuring out inside logit() if I run with GUI or not, and if yes, to find the text box handle using its fixed object name. This will remove the need for those global vars. logit() does not actually need the debug flag (since it is too simple).
For all the other funcs, to convert the debug flag from a global to a normal, passed-as-input var, I think i would need to change the defnition and calls... There is no other way, since i don't really use OOP.
Matt J
Matt J le 4 Juil 2023
If your callbacks are nested, then just make the debug flag an externally scoped variable in their parent workspace. As for logit(), convert it to a nested function as well, and it will have access to everything the callbacks do.
Valtar
Valtar le 4 Juil 2023
Only the GUI callbacks are nested. All the processing functions are not. And I need logit() all other the software; eveywhere instead of fprintf().
I have just realized that if I eliminate GUI related globals in logit(), and instead will search for the needed handle with findobj(gcf,....), then it could be slower than the global var... I can propably store the needed handle in guidata, and in logit() just check if guidata exists (meaning that the SW is in GUI mode), and if yes, just grab the needed for the output handle.
Matt J
Matt J le 4 Juil 2023
Modifié(e) : Matt J le 4 Juil 2023
You can make wrapper for logit which will use the externally scoped variables. You can use the wrapper within your GUI as a local, specialized version of the general function.
function guiMain
debugFlag=true;
...
function logitLocal(varargin) %wrapper
if debugFlag
logit(something)
else
logit(something_else)
end
end
end

Connectez-vous pour commenter.

Image Analyst
Image Analyst le 4 Juil 2023

0 votes

@Valtar by not using app designer because you think it gives you better control, well, I take issue with that. I'd like to see some example of that. But by doing all the tedious things of setting up callbacks, etc., I think you're actually creating more work for yourself, or you just end up with a simple, more basic app than you could otherwise if you'd have used App Designer.
Globals create variables in the global workspace, which can stay there until you shutdown MATLAB or call "clear globals". This can cause problems if you end up using global variables with old, no longer appropriate, values in your new runs. With app designer the globals are all internal and don't live beyond the running of your app.
However you get the effect of globals with app designer by creating and using a property of the app object. Any function in there can automatically see app.varName (or whatever you call it) without you having to explicitly say "global varName" inside every function that needs to use that variable.
Jan Kappen
Jan Kappen le 3 Sep 2024

0 votes

What about a global logger class? You set select the log-levels at creation time.

4 commentaires

Rik
Rik le 4 Sep 2024
Why don't the other anwers work for a logger? I don't really see why they wouldn't.
Jan Kappen
Jan Kappen le 4 Sep 2024
Modifié(e) : Jan Kappen le 4 Sep 2024
Because there are logging classes existing - isn't it easier to just use an existing, tested, and capsuled module instead of messing around with global variables?
Rik
Rik le 4 Sep 2024
Ah, I thought you meant people could use a global to store an instance of a logger class, not that an instance of a logger class could replace the use of a global.

Connectez-vous pour commenter.

Catégories

En savoir plus sur Large Files and Big Data dans Centre d'aide et File Exchange

Tags

Question posée :

le 4 Juil 2023

Commenté :

le 4 Sep 2024

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by