Reference: A2752
There has been a serious concern as to why novice programmers have difficulty understanding programming. For two decades, empirical studies of novice programmers have tried to address the problems that novice programmers are experiencing [2, 10]. However, it seems that nothing has been done to solve these problems; or the proposed solutions, such as going from one paradigm to another, remain unsuccessful. The main argument has often been why novice programmers do not learn programming, rather looking through their eyes and looking closely at what is wrong with programming, languages, environments and instructions. This study traces some of the problems novices have encountered back to the beginning era of programming and programming languages. There is a need for radical changes in helping novices with programming issues and to view problems from the eyes of novices.
The field of programming has not incorporated the advantages of existing technologies to reshape concepts that are five-decades old. Today, the field of programming contains many inconsistencies and misconceptions that cause novice programmers to think illogically rather than logically. Every now and then, more information is added to existing languages and programming design, making programming more overwhelming for the novice programmer. It seems that there is no system to set a learning priority for novice programmers. Empirical studies still indicate that programming control flow, construct misconceptions, object implementation, and programming environments are major problems for novice programmers.
Isn’t it time to look and investigate some of the novice problems for some solution and think new? Some of the benefit would resolve the problems of lack of interest in the field of programming and alleviate drop out and failure of novices who dream to learn programming and become programmers. Hopefully this will lead the novices of today to become the experts of tomorrow who will take the responsibility of solving some of the current and future programming problems.
WHAT IS WRONG?
A survey of 351 faculty teaching novice programmers indicates that the third most difficult subject for students was Object Oriented constructs and the second most difficult subject was general programming concepts [1]. Novices may be blamed for lack of maturity and problem solving skills. However, when they are asked to solve problems such as finding the average of a series of numbers, sorting numbers, and searching for a number manually, they have no difficulty expressing it or writing it down. The problem arises as novices try to transfer their knowledge to programming. Many novices ask the question: Why do I have to do it this way, with many limitations and gibberish notations and how will this program be used in real life? Long term artificial examples will overwhelm and discourage novice programmers from continuing, and as a result may block logical thought processes. Obviously, some friendly programming concepts, language constructs, and small practical examples will have short term learning rewards, which motivate the novice programmer to proceed.
Let's trace the history to find why programming and programming languages haven’t changed much. Like any new concept or design, the original environment and people involved play an important role. The field of programming has been influenced by its original surrounding machinery, as well as the primary human contributors from the fields of mathematics, physics, and electrical engineering. It is agreed that programming is a human activity and, therefore, humans should be its prime consideration. Researchers in computer-human factors and computer-human interactions have incorporated many psychological and sociological aspects in the field, making computer products friendlier for the end users. However, it seems that programming hasn't changed much since it was developed a half century ago. It gives the impression that language designers rushed to finish the product using available tools for convenience and, on top of this, insist on keeping it untouched. Yes, in a rushed situation we might use whatever resources and tools are available for a design prototype. Since these tools and resources were not fully appropriate, these prototypes should not have become the final design and product. When the rush is over, the design should be reconsidered using optimal tools. The bottom-line is that when implementing a new idea, lack of materials and resources at the time of design should not be the cause for a poor design to become a standard one. This circumstance raises the question: Why has computer programming not changed in its five decades of existence? Who is responsible for this situation? Should we blame computer enterprises of yesterday or giant software companies of today? Let’s not forget that programming has reshaped today’s technology and its growth with one drawback -that programming itself has not changed significantly. An ancient expression may summarize the situation: “A craft-man who drinks from a broken jar.”
Many problems
of today’s programming persist from its original generation. You may wonder why
certain basic mathematical operations such as multiplication and division are
not on the computer keyboard. Instead, we are using * and / respectively.
Another more troubling example is when we compare two values for equality. Why
is that we have to give up the equality operator “=” to use it for assignment
and use
“= =” or even “= = =” (triple equal in JavaScript) for equality? Why isn’t the
arrow “←” used for assignment since it is self-explanatory? There are many
questions of this type. Some of them can be answered if you look at keypunch 026
of IBM’s keyboard prior to the computing age and which was then adopted for use
as the computer keyboard. The choice of operators was affected by what was
available on the keypunch 026, even before the computer was designed. FORTRAN,
as the first popular language in the USA, uses “=” operator for an assignment
statement and .EQ. for equality. Similarly, ALGOL, as the first popular language
of Europe, uses “:=” for assignment and “=” for equality. You can observe
how these two pioneer languages decided early on about how to solve this
beginning programming confusion. COBOL, as a popular business language, later
added a natural flavor using the word “giving” to solve this problem, e.g. “add
a to b giving c.” More interestingly, APL used the left
arrow (←) for assignment [6]. This made sense,
but required a special keyboard or extension to the existing keyboard. The
popularity of the C language led to the use of “= =” for equality and “=” for
assignment, with a justification that programmers use assignment more than
equality. Therefore, we must sacrifice the equality operator for the assignment.
Similarly, “&&” is used as a logical operator and a single “&” is used as a bit
wise operator for binary operations. The reasoning behind giving the right of
equality to the assignment operator may cause a pause, in a head-scratching way,
as how this can be socially and logically acceptable?
After the C language, C++, JAVA, and other languages paid the same respect in selecting the equality operator. In fact, JavaScript went even further and introduced triple equality “= = =” in addition to double equality.
The problem of using “= =” for equality and “=” for assignment causes problems not only for novice programmers, but for expert programmers as well. Experts have inadvertently used “=” in place of “= =”, causing delays of software releases and such errors have been costly. The main problem is that equality and assignment are syntactically interchangeable, but they are semantically different.
Regarding the use of * for multiplication instead of x, the novice programmer’s belief is that the computer should not confuse the multiplication operator x with the letter x. If this is the case, would it be possible to represent the multiplication symbol (e.g. χ) slightly thicker or thinner than the letter x? The next question is, why is / used for division instead of the typical mathematical division operator ÷ ? Interestingly, backslash \ and the keyword div are used to represent integer division in Visual Basic and Pascal, respectively. The question of the division operator shed some light on the problem of selecting certain operators. It was not an issue of lack of computer program intelligence; rather, it was a question of availability and convenience. One major problem was typing in a linear form. In this way, a programmer has to represent anything two dimensional in a linear form. In fact, the name FORTRAN comes from Formula Translation. The problem of today’s programming should no longer be translation of formulas, but to expect intelligence from the computer in terms of problem solving. Is it still a problem to distinguish the multiplication sign from the letter x? Can we represent x to the power of n or x sub i nonlinearly in case of an array (e.g. xn , xi)? Why do we continue to do things in primitive and restricted ways such as using square brackets or parentheses to represent a simple subscript? If programmers have the power to change technology, then it is time to change programming. Can we not afford a new keyboard providing us with the necessary operators such as arithmetic and logical operators? Programming instructors tell beginners that programming is about logic, meaning making sense. Do we make sense in our selection of operators? Isn't it time to use notations that are no longer arcane and ambiguous, and not to be a source of confusion and misunderstanding to novice programmers? How about making programming and languages more context-dependent, making them natural and intelligent.
AN EXAMPLE OF NOVICE PROGRAMMER MISCONCEPTION: PLACING ASSIGNMENT FOR EQUALITY
The following C/C++ program intends to compare two values of variables a and b for equality. However, instead of the “= =” operator, the program uses “=” which will execute without reporting a syntax error. A group of novice programmers were asked to run the program and, based on the given input and output, determine what the program does. For example, for the input of a=0 and b=0 the program displays, “BOTH ARE NOT EQUAL” and for the input of a=0 and b=1 the program displays “BOTH EQUAL”. Moreover, for input of a=1 and b=1 the program displays “BOTH EQUAL”. In this experiment, none of the novice programmers could determine the real cause of program execution as to why it works the way it works. The real surprise is that any value other than zero is true (C philosophy) and, in an assignment statement, a right-hand side value is assigned to the left-hand side variable.
main( ){
int a, b;
cin >>a>> b; // a=0; b=0; // a=0; b=1; // a=1; b=0; // a=1; b=1;//
if (a=b) cout<<”BOTH EQUAL “;
else cout <<” BOTH ARE NOT EQUAL”; }//MAIN
If you are criticizing C/C++, think again, since you can write a very powerful program such as the one which follows below. More experienced student programmers were asked to run the program below first mentally and then on the computer. No one could predict the exact output. The majority could say what the program did when running it on the computer, but no one could explain how it worked.
void ypoc(char s, char t){ while ( *s++= *t++); }
void main( ) { char a[2], b[20];
cin>>a>>b;
ypoc(a,b);
cout<<”a is “<<a<<” b is “<<b<<endl; }//MAIN
The above program copies one string to another string. An equivalent program which is more readable will take four times more code than what is in the above program.
There are rules that are assumed in our daily life; but if a rule leads to unwanted results, it may lead to chaos and disaster. The default rules of programming languages are like hidden laws or hidden driveways that are not visible to the eyes of the programmer. Programmers should be aware of these default rules, otherwise problems will arise.
The following are some default rules of programming languages, starting with some of the early default rules:
FORTRAN DATA TYPE NAMING - Any identifier that starts with a letter I through N is considered an integer. Therefore, in an assignment statement such as netpay = 1000.99 the decimal value will be taken off by default. Similarly, count = 0 only stores 0.0 and causes problems when used as an index. However, some programmers used their own style by spelling “count” as “kount” to stay away from the problem.
INTEGER DIVISION – When an integer is divided by a larger integer, the result is always zero, due to truncation. Don’t be surprised if the following Fahrenheit to Celsius conversion c = 5/ 9 * (f – 32) always will be zero no matter how hot or cold Fahrenheit gets.
PHYSICAL LIMIT OF MEMORY - How much is the max or min value of a data type like integer or float? Obviously, it depends on the compiler or the language, but there is a limit. For example, when adding 1 to 32767, your number may become –32768. How does it happen? Even for the larger size of integer (32 bits), after 2,147,487,647 your number turns to the negative side (-2,147,487,648).
In JavaScript the result of the operation ‘3’ * ‘4’ is 12 and similarly the result of the operation ‘3’ + ‘4’ is ‘34’. Do you observe any inconsistencies? Obviously, the designer has a good reason to justify the + operator since it is overloaded for the concatenation of two characters. In C founded languages the use of the dash (minus sign) for identifiers is illegal. Therefore, the underscore is recommended whenever a dash is needed. In other languages such as Pascal the use of the dash is permitted. Novice programmers have their own generalization that a computer will confuse the dash with the minus sign, which is used in subtraction. While this is true in one language, it is not true in others and it should not be generalized similarly. Isn't computer programming capable of handling situations like this?
For beginners it is surprising that every character in the computer (e.g. keyboard character) is represented by a unique numeric code. Everything typed in the computer is going to be represented by a standard code such as ASCII. For example, capital A is represented by 65, lower case a by 97, blank space by 32, numeric 0 by 48, and NULL by 0. Knowledge of ASCII makes it easier to understand why a lower case word is different than its uppercase counterpart and how one can be converted to the other. However, a novice programmer questions why the capital letter A has a code of 65, while its lower case has a higher number 97. The novice also questions the rationale behind representing the character zero ‘0’ with an ASCII code of 48. As a result, in order to convert a character digit to its numerical value, the number 48 must be subtracted. Would it be better if character zero ‘0’ was represented by numeric code zero? Would it be better if a lowercase letter has a lower value than its upper case letter with a difference of 1? Interestingly, the difference between lower case and upper case letters is 32, which is the code for a blank. It seems that novice programmers have some legitimate questions as to whether it would be better to group all arithmetic operators (+, -, *, / ) and punctuations together? I personally could benefit from this grouping when I am teaching compiler and system courses.
PROBLEM WITH TEXTUAL REPRESENTATION OF PROGRAMMING
Since the beginning of programming, programs have been written in a textual and linear form, one statement after the other. This has been true even though the execution flow will not take place sequentially in this order. A group of statements may be skipped or repeated when using if/else statements, loops, and function calls. For example, when an if statement is executed, it’s else part will not be executed, or vice versa. Block structured programming was introduced to overcome the limited memory of early computers. Block structured programming was also a technique to eliminate the problems of using “go to” statements. In block structured languages, variables are declared at the beginning of the block and the storage of the variable is released at the end of the block. A program, like anything else in life, has a beginning and an end. However, the beginning of the execution of a program is not always written as the first statement, nor is it always on top. A main program identifies the program’s execution. A program may depart from its linear flow at some point. The start of a program is at the beginning and it halts at the end. The "if" part of a conditional statement is followed by the "else" part, giving novice programmers the impression that, after execution of the if statement, execution of the else block will follow. It is important to separate the if part from the else part. Therefore, any technique that would distinguish these two paths (branch) will eliminate this misconception. A three-dimensional view to visualize the two different paths for “if” and “else” could be a new monitor design. Also, a transparent view of memory, allowing users to watch the values of variables, could be used instead of debuggers that are difficult to use.
FORTRAN and ALGOL are two of the first languages that were developed in the 1950’s. Since then, other languages streamed out from these two sources. These streams either came together at some point or drifted further apart. Some new streams were created but either they dried out or their usage became limited. From FORTRAN to C++ or from ALGOL to Java, most of the new languages are created by adding or subtracting from FORTRAN and ALGOL. The Programming Language One, or PL/I, was created by combining FORTRAN and COBOL. BASIC (an acronym for Beginner's All-purpose Symbolic Instruction Code) was originally designed by professors John Kemeny and Thomas Kurtz at Dartmouth by simplifying FORTRAN. BASIC made programming simpler than ever and made the public aware of programming [9].
Professor Nicklaus Wirth shortened ALGOL making the language PASCAL. PASCAL made teaching programming easier than ever. PASCAL soon became the sole language of novices learning programming. However, at the same time, programmers in the business world began to appreciate the speed and creativity of the C language. They demanded that graduating students know C. Why should students learn something that the market was not using? Slowly, C replaced PASCAL as the language of choice. C++ was created from C and other languages such as Lisp and Smalltalk. Changes in syntax and semantics were made to Java to make C#. I did not include other languages like Lisp and APL in this paper; although they are powerful, they are not as frequently used. JavaScript sounds similar to Java, but is used on the front-end of web applications and has some limitations. For example, JavaScript cannot access server files. While the C language is powerful, many new languages such as C++, Java, JavaScript, Perl, PHP, and C# have adopted the control flow, keywords, and operators of C and have made additions. Recent attempts to teach novice programmers using Python [8] and Alice [4] have created new issues and have not made programming less confusing for beginners.
Before the introduction of the Object-Oriented paradigm, procedural was the main paradigm. The promise of Object-Oriented programming was to make programming more like the real world, but the benefits are not completely available to beginners. The three pillars of Object-Oriented programming are: encapsulation, polymorphism, and inheritance. It takes time for novices to become acquainted with the idea of working with classes and the rest of the OOP pillars. In order to understand class, beginners must first know about functions and variables. Using the Object-Oriented paradigm, students need to know more just to write a simple program like “Hello World.”
Once again, we hear dissatisfaction with the existing paradigm – OO for novice programmers and it has been shown that novices do better with procedural programming [5]. The replacement of the OO paradigm may take a long time, as it did to replace earlier paradigms.
Novice programmers face numerous questions when learning programming which are not possible to explain right away. Answering each question may require a whole lecture, such as explaining the syntax or semantics of certain keywords. Usually, programming instructors with many subjects to cover have no choice but to delay explanation to a later time. Some instructors never provide the reasoning and tell students to take things the way they are. Some students never find the answers or simply give up and use symbols and words without knowing why.
To say that we have to learn to program with objects or we can’t be good programmers is similar to saying that we have to start with a Shakespearian dialect while learning English, or we will never be able to speak English well. Obviously, learning that way may not work for everyone.
The following is an example of saying simple “HELLO” using OOP.
using System;
namespace HelloWorldApplication
{
class HelloWorld
{
static void Main (string[] args)
{
Console.WriteLine(“HELLO “);
}
}
}
The following illustrates ways of saying hello in different languages. Some simply say it the way it is, use a keyword, have a precondition such as including a library, or use an object of a class. Requiring knowledge of the object-oriented paradigm to perform a simple task such as saying hello makes learning programming difficult for novice programmers [11].
Lisp: ’hello
Basic: print “hello”
Fortran: print*, “HELLO”
Pascal: write(“HELLO”);
C: printf(“HELLO”);
Cobol: DISPLAY “HELLO”
C++: cout<<”HELLO”;
Java: System.out.println(“HELLO”);
JavaScript: document.write(“HELLO”);
C#: System.Console.WriteLine(“HELLO “);
There are three major steps in viewing the output of a program. The program must be entered into a computer using an editor. An editor facilitates typing and saving. The second step is to translate the program using a compiler or interpreter. A translator tries to recognize the statements of a program and report the proper errors to the programmer. The final stage of a program is to execute its translated form. An executer takes charge and executes the entire translated program (object code). In the case of an interpreter, execution takes place statement by statement. In the case of a compiler, the entire program (statements) is executed. Early programmers were responsible to find their own editor and their own translator and, finally, run the program in an operating system environment. Early programmers had to do these steps separately and be involved in every detail. Later on, the operating system provided an editor. As time progressed, specialized software known as Integrated Development Environment, or simply IDE, was developed by several companies and became part of the operating system to assist programmers, especially beginner programmers. Among the early popular IDE’s, Turbo Pascal/C++ (Borland) and Microsoft were less complicated. Nevertheless, as time went on, more features were added and they became overloaded with a series of gadgets. Instead of being a helpful tool, the IDEs were extremely overwhelming for novices and became something that novices needed a course to learn. Other IDEs such as CodeWarrior, SUN Microsystem SDK, and Borland JBuilder / C++ Builder were introduced. In Unix and Linux environments, usually the editor is separate from the compiler. For example, vi editor while at its time was the best, it is very confusing for the novice of today. In most IDE systems, debuggers are very complicated and novice programmers still prefer to use their own ways to debug their programs, such as using display messages to view values of variables. Compiler error messages are not exact and can even be wrong. Novices themselves, by gaining experience, interpret the compiler error messages. For example a compiler error message “missing a ; (semicolon) may really mean missing a quotation in a program. A wrong error message leads to a novice’s confusion and loss of trust in the messages from the compiler. At first it is a shocking feeling for novice programmers to see how the compiler could be wrong, especially when it is coming from a well-known enterprise such as Microsoft. This led a novice to conclude that if the compiler can go wrong therefore I can be wrong too. If the compiler cannot determine the exact cause of errors, it should prompt you with the probable causes of errors rather than prompting the user with definitive error messages. Compiler error messages may be incorrect and line numbers are often inaccurate. Can we build an intelligent translator? I am sure the answer is yes. An intelligent compiler environment will not only assist a novice programmer with detecting errors accurately, but will also help in error recoveries. It will not only help with syntax and semantic errors, but also with logical errors [7].
Historically, available tools such as the IBM keypunch 026 have influenced the way programming and its languages have developed. It is time to change the programming languages and the way we program with them. For the past five decades, programming has contributed to the reshaping of our technology, but when it comes to programming itself, programming hasn’t embedded the technology change. To assist novice programmers, several languages and programming techniques have been designed here and there. While at some stage, these programming languages may have been successful locally or periodically, they had little luck of becoming popular for a longer time. These languages couldn’t resist the market demand and have gone out of popularity or even ceased to exist. One noticeable problem was that the language and programming techniques used for training were different from the programming technique and language used in the market.
One suggested solution is that programming educators and novice programming designers establish a task force to address the problems of novice programmers and recommend a common ground. For example, conducting a large survey is a plus in finding and analyzing the most common novice programming problems. One goal of the task force would be to find a way to standardize programming and languages as a whole and not just as a part, develop an agreed upon foundation, and turn personal gut feelings into common sense. In addition to the standardization of programming and languages, the arcane and illogical operators, expressions, and definitions should be eliminated. Alternative ways should be presented. Another objective of the task force would be to prioritize what novices should learn and propose ways to accomplish this job. The result would lead to less overwhelming of novice programmers with arcane and illogical rules and a more enjoyable and successful learning experience with a subsequent positive view of technology.
Let us not forget that standardization is not a new idea in the field of computer science. In fact, most languages are standard, but here I am suggesting a generic standard programming language with its embedded techniques. However, setting general standards should not interfere with new innovation, ideas, and proposals. At the moment, in programming we are facing a double standard in syntax and semantics, which sometimes contradict one another. One legal identifier, expression, or statement may be illegal in one language or legal elsewhere with different semantics.
Lastly, a pictorial representation of programming could eliminate confusion created by the linear representation of programming [3]. A visual programming language could be designed to represent control flow visually by using overlapping or transparent windows.
Novice programmers tend to generalize what they are learning in programming. They do not think there are other ways and differences; therefore, any contradiction leads them to become disillusioned with technology and withdraw from becoming engaged with it. Many generalizations made by novice programmers are generated on the fact that their learning is characterized by trials and errors rather than logical, achievable experiences. Therefore, the first language and the first programming techniques play an important role in the further learning of programming. Almost everything is viewed from the angle of the first learning, whether the language is C++ or Visual Basic; similarly, whether the programming paradigm is procedural or object-oriented.
The object-oriented paradigm has made the job of learning programming more difficult than before. In order for novice programmers to understand the benefit of object-oriented programming they need to have some exposure to the three pillars of OOP: encapsulation, inheritance, and polymorphism (functions and operator overloading). At early stages of OOP it is hard to give a meaningful example of OOP without covering some of the procedural programming first. By covering procedural programming, the mental model becomes procedural. It is a catch twenty-two. There is no question about the impact of OOP in the long run. One drawback for novices in the early stages of learning OOP is spending time juggling methodologies rather than real problem solving, which is the purpose of programming. The result is learning fewer algorithms and more about programming paradigms which are supposed to be helpful in problem solving, rather than becoming problems themselves. We can hope either a programming revolution will bring about a total change, like another invention that we never imagined in the past, or that an evolution will sufficiently enhance the existing problems that novices face.
References
1. Dale, N. Most Difficult Topics in CS1: Results of an Online Survey of Educators, The SIGCSE Bulletin 38, 2 June (2006).
2. Ebrahimi, A. Novice Programmer Errors: Language Constructs and Plan Composition. In International Journal of Human-Computer Studies Volume 41, Issue 4 (October 1994), 457 – 480.
3. Ebrahimi, A. VPCL: A Visual Language for Teaching and Learning Programming (A Picture is Worth a Thousand Words) Journal of Visual Languages and Computing, 3, (1992), 299-317.
4. Conway, M., Pierce, J., Pausch, R. et al. Alice: Lessons Learned from Building a 3D System for Novices. Proceedings of CHI 2000, pages 486-493.
5. Glass, R. “Silver Bullet” Milestones in Software History Communications of the ACM, 48(8), (2005), 15-18.
6. Iverson, K. A Dictionary of APL ACM SIGAPL APL Quote Quad (18) 1, (1987), 5-40.
7. McIver, L. The Effect of Programming Language on Error Rates of Novice Programmers. In A.F. Blackwell & E. Bilotta (Eds). Proc. PPIG 12, (2000), 181-192.
8. Oldham, J. D. What happens after Python in CS1? J. Comput. Small Coll. 20, 6 (Jun. 2005), 7-13.
9. Sammet, J. Programming Languages: History and Fundamentals Prentice-Hall Englewood Cliffs, N.J., 1969.
10. Soloway, E. Learning to program = Learning to construct mechanisms and explanations. Communications of the ACM, 29(9), (1986), 850-858.
11. Westfall, R. ‘Hello, World’ Considered harmful, Communications of ACM (44) 10, (2001), 129-130.