Writing better code

So my code is starting to get messy and I’ve been visiting the clean code references Ignatz sent me.

I’ve seen a lot of people comment that local variables are preferable to global for optimisation and readability purposes, but I have difficulty using them properly in practice. I’m sure there’s a Codea trick I need to learn.

Often when I’m using variables it’s to animate something. So x goes up by 1 each draw cycle say. My problem is that I can’t see where I can place my initial local variable definition in such a way that it won’t reset each time the program cycles through. At first I thought that maybe I could set the local variable up using “if local x_dist = nil then x_dist = 500”, say, but that doesn’t seem to work either. Is there a trick to this?

Also: the reference Ignatz sent me says that functions should do one thing well rather than lots of things messily. Makes sense but I find it difficult to split up my functions because any local variables needed for an overall process are then lost to the split out mini-function, unless I pass them all to that mini function, in which case I seem to be breaching the best practice principle that functions shouldn’t have too many arguments. I can’t seem to do right for doing wrong!

So far, the only tip I’ve managed to implement successfully is better naming conventions, which is already a massive help, but the code I write consists of sprawling nested conditions and I can’t help but feel I’m doing it wrong. If you want an example of the sort of code I write, look at my map generator on the code sharing board! So many nests I could start an aviary.

Any suggestions?

[edited to correct typo]

Variable scope

If something increases with each draw cycle, it needs to be global rather than local

We usually initialise global variables in setup or an initialisation function, eg

function setup()
   counter=0
end

function draw()
   counter=counter+1
   --etc
end

PS There is a trick for setting variables up

--if variable doesn't exist, (create it and) set it to 0
a = a or 0

This trick is good for functions with lots of parameters. You can say something like this, to set defaults, where the user hasn’t provided values

function Calculate(a,b,c)
    a=a or 1 --defaults to 1 if a not provided
    b = b or "aaa"
    c=c or {}
end

@epicurus101 - In general, it’s OK to create a mess first time through while you are solving the problem (especially for us hobbyists). Then you “refactor” to clean up.

Some suggestions on your map code

Avoid hard coding

You’ve hard coded a lot of numbers, eg the loop start and end values like

for y=16,35 do

Better is to (say) create a Settings function where you define variables for all these numbers. Then if you ever want to redesign or tweak the numbers, it’s dead easy to do.

Avoid duplication

Your tablegen function has a lot of duplication of this code, only the x and y items in square brackets change

if math.random(1,100) <= consolprob then
    bigmap[x][y+1] = 3
elseif math.random(1,100) <= (branchprob/2) then
    bigmap[x][y+1] = 2
else
    bigmap[x][y+1] = 1
end

Then there is a variation on this code that only uses consolgen.

This leads me to suggest this function, which works for the first set of cases that use branchprob and consolprob, and the second set, which depend only on consolprob.

function AddNode(X,Y,consolprob,branchprob)
    if math.random(1,100) <= consolprob then
        bigmap[X][Y] = 3
    else
        if branchprob and math.random(1,100) <= (branchprob/2) then
            bigmap[X][Y] = 2 --only used if branchprob provided
        else
            bigmap[X][Y] = 1
        end
    end
end

which means you can reduce that section of tablegen to [unchecked]

for y=16,35 do
    for x=1,61 do
        local consolprob = (math.abs(x-31) * 3) + (2 *y) + (branchprob*1.5)
            if bigmap[x][y] == 1 then AddNode(x,y+1,consolprob,branchprob)
            elseif bigmap[x][y] == 2 then 
                AddNode(x-1,y+1,consolprob,branchprob)
                AddNode(x+1,y+1,consolprob,branchprob)
            elseif bigmap[x][y] ==3 then
                if x>31 then AddNode(x-1,y+1,consolprob)
                elseif x<31 then AddNode(x+1,y+1,consolprob)
                else bigmap[x][y+1] = 1
                end
            end
        end
    end
end

(check I didn’t make a mistake)

