Development—Quality Practices

Let's quickly summarize where we're at on the path to better application quality: At this point, you should be clear on the idea that quality stems first from well‐written application requirements and a quality‐driven design. I've covered the fact that testing, while extremely important, cannot generate any quality above and beyond what's in those requirements and in your design. At its best, testing can help ensure that the quality specified in the requirements and the design is actually met.

This chapter is about the actual development process, so you might wonder why I've started with a paragraph that mentions testing twice and doesn't mention development at all. I did that largely based on my experiences with application development—developers, I've found, don't really like to test as they go, and they're often not given good guidelines or opportunities for testing as they go. I think one of the most important things developers can do to ensure better quality is to do better testing. Obviously, though, developers can also contribute additional quality by following better coding practices, and they can also ensure a better‐quality application by developing directly to the design and to the requirements. That seems obvious, but sometimes in the thick of development pressures, it's easy to lose track of.

Managing Coding Practices

How developers code, and the practices they follow while they code, is obviously of tremendous importance to the delivery of a quality application. It's a bit more in‐depth, however, than simply making sure developers indent their code properly and other cosmetic issues. There are, in fact, significant best practices that many development teams overlook or aren't aware of.

Caution: This section will touch fairly lightly on coding practices, but that isn't intended to be a reflection of their importance; it's simply that I only have so much room to work with. A more in‐depth discussion of this topic—one that speaks almost entirely from the developer viewpoint—can be found in The Definitive Guide to Building Quality Code, from Realtime Publishers.

Code Metrics

One thing developers should become practiced at is assessing the potential quality of their code. To turn that concept on its head a bit, you could say that developers should be used to performing a risk assessment of their code. How many bugs is a given piece of code likely to contain? If you make a change to one module, how likely is it that you'll create a ripple of changes throughout other modules?

In Software Measurement: A Visualization Toolkit for Project Control and Process Measurement, by Simmons, Ellis, Fujihara, and Kuo, the authors defined a set of code metrics designed to help predict the number of defects per thousand lines of code. They proposed a basic metrics set that encompasses reliability, verification, and usability, with each one acting as the leg of a triangle:

  • Usability is a product's fitness for its intended purpose.
  • Verification is the ability to determine whether a product is usable and reliable.
  • Reliability is usually defined as bug potential, bug‐fix efficiency, and bugs that still exist in "finished" code.

More recently, the function point has been proposed as a quality metric because it doesn't rely on simple line of code counts, which rarely provide satisfactory quality metrics. Instead, a function point represents interfaces within the application, since interfaces are most often where errors occur. A typical function point, developed by IBM, contains a truly impressive amount of mathematical formulas; I review them in The Definitive Guide to Building Quality Code. The end result is essentially a score—a function point value— indicating the potential quality of the software. It's not measuring bugs, but rather measuring (in part) the potential for really bad bugs. There's an article at http://www.informit.com/articles/article.aspx?p=30306 that discusses the topic in some depth. For me, the practical upshot of the discussion is that you need some sort of automated tools to help you figure out code metrics like that—the math alone is unapproachable for many people, and assembling the necessary data manually is entirely too time‐consuming for my liking.

Another tactic, which also relies on some kind of automated toolset to get it done, is a code complexity assessment. I really like these, as they provide a more practical look at the potential "hot spots" or "risk areas" within the code itself. Premium editions of commercial IDEs may include a basic automated code metrics analysis, which produces something like the report shown in Figure 7.1.

Figure 7.1: An example of Code Metrics analysis.

