Learning to Code in JW Lua | Part 4: Best Coding Practices

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

  1. 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
  1. 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.

  1. 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)
  2. 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

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.