2

What are the best practices when using global variables?

Normally, the common answer to this is to avoid using global variables and use local variables, properties and arguments to pass data around. However, I'm working with a domain specific language that does not support these concepts.

In the language I'm using, there are procedures, and the only mechanism for passing parameters to them is to define a global variable - something like:

def x
def y

sub main
  set x = 100
  call foo  -- prints 100

  set y = 5
  call baa
  print y   -- prints 10
  call baa
  print y   -- prints 20

end

sub foo     -- Really, the ideal signature would be `void foo(x)`
  print x
end

sub baa     -- Really, the ideal signature would be `int baa(ref y)`
  set y = y * 2
end

Given this will tend to cause confusion for all the normal reasons to avoid globals, what can be done to ensure the code is of high quality, still readable, reliable, or any other concerns I've not listed but are normal things to think about?

Some items I've considered

  • Every procedure adds its own name as a prefix to its arguments, so the above would be named foo_x when passed to foo, but this means lots of additional declarations if I want to pass the same variable to multiple procedures
  • Keep documentation of every variable somewhere and consult it when deciding on a new variable (possibly with some sort of Hungarian notation)
  • Document which method "owns" which variable and do not change it outside that scope (to try to simulate variables being local to a method, and only ever passed by value), so the above would be named main_x
  • A combination of these, something like:
def main_x  -- local variable x for main method
def foo_x   -- argument to foo method
def main_y  -- local variable y for main method
def baa_y   -- argument to y method, should be passed by reference

sub main
  set main_x = 100 -- set up initial value of x

  set foo_x = main_x   -- prepare to call foo
  call foo

  set main_y = 5       -- set initial value of y
  set baa_y = main_y   -- prepare to call baa (first time)
  call baa
  set main_y = baa_y   -- get the result of baa
  print main_y
  
  set baa_y = main_y   -- prepare to call baa (second time)
  call baa
  set main_y = baa_y
  print main_y
end

sub foo
  print foo_x
end

sub baa
  set baa_y = baa_y * 2
end

This approach means the functions don't need to know about each other and can have distinct variable names, but it starts to get quite complicated to read.

9
  • 4
    What about getting rid of that language?
    – Doc Brown
    Nov 27 '21 at 12:25
  • Different languages is something I have looked at, but every other language that is capable of doing the job doesn't meet other technical criteria. One alternative language for example does not even have procedures. I realise this looks like an X-Y problem without further background, but explaining for pages and pages why this really is the problem wouldn't help solve it.
    – Chris
    Nov 27 '21 at 12:47
  • 2
    @rvs it is a modern language, but it's a DSL with a graphical interface rather than a code interface. My pseudocode above is an approximation of the problem rather than how it's literally written (which is drag drop wisiwig stuff). The application of this is to build product tutorial videos - the gist is that when your UX changes, these scripts know how to rebuild your docs from scratch by following procedures you've written. The users are product people, not devs, so it's not practical to switch to a "better" language like Java. This is pretty advanced usage for this tool though.
    – Chris
    Nov 27 '21 at 13:22
  • 2
    @rvs and sometimes languages grow organically. E.g. Ansible (2012) is a tool for devops/configuration management that is driven via YAML files. It has “roles” that behave like subroutines. But it has no scoping, so that the convention is to prefix every variable with the role name. It's extremely tedious, but Ansible is still better than the alternative tools.
    – amon
    Nov 27 '21 at 13:29
  • 1
    Also, avoid recursion, since using global variables as parameters and locals will not automatically stack as in other languages. In other languages parameters and locals are sometimes called automatics, as they are dynamically created upon invocation, and so recursive invocation means each one has its own copy of those variables.
    – Erik Eidt
    Nov 27 '21 at 14:55
7

I am very sorry that you have to deal with this. There's a reason why such procedural programming has died out. In such a limited language, it is very difficult to keep track of data flows. Dealing with this requires extra discipline.

Your idea of encoding the owning function into every variable name is one way to provide such discipline. It could be sensible to further encode the role of this variable:

  • local variable, like your main_* variables
  • input variable, like your foo_x
  • output variable, i.e. the return value
  • in/out variable, like your baa_y

Related variables should be declared close together, and ideally close to the function to which they relate.

But it's important to not apply this too mechanically. E.g. your main_* variables seem unnecessary and obfuscate what is going on. It is also OK if multiple functions operate on the same set of data (like methods on an object) without having to move the data between different variables.

When dealing with such limited languages, it might be worth considering whether you could build a better language that compiles to this limited language. Even just using the C preprocessor might be helpful here. E.g. assuming that a semicolon ; can be used instead of a line break, we might declare a macro to assign the correct variables:

#define FOO(x) foo_x = x; call foo
#define BAA(result, y) baa_y = y; call baa; set result = baa_y

Then:

sub main
  FOO(100)

  BAA(main_y, 5)
  print main_y
  
  BAA(main_y, main_y)
  print main_y
end
1
  • Thanks. Actually the additional main values are there to explain what I mean, but agree they're redundant. Transpiling down to this language is something I've kicked around a bit to solve this, yes, but it still needs to be human readable after the translation. So the question would still be what should the code look like, even if I use a tool to write it more efficiently.
    – Chris
    Nov 27 '21 at 12:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.