Bottom     Previous     Contents

Chapter Seven
Making Programs Work

So far we have tackled the subject of advanced programming from the programmer's point of view. However, there are people other than programmers interested in programs. In particular, it is all too easy to forget the intended user, especially when the program is in some way difficult or ingenious. A program that isn't used is a wasted effort and anything that you can do to encourage people to use and trust your programs is well worth the effort.
Users are influenced by the external features of your program, that is, what it does and how it goes about it. Thus, in addition to good internal structure, a program must be reliable and convenient to use. Unfortunately there is no programming method that will guarantee that a program is reliable and convenient to use. Using modular and structured programming certainly helps in reducing the number of bugs in a program that result from confusion over how the program works. It also makes the location and elimination of bugs easier. But unless you spend time detecting the presence of bugs by testing the program, it is likely to be unreliable.
In some ways the main benefit of using the sort of programming methods described earlier m thus book is that they give you the time to concentrate on other more demanding aspects of programming. Instead of making hard work of producing a program that is only just adequate, a programming method should free you to consider overall program design and reliability. This is an important shift in emphasis, as good design and reliability are the areas where programming skill really counts. However, while it is true that there is no programming method or system that will guarantee well designed reliable programs, this doesn't provide a reason for ignoring the problem or for taking a sloppy approach. There are principles of programming testing and debugging that will ensure that the time that you do spend on program reliability is effective. There are also ways of evaluating the success of your program in terms of ease of use, and then improving it in the light of this evaluation. All in all, it seems fair to sum up the current situation by saying that the art and craft of programming lies in the testing and debugging of programs, and is measured by the ease of use of the resulting products.

The natural history of bugs

Bugs - misbehaviour of programs - are a sad fact of the programmer's and computer user's life. Only the very simplest programs can be said to be 'bug-free' and even then such statements usually only have the force of a 'hope' rather than an assertion of fact. To say that a program is bug-free usually means nothing more than no program misbehaviour has been observed for a 'period of time'. Of course as the 'period of time' increases the level of confidence in the statement that a program is bug-free also increases, but it never reaches certainty. (Theoretically there arc methods of proving that a program is correct based on Boolean logic but cum ently there is nothing of any practical value.)
The state of the art in reliable programming is that after generous testing one can say that the number of bugs remaining is likely to be small and that they are likejy to be relatively unimportant. It has been said that if the products of other branches of engineering were as unreliable as a typica] program. bridges would be falling down as fast as they could be built and motor cars would need servicing once a day! This may seem a very gloomy picture to paint at the start of a chapter that deals with program reliability but the point is that most programs are not tested and debugged at at! well. Aithough it is impossible to claim that all the bugs have been removed from a program, it is certainly possible to improve on the current situation. It is the typically undertested program of today that has caused programming to be categorised as the most unreliable branch of engineering! It is important not to despair because the goal of a bug-free program cannot be attained with certainty; what matters is that a program is refiable enough for failure to be the rare exception rather than the regular rule!
All this discussion of the inevitability of bugs doesn't really provide any idea of the type of failures that can occur. As a program is nothing more than a very precise set of instructions it is subject to the same types of failures that are found in written English. In general BASIC bugs fall into three categories:

(1) Incorrect use of BBC BASIC or syntax errors. For example, writing RINT instead of PR INT. Errors of this sort are the programming equivalent of spelling mistakes in English.

(2) Misunderstanding or confusion about what a part of the program actually does. For example, you may think that a variable is being used to keep a total but because another part of the program uses it, this is not the case. in English this would be equivalent to not saying what you intended to say.

