Gotcha (programming)

From Wikipedia, the free encyclopedia
Jump to: navigation, search

In programming, a gotcha is a feature of a system, a program or a programming language that works in the way it is documented but is counter-intuitive and almost invites mistakes because it is both enticingly easy to invoke and completely unexpected and/or unreasonable in its outcome.

Gotchas in the C programming language[edit]

Equality operator[edit]

The classic gotcha in C is the fact that

if (a=b) code;

is syntactically valid and sometimes even correct. It puts the value of b into a and then executes code if a is non-zero. What the programmer probably meant was

if (a==b) code;

which executes code if a and b are equal. Modern compilers will generate a warning when encountering this construct. To avoid this gotcha, some programmers[1] recommend keeping the constants in the left side of the comparison, e.g. 42 == x rather than x == 42. This way, using = instead of == will cause a compiler error. Others[who?] find this workaround yields hard-to-read code, and also doesn't work when you have two assignable variables.

Function calls[edit]

Example from C and relatives:

/* File name: fortytwo.c */
#include<stdio.h>
foo() {
   printf("  42!\n");
}
int main() {
    printf("I will here below write '42':\n");
    foo;
    printf("I think I wrote '42', but I'm not quite sure...\n");
    return 0;
}

will produce (when compiled and run):

$ ./fortytwo
I will here below write '42':
I think I wrote '42', but I'm not quite sure...

What foo; expression counterintuitively does is returning the address of the function foo. What the programmer intended was probably instead the function call:

    foo();

where the parenthesis indicates a function call with zero arguments.

Gotchas in the C++ programming language[edit]

Initializer lists[edit]

In C++, it is the order of the class inheritance and of the member variables that determine the initialization order, not the order of an initializer list:

#include <iostream>
 
class CSomeClass
{
 public:
  CSomeClass(int n)
  {
   std::cout << "CSomeClass constructor with value ";
   std::cout << n << std::endl;
  }
};
 
class CSomeOtherClass
{
 public:
  CSomeOtherClass() //In this example, despite the list order,
  : obj2(2), obj1(1) //obj1 will be initialized before obj2.
  {
   //Do nothing.
  }
 private:
  CSomeClass obj1;
  CSomeClass obj2;
};
 
int main(void)
{
 CSomeOtherClass obj;
 return 0;
}

Gotchas in JavaScript programming language[edit]

JavaScript function closures inside loops don’t work intuitively.

var func = [];
for(var i = 0; i < 3; i++) {
   func[i] = function() {
       alert(i);
   }
}
func[2]();
func[0]();

Correct way:

var func = [];
for(var i = 0; i < 3; i++) {
   func[i] = (function(i){
       return function(){
           alert(i);
       }
   })(i); //Here it is
}
func[2]();
func[0]();

Gotchas in Ruby programming language[edit]

Scoping[edit]

Scope of a local variable starts at its definition and ends at the end of the method. Within its scope, the local variable obscures current object's methods.

irb(main):001:0> class Blah
irb(main):002:1>   def foo
irb(main):003:2>     puts "I'm foo, and I got called"
irb(main):004:2>   end
irb(main):005:1>
irb(main):006:1*   def bar
irb(main):007:2>     if false then
irb(main):008:3*       foo = 1
irb(main):009:3>     end
irb(main):010:2>     puts "I am about to call foo"
irb(main):011:2>     foo
irb(main):012:2>     puts "I believe I called foo"
irb(main):013:2>   end
irb(main):014:1> end
=> nil
irb(main):015:0> x = Blah.new
=> #<Blah:0xb7d3b5b4>
irb(main):016:0> x.bar
I am about to call foo
I believe I called foo
=> nil
irb(main):017:0>

Iterating Range[edit]

Ruby has a data type called Range; an object of this type constitutes a set of everything between the start and end of the range (including or not including the boundaries, depending on additional conditions). Since Range is a subclass of Enumerable, one would intuitively expect that iterating a valid Range object will always give you every single object in that set, from start to end. This expectation turns out to be incorrect:

irb(main):001:0> (1..3).each { |i| puts i }
1
2
3
=> 1..3
irb(main):002:0> (3..1).each { |i| puts i }
=> 3..1
irb(main):003:0>

Returns from the block[edit]

return statement returns from the context it was defined in, not from the context it is being executed in. For example:

def boo()
  transaction do
    ...
    return if(something)
    ...
  end
end

One might expect that return would allow the transaction to be properly closed, but unfortunately that's not the case—the code in transaction() taking care of that will never be executed if something==true.

Implicit exception catching[edit]

begin
  ...
rescue => err
  ...
end

One would expect that the rescue block would catch any exception thrown, but this is not true—it will only catch RuntimeError (which is a subclass of StandardError, which in turn is a subclass of Exception). If you intend to catch all exceptions indeed, the following syntax should be used:

begin
  ...
rescue Exception => err
  ...
end

Implicit array creation[edit]

Under certain circumstances, comma implicitly binds the values into an array:

irb(main):001:0> 1, 2
SyntaxError: compile error
(irb):1: syntax error, unexpected ',', expecting $end
        from (irb):1
irb(main):002:0> a = 1, 2
=> [1, 2]

So, certain typos that would be immediately noticed in other languages, would not trigger a syntax error in Ruby and may lead to some time-consuming investigations:

irb(main):001:0> def hello
irb(main):002:1>   a = 1
irb(main):003:1>   b = 2,
irb(main):004:1*   c = 3
irb(main):005:1>   d = 4
irb(main):006:1>   puts "This is my program that will say Hello to the world!"
irb(main):007:1>   if a == 1 then
irb(main):008:2*     if b == 2 then
irb(main):009:3*       if c == 3 then
irb(main):010:4*         if d == 4 then
irb(main):011:5*           puts "Hello World!"
irb(main):012:5>         end
irb(main):013:4>       end
irb(main):014:3>     end
irb(main):015:2>   end
irb(main):016:1>   puts "My program has said Hello!"
irb(main):017:1> end
=> nil
irb(main):018:0> hello
This is my program that will say Hello to the world!
My program has said Hello!
=> nil

Gotchas in DOS batch file language[edit]

One would expect that the batch file would execute commands exactly as if they were typed manually. This is not true in the particular case when one of the commands is determined to be another batch file, in which case it simply replaces the one currently being executed.

C:\test>copy con main.bat
@echo off
echo Starting main.bat
subr
echo Completing main.bat
^Z
        1 file(s) copied.
C:\test>copy con subr.bat
@echo off
echo Starting subr.bat
echo Completing subr.bat
^Z
        1 file(s) copied.
C:\test>main.bat
Starting main.bat
Starting subr.bat
Completing subr.bat
C:\test>

References[edit]

External links[edit]