You can see that by doing this, your code becomes much more readable and it is much easier to make changes. And all I’ve done is look for repetitive patterns of code, and figured out a function that handles them.

As you’ve noted, if you use local variables with other functions, you have to pass them through as parameters, as I’ve done above.

But don’t get too hung up on local variables. Most of the time, speed is not crucial, after all, you aren’t generating maps 60 times a second, are you? But there is a second reason for local variables, and that is to avoid accidentally using the same global variables twice and overwriting them. A good way to avoid this is to wrap all your maze code (including functions) in a table, like so

Map={}

--run this to set the map up
function Map.Setup(--list all the setup parameters in here--)
    --now define global variables within the Map library
    --these can be seen and used by *any* code in this project
    --but because of the Map prefix they won't collide with
    --any variable names used elsewhere
    Map.Width = w or 50 --assuming w is a parameter
    Map.MaxLength=m or 100
    -- etc
    Map.tablegen()
end

function Map.tablegen()
    --code ---
    Map.branchprob= --formula here
    --(this is a bit long, so you might use a shorter local name inside tablegen,
    --but define Map.branchprob as above, so other functions can get the 
    --value without having to pass it as a parameter)
    --code--
    AddNode(x,y) --no need to pass consolprob!
   --code--
end

--now AddNode can use the "global" value of consolprob
function AddNode(X,Y)
    if math.random(1,100) <= Map.consolprob then
end

EDIT - I meant to include a link to this post, which explains the value of wrapping things like map code in tables

https://coolcodea.wordpress.com/2013/04/12/31-a-lesson-from-the-roller-coaster-demo/

Thanks Ignatz. I tried yesterday to clean up my map generator code as an exercise, but I got in a complete mess because I tried to improve too many things at once (including adding some parameters for map size etc…), and lost track. The lesson I learnt was to rewrite a bit at a time, and keep checking that I’m not messing anything up.

Your default trick gave me an idea, which I really thought might work for my local variable “problem” (which I realise isn’t much of a problem). But it doesn’t so maybe you are able to diagnose why it doesn’t work.

What if you put in the draw function:-

local y = y or 30 – this sets local y to 30 on the first cycle, right?
ellipse(400, y, 10) – first time round it’s drawn at 400,30
y = y + 1 – next time round shouldn’t it be at 400,31 etc?

So I’m expecting movement. But my dot doesn’t move!

Does the local y value get discarded each time the routine cycles, even within the chunk?

I promise to stop being so obsessed with local variables once I really get how they work. The problem with all the lua resources out there is that I think the way Codea draws stuff on screen makes it sufficiently different that these nuances don’t come across

No, because you are defining it as local, so when draw ends, y is deleted. So y needs to be global.

local applies only within each chunk. A for loop is a chunk, so is an if statement, so local is very local indeed.

a=1 --global variable

function A()
    --a=1 at this stage
    local a=3
    --now a=3
    for i=1,10 do
        local a=4
        --a is now 4
    next i
    --a is back to 3 again
    if q>3 then
        a=2
        --a is 2
    end
    --a is 3
end
--a is 1

You can use local variables in draw, but to keep their state between cycles then they need to be local to the tab, not to the function:

local x
function setup()
    x=0
end

function draw()
    x=x+1
end

This increments x on each draw cycle, but it is a local variable. In particular, in another tab you wouldn’t have access to it.

@LoopSpace very good and obscure information

this works because a tab is treated like a “chunk” of code, so variables can be made local within a tab

I see how it works, I just never really thought about it that way :stuck_out_tongue: it’s one of those obscurities in code I’m sure many forget about

It just takes practice. The more you use things like that, the sooner it becomes natural.

That’s extremely helpful and deals exactly with my issue! Thanks Loopspace!

@LoopSpace and make sure to have your local variables at the very top of the tab, they won’t be recognized if they’re at the bottom.

