📜 ⬆️ ⬇️

My favorite programming mistakes

During my programming career, I made a huge number of mistakes in several different languages. In fact, if I write 10 or more lines of code that work the first time, I become suspicious and accept to test it more carefully than usual, suggesting to find a syntax error, or an incorrect reference to an array, or an incorrectly written variable, or something else.

I like to subdivide these errors into three large groups: failures, errors and omissions. Failure is when you are sitting stupidly looking at the screen and quietly saying “oh”; things like deleting a database or a whole site, writing something on top of the result of a three-day job, or accidentally sending a letter to 20 thousand people.

Errors can be different: from simple syntax errors (for example, forget to put}) to critical errors and errors in calculations.
')
When a mistake is so unobvious and elusive that it is almost perfect, I call it a flaw. This happens when a piece of code is faced with completely unpredictable and very unlikely circumstances. You lean back in your chair and think “Wow!”, As if seeing a bright rainbow or a shooting star.

Debugging not turned off

The first two mistakes, which I will mention in this article, were full-fledged failures.

When I first started freelancing, I wrote a number of PHP libraries to process database requests, forms, and page templates. The debug mode was built into the libraries at a fairly deep level, which depended on the global variable $ DEBUG .

I also saved a local copy of each major site I worked on for development, debugging, and testing. Thus, when a problem arose, I could always set $ DEBUG = 1; who informed me of various things, for example, all database statements executed. I rarely used this method of searching errors for online sites, it was intended for local use.

But one day I was working late at night fixing a small problem on one popular e-commerce site. I set $ DEBUG = 1; at the top of several pages and switched between them. From fatigue in my head everything mixed up, and in the end I somehow added the debug variable to the most important page of the site that users see immediately after clicking the Pay Now button, and in this form uploaded to the working version of the site.

The next morning I left home early, and, returning at 9 pm, I found 12 messages on the answering machine, one more irritated by the other, and much more e-mails. For about 20 hours, users who clicked on "Pay Now" have seen something like this:
image
I spent only 10 seconds to correct the error, but much more time was spent on apologizing to the client for a whole day of lost orders.

Lessons learned

I thought about this case and found that I should:
1. Avoid working late at night
2. Carry out full testing every time I make even minor changes in order processing.
3. Ensure that debug reports will never appear on a running site.
4. Provide the customer with emergency contact information.

Attentive debugging

In connection with the third paragraph, I wrote several functions so that the display of debugging messages is displayed only for me:

 function CanDebug() { global $DEBUG; $allowed = array ('127.0.0.1', '81.1.1.1'); if (in_array ($_SERVER['REMOTE_ADDR'], $allowed)) return $DEBUG; else return 0; } function Debug ($message) { if (!CanDebug()) return; echo '<div style="background:yellow; color:black; border: 1px solid black;'; echo 'padding: 5px; margin: 5px; white-space: pre;">'; if (is_string ($message)) echo $message; else var_dump ($message); echo '</div>'; } 

The $ allowed array contains my IP address for local testing (127.0.0.1) and the external IP.
Now I can output things like:

 $DEBUG = 1; Debug ("The total is now $total"); //about a debugging message Debug ($somevariable); //output a variable Debug ("About to run: $query"); //before running any database query mysql_query ($query); 

And to be sure that no one except me will see any debugging messages. Provided that the above variables are specified, the code will look like this:
image
For more security, I could also transfer error messages inside HTML comments, but then I would have to dig into the code for a long time to find the right piece.
I have another useful code snippet that can be put at the top of the page or configuration file so that all PHP notifications, warnings and errors are visible only to me. Errors and warnings will be recorded in the logs, but not shown on the screen.

 if (CanDebug()) {ini_set ('display_errors', 1); error_reporting (E_ALL);} else {ini_set ('display_errors', 0); error_reporting (E_ALL & ~E_NOTICE);} 

Debuggers

Such a method is convenient for quickly finding errors in well-defined code fragments. There are also various debugging tools, such as FirePHP and Xdebug , that can provide a wealth of information about the code. They can also act invisibly by listing all function calls in a log file, without being shown to the user. Xdebug can be used like this:

 ini_set ('xdebug.collect_params', 1); xdebug_start_trace ('/tmp/mytrace'); echo substr ("This will be traced", 0, 10); xdebug_stop_trace(); 