You can use third‐party tools to add the same (and often more extensive) capabilities to the commonly‐used Professional edition of Visual Studio, but this is a good example of an entry‐level report. Let's look at what it's telling us:

  • The maintainability index is an overall assessment of the code's complexity, how many modules it depends on, how many modules depend on it, and so forth. Lower numbers are worse, and in this example, result in a yellow or red icon. Those are your "hot spots;" modules of code that are the most likely to contain defects or cause problems with other modules. These are the ones developers need to pay special attention to, the ones that can be targeted for extra testing and quality assurance, the ones that deserve additional peer reviews, and the ones that managers should focus additional attention on. In other words, if you're working on a large project and have limited resources for quality—who doesn't?—the modules with a lower maintainability index should definitely get their share of those resources.
  • Cyclomatic complexity is basically a measure of the number of paths through the code. Each If construct in the code, for example, represents at least one additional path that the code can take. An If…ElseIf…Else path contributes at least three additional paths. Each path through the code can contain its own unique bugs, and you can't typically test more than one entire path at a time. That is, in order to catch all the defects, you'll have to test at least once for each code path. A module with a high cyclomatic complexity is going to require more testing and more complex tests, and has a greater likelihood of containing defects that your testing doesn't catch.
  • Depth of inheritance measures the number of code modules—typically types—that are above the measured type in the inheritance tree. In other words, for any given type, the depth of inheritance tells us how many things that type depends on. Exceedingly deep inheritance can indicate over‐engineering and can create real problems because the deeply depended‐upon types can create massive ripple effects when they're changed in any way. Modules with a high depth of inheritance deserve extra testing and extra monitoring.
  • Class coupling is similar to depth of inheritance, only it doesn't focus strictly on object‐oriented inheritance. Instead, it takes into account all the code modules that a given module depends upon—modules it makes calls to, for example. Again, the higher the code‐coupling value, the more sensitive a module is to changes made in other modules that it depends on.
  • Finally, lines of code is a commonly‐used measurement that, honestly, I don't think deserves a ton of your attention. Some tools exclude comment lines and whitespace; other tools include it. The line count doesn't really tell you anything. Code with a lot of lines isn't necessarily more prone to defects than code with fewer lines, although there's certainly a broad statistical correlation. In fact, it's a lot like any other statistical measure—you might know that one in a hundred people will have a given medical condition, but if you personally know two hundred people, it's very possible that none of them will actually have that condition. Statistics like that only work well on very large samples—larger than most individual projects. Where you can intelligently use a "lines of code" measurement is simply for planning purposes: Modules with more lines of code will take longer to peer review, for example, simply because they take longer to read; you can use that information to plan schedules and other project plans.

Development Methodologies

One of the best things you can do to help keep a project on‐track is to follow a good software development methodology. A methodology serves to coordinate the many pieces of the development process, including requirements planning, testing, development, and so forth. It doesn't matter which methodology you choose to use, so long as you understand how it works and have the management resources necessary to implement it. In the next few sections, I'll outline three of the more well‐known methodologies, and explain some of their differences and how they work.

Waterfall

The Waterfall model is a sequential development process with distinct phases that follow each other: requirements, design, implementation, testing, and maintenance. The first formal description of the model dates back to a 1970 article by Winston Royce, although he did not use the term "waterfall." Ironically, Royce was presenting this model as an example of a flawed, non‐working model.

The distinct phases of a common Waterfall model—requirements, design, and so forth— are not themselves seen as flaws; in fact, most subsequent methodologies use similar phases. What's often seen as Waterfall's biggest flaw is the strictly sequential nature: Once you are finished with the design, you never revisit it. This creates a rigid structure that does not adapt well to the ever‐changing realities of business software development. In fact, the inflexibility of the model is what led to the naming of the Agile methodologies, which were intended to offer exactly the opposite experience.

Figure 7.2: The Waterfall development methodology.

Waterfall is widely used by large development environments in organizations that typically have a rigid culture, such as the US Department of Defense, NASA, and others. The US Department of Defense moved away from a strict waterfall model in 1994 with MIL‐STD498, and then moved further still with IEEE 12207, two alternate development methodologies.

Waterfall's supporters feel that the model—often characterized as "Big Design Up Front" or "BDUF," emphasizes time spent in planning, thus saving time later. Ample evidence supports this claim, although many businesses have emotional difficulty committing a great deal of time creating what seems to amount to nothing more than paperwork. A trick with Waterfall is that it only works if you commit to it—you can't pretend to do a big, comprehensive design and then start developing, because under Waterfall you'll never revisit that design—if it isn't truly complete, your project will suffer. Waterfall is also simpler, especially for inexperienced development teams and managers, than many methodologies that are more iterative and flexible. Waterfall also places an emphasis on documentation and source code, which supporters feel help improve long‐term maintainability. Primarily, supporters feel that Waterfall is well‐suited to stable software projects—such as shrink‐wrap software—with a fairly known and fixed set of requirements. Because of its simplicity, Waterfall is often used in generic illustrations of software development where the precise methodology isn't particularly important, as I've done in this book.