@SkyTheCoder Well, you have to declare them local before they are used. But it’s okay if they aren’t used until the bottom to wait until then before declaring them. I often do that if a variable is to be used in a particular function I’ll declare it just before defining that function rather than put them all at the top.

@epicurus101 - it might be worth having a look at the thorny topic of ‘Code Smells’ in respect to software development ( http://en.wikipedia.org/wiki/Code_smell for an overview )

As odd as it sounds, this may well give you some further insights of common pitfalls and traps coders tend to fall into - especially as the code base expands without a clear structure.

Don’t worry, I think this is fairly common, and my stuff often ‘stinks’ as badly as most other peoples until a few rounds of refactoring kick in. :wink:

@andymac3d I looked at the link for CodeSmells and I guess when I write code, my code really stinks. There are several things that I do wrong. I tend to use 1 or 2 character variable names that don’t resemble what they’re used for. I’ll use duplicated code. Use of literals instead of named constants. Too many loops. I tend to ignore the use of “local” and I use too many globals. Usually if the code is long, I’ll go thru it and fix the “smells”. I’ll rename variables to proper names. I’ll break up long code or duplicate code into functions. I’ll give literals constant names if they are used in several places. But then I’m a lazy coder, so a lot of times I won’t. Since my code just ends up here, I leave it up to whoever uses it to fix it if they want.

@dave1707 - I think your code is fine for showing relative beginners how to do stuff inside a page of code.

It’s when they launch into bigger projects that they need more structure. This is easier if they have learned to structure their smaller projects. But beginners (and especially young, impatient beginners) don’t see the need for a lot of structure in their small programs.

So I think we’ll always have this to and fro between just doing it, and doing it right. Personally, I think the right time to teach structure is when the beginner starts to realise that you can’t scale a mess.

@Ignatz It would be nice for new coders to learn structure, but a lot of them seem to have trouble learning to code. For just playing around, structure doesn’t matter. It’s not that important unless they have plans on becoming a professional. I think a more important thing is for them to format the code so it’s easier to read and see mistakes. A lot of times when someone PM’s me their code and wants to know why it doesn’t work, I spend a lot of time formatting it and end up finding they have an “end” statement in the wrong place. If they format it themselves, they would see the error.

This is a really useful thread +1 to @epicurus101 for starting it.

If you really want to improve your code check out the (free online) book “Clean Code” (https://www.ufm.edu/images/0/04/Clean_Code.pdf) - although as with most things you can go so far overboard on this. What @Ignatz said about “us hobbyists” is true - you need to find your own balance in this, but kudos for you realising that you could always do better and actually taking action to do it!

As for “code smell” the “stinkiest” code I ever “smelt” was an Win32/MFC app that I inherited from a previous developer to fix / maintain. After trying (or more accurately failing) to write his own “owner draw” library, the entire app was contained in a single dialog box class (Dlg.cpp), the file itself was over 100,000 lines long and the ::onPaint() method (single function) was over 2,000 lines long. The app actually was used to generate several different apps depending on the build targets (and remembering to copy in a new set of resources) - it was so bad that every Win32 control was lumped into a single box and was dynamically repositioned (and repurposed) for each “screen”, you couldn’t edit a single thing and what was an OK button on one screen was a file dialog open on another etc etc.

Suffice to say that was a lesson in how “not” to write code and not an experience I’d like to repeat - although you’d be amazed (and disheartened) to see just how common code like that is out in the wild. :frowning:

Hi @epicurus101,

There is a best practice thread here, I’m biased, I started it:

http://codea.io/talk/discussion/3494/coding-best-practices/p1

As a general rule, given any level of complexity it is best not to do any real work in setup() or draw() or touched() except to delegate the functionality to class instances or static table objects which can hold their own state…

I’ve written a paper on ‘Writing complex Codea apps’ which im hoping to distribute shortly…

Brookesi

If you’re writing a complex Codea app, you may be interested in my Complex library which implements the complex numbers in Codea.