This code logs all function calls and their parameters in the /tmp/mytrace.xt file, which looks like this:
image
Xdebug also shows much more information about any PHP warning or error. However, it must be installed on the server, so it is most likely impracticable for most hosting sites.
FirePHP, on the other hand, works as a PHP library that interacts with the Firebug addon. You can display debugging information from PHP directly to the Firebug console - again, invisible to the user.
In both methods, a function such as CanDebug above is still useful to ensure that stack traces and log files are not available to every Firebug owner.

Turn off debug mode

Debugging email scripts is more confusing. Checking whether the script sends a letter in the right way is definitely difficult without actually sending the letter. What I once did by mistake.

A few years ago, I was asked to create a massive email script to send daily e-mails to more than 20 thousand subscribers. During development, I used something similar to the CanDebug function to be able to test the script without sending a letter. The e-mail function looked like this:

 function SendEmail ($to, $from, $subject, $message) { if (CanDebug() >= 10) Debug ("Would have emailed $to:\n$message"); else { if (CanDebug()) {$subject = "Test to $to: $subject"; $to = "test@test.com";} mail ($to, $subject, $message, "From: $from"); } } 

If I set $ DEBUG = 1, the script sent e-mails (all 20 thousand) to the test address, which I could check. If I set $ DEBUG = 10, he informed me that he was trying to send an e-mail, but in fact did not send anything. Soon after the launch, problems started with the script. I think his memory has run out due to the inefficient processing of information 20,000 times in a row. At some point, I went into correcting some error, forgot about my $ DEBUG variable (or my external IP changed at the wrong time) and accidentally sent letters to 20 thousand people.

I apologized to the agency I worked for, but fortunately there were no particular consequences. Probably, many letters were blocked by spam filter. Or perhaps the recipients were simply pleased that the letter was empty.

Lessons learned

I was very glad that I simply left the word “test” as the subject and content of the letter, and not any statement reflecting my dissatisfaction with the resulting bug. I learned what was needed:
1. Be especially careful when testing mass email scripts - check if the debug mode is working.
2. Send test emails to as few people as possible.
3. Always write something polite in the text of the letter, for example, "Please ignore this test message." It is undesirable to write something like “My client is a fool” - is it enough that 20 thousand unsuspecting investors will read this?

Blank php page

Now we have reached from the realm of failures to the field of subtle errors. If you want to see an error that is hard to get rid of, bury the following somewhere in the depths of your code:

 function TestMe() {TestMe();} TestMe(); 

Depending on the browser and versions of Apache and PHP on the server, you may get a blank page, “This Web page is not available,” a critical error related to a lack of memory, or the sentence “Save” or “Open” page:
image
This essentially causes endless recursion, which can cause a memory shortage and / or termination of the server flow. If it stops working, a small trace may remain in the error logs:
[Mon Jun 06 18:24:10 2011] [notice] child pid 7192
exit signal Segmentation fault (11)
This gives us an indication of where and why the error occurred. And all the fast debugging methods with the addition of lines of output in different places will not give a special result, as long as the problem code is executed, the whole page will not work. This is mainly due to the fact that PHP sends generated HTML only periodically to the browser. Therefore, adding a set of flush () expressions; at least show you what your script did immediately before the recursive error.
Of course, the code that causes this error may be much more sophisticated than that shown above. It may include methods that call classes in other classes that refer back to the original classes. And this error can only happen in difficult-to-reproducible circumstances, and only because you have changed something else somewhere else.

Lessons learned

1. Know the location of error logs in case something is written there.
2. In such situations, stack-tracing debuggers like Xdebug can be very useful.
3. Otherwise, save a lot of time to go through all the code, line by line, and comment out unnecessary parts until it works.

Wrong type of variable

This error often happens in databases. If these SQL statements are given ...

 CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(60), category VARCHAR(10), price DECIMAL(6,2) ); INSERT INTO products VALUES (1, 'Great Expectations', 'book', 12.99); INSERT INTO products VALUES (2, 'Meagre Expectations', 'cd', 2.50); INSERT INTO products VALUES (3, 'Flared corduroys', 'retro clothing', 25); 

... guess what will come back if you run the following?

 SELECT * FROM products WHERE category='retro clothing'; 