(3) Not catering for a wide enough range of ways that the program will be used. If a program is intended to be used in a very exact way then it is up to the program and not the user to ensure that these conditions are met. For example, if a program is designed to work out the roots of a quadratic equation it should not {ail because the user enters a cubic equation for it to solve! The proper response to such a request is to inform the user that only quadratic equations can be solved, but most programs in this situation would stop with a meaningless (to the user) error message. In English this is equivalent to being vague or ambiguous.

The first two types of error are in some ways more fundamental than the third in that a program that doesn't contain any of the first two types of bug will work as the programmer intended. I he third type of bug really concerns what happens when the program is used in ways that are outside the conditions that the programmer had in mind while writing the program. This is often referred to as the robustness of a program. The more robust a program is, the better it behaves in a wider range of situations. Unfortunately the attitude that most programmers take to this sort of 'creative' use of a program is that any error messages or strange results are the sole fault of the user. This is a very limited attitude towards programming that most users find difficult to accept- A good product should behave well in all circumstances, not just in the ones that the programmer has taken into account.
Although the reliability of programs under ail conditions Is important it is better to deal with methods of finding and eliminating the first two types of error first. Ensuring program robustness uses a very different set of techniques that are described towards the end of this chapter (see the section Programs fit to use).

Bug detection

The first thing to say about debugging is that it consist of two distinct phases of 'bug detection' and 'bug location'. It is one thing to know that a bug exists within a program- that is, to have detected it, and quite another to know what its cause is, that is, to have located it. Although these two phases are clear enough many programmers confuse the two and tend not to realise that there are different methods used to detect and locate bugs.
Given a program that has reached a point in its development where the programmer concerned believes that nothing extra needs to be added, the next step is bug detection. That is, the program has to be tested to discover if it performs as intended. The most common method of doing this is for the programmer simply to use the program for a while and as long as it doesn't crash or do anything obviously wrong it will be passed as working. However, this unsystematic approach to program testing is both inefficient and unreliable. The programmer may spend a long time testing the program by using it to do the same sorts of things a great many times. Obviously the most efficient use of time spent testing involves making the program do as many different things as possible. Looked at in this way, testing is about finding sets of input data that take the program through as many different situations as possible. The problem is how to construct such sets of test data and this brings us back to the subject of the flow of control diagram introduced in Chapter Two.
Obviously, it is possible to test a program using such a limited set of test data that some parts of the program are never used. If there are parts of a program that haven't been used during testing, then no matter how long was spent testing the program it is only partially tested. An unused line wan untested line and can contain any number of bugs. Another way of thinking about this is that complete testing involves following each path through the flow of control diagram. At each IF statement the flow of control line divides into two alternative paths and the test data should include a set of values that tests each path. You can think of the flow of control diagram as a map that should be used to explore the program.
It is also important to test all parts of the program equally. Bugs tend to hide just as often in what the programmer might think of as an easy part of the program as in a hard part. In fact the difficulty of implementation of a part of a program seems to have very little to do with where bugs are found. However, programmers tend to over-test the sections of a program that were troublesome during the development of the program and ignore those that were quickly dealt with. If anything, a section of program that was quick and easy to implement is more likely to harbour bugs because it has been scrutinised less. The only sensible attitude is to try to forget which sections of the program were easy and which were difficult, and make sure that testing uses every part of a program.
Another problem inherent in testing is that many cases seems so trivial as to be hardly worth thinking about. The question is 'how do you know that you have found a bug'? If the program is a game or something similar, then a bug is usually immediately apparent, but in other cases it can be much more difficult to decide if the result is correct. It is important that before you try a program with a particular set of test data you predict what you expect to see in other words, you work out the correct result. The reason for this emphasis on predicting the outcome before trying the program is that it is all too easy to convince yourself that whatever the program actually does is correct Sometimes the need to believe that a program is working correctly can defeat common sense! If it is difficult to predict the correct results of a program, for example, solving a very difficult set of equations, then there are three ways of dealing with the problem:

(1) You should first try some simple test data and estimate (even if only approximately) what you expect to see.

(2) You should then try some more difficult data and make sure that the results change in the way that you expect.

(3) If there is another program that calculates the same or similar result

Without some independent method of checking the results of a program there is no guarantee that it is correct and any results it produces have to be taken on trust. Claiming that a program is working something out using a correct method correctly applied is not a proof that the result is correct! The weak link in the argument is that you can never be sure that the method is correctly applied unless you have checked that the result is correct!

Practical testing

In theory testing is easy. All you have to do is:

(1) Construct sets of test data that take the flow of control down each of the possible routes through the program.

(2) Predict what you expect to see as the result of each set of test data and then run the program using the test data.

(3) Compare the results that you get with the results that you predicted any discrepancy and you have detected a bug

In practice, even if you could follow this plan for complete testing it would still leave some bugs undetected! The trouble is that the number of test sets of data that are necessary for medium sized programs is larger than can normally be handled during routine testing. The best that can usually be managed is to make sure that at some point during testing each branch of the flow of control diagram is tried out at least once. (Notice that full testing involves taking every possible combination of branches in the flow of control.)
One type of bug that even complete program testing will not detect is related to using up the computer's 'resources'. For example, you may have tested a section of a program that plots a shape on the screen and have used sufficient test data to explore all of the possible routes through the program but it's not until you try to plot the shape near the edge of the screen will you discover the bug caused by part of the shape going off the screen. In a sense this error is the result of trying to use more of a resource i.e. the graphics screen, than the computer has. Another, and common example, of a resource error is to be found in the misuse for FOR loops. If you indulge in the practice of jumping out of FOR loops before they are finished then you will eventually generate an error. The reason for this is that each FOR loop that you leave unfinished is still 'active' and BBC BASIC can only handle a maximum of ten FOR loops at any time. When you test the program this bug may not show itself because the test data doesn't cause the FOR loop to be left sufficiently often. However, as soon as testing is over and the program is being used 'for real' the intensity of use is much higher and hence the message "TOO MANY FORS" appears and the program crashes. (The same problem can occur with REPEAT . . . UNTIL loops.)
The only way of finding general resource bugs during testing is to include sets of extreme test data. Of course what 'extreme' means depends very much on the nature of the program In a graphics program you should always try to make the program malfunction by extremes of position and size in the objects being drawn. In a numerical program you should try very small and very large values. However, the best way of avoiding the too many FOR or REPEAT . . . UNTIL loops bug is to read through the program and make sure that there are no GOTOs transferring control into or out of such loops. Of course if you have followed the principles of structured programming there will be very few GOTOs and none of them will transfer control into or out of FOR . . . UNTIL loops on purpose!
Some programmers and some users have the odd gift of being able to invent test data that will detect bugs. However, if you can adopt the attitude that program testing is only successful when bugs are detected then it is surprising how easy it is to think of data that makes a program fail. If this sounds obvious it is worth pointing out that most programmers are disappointed to find a bug during testing and this attitude probably subconsciously affects the test data they select. Remember that the object of testing is to find bugs not to prove that your program is working. A testing session that finds no bugs is more or less wasted time.

Bug location

Once you have detected that a bug exists the question is how to find out what is causing it. In other words, after bug detection comes bug location. The most commonly employed method of finding a bug is just looking at the program listing! The surprising fact is that this works in most cases. Often the bug causes the program to crash with an error message that names the line that contains the error. If the line named in the error message doesn't contain an obvious bug then the next thing to do is to examine lines that define variables used in the line. For example, if the error message uses the variable 'A' then examine lines that come before the line in question that also use 'A'.
So many bugs are obvious once they have been detected that some programmers never learn what to do when a bug is difficult to locate. If just looking at the program listing fails to reveal the problem after a few minutes then the chances are that 'just looking' will not work at all. If you think that debugging involves studying a listing into the early hours of the morning then the chances are that you have been wasting a lot of time. Debugging is an activity; it needs both the program listing and the computer to work efficiently. In fact the technique of debugging is an extension of program testing. You should predict what the program should be doing and compare it to what the program is actually doing. The first place that you find a discrepancy is the location of the bug. The difference between the predictions that you make during testing and debugging is the level of detail. The predictions used during testing merely concern the outward behaviour of the program, while those needed in debugging have to predict the internal behaviour of the program. To be precise you have to specify the order in which you expect the statements to be carried out, that is the flow of control, and the values that you expect to find in each variable. If you find that the order of execution is not what you expected or that a variable has a different value stored in it, even if you have not found the actual bug you will be much closer to it.
This method of prediction and examination sounds easy enough but how do you find out the actual order of execution and the content of variables? In most versions of BASIC there is no problem with either task but BBC BASIC presents a number of difficulties. However, with the assistance of the 'trace/debug' program given in Chapter Nine debugging BBC BASIC programs is easier than any other! In assembler the problem is no more difficult and in fact the fundamental method of debugging - the break point - was introduced by assembly language programmers long before high level languages were invented.

BASIC and assembly language break points

A break point is a temporary halt in a program so that the order of execution can be determined and variables examined. In BBC BASIC a break point can be inserted into a program by using the STOP command. The STOP command baits the program and prints the message STOP at line xxxx where 'xxxx' is the line number of the STOP command. Thus by placing a number of STOP commands in the program you can discover the order of execution of its statements. When debugging never assume that the program is taking a particular route through the flow of control diagram; place STOP commands along the way to verify that it is. Once the program has stopped you can also discover the values stored in variables by using direct PRINT statements. Once you have finished examining variables the program can be restarted using a GOTO yyyy command entered in direct mode, where yyyy is the line number of the next instruction following the STOP command. The only trouble is that although you can restart a program following STOP using GOTO, the BBC Micro will forget about any FOR loops, REPEA T. .. UNTIL loops or procedures that it was in the middle of when the STOP was encountered. This means that when you restart the program by using GOTO the program will crash as soon as it tries to execute the NEXT, UNTIL or ENDPROC statement. Thus you cannot use STOP/GOTO to place break points within FOR loops, REPEAT. . .UNTIL loops or, most importantly, within procedures. This is an unfortunate limitation on what in other versions of BASIC is the main debugging tool.
The simplest way to overcome this problem is to add PRINT statements to the program that tell you what the current line number is and print out any variables of interest. This sound foolproof but in practice the debugging messages tend to get lost on the screen or rapidly overprinted by other messages. The best solution is to use a specially written debug procedure:

9000 DEF PROCdebug(L_NO,V1S,V2$,V3$)
9010 LOCAL X,Y
9020 X=POS:Y=VPOS
9030 SOUND 1,-15, 100,10
9040 PRINT TAB(0,0);"At line ";L_NO;
9050 PRINT V1$;"=";EVAL(Vl$);
9060 PRINT V2$;"=";EVAL(V2$);
9070 PRINT V3$;"=";EVAL(V3$)
9080 REPEAT:UNTIL INKEY(-106)
9090 PRINT TAB(X,Y);
9100 ENDPROC

This procedure will print the line number and the names and values of up to three variables on the top line without disturbing the current cursor position. It will then wait until the COPY key is pressed before returning control to the program, For example, try:

10 FOR I=1 TO 100
20 PRINT I,SQR(I)
25 PROCdebug(25,"I","","")
30 NEXT I
40 END

Line 25 uses PROCdebug to inform the programmer that line 25 has been reached and the current value of I.
Once you have seen a procedure like PROCdebug and used it a few times you will soon be adding improvements of your own. For example, it could dump all the variables used by the program. (If you want to inspect such a routine, see the heap dump program in The BBC Micro.- An Expert Guide.) You could even make it prompt you for the names of the variables that you would like to examine, but of course you would have to be careful about screen layout!
If you know BBC BASIC well you might be wondering why the TRACE command has not yet been mentioned as a way of following the flow of control through a program. Following TRACE ON the line number of each statement is printed just before it is obeyed. The trace can be stopped by using TRACE OFF. Both statements can be entered in direct mode or included within a program. For example. you could include a TRACE ON command just before an IF statement to discover which way the flow of control went and turn it back off with a TRACE OFF immediately afterwards. Used in this limited way TRACE ON and TRACE OFF are extremely useful additions to the range of debugging aids but if the trace is switched on at the start of a program and left on the sheer quantity of information printed on the screen is overwhelming. A program that enables the flow of control to be traced through a complete program without any problems is given in Chapter Nine.
Debugging 6502 assembly language programs follows the same fines as for BBC BASIC. However, there is the added problem that assembly language is not run in such a 'protected' environment as the BASIC interpreter provides for BBC BASIC. In other words, it is possible for an assembly language program to run 'wild' and obliterate system variables, etc., in such a way that the only method to recover control of the machine is to switch it off and on. The best way of handling the testing and debugging of an assembly language program is to use a special 'debug' program that provides facilities such as single instruction execution and full trace. However, for small programs the same method described for BBC BASIC can be used. That is an assembly language 'debug' subroutine can be written that will print out the current address and contents of all the registers whenever required. Obviously in assembly language debugging the address of the last instruction to be executed is needed to follow the flow of control but values stored in variables are less important than the current contents of the registers. The reason for this is that generally assembly language variables arc changed by way of storing the registers. You should be able to see that an assembly language version of PR OCdebug is not difficult in principle. However, in practice it turns out to be quite a long program!

The common errors of languages

The earlier sections of this chapter have supposed that when you are looking for a bug you have no idea where it might be, except for the evidence provided by predict and examine type debugging. However, in practice all computer languages have their 'common errors'. For example, as already mentioned the 'too many FOR loops' error often occurs in BBC BASIC because of the accidental jumping out of unfinished FOR loops. It obviously makes sense to look for the most common errors before looking for new and exotic bugs!
In all versions of BASIC the greatest source of bugs is the line numbering system. This has already been raised as a problem in Chapter Two, but it is worth describing the types of effect that mistyped line numbers produce. A single mistyped digit that is part of a fine number will result in a line of BASIC being added to the program at an effectively random position! This random scattering of legal statements within a program gives rise to a type of error that is typical of BASIC. Whenever a program starts to behave in apparently illogical ways it is worth checking that there are no stray lines embedded in the program. This type of check is one of the few debugging activities that is best conducted with a program listing away from the computer. The problem of eliminating misplaced lines in the first place is very difficult. If you are entering a large number of lines at one time a mistyped digit will not only add a line at another position, it will also leave a gap in the program where you intended the line to be. In practice, it is the absence of a line that was recently typed that is detected first. Unfortunately the usual reaction is to suppose that for some reason the machine just didn't accept the line. Because of this the line is just typed in for a second time and there is no immediate search for the version of the line that went missing! The misplaced line is found sooner or later but often after a great deal of time-consuming debugging. The rule is that you should always check that any line or block of lines that you have typed has been added to the program in the correct place. Any missing lines should not simply be retyped, but the program should be searched and the lines found and removed.
Another common BASIC bug the unwanted interaction between modules was introduced in Chapter Two. One of the most likely causes of subtle and difficult-to-find bugs is the use of the same variables by different procedures for different tasks. This is a problem that can be largely eliminated by using local variables wherever possible but even BBC BASIC sometimes suffers from this distressing bug.
The most common assembly language bugs are due to confusion over addressing modes. For example, it is all to easy to mean:

LDA #&70

and actually write:

LDA &70

Other assembly language bugs are often caused by misunderstanding how instructions set and affect the condition codes. Never assume that a branch is being taken just because it is self-evident. Check that the instructions just before the branch really do change the condition codes in the way that you expect. Apart from these two bugs, assembly language also suffers from the 'unwanted interaction between modules' problem encountered in BASIC.

Stepwise testing and debugging

The question still remains of what to do once you have detected and then located a bug. Should you fix the bug and carry on testing or carry on testing and put off changing the program until later? The answer to this question is not at all easy. If you change the program by fixing the bug then you are no longer testing the program that you started with and in theory at least you should go back and start testing all over again! If you think that this is rather extreme advice, it is worth saying that many embarrassing bugs result from making apparently minor last minute changes that cause problems which would never be guessed at in well tested parts of the program. If you make any change to a program. your level of uncertainty about its correctness must drop back to where it was before you started testing. In practice, common sense has to be applied to which tests to repeat and which not to.
An advantage of using stepwise refinement is that the program can also be tested in a stepwise manner. As each module is added to the program, its specific action should be tested. In this way, by the time the program reaches its final form, most of the worst bugs are likely to have been removed. However, this is not to say that stepwise testing will produce a bug-free program As already explained in previous sections, some bugs arise from unwanted interactions between modules and to detect these it is important to test the whole program. Thus, stepwise testing and debugging should be used but not as an excuse to limit or avoid final testing!

Programs fit to use

Reducing the number of bugs in a program is a difficult and time consuming task but it is not enough to ensure the success of a program! Indeed, a well debugged program is the minimum requirement For a program to be successful it has to be easy to use and well behaved. It is virtually impossible to explain how to write programs that are easy to use. The best that can be done is to suggest guidelines and ways of evaluating the ease of use of a program. However, it is possible to describe ways of producing programs that are well behaved.
Essentially, a well behaved program should never crash or produce results that it was not intended to. A program that is free of bugs can crash in one of two ways. Firstly, the user can enter values that the program was not designed to handle. For example, entering a value that is too big, or too small or even of the wrong type. Secondly, an internal or external condition can cause an apparently correct program to fail. For example, if a string becomes longer than 255 characters then it cannot be stored in BBC BASIC and the program will crash. Another example is trying to read a file that doesn't exist, or a disk error.
The first type of crash can be avoided by testing all input values for the correct type and range. This is not as difficult as it sounds. Every INPUT statement should be followed by an IF statement that checks the input values and either issues an error message reporting out of range values or simply transfers control back to the input statement. Any INPUT statement that is not followed by such a check for illegal values is a potential route for garbage into the program - and every programmer is familiar with the well known saying 'Garbage In. Garbage Out'. In reality this saying is more often realised as 'Garbage In, Nothing Out' because garbage data usually crashes the program! You can see examples of input protection in most of the programs in this book.
The second type of crash is very difficult to avoid. In many ways the causes of such crashes are very like ordinary run-of-the-mill bugs. The difference is that unlike bugs they are the consequence of perfectly correct BASIC. For example, the part of a program that tries to read a file that doesn't exist is perfectly correct BASIC; it just happens to be working in an environment where it cannot carry out the task it was designed for. Sometimes R is possible to detect and program around such situations, but often the biggest problem is realising that such an event can happen. Often it isn't even practical or possible to check for the conditions that cause the crash for example, the work involved in checking that a string will not become longer than 255 characters as a result of the next operation is possible but not practical. The only practical way of handling such exceptional circumstances is to use the ON ERROR 'statement' command. When an error occurs the BBC BASIC interpreter will carry out the 'statement' part of the most recent ON ERROR command. Thus, ON ERROR can be used to detect and handle errors as they happen. That is, instead of trying to write BASIC statements that will detect situations which will cause a crash before they happen, the ON ERROR statement can be used to detect the situations by the fact that it does cause the system to crash! However, the problem with detecting a crash after the situation that caused it has already occurred, is that it is very difficult to 'backtrack' to the point just before the crash.
To be precise, after an ON ERROR statement has been acted upon all of the variables have the value that they had just before the error occurred but any active FOR loops or REPEAT . . . UNTIL loops are finished and any FROG calls are 'forgotten'. This means that following an ON ERROR statement you cannot transfer control back into a FOR or REPEAT . . . UNTIL loop or procedure. This limits the way that you can restart the program to a transfer of control back to a point in the main program. This is very restricting and often it is better simply to issue an error message explaining the problem that has arisen and then re-run the entire program by using RUN as a program statement. To enable the error handling routine to discover the nature of the error, BBC BASIC provides the variables ERR, which holds the error number of the last error, and ERL. which holds the line number that the last error occurred in.
Following this description you should be able to see that error handling m a BBC BASIC program is very simple and limited. An ON ERROR GOTO xxxx statement should be included at the start of the program where 'xxxx' is the line number of an error handling routine. The error handling routine should test ERR and ERL to find out what sort of error has occured and where. On the basis of this test messages may be issued, some variabies initialised and some files closed. Then the error handling routine should use a GOTO statement to transfer control to a procedure call within the main program but outside any FOR or REPEAT .. .UNTIL loops. Because of the need to use of GOTOs and line numbers it is better to add the ON ERROR type of error handling at a point where the program is very nearly finished. This also has the advantage that during testing you can keep notes of any crashes that the error handler should deal with. Often, however, the only solution is for the error handler to make any valuable data safe and then offer the user the chance to re-run the program. For example. if you detect a disk error within a text processing program it is unacceptable not to try to handle the error, but it is equally unacceptable to deal with it by losing am, text that was entered and re-running the program from scratch (there are a number of commercial text processors that do behave in this way in response to relatively minor errors). The correct way of handling such an error is to give the user the opportunity to change disks and save the text immediately. The rule is that during all error handling the main concern should be to minimise the user's wasted effort and this usually means minimising the loss of data or results. The applications programs in the remainder of this book have all been written with crash- proofing in mind.
Given a fully debugged and crash-proofed program the only remaining consideration is its ease of use. As mentioned in the introduction to this section it is wry difficult to say in genera! what makes a program easy to use. The range of user 'interfaces' found in programs is too wide to comment on in detail but it seems obvious that for a program to be easy to use it must tend to do things in the order that the user requires. It should also give the user as much freedom to determine the course of action as possible but without forcing the user to specify everything in minute detail. Programs that drag the user through tasks in an order determined solely by the programmer convey a sense of restriction and claustrophobia soon sets in. However. programs that are completely free and respond to the user's very detailed commands are very tedious to use after a while. The way to find out if your program does things in the right order is to take note of the sort of mistakes that users make in the early stages of running your program. If you ask for the maximum value followed by the minimum value and users keep on entering the minimum value followed by the maximum value then it is your program that is at fault! Remember the user is (nearly) always right.
Apart from watching the way users react to your program there are some general points that are worth keeping in mind while you are designing and implementing it.

(1) Try to keep the screen display dear and uncluttered. It is important to realise that a screen display you have developed in the course of the program will look simple to you because it is familiar, but to a new user it might appear a jumble

(2) Use colour and sound to draw attention to the important aspects of your program, screen display or whatever. As the important aspects are likely to be few in number, this implies that sound and colour should be used sparingly. For example, do not use a different colour and sound effect for every message. Highlight only the most important messages on the screen and only use sound to draw attention to a mistake or to reinforce very important points. Loud, colourful programs quickly become tedious.

(3) Always ask clear and unambiguous questions and do not use jargon unless it is the user's jargon. For example, do not ask 'Dimension of array?' when you mean 'How many entries?' If your program is intended for users that have their own jargon then it makes the program more friendly if you use their jargon. However, make sure that your idea of their jargon is up to date there is nothing less likely to instil confidence than using a term that has been redundant since 1800!

(4) Never present too much information at once and never allow information to scroll off the screen before the user can read it. Make your program respond to single key presses that indicate when the user has finished reading whatever information is on the screen. For novice computer users a slower rate of printing information on the screen is also helpful. The usual BBC rate of printing can convey the impression that the computer is so fast they cannot keep up! It is also worth nothing that messages given in upper- and lower-case are much easier to read than upper-case only.

(5) Always try to allow the user to correct mistakes and abort program actions. Always use INPUT rather than GET$ or INKEY$ so that the user can correct entries with the delete key. Allowing the user to abort program actions is much more difficult. For example, how often have you had to sit through a program doing something that you didn't really want it to do because you entered a command by mistake and can find no way to stop it! If at all possible it is a good idea to monitor the pressing of the ESCAPE key using the ON ERROR statement. If the ESCAPE key is pressed then the error handler will be entered with ERR set to 17. Following this, the error handler's problem is to work out how to stop the program's current action without causing a real error and then how best to return control to the user.

(6) Finally, do not give error messages that are either too technical or too friendly. That is, messages such as "INTEGER OVERFLOW CONDITION ENCOUNTERED" and "YOU GOT IT WRONG" are equally to be avoided. Try to inform the user of exactly what is wrong without going into too much detail.

Similar guidelines could be added indefinitely, becoming increasingly more specialised. The most important thing, however, is to think about how the user wants to do the job and then try to make sure that your program helps rather than hinders. In the final analysis, it is the user who either likes or dislikes your program so take notice of any friendly users you can find to try your programs!


Next     Top