30 June 2019

TT's generic programming guidelines

Declare your local variables close to where they're used

Do not declare all your variables at the top. Instead, declare them where you use them first. That makes it easier to see their type and also leads to better locality, avoiding accidental use of stale (old) values.

Bad style:

var i as integer
var item as FileReference
var files() as FileReference

for i = 1 to directory.Count
    item = directory.Item(i)
    if item.isRegularFile then
        files.Append item
    end
next

Better style:

var files() as FileReference

for i as integer = 1 to directory.Count
    var item as FileReference = directory.Item(i)
    if item.isRegularFile then
        files.Append item
    end
next

Working with Booleans

Naming

Functions, properties and variables of boolean type should be named so that, when reading them in a statement, end up with proper grammar.

Examples:
  • function isHidden() as boolean
  • var hasVisibleItems as boolean

Avoid testing for true & false

It isn't good style to write boolean tests like this:
if hidden = true then ... // bad style
nor:
if hidden = false then ... // bad style
Instead, along with the naming rule above learn to make it into a proper sentence:
if isHidden then ... // good style
and:
if not isHidden then ... // good style

Keep function code concise

Avoid having long functions. Ideally, a function (subroutine) should have no more than 20-40 code lines, so that it's easy to look at in a single editor page, without scrolling.

If the code gets longer, try to split it up into subroutines, even if those subroutines only get called once by your shortened main function. And if you manage to name these subroutines in a good way, so that each subroutine's name expresses clearly what it does, your main function will be quite self-explanatory about what it does, without the need for comments.

Exit early, avoiding nested if / else constructs

Compare these alternative ways to code the same result (which is to collect hidden files into an array, and returning errors otherwise).

Convoluted version (sub-optimal):
if fileReference.isValid then
    if fileReference.isDirectory then
        if fileReference.isHidden then
            hiddenFiles.Append fileReference
            return true
        else
            return false
        end
    else
        return false
    end
else
    return false
end
Alternative, better readable, version:
if not fileReference.isValid then
    return false
end
if not fileReference.isDirectory then
    return false
end
if not fileReference.isHidden then
    return false
end
hiddenFiles.Append fileReference
return true

The latter version is not necessarily shorter - it may even be longer. But by sorting out all the "bad" cases first, it makes it clear what's left over in the end. This flow of control is easily to oversee because you do not have to scan for the else parts that may be coming for each if.

Avoid code duplication

Whenever you have the urge to copy some code and modify it for a similar purpose, don't do it! (There are always exceptions, of course.)

Duplicated code can easily lead to mistakes later when you find an issue with it and only end up changing the one case where you see the issue but then forget to also update the other copies of the code that perform similar operations and need to get fixed as well.

Instead, instead of copying lines and then making small changes to the copied lines, share the same code by putting them into a subroutine, and if there are different details to perform for one or the other, add a parameter that will tell the subroutine what to do. What way, most of the code will be the same and doesn't need to be duplicated.

More on this: DRY

Avoid getting the same values repeatedly

Consider this code that iterates over values in an array:
for i as integer = 0 to items.count-1
    if items(i).isSelected then
        addToResult (items(i))
    end
next
Note that items(i) is fetched twice here. Avoid doing that. Not only may it make your code slower, it also can make it more difficult to alter your code later. Instead, use a local variable to fetch the value and use that, even if that makes the code sligtly longer:
for i as integer = 0 to items.count-1
    var item as MyItemType = items(i)
    if item.isSelected then
        addToResult (item)
    end
next

Document what you want to achieve in your code

If you write code to implement any kind of algorithm, such as searching for matching words in a text (string), don't add comments about the obvious that your code does. Instead, write comments that explain what the code is supposed to do. E.g, explain the algorithm you want to write as code. That way, if you make a mistake in your code, you or someone else can later understand what was planned to be happening in the code, and can fix the code accordingly.

If you do not explain the intent of the code, then it is difficult later to understand if an unexpected result is a bug or an intentional behavior, and when that happens, people tend to not touch the questionable code and instead add another copy of the code which behaves slightly different (with the bug fix in it), and soon you have a mess of duplicate code that will lead to a difficult-to-maintain overall project.

If in doubt, ask your fellow programmer who wrote the original code about the intention and then add the documentation once it's clear. Then add your fix and maybe also add a comment why you fixed it that way, explaining how it changes old behavior.

Document the behavior of a function

If you write a function, it can also be helpful that you write in plain english what you expect the passed parameters to contain and what the result will be. Also: What will happen if they do not meet the expections?

For example, if you write a function that returns the index of the occurance of a string in an array of strings, explain what happens if the item is not found at all (you could return -1, the index past the last item or raise an exception), and what happens if there are multiple possible results (will it return the index of the first or of the last occurance, or will it even do something else to indicate that the result is ambiguous).

Avoid side effects

Functions should not have side effects, i.e. they should not modify state outside of their scope. The reason for this is that such side effects are hard to detect and thus difficult to debug.

For instance, if you make a function that calculates something and returns a result, that function should not also change some properties or static vars unless they're solely meant for that function (such as for caching).

If you want to maintain state, pass the variables/objects for keeping the state as (inout, byref) parameters to the function, so that the caller knows that a state has changed, and can decide where to store it.

Use a version control system for tracking each of your changes

Use git or something like that, and commit your changes frequently, even if you work all by your own.

For instance, if you are fixing a bug, commit that fix as a single commit, so that, if one later suspects that your bug fix was causing new problems, the particular commit can be undone easily to check whether that changes the result of the new problem.

More

See this older article of mine on some more guidelines