The answer is nothing, since the category column is only 10 characters long, so the category of the last product is trimmed to retro clot. The unexpected disappearance of newly modified products or new menu items can bring a lot of confusion. But usually this is fairly easy to fix:

 ALTER TABLE products MODIFY category VARCHAR(30); UPDATE products SET category='retro clothing' WHERE category='retro clot'; 

image
I made a more serious mistake when working on my first major e-commerce site. At the end of the ordering process, the site asked the customer to enter the credit card details and then invoked a Java program that sent a payment request to the Barclays ePDQ system. The amount was calculated in pence. I was not familiar with Java, so I took as a basis the found sample code, which represented the sum as
short total;
The java program was called from the command line. If she did not return anything, then the transaction was considered completed, the client received an e-mail and the order was completed. If an error occurred while checking the credit card, the program returned messages like “The card is not authorized” or “The card did not pass the authenticity check”.
Short integers can hold values ​​between -32768 and +32767. These numbers seemed huge. But I didn’t pay attention to the fact that the sum was calculated in pennies, not pounds, that is, the largest possible amount was £ 327.67. And the worst thing is that if the order amount was greater than this, the Java program simply stopped working and returned nothing. It looked exactly the same as the successfully completed order, and then the buying process went on as usual. The error was noticed either by the accounting department, or by a vigilant and honest buyer only after several months, after several large unpaid orders.

Lessons learned

1. When assigning a type to a column in a database or variable, one must be prudent.
2. We must make sure that the successful completion of the program is different from the program that stops working.

“Errors of one penny”

Among my favorite mistakes can be called those that cause a discrepancy of only 1 pence (cent, era or another coin). I like them because they are very difficult to track down, and they often come down to rounding errors.
A few years ago, I needed to create a quick JavaScript function for one site that displays the amount of money. I used this:

 <script type="text/javascript"> function GetMoney (amount) {return Math.round (amount * 100) / 100;} </script> 

However, it soon became clear that such amounts as 1.20 were displayed on the screen in the form of 1.2, which looked unprofessional. So I changed the code like this:
 <script type="text/javascript"> function GetMoney (amount) { var pounds = Math.floor (amount); var pence = Math.round (amount * 100) % 100; return pounds + '.' + (pence < 10 ? '0' : '') + pence; } </script> 

The main difference is the extra 0 in the last line. But since now the penny is calculated separately, the% operator is needed to get the remainder when the quantity is divisible by 100. Try to find such unlikely circumstances in which this code will cause an error.
It happened on the site where they sold beads. Then I learned that beads can be sold in various forms and quantities, including custom-made blends containing fractional values. One day, a customer bought a 1.01 item worth ÂŁ 4.95 and paid only ÂŁ 4.00. Since the amount was determined as 4.9995, the program rounded up pennies to 100, and% 100 left 0 pence. Thus, the amount paid decreased to 4 pounds.
image
A simple flaw in rounding, where for 101 beads sold for ÂŁ 4.95 for a hundred, paid ÂŁ 4 instead of ÂŁ 5.
I quickly corrected the code:

 <script type="text/javascript"> function GetMoney (amount) { var pounds = Math.floor (amount); var pence = Math.floor (amount * 100) % 100; return pounds + '.' + (pence < 10 ? '0' : '') + pence; } </script> 

However, this was not a good fix, as it rounded ÂŁ 4.9995 to ÂŁ 4.99, which prevented synchronization with any relevant calculations from the server. Even worse, when ordering 0.7 of something worth ÂŁ 1.00, the amount came out 69 pence instead of 70! This happened because floating-point numbers such as 0.7 are represented in binary code as rather 0.6999999999999999, which will then be reduced to 69 pence instead of rounding to 70.
This is a real "single-penny error." To fix it, I added another rounding at the beginning:

 <script type="text/javascript"> function GetMoney (amount) { var pence = Math.round (100 * amount); var pounds = Math.floor (pence / 100); pence %= 100; return pound + '.' + (pence < 10 ? '0' : '') + pence; } </script> 

Now I had four lines of very complex code to do one very simple thing. Today, while I was writing this article, I found a feature built into Javascript that would handle all of this:

 <script type="text/javascript"> function GetMoney (amount) {return amount.toFixed (2);} alert (GetMoney (4.9995) + ' ' + GetMoney (0.1 * 0.7)); </script> 


Discount with PayPal

