Difference between revisions of "Blitz:Good Practice"

From Amiga Coding
Jump to: navigation, search
(Check for Success)
m
 
Line 3: Line 3:
 
===Indent Your Code===
 
===Indent Your Code===
 
Indenting blocks of code helps keep the code very easy to read and helps avoid errors and bugs caused by things like Else statements associated with the wrong If statement for example. Procedures, loops, If/Then/Else and Select blocks, all benefit greatly from being indented, especially when nesting several layers deep. For example:
 
Indenting blocks of code helps keep the code very easy to read and helps avoid errors and bugs caused by things like Else statements associated with the wrong If statement for example. Procedures, loops, If/Then/Else and Select blocks, all benefit greatly from being indented, especially when nesting several layers deep. For example:
<code>If AddItem(a())
+
<pre>If AddItem(a())
 
For i=1 To 10
 
For i=1 To 10
 
If i=2
 
If i=2
Line 15: Line 15:
 
Else
 
Else
 
NPrint "Unable to add item!"
 
NPrint "Unable to add item!"
End If</code>
+
End If</pre>
 
Compared with:
 
Compared with:
<code>If AddItem(a())
+
<pre>If AddItem(a())
 
   For i=1 To 10
 
   For i=1 To 10
 
     If i=2
 
     If i=2
Line 29: Line 29:
 
Else
 
Else
 
   NPrint "Unable to add item!"
 
   NPrint "Unable to add item!"
End If</code>
+
End If</pre>
  
 
===Comment Your Code===
 
===Comment Your Code===
 
Comments are very important for being able to read your code at a later date. When writing your code, they may not seem important and it will probably work fine without them, but when you have a complex program with lots of different sections, it can quickly become confusing, especially with so-called "magic numbers" and arguments whose functions aren't immediately obvious. For example:
 
Comments are very important for being able to read your code at a later date. When writing your code, they may not seem important and it will probably work fine without them, but when you have a complex program with lots of different sections, it can quickly become confusing, especially with so-called "magic numbers" and arguments whose functions aren't immediately obvious. For example:
<code>x = 27
+
<pre>x = 27
 
Dim y(x)
 
Dim y(x)
y(2) = 200</code>
+
y(2) = 200</pre>
 
Compared with:
 
Compared with:
<code>x = 27 ; Initial number of enemy ships
+
<pre>x = 27 ; Initial number of enemy ships
 
Dim y(x) ; Scores for each ship are held here
 
Dim y(x) ; Scores for each ship are held here
y(2) = 200 ; Ship 2 is the big one so has 200 points</code>
+
y(2) = 200 ; Ship 2 is the big one so has 200 points</pre>
  
 
===Keep Lines Simple===
 
===Keep Lines Simple===
 
It can be tempting to put multiple statements on one line, especially in the case of If statements, and if most of your BASIC programming has previously been on 8-bit machines this might even have been encouraged. But there's no real memory benefit in doing so since your program is compiled rather than interpreted, and the compiler just sees the code as the same. So instead of:
 
It can be tempting to put multiple statements on one line, especially in the case of If statements, and if most of your BASIC programming has previously been on 8-bit machines this might even have been encouraged. But there's no real memory benefit in doing so since your program is compiled rather than interpreted, and the compiler just sees the code as the same. So instead of:
<code>If score >= 200 Then winner = currentplayer:gameover = True:Gosub gamewon:Gosub resetgame</code>
+
<pre>If score >= 200 Then winner = currentplayer:gameover = True:Gosub gamewon:Gosub resetgame</pre>
 
This is far neater and easier to read:
 
This is far neater and easier to read:
<code>If score >= 200
+
<pre>If score >= 200
 
   winner = currentplayer
 
   winner = currentplayer
 
   gameover = True
 
   gameover = True
 
   Gosub gamewon
 
   Gosub gamewon
 
   Gosub resetgame
 
   Gosub resetgame
End If</code>
+
End If</pre>
 
On 8-bit machines like the Commodore 64 and Atari 800, this would use an extra few bytes of memory. But Blitz will compile both these examples into identical code so the only difference is in readability.
 
On 8-bit machines like the Commodore 64 and Atari 800, this would use an extra few bytes of memory. But Blitz will compile both these examples into identical code so the only difference is in readability.
  
 
===Use Spaces for Readability===
 
===Use Spaces for Readability===
 
