Getting Down To Business
In the first two parts of this article, I spent lots of time and bandwidth spouting off about requirements, design, and the various types of documents you should develop prior to actually beginning implementation of a software project. If you're the kind of developer who hates documentation (is there really any other kind?), you've probably been wishing I'd just shut up and get to the point...or, in this case, the code.
Well, your wishes are about to be answered.
In this third part, I'll be focusing exclusively on the implementation phase of a software project, suggesting some general techniques and approaches that might come in handy when you finally sit down to write your application. The ideas discussed in the following pages are not new - heck, they're not even exhaustive - but they should nevertheless help you deliver cleaner, faster and more maintainable code.
The Name Game
Shakespeare famously said, "What's in a name? A rose by any other name would smell as sweet". And this is true - so long as you're talking about roses. In the world of software development, though, naming conventions and coding standards acquire significance in making your code easier to read, understand and maintain.
There are some basic rules to be kept in mind when naming objects within your application. Here's a brief list:
Choose meaningful names for your objects. "itemCode" is far more descriptive that "icd".
Capitalize the first character of each word in your chosen name, or use underscores to separate words - this makes the name easier to read.
Use names that are short and easy to pronounce.
Ensure that the name chosen is not ambiguous.
Use capitalization and/or underscores to differentiate between private and public variables, or local and global variables.
Begin function or method names with a so-called "action word" - for example, "getItem()" or "doQuery".
It's also important to decide on and follow a specific coding standard when naming your variables and functions, and to carry this convention through consistently in all your scripts. This coding standard should specify the naming conventions for variables, functions and objects, file name prefixes and suffixes, file organization rules, indentation and comment style, and comment and white space usage. Ensure that the document specifying the coding standards includes examples that illustrate each rule clearly.
Of course, having a standard by itself is fairly useless - it needs to be enforced for it to provide any real benefits. Make it a point to review the manner in which code is written during your code inspection sessions, and ensure that your development team follows the standards previously decided. This will make code review simpler and faster, and also make it easier to transition a released application to a new team for maintenance or bug fixes.
Make sure that your application filesystem is structured properly, and that the different components are all in the right place. Spend some time developing a suitable directory structure within which your application code will reside, and clearly demarcate the locations in which different bits of data are to be stored. If your application files will be named according to a specific notation, consider all cases when defining this naming convention and ensure that it covers all possible situations.
Finally, comment, comment, comment! It might seem like a drag to add comments to your code while you're developing an application, but observations have shown that comments play an important role in improving the quality of your code. Not only does a comment serve as a handy reminder of the thought process that went into a particular bit of logic, it also helps other developers read and review your code.
Breaking It Down
You should also spend time modularizing your application by breaking it up into discrete components - this simplifies development by allowing you to focus on smaller pieces of the puzzle at any one time, and also makes the code easier to maintain.
Code modularization can be as simple or as complex as you like. At one end of the scale is very basic abstraction - separating common interface elements or configuration variables into independent files, which may be included wherever required. Global changes then become as simple as altering a single file, with the changes appearing instantly across the application.
The next step is to do the same with your code - separate common functions into subroutines that can be invoked wherever needed in your application. This has a couple of advantages: first, a subroutine allows you to separate your code into easily identifiable subsections, thereby making it easier to understand and debug. And second, a subroutine makes your program modular by allowing you to write a piece of code once and then re-use it multiple times within the same program.
As your familiarity with code modularization increases, you will find yourself grouping your functions into reusable objects, and writing code using OOP techniques. Remember to clearly define the input and output interfaces of each object, and the interfaces between objects. Create standard APIs so that you can implement "black box" techniques and thereby minimize the impact of a change in one module on other modules.
Since objects allow for inheritance and extensibility, you should consider building a library of standard objects, and using this library whenever needed in your development activities - this can significantly reduce the time you spend on coding standard functions, and also gives you a base of stable and robust code. As you develop new objects, always try to make them as generic as possible, so that they become reusable and useful for subsequent projects. And don't forget the Web - there are rich code repositories for almost every programming language online, and you can often substantially reduce development time by using a free, open-source widget instead of building your own from scratch.
Batteries Not Included
In order to increase the portability and maintainability of a Web application, consider making the following components standard inclusions in every project you develop.
- An interface abstraction layer: By substituting variable "placeholders" for actual content in an HTML page, tag-based scripting languages like PHP, Perl and JSP make it easy to construct dynamic Web pages; simply alter the values of the variables embedded within the HTML code, and the content displayed on the page changes appropriately.
This convenience comes at a price - most of these scripts are so closely interwoven with HTML code that maintaining them is a nightmare. Since both the HTML user interface elements and the program logic are in the same physical file, it becomes difficult for users with no programming experience to modify one without affecting the other. And having a developer hand-hold an interface designer through the development process is expensive to the organization as a whole, in terms of both time and money.
Consequently, one of the most important things you can do when implementing a Web application is separate the user interface from the business logic. This is typically done via a template engine, which works by abstracting interface elements into independent templates, text files typically containing both static elements (HTML code, ASCII text) and template variables, and providing developers with an API to link templates together, and to fill them with data. When a template engine reads a template file, it automatically replaces the variables within it with their values; these values may be defined by the developer at run-time, may be read from another file, or may be extracted from a database.
Since templates may be nested or inherited, a template engine also adds reusability to your Web application (a template can be used again and again, even across different projects) and makes it easier to localize the impact of a change.
A number of template engines exist to help you accomplish this separation - FastTemplate, patTemplate and Smarty (PHP), CGI::FastTemplate (Perl), Cheetah (Python) and Velocity (JSP) are some of the more common ones.
- A database abstraction layer: If you've worked with different databases, you've probably seen that each database operates in a slightly different manner from the others. The data types aren't always uniform, and many of them come with proprietary extensions (transactions, stored procedures et al) that aren't supported elsewhere. Additionally, in some programming languages, the API to interact with these databases is not always uniform; you may need to change specific function calls in your code as your RDBMS changes.
For all these reasons, switching from one database to another is typically a complex process, one which usually involves porting data from one system to another (with the assorted datatyping complications), rewriting your code to use a new database API, and testing it to make sure it all works. And that's where a database abstraction layer can help.
Typically, a database abstraction layer functions as a wrapper around your code, exposing a set of generic methods to interact with a database server. These generic methods are internally mapped to the native API for each corresponding database, with the abstraction layer taking care of ensuring that the correct method is called for your selected database type. Additionally, most abstraction layers also incorporate a generic superset of datatypes, which get internally converted into datatypes native to the selected RDBMS. This increases the portability of your application, and also helps you keep things simple over the long term.
A number of different database abstraction layers exist to help you accomplish this: DBI (Perl), Metabase and ADODB (PHP), and JDBC (Java).
- An exception handler: No developer, no matter how good (s)he is, writes bug-free code all the time. Consequently, most programming languages come with built-in capabilities to catch errors and take remedial action. Typically, this action involves displaying a warning message and - depending on the severity of the error - terminating program execution.
Now, the error messages displayed by most programming languages tend to be both cryptic and overly technical in the information they provide. While this behaviour is acceptable during the early implementation phase of a software project, it cannot continue indefinitely; it is generally considered more professional to intercept these errors and either resolve them, if resolution is possible, or notify the user with a clear error message, if not.
It's therefore a good idea to include a generic exception handler in your application, one that is capable of tracking and handling the different types of errors that may occur during program execution. Typically, this exception handler is a layer over and above the rudimentary error-handling functions provided by the programming language, and it allows you to define custom error types, catch errors as they occur, control the display of the subsequent error messages, and optionally log error messages in a custom report format.
An Elephant's Memory
For large projects that involve multiple developers; small- to medium-size applications that are updated on a frequent basis; or software products that go through a regular upgrade cycle, version control is a critical issue, and one which, if ignored, can cause significant problems over the long term.
A version control system is a very powerful tool in the arsenal of any developer or software programmer. It is a system which allows you to keep track of the software code you write, to maintain it in a logical manner, and to easily backtrack to previous versions or releases of the software. By storing your code in version control software, you can easily mark specific points in development, log changes made over time, and extract a snapshot of a specific file as it looked six or eight months in the past.
In addition to keeping track of different software versions, version control software also helps to manage large, distributed software development projects (common to many open-source projects), in which developers located in different geographical locations collaborate to build a piece of software. In such situations, keeping track of changes made by individual developers to the overall body of code is almost impossible, and version control software provides an elegant solution to the problem by allowing each developer to work on copies of the original source and then merging the changes into the main code tree. Finally, most version controls systems also support "code branches", which are essentially offshoots of the main code tree, usually initiated to fix bugs in older versions of the code.
During the implementation phase of your software project, it's important that you commit your work to a version control system on a regular basis, so that your changes are logged and can be reversed in the event of a problem. Saving your code in a version control system also makes it possible, over the long term, to retrieve earlier versions of the code in case your customer detects problems in a later release and needs to be downgraded while you fix the problem and issue a new release, and also serves to provide a history of the progress of the project.
Most version control systems also ask you to write a brief description of the changes made to the source tree every time you commit code to the system. Many programmers find this to be a tedious task and don't bother entering this information...and this oversight can come back to bite them if they ever need to find out what changed between two versions of a piece of code. Make it a point to have your development team clearly and consistently identify every change to the source tree, and enforce this rule strictly.
Since your source code repository is pretty important, you should make sure that it's backed up periodically, and that backups are stored in a safe location. Make sure that your development team leads and project managers know how to restore data from the backup, and verify, via at least one dummy run, that the process actually works and delivers the desired results. Finally, make sure that as additional project assets are developed - documents, schedules, bug reports et al - they are also archived on a regular basis, so that you can refer to them at any time in the future.
That said, it should be noted that version control software is merely a mechanism to manage different versions of your code. It does not help you write better code, provide you with deep and meaningful insights on software architecture, or assist you in building you a better thingamajig. Think of it as an elephant, one with a long memory - if you're a good coder, it'll help you reminisce about the bee-yoo-tiful software you wrote in your younger days...and if you're a bad one, it'll make it hard for you to forget your mistakes.
A Quick Inspection
In order to ensure that a project is being implemented correctly from a technical standpoint, it's critical to have frequent code inspections and peer reviews. These reviews are a project manager's basic mechanism for validating the design and implementation of a software application, and play a significant role in catching and resolving potentially serious errors at an early stage. Code inspections also help enforce consistency between the different components of a software project, and play an important role in making novice developers aware of their mistakes, and teaching them how to correct the deficiencies.
Code review is used to verify the logic, control flow and interfaces of a particular piece of code, and should be not be performed by the original author of the code. Rather, a code review should be performed by one or more team members or senior developers, and the results should be disseminated to the code author, together with notes on how the errors discovered may be resolved. The choice of code reviewer(s) should be made keeping in mind the characteristics and requirements of the code that is to be reviewed - it makes very little sense, for example, to have a database specialist inspect encryption algorithms.
There are some basic guidelines to be kept in mind during the code inspection process, and some fundamental questions that a reviewer should ask. Here's a brief list:
Is the code clearly commented? Is the comment style consistent and readable? Are nested code blocks clearly indented? Is the specified coding standard followed?
Are variables initialized before they are used?
Are any variables duplicated?
Are variables names descriptive? Is there a clear demarcation between global and local variables? Are constants properly declared?
Do all loops have an exit condition?
Are all function inputs used? Are all required function outputs produced?
Do functions adequately check for invalid or out-of-bound inputs? What happens if you provide a function with a value that is out of range, or in an unexpected format?
Are function return values checked for errors, or for null returns?
Are all opened file handles closed? Are all created objects destroyed?
Are multiple processes acting on a single file? Does the code include file locking or process queuing mechanisms to avoid data corruption in such cases?
Is the database schema normalized? Does it contain redundant data?
Are SQL queries optimized? Are all the fields requested in a recordset actually being used?
Is user input being thoroughly validated before being processed?
Are all errors handled in a standard manner? Are error messages descriptive and helpful?
If a component was specifically designed for reuse, is there anything hardwired into its internals that would prevent it being used in this manner?
Once the code review is complete, a decision must be made as to whether the code is acceptable as is and can proceed to unit testing, or if modifications and a further review is required. If further review is required, the project schedule should be updated accordingly.
In order for a code inspection to be effective, it's important that the process be a participatory one, and that it focus on locating and correcting mistakes rather than on apportioning blame. A good code review session can leave its participants feeling like they learned something; a bad one can make them want to fling themselves off the nearest skyscraper. If you're the one inspecting the code, remember to keep your criticism constructive - and if you're the poor guy in the hot seat, try and leverage off the ideas and knowledge of the more experienced programmers in your group.
That's about all I have time for in this article. In the next part, I'll be discussing how to test the code you've developed, with an overview of unit testing, system testing and acceptance testing procedures. I'll also discuss the process of software delivery and installation, and spend some time on that other bane of developers - documentation. All that and more, coming soon...but until then, here are links to the various software tools discussed in this article:
PHP FastTemplate, at http://www.thewebmasters.net/
Smarty, at http://smarty.php.net/
patTemplate, at http://www.php-tools.de/
CGI::FastTemplate, at http://www.cpan.org/
Cheetah, at http://www.cheetahtemplate.org/
Velocity, at http://jakarta.apache.org/velocity/
Perl DBI, at http://www.cpan.org/
Metabase, at http://www.phpclasses.org/
ADODB, at http://php.weblogs.com/adodb/
Carp, at http://www.cpan.org/
PHP ErrorHandler class, at http://www.phpclasses.org/browse.html/package/345.html
PEAR, at http://pear.php.net/
CVS, at http://www.cvshome.org/
In case you'd like to read more about the techniques discussed in this article, consider spending some time at the following links:
Web Development With PHP FastTemplate, at http://www.melonfire.com/community/columns/trog/article.php?id=86
PHP Application Development With ADODB, at http://www.melonfire.com/community/columns/trog/article.php?id=142
Version Control With CVS, at http://www.melonfire.com/community/columns/trog/article.php?id=33
Template-Based Web Development With patTemplate, at http://www.melonfire.com/community/columns/trog/article.php?id=130
Error Handling In PHP, at http://www.melonfire.com/community/columns/trog/article.php?id=120
Carping About DBI, at http://www.melonfire.com/community/columns/trog/article.php?id=60
Object-Oriented Programming In Perl, at http://www.melonfire.com/community/columns/trog/article.php?id=34
Cracking The Vault, at http://www.melonfire.com/community/columns/trog/article.php?id=64
Exception Handling In Python, at http://www.melonfire.com/community/columns/trog/article.php?id=84
Speaking SQL, at http://www.melonfire.com/community/columns/trog/article.php?id=39
Until next time...stay healthy!
Note: Examples are illustrative only, and are not meant for a production environment. Melonfire provides no warranties or support for the source code described in this article. YMMV!This article was first published on 06 Sep 2002.