You may know about Morbus, a Garry's Mod gamemode that I worked on over a span of a couple years back in 2011. Back when I started working on Morbus I wasn't that good of a programmer, not very good at all. Looking back at some of my old code I see things like improperly indented lines, redundant code, hard coded values and magic numbers, a lack of comments in many areas, and probably the worst of sins, poor software engineering.
When I first began work on Morbus I didn't understand how important it is for code to be modular and easy to extend in the future. Overtime I started to learn how important this was and as much as I wanted to, I never went back and recoded all of Morbus so that it was modular and extensible. Thus Morbus slowly became a monstrous mixture of hard coded spaghetti code with a few modular systems scattered about. Sometimes I wonder if it would have been better to just keep everything non-modular since the constant switching back and forth between modular systems and hard coded systems makes working on Morbus quite challenging and rather draining.
Eventually (today) I decided i've had enough of it, i'm no longer going to deal with that monster code that it is an i'm either going to recode all of it or never touch it again*(tm). But before I cross that bridge here are some of the lessons I learned over the years from Morbus, hopefully they'll help you out in some way.
This was a pretty early lesson, keeping your files and folders neat and organized will help you immensely. How you organize them is the real challenge though, with Morbus I always started by dividing things into 3 categories: server files, client files, shared files. (In Garry's Mod you can control what scripts are run on the client / server) This probably the best idea I ever had since it allowed me to clearly see which scripts were on the server machine, the client machines, and which were shared. After that I divided things up based on their overall purpose and functionality, for example all hud scripts were in a hud folder, all player scripts were in a player folder. This probably all seems very straight forward and intuitive but you'd be surprised how often I see people not organize their code into folders. In the end though, do whatever works best for you, and what you think will work best for others who look at your code, that's what really matters.
If this were C++ i'd say never use the global namespace, but Morbus is in lua so we have global variables. Inside of lua whenever you declare a variable it is automatically global, unless it's inside of a table or you used the local keyword. If everything in your code is a global variables (and/or functions) then you're going to start running into some very serious problems later on, it's never fun to find out that someone else already declared the GetTarget function or <generic function/variable name here>
So how do you solve this in lua? By putting all of your functions inside of tables which are global variables. This way you emulate C++ namespaces and it works wonders when it comes to organizing your code, just take a look at this to see what i mean:
All of those functions and variables are contained inside of the global table SMV so now whenever I want to access something from inside of the Simple Map Voting (SMV) module I just use the SMV table. Never do I have to worry about having two functions with the same.
This is one of my biggest regrets from Morbus, not because I don't have to worry about duplicate function names though, but because it allows clarity when reading code and deciphering what function calls are from what files / modules.
What i'm about to show you is the ResetStatus function from Morbus which is responsible for essentially giving the player a clean state (used at the start of every new round) so that no unwanted state data gets carried over. Prepare yourself, this is real code from Morbus.
Wow, ok, that's just a huge function that manually resets nearly 40 different variables to default (magic) values? What do some of those variables even do? Are all of these things even needed? Beyond the obvious problem with this function, look at the names of those variables, try to imagine what they do. When are they relevant? Where are they relevant? Why are they relevant? Is every one of these function calls needed?
So now that you have seen the mess, I'm going to organize it just a little bit so that related state data is next to each other..
The code is still just as bad, but it makes a little bit more sense now, which is great, but not good enough. To really fix this function we need to migrate all of these variables into their own modules within the player, and then have individual function calls for resetting each module back to it's original state. What's even better is then in the future if we start adding new features what require specific modules to be reset to their original state we can easily integrate them since we have all of that information there.
I would say out of all my woes with Morbus, this is the greatest. It has made Morbus a truly awful pain to work in since most state data is written to directly (by setting the variables) and there are very few functions calls that do it for you. This causes problems like: "If you change the Mission type then you need to call function X, Y, and Z to ensure everything is ok." The salt still flows from me on this one.
Age old lesson that I was apparently not aged old enough in programming to understand. Don't repeat yourself. If you're writing the same code you did 10 minutes ago then you're doing something wrong. I could write an essay on this but in all honesty it's pretty self-explanatory.
Take a look at this beautiful function. No it's not the same one as above. Or is it?
Another age old one, but in all honestly no amount of commenting could have saved Morbus from sinking. According to cloc, Morbus is around 11,000 lines of code, if you take away about 1000 which is just hard coded values (names, and content paths) you're left with around 10 thousand lines of code. Now take a guess how many lines of comments Morbus had. 3,000? 2,000? 1,500?
How about 645. Well I guess that's something, it's around 1 line of comment per 15 lines of code. But we should probably take away file headers (yes there were actually file headers) which were at a minimum 3 lines of comments for each of the 92 files in Morbus.
That leaves us with a grand total of 1 line of comment for every 27 lines of code.
Morbus might as well not of been commented at all.
There's more, but there's only so much kicking of a dead horse that one can handle before they decide there really isn't much else left to kick. A lot of these things seem pretty intuitive and an obvious no brainier for skilled programmers but I wish I had taken these lessons to heart earlier than I had. So next time you see yourself using a global variable, or not commenting your code, just think of good ol' Morbus and the diseased code that makes it.