However, critics of Waterfall say that it is impossible, for any non‐trivial project, to get one phase—such as the requirements or design—of a project completely worked out before moving to the next. They argue that today's rapid software development requirements force you to continually revisit earlier phases, and that attempting to stick with a strict Waterfall model impedes flexibility and encourages the poor practices that lead to poor quality. Modified versions of Waterfall, such as Peter DeGrace's Sashimi, offer improvements such as overlapping phases or "phases with feedback" to help address the otherwise‐sequential nature of Waterfall.

Agile

Agile is a group of software development methodologies that are based on similar principles, and was developed largely in backlash to the Waterfall method. Agile methodologies generally promote project management processes that encourage inspection and adaptation—both excellent points that help produce a higher‐quality product. Agile recognizes that most organizations are interested in rapid software development (generally, only large commercial software manufacturers aren't interested in developing as rapidly as possible, and even they keep a close eye on the clock), and is specifically designed to help align development with business needs.

Agile typically does things in small increments rather than trying to formulate longreaching grand plans. Iterations are short timeframes known as timeboxes, typically lasting less than a month. Each timebox includes a team working through an entire development process, including planning, requirements analysis, design, coding, unit testing, and acceptance testing. Short timeboxes produce less risk because they are inherently simpler projects—you can only aim for so much in a week or two. Each iteration in and of itself might not represent a useful end product, but subsequent iterations repeat the process until a workable release is ready. You can think of each timebox as ending in a distinct functional milestone. The Agile Manifesto (http://agilemanifesto.org/) succinctly outlines the guiding principles behind these methodologies.

Figure 7.3: An example Agile development methodology.

Some of the original Agile methodologies included Scrum, Crystal Clear, Extreme Programming, Adaptive Software Development, Feature Driven Development, and Dynamic Systems Development Method.

Each Agile timebox is essentially a mini‐Waterfall, with the theory that it's easier for mere mortals to sit down and plan out a couple weeks' worth of work, see how it goes, and then sit down and tackle the next iteration. Agile methodologies do require more experienced and involved managers because the focus on continual review and feedback, plus the need to coordinate successive timeboxes, places a great deal of responsibility on management. Because Agile is designed to accept and embrace change throughout the project, it introduces commensurately higher risk of missed deadlines, having to re‐do work, and so forth; many organizations are frankly uncomfortable with Agile and prefer to stick with modified Waterfall‐based methodologies.

Incremental/Iterative (Extreme Programming)

While Agile can be seen as a set of mini‐Waterfall timeboxes, Extreme Programming is an Agile development methodology that almost entirely eschews the sequential Waterfall approach. It encompasses and embraces almost continual changes to the requirements as a natural part of development, and believes that adapting to change is more realistic and beneficial than attempting to define all requirements at the outset of a project.

Extreme Programming defines several "activities," including Coding, Testing, Listening, and Designing, which take place more or less simultaneously during a project's life cycle. It emphasizes coding standards, collective code ownership, simplified design, and continual testing—all of which can lead to higher‐quality code. It is especially well‐suited to new or prototype projects, where requirements are not understood at the outset and which evolve rapidly as the project continues. Small projects also work well with Extreme Programming. Other types of projects may also work well, but require high levels of discipline, and a very functional, communicative team that have few motives beyond the project itself—in other words office politics can kill an Extreme project extremely quickly.

Extreme Programming's biggest drawback is the equally extreme amount of discipline that must be maintained by the entire project team in order for the methodology to work properly. Extreme projects may have unstable requirements, which can lead to scope creep and—especially on outsourced projects—skyrocketing costs. Extreme also requires that programmers adopt a user‐centric viewpoint, and assumes that programmers want to do what's best for the user, and that programmers understand what's best for the user; this may not always be the case. More rigid methodologies use a change control board to resolve the conflicts between what users need and what programmers want to do. Extreme is seen by many as a slightly organized form of "cowboy coding" (that is, coding without any methodology at all), and poorly‐managed Extreme projects can quickly devolve into ad‐hoc programming that wastes resources.

Perhaps its biggest drawback is that little evidence exists to support the viability of large Extreme Programming teams; claims have been made for teams as large as 60, but the pervasive feeling amongst experts is that a dozen or so team members is about the limit, unless the project can be successfully partitioned into multiple standalone teams.

Note: There are numerous pros and cons to Extreme Programming that I'm not covering here; there are an equally numerous number of books and articles dedicated to the topic, and I encourage you to seek them out if you're interested in learning more about this methodology.

Coding Standards

When we talk about "coding practices," standards are what we usually have in mind. Standards are ultimately designed to help make code more maintainable by making it easier to read for everyone in a team and to help prevent common mistakes by encouraging standardized practices. It can be difficult to get everyone on a development team to agree to standards and to follow them, but every expert agrees that coding standards are where a quality development process has its foundation.

Style Isn't Cosmetic

It's easy to understand how cosmetic issues such as comment formatting could be considered "style." It's even fairly straightforward to understand how "style" can dictate how code is modularized, at least at a high level. It's less straightforward to see how "style" can dictate things like how clever a programmer should be. Yet Brien Kernighan and P. J. Plauger filled their book, The Elements of Programming Style, with cautions on writing overly‐complex, or what they termed overly "clever," code. One quote sums up their feelings: "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."

Although this sentiment is difficult to capture in a set of "style rules," it nicely sums up the whole point of coding standards—to help simplify code so that maintenance and debugging is somewhat easier, and can be more easily accomplished by someone other than the original programmer (who naturally has a greater affinity for the code).

Consider this pseudo‐code, which Kernighan might consider overly clever:

Var = FunctionA(FunctionB(FunctionC(Input1),Input2),Input3);

A problem with this coding style—nesting functions as inputs to other functions—is that it requires a human being to perform fairly complex mental gymnastics in order to sort out what that single line of code is doing. That makes peer reviews harder and makes it more likely that someone will overlook what would otherwise be an obvious bug or other problem. Some coding standards require that an individual line of code perform only one granular task:

Var1 = FunctionC(Input1);

Var2 = FunctionB(Var1, Input2);

Var3 = FunctionA(Var2,Input3);

The idea is to make the code less compact and easier for a human to follow. The compiler for most languages would treat these two pieces of code nearly identically, but one is distinctly easier for a human to read, debug, and maintain in the future. This is perhaps the ultimate expression of the purpose of coding standards—to write code in a consistent fashion that supports future maintenance and helps reduce defects.

Common Coding Standards

If coding standards began by addressing issues such as formatting and naming, they've definitely evolved to include much more. In fact, the switch from the word "style" to "standard" implies the additional depth that modern coding standards address: how modules are linked, how error handling is performed, how classes are constructed, and even in some cases, how user interface (UI) elements are utilized.

Naming Conventions

Naming guidelines typically seek to establish consistency in the naming of code elements: functions and subroutines, classes, variables, constants, and so forth. Naming standards focus on the use of capitalization, embedded data within names, and so forth. For example, Microsoft's Visual Basic Scripting Edition (VBScript) language was commonly used with Hungarian naming conventions for variables, in which each variable name included a threecharacter prefix that indicated the variable's intended data type: strName for a string variable, for example, or intCounter for an integer.

Naming conventions also strive for readability: Most coding standards discourage the use of abbreviations or acronyms, preferring longer names such as "DirectoryUserName" over less‐obvious alternatives like "ADUN." The idea is to keep the code readable for months and years into the future—and 2 years after writing "ADUN," you might be left wondering what it was supposed to mean in the first place. Exceptions are made that allow for widelyrecognized acronyms in extreme circumstances: "DN" standing in for "DistinguishedName" is acceptable because the shorter version is used industry‐wide, and it does help create somewhat more compact code.

Naming conventions also tend to favor clarity: A function name such as GetLength conveys the purpose of the function even to a non‐programmer; an alternative such as GetInt might be technically correct if the length is indeed an integer, but it's less specific and clear about what the function is actually getting. In general, language‐specific terms such as Int, String, Char, and so forth shouldn't be used. Instead, use variable names with more semantic meaning, such as Length, UserName, and so forth.

Commenting Conventions

Any developer with a modicum of experience has had to, at one time or another, read through a few hundred lines of code written by another developer—and without a comment in sight. In contrast, I have personally had to read through code where every one or two lines of code was accompanied by a dozen lines of witty commentary. Clearly, comments are like food—you need a certain amount to thrive, but too much is definitely a bad thing.

Some commenting conventions are designed to improve readability: It's generally agreed, for example, that comments following a line of code are a Bad Thing, because they're simply more difficult to read. Other commenting conventions can seem nitpicky, such as requiring that the comment character be followed by a space, and then a comment that starts with a capital letter and ends with a period—conventions which contribute dubious value to actual readability and technical documentation.

Most organizations adopt commenting conventions designed to convey a sense of how comments should be used. Guidelines might suggest:

  • Use a comment when a variable is first declared, to specify what that variable will be used for
  • Use a comment before logic constructs to describe the logical decision being made, and then again within each logical branch to indicate the decision that led to that branch
  • Use comments before or at the beginning of functions and other modules to describe their purpose, their expected input, and their output, along with any notes on expected input ranges or other information

Formatting Conventions

Formatting is designed to make code easier to read and, believe it or not, to help prevent bugs. Probably the most universally‐accepted formatting convention is that code within a construct be indented:

ForEach ($server in $servers) {

$server.Restart();

}

This technique has numerous advantages:

  • It's easier to make sure that each construct has been properly closed
  • It's easier to see the conditional, looping, or other code contained within the construct
  • The white space directly under the construct's statement ("ForEach" in the previous example) helps draw the eye to the construct itself, making it easier to follow the code's logic

This is such a universally‐accepted practice that most development environments, including Visual Studio, go to great lengths to produce this formatting automatically. Automated code‐reformatting tools (sometimes called "code beautifiers") automatically indent code in this fashion, as well. As with any standard, of course, interpretation leads to inconsistency: Most organizations (and tools) get very specific about this formatting, requiring that each indentation be a tab character with tab stops every four spaces, for example.

Other formatting conventions are also designed to improve readability, such as requiring whitespace around operators:

A = (b + c) * d

Rather than:

A=(b+c)*d

Functional Conventions

Coding standards become more complex and language‐specific when you move away from mere formatting and naming and start moving into more functional conventions. These conventions might include things like requiring all functions to have only a single exit point, meaning functions must not exit midway through the code unless they are raising an error. Or, saying that all elements must be declared with the tightest scoping possible that still enables their intended purpose. For example, variables must be declared as private unless they are explicitly needed in different scopes.

OEM Conventions

I like to point out that many software manufacturers—including Microsoft—publish their own internally‐used coding standards. These are often an excellent place to start for any organization developing their own standards. Using a manufacturer's standards helps make it easier to incorporate their code samples into your project, and often makes it easier for new team members to jump into the code because they're more likely to be familiar with those industry‐wide standards. Microsoft publishes a few sets of standards, and other widely‐accepted standards are also available. Here's a selection:

  • Visual Basic coding conventions are available at http://msdn.microsoft.com/enus/library/h63fsef3.aspx. Most of these are actually applicable to C# as well, and this is one of Microsoft's most comprehensive yet concise documents on coding conventions.
  • A C# Coding Style Guide by Salman Ahmed is at http://www.csharpfriends.com/articles/getarticle.aspx?articleid=336. This is a concise guide with commonly‐accepted conventions, focusing on both cosmetic and functional issues.
  • An ASP.NET‐specific guide is at http://www.visualize.uk.com/resources/asp‐netstandards.asp, and applies to both C# and Visual Basic. Based on Microsoft guidelines, this guide provides a few ASP.NET‐specific items, such as naming for UI elements (using Hungarian Notation), and a focus on XML‐based comments.

Enforcing Standards

In many cases, development tools—such as Visual Studio—can help automatically enforce and support some coding standards, especially those related to formatting. Other tools— often third‐party plug‐ins to Visual Studio—can help support and enforce a wider array of standards, such as naming conventions and standards that go beyond simple formatting. You should by all means investigate and use such tools if possible, as they allow developers to focus more on the creative process of programming while helping them maintain good coding standards.

Getting Developer Buy‐In

Sadly, not every developer welcomes coding standards. As I've stated, software development is as often more art than science, and no artist likes to be told how to create. Developers who don't have a background in following coding standards have almost always adopted at least a loose personal style, and switching over to a formal set of standards can consume time and seem frustrating.

Organizations have resistance to standards, too, not the least of which is the additional time and effort needed to observe and maintain the standards over time. Fortunately, modern programming tools make this a bit easier, and in the end, the time and effort that goes into observing standards must be see as an investment. The return on that investment is a decrease in defects coming out of unit testing, an increase in code maintainability (meaning lowered future costs), and ultimately higher code quality.

Collaborate with developers when creating standards. Developers will be much more likely to accept standards if they feel they have played an active role in their creation. Align standards with the corporate culture as much as possible—for example, smaller entrepreneurial organizations may adopt standards that allow developers more personal freedom, while larger organizations with numerous documented practices and processes will feel more comfortable adopting more rigid standards.

Be able to modify the standards once they are established. In my experience, a small standards committee is usually a good idea, as it allows developers to continue to have an active role in standards process. New team members should be given a voice in this committee, as it helps bring a fresh viewpoint and helps rotate the committee membership amongst the team members.

Peer Reviews

Peer review can be a sensitive subject. Not many developers like to have someone looking over their shoulders and critiquing their art (remember that coding is as much art as science, and developers can be as sensitive and temperamental as artists). Unlike a painting, however, code is functional art, and can benefit from a second look by another developer. Peer reviews offer a number of benefits, which should be communicated to developers:

  • Junior developers can learn from their own mistakes, and mini‐mentoring sessions can be incorporated into the review.
  • Everyone can gain more practice in observing coding standards. In fact, by having each developer act as a reviewer, you'll find that they start observing coding standards more consistently in their own code.
  • Peer reviews can be a team‐building opportunity when managed properly, rather than a competitive event.

The key is to make sure everyone knows that the peer review is an opportunity to put the collective code into the best possible condition—not to "pick on" any one person. The code is "owned" by the entire team (managers can emphasize that by rotating developers through different portions of the code on a regular basis), not by one developer; a peer review is a chance to make that common property the best it can be.

Peer reviews are, unfortunately, easy to skip. They take time, and time is often something development teams are perpetually short on. Management may not understand the value of peer review, and may not want to allocate time to it. That's a point of education: Industry statistics are very clear that peer‐reviewed code is almost universally of higher quality than code that isn't peer reviewed. If management wants code that can outlast the career of a single developer—in other words, more maintainable, standardized code—then peer reviews are a must.

Start by adopting a set of guidelines for peer reviews. NOAA, for example, uses the site at http://www.nws.noaa.gov/oh/hrl/developers.html to document their programming standards and conventions and to document checklists for peer reviews. These checklists help ensure that peer reviews don't descend into personal attacks and help keep the review focused on value‐added activities.

Typically, it's not practical for every line of code in an application to be peer reviewed. Instead, select a sampling of subroutines to review, and ask that the developer being reviewed take any suggestions and apply them to all the code—not just the reviewed code. This is where those code metrics can come in handy—they'll help highlight "hot spots" where peer review can offer the most value to the project.

Code Analysis

Static code analysis is a process performed by automated tools against the static—that is, not running—source code. It's designed to help look for specific potential problems and standards violations. In some instances, code analysis can be run by developers at will, usually through some kind of add‐in to their development environment (numerous third parties offer them for Visual Studio, for example). Other tools integrate with a source control solution and run an automated analysis on any checked‐in code, refusing to accept code that doesn't pass all the analysis rules. Microsoft's Visual Studio Team Edition supports that kind of functionality, for example. Figure 7.4 shows the Team Foundation

Server's Check‐in Policy, with several code analysis policy rules selected.

Figure 7.4: Visual Studio's Code Analysis Policy Editor.

Note: Most third‐party code analysis plug‐ins allow developers to run ad‐hoc analyses from within Visual Studio without the need to check in the code, and without the need to have everyone on the team using the more expensive Team editions of Visual Studio.

Testing During Development

There's no question that testing during development—commonly referred to as unit testing, since developers test the units of code as they're writing—can help make for a better‐quality application. In theory, unit testing can catch most bugs and ensure that each unit of code is producing the proper output—meaning later full‐scale testing of the entire application will go smoother and reveal fewer bugs or missed requirements.

The problem is that developers usually hate unit testing. I know I do. I dislike it because it takes me out of the creative flow of programming, because it's tedious, and because it involves too many external components—test data, test harnesses, and so forth. I also dislike it because I know I'm not an effective tester of my own code: I know what the code should do, and I tend to avoid potentially problematic areas almost by instinct. This is absolutely an area where automated tools can help! Provided you have a solid test plan and are committed to thorough unit testing, automated tools can help relieve developer boredom by making testing as easy as clicking a "start button." Tools can ensure that good test data is used, and that managing that test data isn't a burden on developers. Tools can ensure a thorough test, rather than a quick run through just a couple of code paths. Tools can incorporate test harnesses, if necessary, which allow units of code to be tested independently, outside of their normal context within the application.

But beware: Tools cannot create a culture of testing. They're tools, and they can be used, or they can be set aside. They can be used properly, or they can be used poorly. Tools themselves cannot create quality, they cannot ensure quality, and they cannot, in fact, contribute positively to quality in any way on their own. What tools can do is make quality easier to achieve, by automating the quality‐related tasks—such as testing—that you've already planned for. Tools can make quality more practical, because they allow you to achieve a desired level of quality in less time. But you have to start by desiring that level of quality in the first place.

Testing for Functionality

Functional testing of code units—that is, making sure the code does what it is supposed to do—is what most developers think of when they hear the term unit testing—essentially, giving the code both correct and incorrect inputs and making sure that it produces the proper outputs or results (which can include producing errors, if that's the desired reaction to a particular input). Unit testing should also be ensuring that a given unit of code is meeting the original application requirements—displaying the proper user interface and enabling the required workflow, for example.

Testing for Performance

Too few developers—very, very few in my experience—worry much about application performance while they're writing code. I can completely understand why: It's very difficult to test the overall performance of an application when it isn't done, yet. However, developers can and should look at the performance of their individual units of code. As the project progresses, the performance of individual units can be considered when overall application performance isn't looking good, and management will know where to direct their attention.

A good design will also include performance metrics for the most critical code modules, and unit performance testing can help make sure that those milestones are met—in theory, that per‐unit performance will roll up to acceptable application‐level performance.

Testing tools can help developers monitor code performance as they develop the code. These can be used to get a basic feel for the performance of code units, even though at the time you might not be able to determine what performance is "good" and what performance is "bad." Tools can also be used as the application progresses, to help test ever‐larger units of code for performance, eventually mapping their performance back to performance requirements and helping developers determine earlier whether or not their code is meeting the requirements.

Again, the more expensive commercial IDEs include some built‐in assistance, although third‐party performance testing tools are often more comprehensive and mature (and can be used in conjunction with any Visual Studio edition). Typically, you can at least configure the type of performance testing you want to use, as shown in Figure 7.5.

Figure 7.5: Configuring performance testing options in an IDE.

Some tools or products may offer only a very limited type of performance report, and if they're being accurate, may even refer to it as profiling rather than performance. Essentially, the tool is simply gathering specific statistics on how the application utilizes system resources, either by sampling key performance counters or by using instrumentation within the application. The value of this testing can be limited if the tool isn't delivering detailed performance information. However, with detailed information, you can get pretty deep insight into how your code is performing; Figure 7.6 shows a sample of one commercial tool's performance output.

Figure 7.6: Sample Performance Report.

You can see that this is more than raw performance data, and even helps highlight some areas where you might focus efforts to improve performance.

Managing Test Data

Testing without proper test data is almost entirely pointless. Here's a common scenario: A developer is working on a unit of code that accepts customer information as input. During unit testing, the developer continually tests the code, using customer names like "Jane Doe" and "John Smith." Everything works fine. It's not until much later, during formal testing of the full application, that someone discovers errors when names like "Pat O'Brien" are entered. It turns out that the apostrophe wasn't being properly handled in the developer's code, and it created database errors. Unfortunately, that particular code module is deeply relied‐upon by other modules in the application, meaning any changes at this point run a risk of ripple effects, necessitating more extensive re‐testing. That means time lost, which means money lost, which means the project is one step closer to being considered a potential failure by the business.

All of this could have been avoided with proper test data: Data that's reflective of realworld conditions, that includes improper data (such as names which are too long for the database fields in which they'll be stored), and so forth. Developers cannot and should not (generally speaking) be expected to come up with this data on their own, which is exactly why tools exist to help generate and manage proper test data. For example, the application's designer should be the one thinking about what test data should be run through the application in any given circumstance, because the designer is the one establishing the initial input and output parameters for the application as a whole. The designer might define a set of data to be used for any scenarios dealing with customer information, and would specify a variety of inputs that were both inside the expected ranges as well as some which are outside those ranges.

Tools exist to help manage that data, allowing the definition of multiple sets of data which can be used for various test scenarios. In unit tests, those tools might also specify expected outputs from specific code modules, so that automated tests can be run. Developers might further extend that data, or automated testing plans, to ensure that each code path within a unit was being tested automatically.

Note: Code metrics tools can help establish whether planned unit testing is sufficient: In a module with a cyclomatic complexity of 20, for example, you should be able to discern at least 20 specific tests. This might be a single test incorporating 20 different inputs, for example, with each input designed to test a specific code path.

These testing tools and the test data they manage can also help developers make sure they're thinking of everything. For example, suppose our developer, working on that customer information code, takes a moment to scan through the test data that is planned for his unit testing. He might spot the "O'Brien" and realize—oops, I didn't think to escape the punctuation. He can then add that to his code right away, and use an automated unit test to ensure that his changes work properly.

Some extremely sophisticated testing tools can work in collaboration with static code analysis, as well as the running code, and actually detect code paths that weren't tested. After running the unit test, a developer might be directed to a specific code path that was never executed during the test. The developer could then modify the test or its sample data to ensure that future tests do include all the code paths, making for a more complete and comprehensive unit test.

Good testing tools (and companion test data management tools) should maintain a shared repository of test data. If one developer realizes that adding the customer name "Shönhoff" is necessary to test additional paths within his code, then that test data should immediately be included in all other unit tests that involve customer data. Having a central repository of managed test data makes this easy, and developers can immediately benefit from each other's testing and hard work. Of course, a certain level of security and control is required: You certainly don't want everyone having free reign with your test data. In some cases, developers might need to submit requested test data changes to a manager or lead, who would have permission to work with the central repository.

Note: We'll spend a great deal more time discussing testing—as well as automated tools—in the next two chapters.

Developing to the Design and the Requirements

There's a "sanity" check that needs to occur throughout the development process. Every developer should continually be asking, "Am I developing code that meets the requirements and adheres to the design?"

"Well, of course," you might think to yourself—but the reality is that developers (and I know this because I've done it) get so involved in their work that sometimes they just forget to go back and read the requirements. So they miss some little (to them) detail. There are a few things that developers and managers can do to help prevent that from happening:

  • Make sure developers are familiar with the requirements that they're coding. Obviously, they should be familiar with the technical design or specification as well, but being familiar with the top‐level business requirements will help prevent missed details.
  • Encourage developers to include references to business requirements in code comments. For example, when setting the tab order in a graphical user interface, a developer might include a comment like, "tab order as specified by requirements 37.2.3," so that future developers don't make any changes without consulting that requirement first.
  • Provide developers with checklists drawn from the requirements, and ask them to verify that each checklist item is implemented in the code. That same checklist is essentially what the formal, full‐application testing will be looking at, so it's a good way for developers to make sure they're meeting the requirements.

Summary

In this chapter, we've looked at many things that you can do to add quality to the code development process. We've examined common development methodologies (which in actuality help guide the entire project, not just the development portion), and we've looked at common coding standards. We've discussed the value of peer reviews, and looked at ways that code metrics can help you focus on the areas of code most in need of attention. We've also looked at static code analysis as a way of improving code quality. We've also examined ways that developers can include testing in the development process to help further improve the quality of their code. However, quality doesn't stop here.

In the next chapter, we'll move on to the more formal phase of application testing that most people are at least slightly familiar with. We'll focus on unit testing and functional testing, and look at some ways of rethinking the testing process. The chapter after that will focus on performance testing, and how to make that a more integrated part of your application development lifecycle. Some of the information in those chapters will apply to the developer‐conducted testing that I've discussed in this chapter, too; if there's a single important take‐away from this chapter, it's that all testing is equally important, and should be performed as a continual part of the application development process.