March 8th, 2019
08/03/19
Odors & Ideas
In which we apply the process outlined in our last post and establish a plan for fixing up the GUI.
As we discussed previously, the process of refactoring starts with figuring out what problems you have that might need to be refactored in the first place. In my case, that includes:
-
The GUI module is declared globally, which is an issue for two reasons.
- Performance. In Lua, global variables are significantly slower to access than locals.
- Everything in the app knows about the GUI and makes use of it. Elements refer to the color and font tables directly, and many of my own scripts dig into the GUI's functions themselves. This makes bugs harder to track down and harder to fix, since so many different pieces of code might be working around or even using the "broken" version.
- Most of the app's state is held in the GUI itself. Again, this means every part of an app needs to access all sorts of information by poking into the GUI - elements need the mouse state, a script might want to access the table of elements, etc. Even better, Lua has no concept of
protected
so anything in the GUI table can be rewritten by anything else. - There are a whole bunch of functions in the GUI module that have no real business being there. Color and font stuff, handling of graphics buffers, and a bunch of table and math helpers - literally anything that isn't an element class is in the GUI.
Don't worry if the list seems a little short - it covers a lot of code, and I'm sure we'll find more issues in the process of untangling these.
Testing
Step 2, you may recall, was establishing some test code that we can run to make sure the app is still working properly. For "normal" code - that is, code that just takes in values and spits out other values, this can be done really easily with testing libraries that let you write:
describe("when there are no tracks armed for recording", function()
it("should display a warning to the user" function()
expect(myWarning.isVisible).toBe(true)
end)
end)
Pretty sexy, right?
Testing a GUI is a bit trickier, especially a completely custom GUI that doesn't use any existing standards or specifications. I may come back to this in the future, but for now we'll go with a much more straightforward approach.
The "Developer Tools" portion of my GUI library, as it happens, comes with a few example scripts demonstrating the library's usage. General Demonstration.lua
shows off most of the element types and has some logic that, altogether, cover a pretty broad area of the GUI's codebase, so we can borrow that to test with.
Make a change, run the script, fiddle with a few things, and if the script doesn't crash we're probably in good shape. It's not perfect, but it does the job.
Serious Testing For Serious People
I won't claim to be very good at this (my work has spoiled me by having automated tests run every time I save a file), but tests should be run as often as you can - as a starting point, let's say for each function that we move/extract/rewrite. I've got a hotkey in Reaper bound to "Run the last script", so for me this process is just a quick Alt+Tab
> D
. Make a few changes, run the test, click on a few elements to make sure they're doing what they should. If they don't, or it crashes, then whatever we just changed is responsible. Easy peasy.
Tunnel Vision
Another aspect of refactoring where I struggle is limiting myself to only one task at a time. I'm easily distracted, so I might get partway through extracting some functions to another module and then think of a new one that would be really nice to have while I'm there.
Bad developer. BAD!
In my defense, many of them are things I know will make subsequent parts of the refactoring easier. We'll see an example in the next post where we sort out my messy Table functions.
Separate But Equal Better
In the past I was loathe to split up my Lua code because I found the use of require("modules.stuff.thing")
strange and intimidating.
Never fear, past-Me! Present-Me has picked up a trick or two along the way that will make it all better.
The GUI library's Core.lua
, as I mentioned in the last post, is presently home to everything that isn't an element class. Prior to starting this overhaul it weighed in at around 1800 lines of code, not counting comments or the excessive number of blank lines I tend to use, but (spoiler alert) we're going to bring that down to 400, maybe less, simply by moving as much code as possible somewhere else.
"Somewhere else" is a very powerful idea in code, because things at are somewhere else are a) not in your way and b) somebody else's problem. Even that if that somebody is you, all you need to worry about is what functionality that other piece of code is making available to you and nothing else.
In our next thrilling installment: Yo dawg, I heard you like tables so I put some metatables in your tables so you can table while you table.