When I first started Web Development, I came from a background of programming in C and C++, for various hobby projects I had. I had written several clones of popular arcade games, such as Pacman, and some miscellaneous sysadmin tools. I never worked with anyone else, and everything I learned up to this point was from reading internet tutorials and books I got from the Library. In many areas, such as database systems, I only had very basic knowledge, and being naive, I simply accepted that PHP was the "best" language for building websites. After all, I was new to this type of development, and anytime I asked anyone what the best language for writing websites was, they would say PHP without hesitation.
So one afternoon, I set out to learn PHP. It was very easy to pick up. In fact, I wrote several different utilities in PHP that day. PHP had several things going for it:
At first glance, it seemed that what everyone said about it was completely true. So I set out with this newfound tool, and began writing websites… Then, after a few months, we noticed something strange. One of the sites on the server started having backslashes randomly inserted behind certain characters! None of the other sites we're having this issue, just this ONE site! So after some investigation, we came to the conclusion that somehow Magic Quotes were being enabled on the site, even though they had been disabled in the config, so everytime you saved a form from the admin panel, it would add garbage to it. Curiously, the problem only starting ocurring recently, after deploying a new site. Upon further inspection, we discovered that the configuration for Magic Quotes was somehow being leaked over from this newly deployed site. The cleanup was a mess. Most of the database had had been garbaged, and it took months to fix.
And this is the problem with PHP. At first glance, it looks like a shiny, expensive sports car, with sweet rims and satellite radio, but as soon as you open the hood you discover the engine is actually a midget-powered hamster wheel, the rims are made out of plastic, and the car explodes anytime you run over a speed bump. Forgot to read the fine print? Well now you're stuck maintaining this unholy mess of a car for the next 5-10 years.
Even doing the most basic task in PHP, like comparing the equality of two variables, is like trying to navigate a minefield. Here's some examples of how broken the language is:
cleure@Marvin:~/$ php -r 'var_dump("\n\v\f\t0xff+100-100" == 255);'
bool(true)
cleure@Marvin:~/$ php -r 'var_dump("0x2ca0b3921230067030d887d0a22bd34f" == 59320441436161440500000000000000000000);'
bool(true)
cleure@Marvin:~/$ php -r 'var_dump("61529519452809720693702583126814" == "61529519452809720000000000000000");'
bool(true)
None of these statements should evaluate to true, and yet they do! You literally can't trust anything compared using "==", but you also can't trust "===", because it produces bad results when comparing objects. Because of this discrepancy, you have to write a significant amount of boilerplate, just to do a simple comparison! Even experts find themselves falling into these traps, so just imagine how frequently someone without knowledge of PHP internals has written code with a bug or a security hole without even realizing it! The code looks fine, so it must be fine, right? Well, with PHP there's no guarantee!
Lets talk about Exceptions. In all sane languages, they provide an easy way to handle recoverable errors, while allowing unrecoverable errors to fall through and either wipe out the running process, or fail in someway. This is kind of the whole point of Exceptions, actually. In Python, you have Exception classes such as: TypeError, ValueError, SyntaxError, MemoryError, etc. It enables you to write code like this:
try:
do_something()
except RecoverableError1, RecoverableError2, RecoverableError3:
# These can be recovered
recover()
except:
# Everything else fails
fail()
While PHP allows you to write code LIKE this, the language itself rarely uses Exceptions, and when it does, it uses them in ways that are completely wrong and useless. For instance, PHP's database extension, PDO, uses Exceptions. However, it has exactly one Exception class: PDOException. What happens if you pass an invalid type? PDOException. What happens if you can't connect to the database? PDOException. What happens if you have a syntax error in a query passed to "PDO::exec()"? PDOException… It's as if they said "Hey, this is a really cool idea, and it allows us to write better code… But maybe its a little too useful. We need to make it useless! Yeah, lets make it completely useless, like people will look at it and ask themselves why we even did this in the first place!?!".
That's all I have for now.
Something I find myself often doing when coding in C, is writing function templates to create multiple versions of the same function, but for working with different datatypes. It's somewhat similar to Templates in C++, but it's achieved by abusing the C Preprocessor, rather than using an actual feature of the language.
Consider the following scenario: you're writing a program that manipulates images, but the input format varies wildly (it could be packed RGB15, RGB16, RGB24, etc), and you need to convert it to a common format so it can be worked on. Instead of writing multiple functions to convert from each input format, you could write common function(s) whose behavior is dependent on defined macros. Then you can include the file containing the template functions multiple times, but with different macros set, resulting in multiple functions being compiled to object code from the same function templates. So, for instance, I might have a file named "convert-rgb.c", with the following function templates:
/**
* Convert unpacked buffer (RGB, 8 bits per channel) to an arbitrary packed
* format. This is Macro-tised to support multiple formats.
*
* @param uint8_t *input
* @param int width
* @param int height
* @return CV_PACKED_STORAGE *
**/
CV_PACKED_STORAGE * CV_TO_PACKED_FUNC (uint8_t *input, int width, int height)
{
int x, y, i;
CV_PACKED_STORAGE *output, *dst;
output = malloc(width*height*sizeof(CV_PACKED_STORAGE));
dst = output;
i = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
uint8_t r = (int)round((double)input[i] / CV_R_SCALE) & CV_R_MASK,
g = (int)round((double)input[i+1] / CV_G_SCALE) & CV_G_MASK,
b = (int)round((double)input[i+2] / CV_B_SCALE) & CV_B_MASK;
*dst++ = (r << CV_R_SHIFT) | (g << CV_G_SHIFT) | (b << CV_B_SHIFT);
i += 3;
}
}
return output;
}
/**
* Convert arbitrary packed format to an unpacked buffer (RGB, 8 bits per channel).
* This is Macro-tised to support multiple formats.
*
* @param CV_PACKED_STORAGE *input
* @param int width
* @param int height
* @return uint8_t *
**/
uint8_t * CV_FROM_PACKED_FUNC (CV_PACKED_STORAGE *input, int width, int height)
{
int x, y, i;
uint8_t *output, *dst;
output = malloc(width * height * 3);
dst = output;
i = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
uint8_t r = input[i] >> CV_R_SHIFT & CV_R_MASK,
g = input[i] >> CV_G_SHIFT & CV_G_MASK,
b = input[i] >> CV_B_SHIFT & CV_B_MASK;
*dst++ = (int)((double)r * CV_R_SCALE) & 0xff;
*dst++ = (int)((double)g * CV_G_SCALE) & 0xff;
*dst++ = (int)((double)b * CV_B_SCALE) & 0xff;
i++;
}
}
return output;
}
And then a header file named "rgb16.h", that defines macros associated with the packed RGB16 format:
#ifndef CLETV_RGB16_DOT_H #define CLETV_RGB16_DOT_H 1 #define RGB16_R_SHIFT 11 #define RGB16_G_SHIFT 5 #define RGB16_B_SHIFT 0 #define RGB16_R_MASK 0x1f #define RGB16_G_MASK 0x3f #define RGB16_B_MASK 0x1f #define RGB16_GET_R RGB16_R_SHIFT & RGB16_R_MASK #define RGB16_GET_G RGB16_G_SHIFT & RGB16_G_MASK #define RGB16_GET_B RGB16_B_SHIFT & RGB16_B_MASK #define RGB16_SCALE_R ((double)255.0 / (double)RGB16_R_MASK) #define RGB16_SCALE_G ((double)255.0 / (double)RGB16_G_MASK) #define RGB16_SCALE_B ((double)255.0 / (double)RGB16_B_MASK) #define RGB16_STORAGE uint16_t #endif
But we still don't have anything to "glue" the macros in rgb16.h to convert-rgb.c, so we have to create another header file to define the proper macros, include the "convert-rgb16.c", and finally #undef the newly set macros:
#ifndef CLTEV_RGB16_CONVERT_DOT_H
#define CLTEV_RGB16_CONVERT_DOT_H 1
#define CV_TO_PACKED_FUNC buffer_to_packed_rgb16
#define CV_FROM_PACKED_FUNC packed_rgb16_to_buffer
#define CV_PACKED_STORAGE RGB16_STORAGE
#ifdef CLETV_CONVERT_INTERNAL
#define CV_R_SHIFT RGB16_R_SHIFT
#define CV_G_SHIFT RGB16_G_SHIFT
#define CV_B_SHIFT RGB16_B_SHIFT
#define CV_R_MASK RGB16_R_MASK
#define CV_G_MASK RGB16_G_MASK
#define CV_B_MASK RGB16_B_MASK
#define CV_R_SCALE RGB16_SCALE_R
#define CV_G_SCALE RGB16_SCALE_G
#define CV_B_SCALE RGB16_SCALE_B
#include "convert-rgb.c"
#undef CV_R_SHIFT
#undef CV_G_SHIFT
#undef CV_B_SHIFT
#undef CV_R_MASK
#undef CV_G_MASK
#undef CV_B_MASK
#undef CV_R_SCALE
#undef CV_G_SCALE
#undef CV_B_SCALE
#else
CV_PACKED_STORAGE * CV_TO_PACKED_FUNC (uint8_t *input, int width, int height);
uint8_t * CV_FROM_PACKED_FUNC (CV_PACKED_STORAGE *input, int width, int height);
#endif
#undef CV_TO_PACKED_FUNC
#undef CV_FROM_PACKED_FUNC
#undef CV_PACKED_STORAGE
#endif
Depending on if CLETV_CONVERT_INTERNAL is defined or not, controls whether the functions are declared or defined. When compiling the conversion functions to object code, you'd define CLETV_CONVERT_INTERNAL in a .c file, and then include the above header file, probably along with several others which set macros for other formats, which would result in several conversion functions residing in a single .o file, but any other file that includes the same headers only sees the function declarations.
As you can imagine, using this method can quickly become overly complex, but it works well when there's a small number of differences between many different functions that do essentially the same thing.