Welcome to the 4th installment of the learning to code in JW Lua series!
We’re going to take a step back in this article and next week’s article to help give you a better grasp with what’s actually going on. Because learning to code is good, but coding well is really what we’re after.
This series creates a bit of a conundrum because there’s three huge topics to cover:
- Programming Practices
- Lua language
- JW Lua
Each one of these topics is enough to have 10+ hour paid courses for, but we’re trying to cover them all at once. Hence, there are many footnotes to each article.
We’ve taken quite a bit of time learning about the Lua language and JW Lua, so it’s about time to step back and look at some fundamental programming practice that you need to know if you’re going to code.
Just like with creating music, thoughtless/sloppy code really degrades the value of the final product. So today, we’re going to look at some of the aspects of writing great Lua code.
Basic Factors of Great Code
There are a couple of things that really define well-written code:
- Readability
- Editability
…and that’s about it.
And, in reality, writing well written code is not too difficult to do. Yet there are many coders who don’t follow the basic practices of writing good code. Writing well written code will save you lots of time in the long run.
Readability
Code that reads well is code that you can understand right away. Everything makes sense. Take these two examples:
function a() local b = finale.FCTextExpressionDefs() b:LoadAll() for c in each(b) do local d = e(c) f(c, d) local g = d:CreateName(); fixCase(c, g.LuaString) end b:SaveAll() end
function styleExpressionDefs() local textexpressiondefs = finale.FCTextExpressionDefs() textexpressiondefs:LoadAll() for ted in each(textexpressiondefs) do updateFontsAndCaps(ted) end textexpressiondefs:SaveAll() end
The difference is almost night and day. They both do the same thing, but the first block of code is painful to decipher what’s going on. (“Decipher” is used intentionally here.)
The second code block actually makes more sense. Even if you don’t know the context of the code, or what it does, you have an idea of what it’s doing. And, if you know what these functions are in JW Lua, it makes even more sense. But even with no knowledge of these functions you can make out their meaning!
That’s good code. That’s good readability.
Editability
The second requirement for good code is editability. Odds are, you are going to want to change your code later on. Or you might give it to your best friend to help them out, and they may want to edit it. The reasons are endless. Perhaps it just doesn’t work right 100% of the time like you expected and you need to fix it.
The code has to be easy to edit to qualify as good code.
Half the battle to making code editable is to make it readable. The other half is writing modular code (more on that when we get to functions) and flexible.
FizzBuzz
Here’s a classic interview question for coding usability called FizzBuzz:
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
Yes, this is actually a real interview question for many coding jobs. Though someone with very little coding experience can create such a piece of code, it really highlights how much you know about good coding practices.
You actually know enough to complete this right now, so try it out in the online Lua editor.
Hint: If you’re stuck trying to figure out how to write this, review last week’s article and all the footnotes.
Answer in
5…
4…
3…
2…
1…
Here’s just one way to write this with Lua:
for i = 1, 100, 1 do if i % 3 == 0 and i % 5 == 0 then print('FizzBuzz') end if i % 3 == 0 and i % 5 ~= 0 then print('Fizz') end if i % 5 == 0 and i % 3 ~= 0 then print('Buzz') end if i % 3 ~= 0 and i % 5 ~= 0 then print(i) end end
There are many ways to program this challenge, and this might be one of the simplest and most straightforward ways. You may have come up with this way yourself. However, it’s not easily readable or editable. There’s lots of repetition and it’s very hard to understand what’s going on just by looking at it.
If you looked the footnote last week “If, then, else”, you’d know that you can clean up the code like this:
for i = 1, 100, 1 do if i % 3 == 0 and i % 5 == 0 then print('FizzBuzz') elseif i % 3 == 0 then print('Fizz') elseif i % 5 == 0 then print('Buzz') else print(i) end end
It’s better. Because by using the else statements, we got rid of many excess conditional statements. Less is more. Simpler is better. It also means the code is more readable.
But if we want to add a new rule like multiples of 7 print “Zazz”, then we have to add a lot:
for i = 1, 100, 1 do if i % 3 == 0 and i % 5 == 0 and i % 7 == 0 then print('FizzBuzzZazz') elseif i % 3 == 0 and i % 5 == 0 then print('FizzBuzz') elseif i % 3 == 0 and i % 7 == 0 then print('FizzZazz') elseif i % 5 == 0 and i % 7 == 0 then print('BuzzZazz') elseif i % 3 == 0 then print('Fizz') elseif i % 5 == 0 then print('Buzz') elseif i % 7 == 0 then print('Zazz') else print(i) end end
Whoa!!! That’s a lot of edits!
And if we need to actually print “Zazz” on multiples of 9 instead of 7, then we have a whole lots of 7s to change. Not too bad on such a small scale, but when you get to hundreds of lines of code and miss one 7…
Let’s make this better.
Variable Naming
When writing a piece of code, it’s always important to make sure your variable names accurately describe the contents. Variable names should:
- Describe the contents
- Be a standard name if one exists
Remember the terrible example I had above?
function a() local b = finale.FCTextExpressionDefs() b:LoadAll() for c in each(b) do local d = e(c) f(c, d) local g = d:CreateName(); fixCase(c, g.LuaString) -- does b + g make sense? end b:SaveAll() end
None of the variables make any sense! a, b, c, d, e, f, g. They don’t describe anything at all. We don’t even know which data type each variable is. Like if at the end of the loop, we don’t know if we can add b and g.
Instead, make sure they are descriptive. I can’t repeat that enough.
Bad:
local a = {2, 3, 5, 7, 11}
Ok:
local table = {2, 3, 5, 7, 11}
Good:
local numbers = {2, 3, 5, 7, 11}
Great:
local primes = {2, 3, 5, 7, 11}
There are some exceptions. For instance, many variable names in JW Lua have standards. You should use those. We’ll go over how to find them out next week.
And there are some conventions that everyone just follows. Like in FizzBuzz, where we looped using the variable i
, that is actually good practice because i
is the standard way of iterating over a series of numbers. Why? I have no idea. But it’s so engrained in programming that it’s actually become good practice.
Variable Names in FizzBuzz
Ok, here’s a slightly better version of FizzBuzz to eliminate some of the redundant conditional statements:
for i = 1, 100, 1 do local str = '' if i % 3 == 0 then -- the .. means combine these two strings -- e.g., 'Fizz' .. 'Buzz' becomes 'FizzBuzz' -- technical term: concatenation str = str .. 'Fizz' elseif i % 5 == 0 then str = str .. 'Buzz' end if str == '' then print(i) else print(str) end end
It also let’s us add the “Zazz” rule quite easily:
for i = 1, 100, 1 do local str = '' if i % 3 == 0 then str = str .. 'Fizz' elseif i % 5 == 0 then str = str .. 'Buzz' elseif i % 7 == 0 then str = str .. 'Zazz' end if str == '' then print(i) else print(str) end end
Very little code has changed. However, we can make it easier to understand with changing a simple variable name:
for i = 1, 100, 1 do local output = '' if i % 3 == 0 then output = output .. 'Fizz' elseif i % 5 == 0 then output = output .. 'Buzz' elseif i % 7 == 0 then output = output .. 'Zazz' end if output == '' then print(i) else print(output) end end
There! Instead of describing the type of the variable as a string, we described its contents as the output. So anyone reading through the code will already know that the variable output
is the output by the second line of code. Much better than waiting until the end.
Indenting
You may have noticed that we often indent. But when, where, and why?
In programing, there’s a concept called code blocks. They are simply put blocks of code. And code blocks always get indented.
Anything inside one of these is a code block and should be indented:
- For loops
- If statements
- Functions (Coming up next)
We indent to give each other a visual cue that we are in a new code block. Nested code blocks get indented again.
Indents are always 4 spaces, not tabs.
There are some programmers who use 2 spaces, 8 spaces, or tabs, but the industry standard is 4 spaces. We use spaces because some languages and editors don’t correctly recognize tabs, and 4 because it’s enough to get the point across but not too much that the code becomes unreadable.
Take a look at the messy code I wrote from above:
function a() local b = finale.FCTextExpressionDefs() b:LoadAll() for c in each(b) do local d = e(c) f(c, d) local g = d:CreateName(); fixCase(c, g.LuaString) -- does b + g make sense? end b:SaveAll() end
And compare it to proper indentation:
function a() local b = finale.FCTextExpressionDefs() b:LoadAll() for c in each(b) do local d = e(c) f(c, d) local g = d:CreateName(); fixCase(c, g.LuaString) end b:SaveAll() end
Which one is easier to read at a glance?
Go ahead and fix your FizzBuzz code to have proper indentation if you didn’t use immaculate indentation already. (All the examples shown so far of FizzBuzz code use proper indentation)
Functions
One of the basic philosophies of well written code is to never repeat the same code twice. While there are definitely exceptions to this rule, it’s a good philosophy to follow.
Of course, when we have a FizzBuzz solution like this…
for i = 1, 100, 1 do local str = '' if i % 3 == 0 then str = str .. 'Fizz' elseif i % 5 == 0 then str = str .. 'Buzz' elseif i % 7 == 0 then str = str .. 'Zazz' end if str == '' then print(i) else print(str) end end
…it’s hard to see how to reduce duplicate code. Well, the answer is with functions.
Just like in math where you can define a function f(x) = 2x, we can define functions in Lua to reduce duplicate code.
function f(x) return 2 * x end print(f(2)) -- output: 4 print(f(4)) -- output: 8 print(f(-2.71828)) -- output: -5.43656
In the general form:
function name(parameters, second_parameter, third, etc) -- any code you want to run return output end
Not all functions need an output, and you can have as many parameters as you want. But you can only have 1 return statement (which means 1 output). There’s more to be said in the footnotes, but that’s the basics.
With functions, we can basically replace any piece of repeated code with a function call. Here’s one way:
function fizzBuzzTest(i, n, str) if i % n == 0 then return str end return ‘’ end for i = 1, 100, 1 do local str = ‘’ str = str .. fizzBuzzTest(i, 3, ‘Fizz’) str = str .. fizzBuzzTest(i, 5, ‘Buzz’) str = str .. fizzBuzzTest(i, 7, ‘Zazz’) if str == ‘’ then print(i) else print(str) end end
As you can see, the actual piece of working code (i.e., the for loop) is a lot easier to read than before. And we can easily add in new values and terms without having to deal with a complicated if-else chain. Crucially, we can start to see how further abstracting the code with functions can make it easier to work with.
function fizzBuzzTest(i, n, str) if i % n == 0 then return str end return ‘’ end function fizzBuzz(first, last, increment) for i = first, last, increment do local str = ‘’ str = str .. fizzBuzzTest(i, 3, ‘Fizz’) str = str .. fizzBuzzTest(i, 5, ‘Buzz’) str = str .. fizzBuzzTest(i, 7, ‘Zazz’) if str == ‘’ then print(i) else print(str) end end end fizzBuzz(1, 100, 1)
Now, we have a function we can call from anywhere in the code! That means, if we want to use fizzBuzz again, we no longer need to copy the for loop. We can even start and end with different numbers.
Next Steps
Now there’s still more work we can do to make the code better. We still have some repeated code:
str = str .. fizzBuzzTest(i, 3, ‘Fizz’) str = str .. fizzBuzzTest(i, 5, ‘Buzz’) str = str .. fizzBuzzTest(i, 7, ‘Zazz’)
And there’s no good way to modify the terms we use from the function call. Or add new terms. And there’s probably some defaults that we’re going to want most of the time, but not all of the time. That’s where the homework comes in.
Sure, sometimes the code may become longer. And that’s ok. Remember, good code is about:
- Readability
- Editability
It’s not about length. Yes, if you can make the code shorter, that can help with readability. But abstraction through functions almost always makes your code easier to use.
Just a heads up, this week’s homework is going to be a step up from the previous weeks’ homework. I believe you can do it!
Homework
- Make the code below into a function with relevant arguments (start, end, power). Remember to use as many of the best practices we’ve gone over.
for i = 1, 50, 1 do print(2 ^ i) end
- In the US, we use tax brackets to calculate our taxes. For the sake of this problem, we’re going to simplify the brackets a bit. We are going to have the following brackets:
- Under $10,000: no tax
- $10,000-29,999: 15% tax
- $30,000-99,999: 30% tax
- $100,000 or more: 44% tax
This means that if you make 121,000 thousand dollars you pay $33,239.55 in taxes.
10,000 * 0% + 19,999 * 15% + 69,999 * 30% + 21,000 * 44% = $33,239.55
Write a function which accepts an annual income and returns the amount of taxes owed. Remember, the IRS has different brackets each year. So make it as editable as possible.
- Fix the errors in the FizzBuzz code we had above. Eliminate the redundant code. Make some of the arguments optional (see homework). And make it so we can add new words easily from the function call. (hint: you’re going to want to use tables)
- Write the worst version of FizzBuzz you can. Make sure it still accomplishes the original task, but make it as uneditable and unreadable as possible. Get creative! And do post your answer to this one in the comments.
Footnotes
- Why use “output” and not “output_str”?
- Everything you need to know about functions (sort of)
- Optional Arguments
- Different types of comments
- Why you should not use comments
Next week is going to answer the question you’ve probably had on your mind for a while: “how do you know what methods and objects to use”?
Luckily, Jari spent the time to create an amazing resource called the JW Lua Class Browser. And you’ve finally gotten all the knowledge you need to fully understand everything in the Class Browser.
It’s going to be one of the most important articles on JW Lua, if not the most important one. So make sure to definitely check it out. And because next week there will be a lot of information, make sure you definitely make sure you’re caught up on all the homework and footnotes for this week and the last two. As always, feel free to leave questions or potential homework solutions in the comments below. I look forward to reading them!
Nick
P.S, I want to take a moment to thank for all the work he’s put into JW Lua. It’s an awesome resource and we should all give him the credit he deserves.
Nick Mazuk is a composer and trombonist in Los Angeles. He mainly freelances by creating arrangements and original compositions for orchestra. In Finale, he specializes in automating tasks so he can achieve great results fast. Nick’s YouTube channel provides more tips and tricks for speeding up your Finale workflow.