JS Variable Hygiene: Preventing Global Var Access

  • Thread starter DaveC426913
  • Start date
  • Tags
    Variable
In summary: That's what modern front end development is like.In summary, the conversation discusses the challenges of managing and updating a one-page vanillaJS app and the need for a more robust and efficient development process. Suggestions include splitting the code into modules, using a bundler like Rollup, and potentially switching to a front end framework like React, Vue, or Svelte. The analogy of building a classic car versus continuously evolving a bicycle is used to explain the benefits of a modern development approach.
  • #1
DaveC426913
Gold Member
22,986
6,661
TL;DR Summary
Making sure I'm not using any more global variables than necessary
To skip the humble-bragging, jump down past the screenshots and descriptions to "Anyway"...

For my client, I made this one-page vanillaJS app that shows a heat map of counties serviced by their three professions (if they add a fourth I'm screwed):

The Niagara county is serviced by one PNSW and one Doula, so its hotspot is a pale blue-green:
1677790381519.png


If you want to only see Registered Nurses, you can turn the others off using the trefoil-like legend:
1677790581540.png


There's also a "just show me the data" overlay:
1677790723487.png


That's it. That's the whole app.Behind the curtain, it has JSON objects (so, read-only and manual update) to serve as database tables, with about 100 data points, and is growing enough that soon I may have to redo the app in a proper database. (I hate that. My app is entirely contained in a half-dozen files and can be moved anywhere without an IT guy to set it up.)

Anyway...

When I built v1.1, it was simpler, and my JSON object(s) were to be read directly. As I add more data, I'm adding functionality, and now my raw data needs to be "massaged" before use. For example:

The Greater Toronto Area - a conglomeration of several counties is very very often used, so I've added some "sugar". Instead of this clunky array:
Code:
var workers = [
    ["Alice", "RN", ["Toronto", "Peel", "York"]],
    ["Betty", "RN", ["Toronto", "Peel", "York", "Niagara"]]
];
it is now
Code:
var GTA = ["Toronto", "Peel", "York"]];

var workers = [
    ["Alice", "RN", ["GTA"]],
    ["Betty", "RN", ["GTA", "Niagara"]]
];
So, I've got a utility in the JS that expands ["GTA"] to its various counties and then strips out any inadvertent duplicates.

Unfortunately, when I wrote this, didn't care about using global variables. So workers array is global and is accessed directly whenever it is needed. I can't do that anymore, and I've got to make sure my code never takes advantage of global variables.

The obvious solution is "use strict".

But if I put that at the top of my js file, all it will do is ensure I don't use any undeclared variables; it won't ensure I don't use global variables. I think what I need is to have all data get pulled in through functions. I guess if I only allowed data to be accessed via getData() functions, then that goes a long way. But is there a way of preventing access to global variables as a form of code-checking i.e. my code will break if I miss one and try to access some global variable somewhere?
 
  • Like
Likes berkeman
Technology news on Phys.org
  • #2
I think it's time to move away from monolithic javascript. Split your code up into modules and use a bundler (I recommend Rollup) to produce production bundles. This opens up the possibility of using Typescript which will make everything much more robust and easier to maintain, and/or also introducing unit and integration testing to the build process.

Or you might want to move straight to a front end framework (React, Vue, Svelte) which will come with a whole development tool chain, and also preferred engines for state management (Redux, Pinia, Svelt Store) which will be much more robust than the POJO (Plain Old Javascript Object) solution you have acknowledged is straining its limits without the burden of introducing a RDBMS.
 
  • Like
Likes jim mcnamara
  • #3
DaveC426913 said:
But is there a way of preventing access to global variables as a form of code-checking i.e. my code will break if I miss one and try to access some global variable somewhere?
Oh I forgot to address this: https://eslint.org/.
 
  • #4
pbuk said:
I think it's time to move away from monolithic javascript. Split your code up into modules and use a bundler (I recommend Rollup) to produce production bundles. This opens up the possibility of using Typescript which will make everything much more robust and easier to maintain, and/or also introducing unit and integration testing to the build process.