Again, on very old 8-bit versions of BASIC, adding extra spaces cost you a few bytes of memory which might become crucial when the program was running. But Blitz Basic, like other modern languages, compiles the code into an executable and so spaces don't impact the finished result at all. So you can use blank lines and extra spaces to make your code easier to read by separating terms and expressions, by separating sections of code with blank lines, and by aligning columns of similar lines. For example:
 
Again, on very old 8-bit versions of BASIC, adding extra spaces cost you a few bytes of memory which might become crucial when the program was running. But Blitz Basic, like other modern languages, compiles the code into an executable and so spaces don't impact the finished result at all. So you can use blank lines and extra spaces to make your code easier to read by separating terms and expressions, by separating sections of code with blank lines, and by aligning columns of similar lines. For example:
<code>#groundcol=3
+
<pre>#groundcol=3
 
#skycol=4
 
#skycol=4
 
#player1col=6
 
#player1col=6
Line 71: Line 71:
 
   End If
 
   End If
 
Until quit
 
Until quit
End</code>
+
End</pre>
 
Compared with:
 
Compared with:
<code>
+
<pre>
 
#groundcol  = 3
 
#groundcol  = 3
 
#skycol    = 4
 
#skycol    = 4
Line 95: Line 95:
 
Until quit
 
Until quit
  
End</code>
+
End</pre>
  
 
===Use Procedures===
 
===Use Procedures===
 
Procedures help to modularise your code, effectively giving independent functions their own little program. This helps reduce bugs and keeps your code readable since blocks of code can be reduced to a single procedure call. For example, including the following at the top of your program:
 
Procedures help to modularise your code, effectively giving independent functions their own little program. This helps reduce bugs and keeps your code readable since blocks of code can be reduced to a single procedure call. For example, including the following at the top of your program:
<code>Statement resetgame{}
+
<pre>Statement resetgame{}
 
   SHARED board()
 
   SHARED board()
 
   For i = 1 To 10
 
   For i = 1 To 10
Line 107: Line 107:
 
   Next i
 
   Next i
 
End Statement
 
End Statement
</code>
+
</pre>
 
Means that instead of including that code in the main part of your program wherever you need it, you can just include the function call:
 
Means that instead of including that code in the main part of your program wherever you need it, you can just include the function call:
<code>If gamewon = 2 Then resetgame{}</code>
+
<pre>If gamewon = 2 Then resetgame{}</pre>
 
There is a slight speed penalty for using procedures instead of Gosubs, but this will only have an impact for procedures called hundreds of times a second.
 
There is a slight speed penalty for using procedures instead of Gosubs, but this will only have an impact for procedures called hundreds of times a second.
  
Line 121: Line 121:
 
===Avoid "Magic Numbers"===
 
===Avoid "Magic Numbers"===
 
"Magic numbers" are literal numbers used in your code, which is fine when you're writing it, but may appear to be random to anyone else or even to yourself at a later date. A good alternative is to substitute a descriptive constant - the compiler will substitute in the actual value during compilation. This will make your code easy to read, and also make it much easier to modify values that might be used more than once in your program. For example:
 
"Magic numbers" are literal numbers used in your code, which is fine when you're writing it, but may appear to be random to anyone else or even to yourself at a later date. A good alternative is to substitute a descriptive constant - the compiler will substitute in the actual value during compilation. This will make your code easy to read, and also make it much easier to modify values that might be used more than once in your program. For example:
<code>Dim entries(100)
+
<pre>Dim entries(100)
  
 
<Some other code here>
 
<Some other code here>
Line 128: Line 128:
 
   result = calculate{entries(i)}
 
   result = calculate{entries(i)}
 
   NPrint "Result ", i, " of 100: ", result
 
   NPrint "Result ", i, " of 100: ", result
Next i</code>
+
Next i</pre>
 
To increase the maximum number of entries your program can use, you would need to search through your code and replace all the 100 values with, say, 200. Instead of this, using a constant means the value only has to be changed once which avoids the risk of forgetting to update the value somewhere:
 
