Isn't writing new code great? Wouldn't the world be a better
place if all were ever had to do is write software from scratch, not having to
worry about methods of classes past? Unfortunately, we all know that this is
not the case. In fact, estimates say that we spend around 80% of our
programming time maintaining old code. So for this blog post will be trying to
tackle that 80%, and I will see what I can do to make it less painful.
Enable Notices (on your development environment!)
The wonderful developers of PHP (no, not PHP developers)
bestowed a great gift when they created the language we all know and love, and
that was the gift of notices.
What are notices? A notice is a type of error message which is less
severe than parse, fatal and warning error messages. A notice may tell you
something along the lines of "Hey, you have used that variable without defining
it! What gives?"
Notices are very useful tool for avoiding bugs during
development as they can catch problems caused by mistyped variable names or
non-existent array indexes. This will save you a lot of headaches.
If you have been developing with notices turned off then please go and turn them on. Please! You can do this in one of three
ways. Firstly, you can do this in your php.ini file:
error_reporting = E_ALL
Or you can insert the following line at the start of your
PHP code:
ini_set('error_reporting',
E_ALL);
Lastly, you can set this on a per-virtual host in Apache
HTTPD using the following:
php_value error_reporting 8191
If you want to test that notices are turned on you can
simply create a file with the following:
At this point I should stress that you should always ensure
that the display_errors ini setting is set to 'Off' for any live/production sites. It is a very bad
idea to allow the public to view your error messages because a) it looks bad,
and b) it can give away a lot of information to potential crackers.
Now that you have turned notices on you may find that your
code creates a lot of them. I would start correcting these at the first chance
you get as you will never see new notices amid such clutter. You may find empty() and is_set() useful.
See also:
Use a Logging System
A logging system can be very useful in tracking down bugs,
especially when they happen in a production environment. Such a system can also
be useful in debugging during development but I find it much easier to use an
IDE to debug my development environment (more on this shortly).
I feel that it is important to log any significant actions
performed (e.g. user created, group deleted, registration email sent) as well
as any errors that occur. Some people advocate logging a message every time you
enter or leave a function. I find that this method to be a bit too verbose,
especially with more complex applications. I prefer to ditch the reams of log
messages for a good IDE and debugger.
When you actually come to log your messages I would
recommend having a fallback mechanism (you never know, the logger itself could
have just broken!) For example, you could try logging to a database, and
failing that you can append to a log file, and failing that you can send an
email. You may find exceptions useful for this.
See also:
Log Errors
We have to accept, that despite out best efforts, errors can
(and do) occur in production environments. When these hiccups do arise we have
to ensure that they are dealt with quickly, otherwise users (or even, gasp, clients) get angry.
Some types of errors we cannot do anything about (think
parse errors) and we just have to ensure that we have a close eye on our error logs
so we notice when they occur. Fortunately, it is these types of bugs that are
normally caught very quickly during development and testing.
As for all the other errors, we need to make sure that they
are caught and dealt with properly. Make sure the user is shown a nice error
page (with a suitably cute 'oops-back-soon' picture) and then log, log
everything in sight! I recommend storing the following:
- The stack current trace (see debug_backtrace() and debug_print_backtrace()).
- The output of get_defined_vars().
However, this is only useful if you call it at the point the error occurs, not
at the point the error is logged. This includes global variables.
- Any and all information about the remote user (IP address, user
agent, session data)
- All global variable data (which includes the contents of
$_COOKIES, $_SERVER etc.)
- Any other status data which is specific to your application
This information will be invaluable when you come to
tracking down errors in production code.
How you choose to actually capture your errors is your own
choice. You can use trigger_error and a custom error handler, but I prefer to
use a custom exception class which takes care of logging for me.
See also:
Check Function Parameters
Checking function parameters can help you catch a lot of
bugs before the erroneous data passes too far through your application. I like
to test that the input parameters are of the expected type and are reasonably
sane.
To avoid too much clutter I generally only do this on
utility methods (as these are used often and in many places) and methods which
permanently manipulate data such as files and databases (as errors here have
the potential to destroy a lot of data).
See also:
Use an Integrated Development Environment and Debugger
I created my first ever website on a Geocities account. To
do this I used was a textarea field in the Geocities admin area. It was OK for
the simple HTML I was writing, but I would not like to create an entire PHP
application in this way!
I have now moved on to using an Integrated Development
Environment (IDE) for the majority of my development, and I highly recommend
that you do the same. I use Zend
Studio and you only have to look at the feature list to see why
I find it completely indispensible, especially for debugging. If you have not
used an IDE before I recommend you have a look at one of the applications
listed at the end of this section.
I also use a remote debugger (ZendDebugger), which
ties into the IDE. The remote debugger is a PHP module that allows you to debug
code on your server using the IDE on your local machine. You can set
breakpoints, inspect variables, examine stack traces, profile code and all the
other benefits you would expect from a debugger. And no, Zend does not sponsor
me.
See also:
Unit Testing
Unit
testing may not be everyone's idea of fun, but I can be very effective for
developing larger projects. It can give you confidence when you have to make
significant changes to the code base, as well as point out problems before your
code goes into production.
There are two catches with unit tests, the biggest of which
is that you have to actually write the Unit tests themselves. Although this
should save time in the long run (or at least lead to a more robust product),
it is hard to avoid thinking that you could be spending the time developing
functionality.
The second catch is that it often forces you to refactor
your code into more test-friendly chunks. This is probably a good thing but it will
take more time. The best approach would be to write unit tests from the very
start of the project or, for an existing project, you can write a unit test for
every bug that is fixed.
If you are using unit tests you should also be aware of the
concept of code coverage.
This is a metric which shows what percentage of your code is run during the
testing process. The higher value for this indicates a more robust set of unit
tests. You can calculate your code coverage using a debugger, as was discussed
in the previous section.
See also:
No Magic! (Or, Avoid Side Effects)
A side effect can be described as a non-obvious effect that
was caused by performing an action (see Wikipedia for a more technical description). For example:
So what is going on here? You can see that we start with a
$radius and $area variable which we use to show the area of the circle. We then
want to display the area of a square with the same dimensions, so we call
getSquareArea. Although this function does what its name implies, it also
alters the $radius variable (intentionally or not). This is defiantly
non-obvious in the rest of our code and can cause severe headaches when it
comes to debugging, especially in more complex applications. Of course, you
should also avoid global variables for similar reasons.
This also applies to modifying function parameters (which were
passed by reference). If you find yourself doing this then you should probably
refactor your code. To do this you can either return the parameter rather than
modifying it, or you can split the function into several smaller functions.
Also, don't forget that objects are always passed by reference in PHP 5.
Use Manual Redirects When Debugging
Many developers (including myself) will make use of
redirects when developing web applications. To refresh your memory, here is how
you do a redirect in PHP:
This technique can be very useful for sending the user to
the correct page, but it can also be very problematic for debugging. For
example, do you keep getting sent off to a bizarre area of your application? Do
you know if it is just one redirect sending you there or many? What do you do
when you get trapped in an infinite redirect loop?
My answer to these problems was to introduce the concept of
manual redirects which would only be used for debugging. Rather than sending a
header to the client, I would send a link to the target page as well as a stack
trace. This would allow me to monitor the redirects that were happening in my
application and clearly see what was happening if the application went wrong.
The code I use looks something like this:
You may find it useful to pull the $debug value from your
configuration system of choice rather than having to pass it for each function
call, but it works in this example.
Keep Things Simple
I think this rule probably exists for every profession out
there, so it should be no surprise that it applies to software development.
It is good practice to write software using a clear
structure and using standard design patterns, but this is only a high level
approach to keeping things simple. We also need to keep your individual
algorithms as simple as possible as this will make your code easier to
understand in six months when it needs fixing, and will also make it easier to
fix.
Here are some ways you can achieve this:
- Keep an eye on functions
that are growing. You may find that you can split the code into several
smaller functions.
- Functions that are only
called in one place may be too specific. You can either bring the code
inline, or generalise using several smaller functions. You can always keep the
specific function and just use that to call and aggregate the new, smaller,
functions.
- Watch out for functions
with very long names or lots of arguments. This can be a sign that the
function could be split into several smaller functions, or it could even be
replaced with a class.
- Use built in functions
where possible. This will help avoid spurious amounts of PHP code and there
is a good chance the internal function will be faster as it is written in C
(and by the pros!) Some of the most underappreciated internal functions are the array functions.
- If you really must have long and complex sections of code, then
make sure you add some documentation.
You and your fellow developers will be thankful of this when it comes to
debugging.
Most of these points are about splitting large functions
into smaller ones. It is also important to ensure you do not end up with lots
of tiny functions, but I feel this is a much more unusual problem.
See also:
Conclusion
This blog post has, once again, become much longer than I
expected! However, you have probably had a good overview of each of the
techniques mentioned. If you have any other good ideas then I would love to
hear them, so please leave a comment!
Trackbacks
I have been writing this blog for a bit over a month now and I thought that it would be good to get some feedback from all of the site’s readers - i.e. you folks! I have been writing this blog for a bit over a month now and I thought that it would be
Tracked: May 16, 09:22
I have been writing this blog for a bit over a month now and I thought that it would be good to get some feedback from all of the site’s readers - i.e. you folks! I have been writing this blog for a bit over a month now and I thought that it would be
Tracked: May 16, 09:23