Or you might want to move straight to a front end framework (React, Vue, Svelte) which will come with a whole development tool chain, and also preferred engines for state management (Redux, Pinia, Svelt Store) which will be much more robust than the POJO (Plain Old Javascript Object) solution you have acknowledged is straining its limits without the burden of introducing a RDBMS.
Oh God no!
All those things are the reason I got OUT of JS dev.

Me: "I like to build one-off classic automobiles. How can I ensure I use the right rivets?"

Henry Ford: "OK, build a giant factory that takes up 6,129 times more space, takes 37 times more time to set up, 678 times more work to ensure it keeps working, and 4,521 times longer to figure why it isn't working. You now live here in this factory town and will never move because it's way too much effort to move. Also, you're no longer an automobile builder; now you are a factory manager, and managing the factory is now what you will spend the rest of your hours doing. You might get down on the factory floor and pick up a wrench once or twice, but don't count on it." ?:)
 
  • Haha
Likes Filip Larsen
  • #5
DaveC426913 said:
Me: "I like to build one-off classic automobiles. How can I ensure I use the right rivets?"
But a one-off classic car is not a good analogy for the software lifecycle. It's more like

"I want to start off with a bicycle, then I need to go faster so turn it into a motorbike but keep the same saddle because it's really comfortable, now I need to carry more stuff so I need a van but I still want to keep all the things I have attached to the handlebars, now I need to go zero carbon so convert it to all-electric...."