To increase the maximum number of entries your program can use, you would need to search through your code and replace all the 100 values with, say, 200. Instead of this, using a constant means the value only has to be changed once which avoids the risk of forgetting to update the value somewhere:
<code>#maxentries = 100
+
<pre>#maxentries = 100
  
 
Dim entries(#maxentries)
 
Dim entries(#maxentries)
Line 139: Line 139:
 
   result = calculate{entries(i)}
 
   result = calculate{entries(i)}
 
   NPrint "Result ", i, " of ", #maxentries, ": ", result
 
   NPrint "Result ", i, " of ", #maxentries, ": ", result
Next i</code>
+
Next i</pre>
  
 
In particular, building and using a GUI for your program involves a lot of constants and repeated use of constants. While the old GUI examples that are supplied with Blitz Basic and AmiBlitz use plain numbers, it can quickly become tricky and slow to work with just the numbers as your interface and program grow in complexity. Remembering that the Cancel button is gadget ID number 27 isn't as easy as remembering it's #cancel...
 
In particular, building and using a GUI for your program involves a lot of constants and repeated use of constants. While the old GUI examples that are supplied with Blitz Basic and AmiBlitz use plain numbers, it can quickly become tricky and slow to work with just the numbers as your interface and program grow in complexity. Remembering that the Cancel button is gadget ID number 27 isn't as easy as remembering it's #cancel...
Line 145: Line 145:
 
===Type Your Variables Appropriately===
 
===Type Your Variables Appropriately===
 
By default, Blitz automatically assigns the quick type to variables used without any type being specified by your code. Most of the time, however, there are more suitable [[Data types| variable types]] that can be used instead - if you don't need the fractional part of the number for example, the word type hold the same integer value range as a quick, is faster, and only takes up 2 bytes instead of 4. It is good practice to explicitly declare the desired variable type. This can either be done at before first use using the DEFTYPE statement, or by specifying the correct type extension when using the variable for the first time. For example:
 
By default, Blitz automatically assigns the quick type to variables used without any type being specified by your code. Most of the time, however, there are more suitable [[Data types| variable types]] that can be used instead - if you don't need the fractional part of the number for example, the word type hold the same integer value range as a quick, is faster, and only takes up 2 bytes instead of 4. It is good practice to explicitly declare the desired variable type. This can either be done at before first use using the DEFTYPE statement, or by specifying the correct type extension when using the variable for the first time. For example:
<code>DEFTYPE .l MyAddress</code>
+
<pre>DEFTYPE .l MyAddress</pre>
 
Or
 
Or
<code> MyAddress.l = 0</code>
+
<pre> MyAddress.l = 0</pre>
 
Once declared, the variable no longer requires the type extension.
 
Once declared, the variable no longer requires the type extension.
 
In AmiBlitz, it is recommended that you use the Syntax statement to enforce stricter typing rules. Syntax 0 is the default Blitz behavior where no declarations are required, Syntax 1 requires all variables to be declared by DEFTYPE or Dim, and Syntax 2 requires types to be specified either using DEFTYPE or Dim, or on their first usage. Both stricter modes will also help to avoid bugs arising from mistyping variable names - an incorrectly named variable will quietly default to 0 in the default mode, which can be a tricky bug indeed to find! For example:
 
In AmiBlitz, it is recommended that you use the Syntax statement to enforce stricter typing rules. Syntax 0 is the default Blitz behavior where no declarations are required, Syntax 1 requires all variables to be declared by DEFTYPE or Dim, and Syntax 2 requires types to be specified either using DEFTYPE or Dim, or on their first usage. Both stricter modes will also help to avoid bugs arising from mistyping variable names - an incorrectly named variable will quietly default to 0 in the default mode, which can be a tricky bug indeed to find! For example:
<code>fileopen = OpenFile(0, "HighScore.txt")
+
<pre>fileopen = OpenFile(0, "HighScore.txt")
  
 
if flieopen
 
if flieopen
Line 159: Line 159:
 
Else
 
Else
 
   NPrint "Error opening file!
 
   NPrint "Error opening file!
End If</code>
+
End If</pre>
 
With the default loose typing rules, this code will run, but will never save the top score to the file and will always give an error, since ''flieopen'' defaults to 0, even if ''fileopen'' is true from successfully opening the file. Moreover, the file will be held open since the CloseFile command won't be run, which prevents other programs from using the file. Using Syntax 1 or Syntax 2 at the start of your code however will cause that example to generate an error during compilation, allowing you to fix the mistake before it causes problems.
 
With the default loose typing rules, this code will run, but will never save the top score to the file and will always give an error, since ''flieopen'' defaults to 0, even if ''fileopen'' is true from successfully opening the file. Moreover, the file will be held open since the CloseFile command won't be run, which prevents other programs from using the file. Using Syntax 1 or Syntax 2 at the start of your code however will cause that example to generate an error during compilation, allowing you to fix the mistake before it causes problems.
  

Latest revision as of 14:19, 25 January 2018

Like most BASICs, Blitz is reasonably easy to read and understand, especially with syntax highlighting provided by the editor. However, even moderately complicated programs will quickly grow unwieldy and difficult to follow, especially if you come back to the code after not looking at it for some time. It can be difficult to remember exactly what you were thinking when you wrote it which makes it tricky to fix bugs and modify the code at a later date. With a little effort and by following some simple suggestions it's easy to write code that's better looking, more easily readable and less prone to bugs.

Indent Your Code

Indenting blocks of code helps keep the code very easy to read and helps avoid errors and bugs caused by things like Else statements associated with the wrong If statement for example. Procedures, loops, If/Then/Else and Select blocks, all benefit greatly from being indented, especially when nesting several layers deep. For example:

If AddItem(a())
For i=1 To 10
If i=2
b(i)=a()+i
NPrint "Section 2"
Else
b(i)=i
NPrint "Other Section"
End If
Next i
Else
NPrint "Unable to add item!"
End If

Compared with:

If AddItem(a())
  For i=1 To 10
    If i=2
      b(i)=a()+i
      NPrint "Section 2"
    Else
      b(i)=i
      NPrint "Other Section"
    End If
  Next i
Else
  NPrint "Unable to add item!"
End If

Comment Your Code

Comments are very important for being able to read your code at a later date. When writing your code, they may not seem important and it will probably work fine without them, but when you have a complex program with lots of different sections, it can quickly become confusing, especially with so-called "magic numbers" and arguments whose functions aren't immediately obvious. For example:

x = 27
Dim y(x)
y(2) = 200

Compared with:

x = 27 ; Initial number of enemy ships
Dim y(x) ; Scores for each ship are held here
y(2) = 200 ; Ship 2 is the big one so has 200 points

Keep Lines Simple

It can be tempting to put multiple statements on one line, especially in the case of If statements, and if most of your BASIC programming has previously been on 8-bit machines this might even have been encouraged. But there's no real memory benefit in doing so since your program is compiled rather than interpreted, and the compiler just sees the code as the same. So instead of:

If score >= 200 Then winner = currentplayer:gameover = True:Gosub gamewon:Gosub resetgame

This is far neater and easier to read:

If score >= 200
  winner = currentplayer
  gameover = True
  Gosub gamewon
  Gosub resetgame
End If

On 8-bit machines like the Commodore 64 and Atari 800, this would use an extra few bytes of memory. But Blitz will compile both these examples into identical code so the only difference is in readability.

Use Spaces for Readability

Again, on very old 8-bit versions of BASIC, adding extra spaces cost you a few bytes of memory which might become crucial when the program was running. But Blitz Basic, like other modern languages, compiles the code into an executable and so spaces don't impact the finished result at all. So you can use blank lines and extra spaces to make your code easier to read by separating terms and expressions, by separating sections of code with blank lines, and by aligning columns of similar lines. For example:

#groundcol=3
#skycol=4
#player1col=6
#player2col=7
#enemycol=8
Dim enemies(5)
For i=1 To 5
enemies(i)=0
Next i
Gosub resetgame
Repeat
  Gosub checkplayer
  If score>200 AND player=1
    quit=True
  End If
Until quit
End

Compared with:

#groundcol  = 3
#skycol     = 4
#player1col = 6
#player2col = 7
#enemycol   = 8

Dim enemies(5)

For i = 1 To 5
  enemies(i) = 0
Next i

Gosub resetgame

Repeat
  Gosub checkplayer
  If score > 200 AND player = 1
    quit = True
  End If
Until quit

End

Use Procedures

Procedures help to modularise your code, effectively giving independent functions their own little program. This helps reduce bugs and keeps your code readable since blocks of code can be reduced to a single procedure call. For example, including the following at the top of your program:

Statement resetgame{}
  SHARED board()
  For i = 1 To 10
    For j = 1 To 10
      board(i, j) = 0
    Next j
  Next i
End Statement

Means that instead of including that code in the main part of your program wherever you need it, you can just include the function call:

If gamewon = 2 Then resetgame{}

There is a slight speed penalty for using procedures instead of Gosubs, but this will only have an impact for procedures called hundreds of times a second.

Name Things Well

Variables, labels, procedures and constants should all be named well. Again, if your only experience of BASIC is from 8-bit systems, you could be tempted to call your first variable a, your second b and so on. With effectively no affect on memory required by your program, it makes much more sense to give your variables short descriptive names instead. Capitals and underscores can be included to help readability. For example, MyScore, MyLives and CurrentEnemy will all make your program much easier to read than a, b and c. Likewise, function names like ResetTable{} and LoadPrefs{} make much more sense than F1{} and F2{}. Giving things names that are too long won't help matters either so it's best to find something maybe between 1 and 3 words in length.

Use Includes

Where possible, use the AmiBlitz include files instead of the built-in Blitz functions for operating system interaction. The include files are regularly updated and fixed, and use the official system-friendly methods for their workings. Of course, if you're programming a game and banging the hardware you won't be worried about the OS...

Avoid "Magic Numbers"

"Magic numbers" are literal numbers used in your code, which is fine when you're writing it, but may appear to be random to anyone else or even to yourself at a later date. A good alternative is to substitute a descriptive constant - the compiler will substitute in the actual value during compilation. This will make your code easy to read, and also make it much easier to modify values that might be used more than once in your program. For example:

Dim entries(100)

<Some other code here>

For i = 1 To 100
  result = calculate{entries(i)}
  NPrint "Result ", i, " of 100: ", result
Next i

To increase the maximum number of entries your program can use, you would need to search through your code and replace all the 100 values with, say, 200. Instead of this, using a constant means the value only has to be changed once which avoids the risk of forgetting to update the value somewhere:

#maxentries = 100

Dim entries(#maxentries)

<Some other code here>

For i = 1 To #maxentries
  result = calculate{entries(i)}
  NPrint "Result ", i, " of ", #maxentries, ": ", result
Next i

In particular, building and using a GUI for your program involves a lot of constants and repeated use of constants. While the old GUI examples that are supplied with Blitz Basic and AmiBlitz use plain numbers, it can quickly become tricky and slow to work with just the numbers as your interface and program grow in complexity. Remembering that the Cancel button is gadget ID number 27 isn't as easy as remembering it's #cancel...

Type Your Variables Appropriately

By default, Blitz automatically assigns the quick type to variables used without any type being specified by your code. Most of the time, however, there are more suitable variable types that can be used instead - if you don't need the fractional part of the number for example, the word type hold the same integer value range as a quick, is faster, and only takes up 2 bytes instead of 4. It is good practice to explicitly declare the desired variable type. This can either be done at before first use using the DEFTYPE statement, or by specifying the correct type extension when using the variable for the first time. For example:

DEFTYPE .l MyAddress

Or

 MyAddress.l = 0

Once declared, the variable no longer requires the type extension. In AmiBlitz, it is recommended that you use the Syntax statement to enforce stricter typing rules. Syntax 0 is the default Blitz behavior where no declarations are required, Syntax 1 requires all variables to be declared by DEFTYPE or Dim, and Syntax 2 requires types to be specified either using DEFTYPE or Dim, or on their first usage. Both stricter modes will also help to avoid bugs arising from mistyping variable names - an incorrectly named variable will quietly default to 0 in the default mode, which can be a tricky bug indeed to find! For example:

fileopen = OpenFile(0, "HighScore.txt")

if flieopen
  Fileoutput 0
  NPrint topscore
  CloseFile 0
  DefaultOutput
Else
  NPrint "Error opening file!
End If

With the default loose typing rules, this code will run, but will never save the top score to the file and will always give an error, since flieopen defaults to 0, even if fileopen is true from successfully opening the file. Moreover, the file will be held open since the CloseFile command won't be run, which prevents other programs from using the file. Using Syntax 1 or Syntax 2 at the start of your code however will cause that example to generate an error during compilation, allowing you to fix the mistake before it causes problems.

Check for Success

Many statements in Blitz return a value that can tell you whether a command was successful or not. Where possible it's a good idea to check this value so that your program can handle unexpected situations, since blindly assuming it was successful can result in crashes and other nasty things. For example, opening a window might fail due to lack of RAM, lack of graphics RAM, no public screen available etc. If that happens, it would be much better for your program to quit (preferably with a decent error message), than to carry on assuming it opened. Trying to draw graphics to a window that doesn't exist is pretty much guaranteed to crash your machine.