The task of creating good software becomes ever more demanding as time goes on. Users are accustomed to having solid, sophisticated applications in front of them all the time, and our humble creations must measure up. Some tried-and-true application design and development techniques are presented here to help you put good code out there.
Decide on a Development Model
A model is an abstract view of a system that ignores some of the implementation details. By consciously selecting a model to drive your development project, you are, in effect, determining which is the most important aspect of your application. Is it the data that the project creates or uses that is paramount? Is it the various states that your application may be in that's most important (as in a device controller, for instance)? Or is the most important aspect the events that the program might have to handle?
These are some of the most important development models:
Data flow—This model shows the data processing that the system must perform, moving from one data form to another. For example, consider a batch process like payroll. At the end of the pay period, hours are entered (data form 1). Then, when batch totals are correct, a program determines the payroll amounts (data form 2) and the data is stored (form 3). Finally, checks are printed (form 4).
- State machine—The state machine model is used to show a system's behavior in response to internal or external status—for example, an ATM machine or a GPS receiver.
- Object—For object-oriented designs, the object model is central to the process. In this model, you determine all of your system entities and identify their classification and composition (how they interact).
Semantic data—A semantic model will describe the logical structure of the data that is imported into and exported from the system. An example of this type of program is a compiler that accepts English-like statements, parses the statements into lexicon segments, and outputs an ontology (a set of data structures and relationships).
Design Your Application to Run In a Web Browser
Perhaps the strongest influence on your choice of application development tools is that of platform. That is, what combination of hardware and operating system(s) is your application targeted for? The answer is to make your application's platform base as broad as is practical. For example, if you can build your application so that it will run in a Web browser, rather than on a specific OS platform like Windows or Linux, you have broadened the application's base and made it less platform-dependent.
Increasingly, worthy applications are built to run from anywhere. That is, you shouldn't have to use a specific computer because that computer has the needed application installed. Instead, you should be able to conveniently access the services your application provides from any computer that happens to be at hand.
If your users are able to run your application and get into their data from whatever computer is at their elbow, your system is perceived as powerful, convenient, and accessible. On the other hand, if a user has to drive to the office to perform the same functions, the system is thought to be old-fashioned.
Conversely, sometimes you don't have a choice. Sometimes you have to design and code your application entirely with a single platform in mind. For example, an application written for a PDA or smartphone will necessarily have to be written in a compatible language like Microsoft's .NET for the Compact Framework.
Reuse Code
Nearly everyone who programs can see the advantages of creating code that can be used over and over. After all, coding's fun, but it's not that fun. If a chunk of code can be applied to more than one task, the cost of producing that code diminishes steeply. Further, the relative reliability of the module is increased because the code is more exercised and any emergent bugs are more likely to have been stamped out.
Two established guidelines for modular code reuse are "loose coupling" and "tight cohesion." The two are related and mutually dependent; loose coupling implies tight cohesion, and vice versa.
Loose Coupling
"Coupling" is the degree to which a given program routine or module depends on code that lies outside of that module. Good coding practice says you should write your modules so they are loosely coupled (that is, as independent of outside code and services as possible). For example, a module that determines the ASCII equivalent to an EBCDIC character string should have all the tools it needs to perform that task without having to enlist the services of an outside system. That means the translating routine should be ready to perform the task when called without any help from outside. For an EBCDIC-to-ASCII translation example, the EBCDIC code page number and the ASCII Internationalized Resource Identifiers (IRIs) should be built in.
Tight Cohesion
The partner of loose coupling is tight cohesion. "Cohesion" indicates how well the lines of source code in a given module work together to provide the module's service. Writing code that is tightly cohesive has several advantages: The code is more understandable if the statements are all working toward the same end, the module is less likely to be broken by an errant maintenance attempt, and the module is more reliable if it does not depend on outside services.
Tight cohesion implies a singularity of purpose: A tightly cohesive module will do only one thing and do it well. Conversely, the "all-purpose, extra-strength" type of module is likely to be brought to bear on a variety of duties and may not do a very good job on any of them.
The Downside
So what's the downside of loose coupling and tight cohesion? Performance, mainly. A loosely coupled system may have to duplicate efforts that are available elsewhere already. Also, constantly calling a standalone module is more expensive than executing "inline" code. Further, the sheer bulk of your code can be significantly impacted, causing your program to become bloated, lumbering, and redundant. And finally, a tightly cohesive module may fly in the face of one of the pillars of OOP: inheritance. If a "customer" object is dependent on the definition of a "person" object, the notion of tight cohesion is compromised.
Refactor
Refactoring is a programming process in which you rewrite your code—at least in part—the way that maybe you should have written it the first time. The problem is that you rarely know what parts of the program will require refactoring until you've worked your way through the code for a while.
Frequently, we programmers set off to write some code, and before long, we realize we are adding the same few lines of code to the program in multiple places. At some point, we stop adding more code and strive to subtract some by creating a new routine to contain the common code and replacing the duplicate code with a call to the new routine. Refactoring source code renders a more-controllable, easier-to-understand program. Maintenance efforts will be easier and more reliable because changes can be made in a single location, and those changes will be reflected wherever the routine is referenced.
Consider Mobile Computing
Recently, Google enhanced its application to become more mobile-friendly. The Google server application examines the type of device that is requesting pages and reacts accordingly. For instance, if you access Google from a PocketPC, with a screen less than 250 pixels wide, Google will send back a PocketPC-sized version of its search and result pages. If applicable, you may want to spend a little time thinking about other device configurations that your application may be used with and then take those needs into account.
Leverage Users' Skills
Do you remember that old adage that was originally a reference to modern art: "I know what I like when I see it"? Well, when it comes to software, the adage should be, "I like what I see when I know it." That means we should look for strengths (and weaknesses) in our users' computer skills and exploit them. (Note that "strength" usually equates to "familiarity.") If you're creating a system that is to be used by accountants, you would do well to make it look and feel as much like a spreadsheet application as is practical (i.e., use the same kinds and arrangements of tools, menus, dialogs, etc.).
Write Secure, Managed Code
Nowadays, programmers have to be concerned with writing secure code. That means being aware of the methods that are used to hack into a program's instruction space and alter it, whether hacked intentionally or not.
The most effective measure you can take toward writing secure code is to let the language manage the memory automatically. In most contemporary versions of high-level languages in use today (including C++), you write what's called "managed code." The memory management tasks are performed capably for you behind the scenes. You lose explicit control over your objects and memory, of course, but you can forget about things like buffer overruns and memory leaks.
Unfortunately, you can't always avoid unmanaged code. For example, you may have to access a legacy API because there simply isn't any other way to do a particular task. In such cases, you must consider what the impact will be on your program's automatic memory-management mechanisms. It could be that an unmanaged program written in a managed language is even more vulnerable because the expectation is that programs written in that language are secure.
Give Your GUI Its Due Consideration: Affordances and Metaphors
The interface your application presents is the user's principle source of judgment about the quality of the system. Our interfaces must be intuitively usable with, ideally, little or no user training. To this end, your system should make use of affordances. Affordances are features that invite their proper use because of their very nature, like the flat plate of plastic on a door that opens out. This plate, because it's flat and because it's on a door, invites (or "affords") pushing. Further, a system's controls should be created to resemble their real-world counterparts, called metaphors. For example, some common applications put a picture of a magnifying glass on a button to indicate a zoom-in feature.
Comment Code and Name Variables Consistently
Yes, it's still true: Comments are the programmer's best friend. Seldom, if ever, are too many comments inserted into a program's source code. (By the way, I find that the concept of "self-documenting" code applies only to the programmer who wrote it in the first place and sometimes not even then). Every minute you spend putting meaningful comments into your code will pay off in the future when you've forgotten what you were thinking of at that moment so long ago.
Personally, I find the most effective layout for source code to be a liberal use of white space (white space is free; use it generously), then a comment that explains what the next few lines of code are going to do and how that effort is important to the rest of the program, and then the lines of code themselves.
// (The filestream – fsParm - was passed in to this method as a parm.)
bReader = new BinaryReader(fsParm);
// See how large of a buffer is needed to read the stream
// and create it...
bytesInBuffer = new byte[fsParm.Length];
Some languages, like Java, have established a standard for creating program documentation. You can use a tool called JavaDoc to create an HTML document about your program from the comments that you've embedded within the source code.
As a added advancement to lucid program documentation, you should have a standard for naming variables. A variable's name should, of course, indicate what the variable is for, but it should also identify where the variable came from, its data type, and its scope.
For example, a variable name like "num1" or "myString" could be anything, really. A more identifiable name like "localWorkString" or "parmIntDateOfBirth" helps you "keep the picture" as you step through a large program's source code.
Use Your Experiences
Of course, many other valid and worthy techniques can be of value in creating solid and maintainable applications. In most cases, good coding techniques are developed anecdotally, and each programmer has a slightly different version of his/her mistake handbook.
Chris Peters has 26 years of experience in the IBM midrange and PC platforms. Chris is president of Evergreen Interactive Systems, a software development firm and creators of the iSeries Report Downloader. Chris is the author of The i5/OS and Microsoft Office Integration Handbook, The AS/400 TCP/IP Handbook, AS/400 Client/Server Programming with Visual Basic, and Peer Networking on the AS/400 (MC Press). He is also a nationally recognized seminar instructor and a lecturer at Eastern Washington University. Chris can be reached at
LATEST COMMENTS
MC Press Online