PayPal is a “single-penny error” that is waiting for its time. Many sites offer codes that give a discount of a certain percentage of the amount of the order. It is calculated at the very end. If you ordered 2 items of 95 pence each, the total will be £ 1.90 and you will receive a discount of 19 pence, therefore pay £ 1.71.
However, PayPal does not support this type of discount. If you want PayPal to show items in your shopping cart, you need to separately calculate the price and quantity of each of them:

 <input name="item_name_1" type="hidden" value="My Difficult Product" /> <input name="amount_1" type="hidden" value="0.99" /> <input name="quantity_1" type="hidden" value="1" /> 

Thus, you should receive a discount for each item separately. A 10% discount of 95 pence leaves 85.5 pence. PayPal does not use fractional numbers, so you need to round them to 86 pence, which gives you a total of ÂŁ 1.72 in PayPal. If rounded to 85p, the total will be ÂŁ 1.70.
To solve this problem, I also had to calculate the discount for each item separately. Instead of the usual calculation of 10% Ă— ÂŁ 1.90, the code accumulates a discount from item to item, each time using the full amount of pence. Assuming $ items is a php array of order items:

 $discount = 0; $discountpercent = 10; foreach ($items as $item) { $mydiscount = floor ($item->price * $discountpercent) / 100; $item->priceforpaypal = $item->price - $mydiscount; $discount += $mydiscount * $item->quantity; } 

Lessons learned

1. Do not reinvent the wheel, even very small wheels that look simple.
2. If you have a discrepancy of 1 pence, check where and how the numbers are rounded.
3. Avoid presenting prices using float format variables, if possible. Instead, for pence and cents, use integers, and in databases use the type of variables with a fixed comma - DECIMAL.
Clock translation

I would not call this error "error." It appears under special rare circumstances, so from the programmer’s point of view, this is more of a “flaw”. It goes beyond the boundaries of everything that a programmer is able to provide.
Can you guess what is wrong with this seemingly innocuous line of code that marks out orders executed more than one week ago?

 mysql_query ("SELECT * FROM orders WHERE completeddate < '" . date ('Ymd H:i:s', (time() - 7 * 86400 + 600)) . "'") 

I used a similar line in the system for recurring weekly orders. She chose the orders completed last week, duplicated them and designed them for the current week. 86,400 - the number of seconds in one day, so time () - 7 * 86400 was exactly a week ago, and +600 adds 10 minutes of spare time.
It was a low-budget method of implementing recurring orders. If I had more time, I would create a separate table and / or shopping cart to distinguish between recurring and non-recurring orders. It turned out that this code worked well for several months and, for mysterious reasons, failed at the end of March.
Elimination of the defect and its consequences took a lot of time, I had to fulfill orders manually. It took even more time to identify the cause, especially because I had to force the whole site to consider that it was another day outside.
I, in general, already talked about the reason in the title of the section: I forgot to take into account the transfer of hours to summer time, when one week is less than 7 * 86400 seconds.
Compare the following three ways to get a date exactly a week ago. The latter is the most elegant. I only recently discovered it:

 $time = strtotime ('28 March 2011 00:01'); echo date ('Ymd H:i:s', ($time - 7 * 86400)) . '<br/>'; echo date ('Ymd H:i:s', mktime (date ('H', $time), date ('i', $time), 0, date ('n', $time), date ('j', $time) - 7, date ('Y', $time))); echo date ('Ymd H:i:s', (strtotime ('-1 week', $time))) . '<br/>'; 

Lessons learned

It is difficult to draw general conclusions from such an error, but a certain lesson was learned:
1. On sites with something repetitive, do not forget to take into account time zones and the transfer of hours.
2. Again, do not reinvent the wheel.

Conclusion

Errors in programming come in different shapes and sizes. In this article, they range from quite obvious failures to incredibly subtle miscalculations. And it seems that they all confirm the law of Murphy: if the trouble can happen, it will happen.

However, for every found, declared and corrected error, it is possible that there are several remaining in the code. They may not be found (because the unusual conditions that cause it were never reproduced), they may not be reported (since most users do not bother to send an error report), or they are not corrected due to excessive cash or temporary costs.

In addition, mistakes are more common on popular sites - mainly because they are made by many people, but also because correcting one mistake may lead to the appearance of new ones in other places. So, you should think ahead and carefully correct the errors.

Source: https://habr.com/ru/post/124233/


All Articles