So you either have a process that builds a bicycle, and a different one for a motorbike, and another one for a van (but this one doesn't work with handlebar attachements at all so how are we going to deliver that?)... Or you have a process that lets you start simple with wheels, frame, handlebars, saddle and refine or replace each of the components over time as the needs change.

Anyway I didn't say you have to go straight to an all-singing all-dancing framework, my suggestion was to simply to break up your code into modules something like the below and use Rollup to bundle them together.
JavaScript:
// workers.js

// Note store each worker as an object, not an array, e.g.
// { name: 'Alice', qual: 'RN', regions: ['GTA', 'Mannitoba'] }.
// This is MUCH more future-proof.
const workers = [];

const gtaRegions = ['Toronto', 'Peel', 'York'];

const getWorkersInRegion = (region) => {
  if (gtaRegions.includes('GTA')) {
    return workers.filter(({ regions }) => {
      return regions.includes('GTA') || regions.includes(region);
    });
  }
  return workers.filter(({ regions }) => regions.includes(region));
};

export { getWorkersInRegion, getRegionsForWorker, addWorker };
 
  • #6
And I don't agree with some of your numbers:
DaveC426913 said:
Henry Ford: "OK, build a giant factory that takes up 6,129 times more space,
So what? RAM is cheap.

DaveC426913 said:
takes 37 times more time to set up,
Oh at least - you are not doing any set up at all (and are paying the price down the line).

DaveC426913 said:
678 times more work to ensure it keeps working,
If that is the case you are doing it wrong. It should take 1/10 of the time to keep it working.

DaveC426913 said:
and 4,521 times longer to figure why it isn't working.
If that is the case you are really doing it wrong. If a functional test worked 20 minutes ago and now it doesn't you know that the code you wrote in the last 20 minutes broke it.

DaveC426913 said:
You now live here in this factory town and will never move because it's way too much effort to move.
You don't need to move, you can just do a bit of home remodelling. Compare that with what you have got - you daren't even put up a picture in case it puts a hole in a pipe.

DaveC426913 said:
Also, you're no longer an automobile builder; now you are a factory manager,
Not at all. Becuase your automobiles come together quicker and work first time you can spend MORE time, not less, on making them better rather than hitting them with a hammer and kicking the tyres until they work (for a while). Better product, more reliable, less time, more fun for you and better value for the client.
 
  • #7
I do appreciate your sentiment, but I reiterate: I did this for 25 years, and I lost interest because it became all about framework work. I like my little one-man car shop; I don't want to manage a factory.

And no, that doesn't mean I have to forego good code hygiene.

(Truth be told, I did build my last site in Angular, so it's not like I'll get struck by lightning if I set foot in a factory.)
 
  • Like
Likes pbuk
  • #8
Regarding your pattern for constructing data that relies on evaluation of global variable (I agree, yuck!), perhaps you can build your data inside a function and then actually insert the common parts where needed.

Sort of (handwaving some JS here):
JavaScript:
function buildWorkers() {
  var gta = ["Toronto", "Peel", "York"]
  var workers = [
    ["Alice", "RN", gta],
    ["Bob", "RN", gta.concat(["Niagra"])
  ]
  return workers
}
 
  • Like
Likes DaveC426913
  • #9
DaveC426913 said:
(Truth be told, I did build my last site in Angular
Oh no wonder you hate the idea of frameworks! My last word on this is to suggest you try something less opinionated, perhaps Vue? You don't even need a build process, you can load it in page from a CDN.

But frameworks !== modular code: separating presentation logic from business logic is still vital to creating a robust application in any language.

And if you want to avoid mistakes with variable scoping then you at least need eslint in your toolkit.
 
  • #10
Filip Larsen said:
JavaScript:
function buildWorkers() {
  // ...
  return workers;
}
No, that's just as bad. If you expose the workers object to the UI logic then you can't make any changes to that object without inspecting every line of code to see if it is affected. UI logic should only access this data via accessors like getWorkersForRegion, getRegionsForWorker, addRegionToWorker etc.
 
  • #11
"I want the features of X but don't want to use X" is a story that does not always have a happy ending.
 
  • Like
Likes pbuk and berkeman
  • #12
pbuk said:
No, that's just as bad.
It was a suggestion for a simple change compared to the OP starting point on how to avoid a web of global variables, and its based on the common javascript mechanism of using a function to "hide" internal structures. Take a look at https://www.dofactory.com/javascript/design-patterns for more examples.
 
  • #13
Filip Larsen said:
It was a suggestion for a simple change compared to the OP starting point on how to avoid a web of global variables, and its based on the common javascript mechanism of using a function to "hide" internal structures.
Yes but the problem is that by exposing the workers object you haven't "hidden" anything, you have simply replaced a global object with a global function that returns that object. Instead of this you should be exposing getter and setter methods, and methods to persist the data to and from localstorage/files/remote APIs etc.
 
  • #14
pbuk said:
by exposing the workers object you haven't "hidden" anything
You are reading some grand design into this that I am not talking about. The OP question was if there was a way to not have global variables, i.e. variables that needs to be defined in the global namespace with a specific name. My suggest was simply 1) insert common parts (e.g. the gta array) directly instead of using some kind of eval scheme (assuming here all data is known at build time), 2) put stuff into a function (even an anonymous self-calling one if needed) to avoid polluting the global namespace. This is a very common way to model modules.

All that said, yes, I agree that a step up in design could be to not deliver POD (plain old data) but actually encapsulate this with a javascript class. Then the design moves to designing a good class with setters, getters, iterators, search, find, etc.
 

FAQ: JS Variable Hygiene: Preventing Global Var Access

What is JS Variable Hygiene?

JS Variable Hygiene is the practice of keeping variables scoped within their intended functions or blocks, rather than declaring them as global variables. This helps prevent unintended access and modification of variables throughout the code.

Why is preventing global var access important?

Preventing global var access is important because it helps avoid naming conflicts, unintended side effects, and makes the code easier to maintain and debug. Global variables can be accessed and modified from any part of the code, leading to potential bugs and security vulnerabilities.

How can I prevent global var access in JavaScript?

To prevent global var access in JavaScript, you can use techniques such as declaring variables with 'let' or 'const' keywords instead of 'var', using functions to encapsulate variables, and avoiding declaring variables in the global scope. You can also use strict mode in JavaScript to catch global variable declarations.

What are some common pitfalls to watch out for when dealing with variable hygiene?

Some common pitfalls to watch out for when dealing with variable hygiene include accidentally declaring variables in the global scope, using global variables when they are not necessary, and not properly scoping variables within functions or blocks. It's important to be mindful of variable scope and access throughout the code.

How can I refactor existing code to improve variable hygiene?

To refactor existing code to improve variable hygiene, you can start by identifying global variables and finding ways to encapsulate them within functions or blocks. You can also use tools like linters to catch global variable declarations and enforce best practices. Refactoring code gradually and testing changes can help ensure that variable hygiene is improved without introducing new bugs.

Back
Top