diff --git a/2024.html b/2024.html index d256baa..072ac32 100644 --- a/2024.html +++ b/2024.html @@ -29,6 +29,7 @@

Technology I want to learn more about in 2024

-

Have a safe an fun 2024.

+

Have a safe and fun 2024.

diff --git a/in-defense-of-global-variables.html b/in-defense-of-global-variables.html new file mode 100644 index 0000000..20b2d36 --- /dev/null +++ b/in-defense-of-global-variables.html @@ -0,0 +1,104 @@ + + + + How to Properly Use Global Variables + + + +
+ << back +

How to Properly Use Global Variables

+

+ Global variables, and global state in general have been under fire for as long as I can remember. + The argument is, you couple, and introduce unpredictability through managing global variables + since "anyone can update them". This makes sense in a cosmological world, filled with people + who WANT to write bad code. It's not the global variables that make code bad, it's how they're used. + We've spent many years inventing abstraction after abstraction, that could just be boiled down to global variables, + and global constants. In the end, applications will always have global state, why make it so dang difficult? +

+

Simplicity, and avoiding traps

+

+ Globals are simple, so simple, that even the thought of using them might turn people off. It's hard to accept + that the simplest answer is always the best one, but it almost always is. 99% of the code I run into, is complex for the sake of "reusability", "readability", or + "scalability", but in the end none of these come to pass. You'll see interfaces, and abstract classes defined for single implementations, + horribly generic code written in obscure places, and a mess of a file structure with hundreds of files. Do you really need a generic interface for + each of your database tables, or what about a "service" for each "domain"? In reality, the most readable, resuable, and scalable code, is the code that + compiles well, and avoids mentally expensive abstraction. If I have to walk through the inheritence graph to figure out what something is doing, it's already neither + scalable, or readable. +

+

+ You'll often see global variables hidden behind some "global context" like abstraction. This is down right deplorable, instead, hide your globals behind the simplest + abstraction on the market, the function. GetDatabaseHandler() is much simpler to reason about than GlobalContextHolder.getSingleton().getContext("database"). + These abstractions also, are prone to error, for instance if you use a hash-table to lookup values with a string key, what happens if I miss type the string? The compiler + won't catch that for you, and you'd be adding a lot of extra instructions just to access some state somewhere (which at its fastest is a pointer dereference). Another common way to do this, + is to pass a class object, .class in Java. However, this is even worse. Typically, a language will use reflection which or runtime type checks which can be extraordinarily slow. + To compound this, you also have no gurantees on the existence of the values, for instance, if you want a DatabaseHandler, and you make the request for it, you have no idea + if that is actually the right name or class until runtime. Contrast that with a function that will be picked up by your LSP or produce a compile-time error message. + Global contexts, and contexts in general are a huge mess of slow, hard to optimize code, you should avoid them at all costs. +

+

What to do instead

+

+ As mentioned earlier, global state should exist one layer under a function. There is absolutely nothing wrong with running a database.InitializeDatabase() function, to + initialize your globals at startup, or having some sort of lazy-init built into your global accessor function. Keeping things simple is how you keep them readable, and scalable. + What's funny, is that's pretty much it. +

+

+ Another thing I recommend is not accessing your globals inside of your functions, instead, pass them as arguments. This is just good-practice in general, your functions should + do their best to be as pure as possible. This also makes your code truly re-usable and adaptable, since you may have multiple different global variables with the same types. + This also makes your code easy to test, since it's not relying on any outside values existing or not. +

+

Examples

+

+ Let's compare some code. In the first example, we'll do the traditional GlobalContextHolder OOP style, + where we literally do our best to make it look like we're not working on global variables. In the second example, we allow our globals to exist, + but instead of accessing them in a foolish way, we accept them as parameters. +

+

Example 1:

+
+                
+public void saveUser(User u) {
+    // Where did this guy come from? Idk, it's probably defined in some XML file somewhere :)
+    DatabaseHandler dbh = GlobalContextHolder.getContext("database");
+    dbh.table("users").save(u);
+}
+
+// Main.java
+public static void main(String[] args) {
+    User user = new User("Bob", 23);
+    saveUser(user);
+}
+                
+            
+

Example 2:

+
+                
+// Database.java
+private static DatabaseHandler DBH;
+
+public static DatabaseHandler getDatabase() {
+    if (DBH == null) {
+        DBH = DatabaseHandler.newConnection("mysql://foo@bar:foo.com:3306");
+    }
+
+    return DBH;
+}
+
+// Main.java
+public static void saveUser(User u, DatabaseHandler dbh) {
+    dbh.table("users").save(u);
+}
+
+public static void main(String[] args) {
+    Database.initialize();
+    User user = new User("Bob", 23);
+    saveUser(user, Database.get());
+}
+                
+            
+

+ Overall, this drastically reduces the mental overhead of the system, allowing for simple unit-testing (if you want), and + a simplistic approach to scaling, since you don't have to worry about costly abstractions that don't actually simplify anything. +

+
+ +