TOC PREV NEXT INDEX DOC LIST MASTER INDEX



CHAPTER 2:

Quality-Improvement Objectives

The major goal of the Ada Analyzer is to support software quality-improvement efforts by locating areas of the code that could potentially be improved and providing the information necessary for a decision on whether to make a change. This chapter defines a set of specific quality-improvement objectives and how to achieve them with the Ada Analyzer. The following information is provided for each major objective:

The first section of this chapter provides a general overview of all objectives. The next section describes several different analysis methods and tips on how the Ada Analyzer can be best configured for these methods. All remaining sections describe specific quality-improvement objectives. These sections are not necessarily intended to be read from start to finish. Instead, users with specific quality-improvement goals should go to the section describing that goal and follow the guidelines described there. A summary of all objectives and the commands supporting those objectives appears in "Quality-Improvement Objectives and Command to Use" on page 278.


Overview

Software quality can be measured in many dimensions. Perhaps the highest priority is that a program should function correctly. A program's overall quality is also measured by its efficiency, portability, maintainability, readability, consistency, and adherence to development standards. Even correctness is elusive since bugs always remain in software, especially in untested paths and input scenarios. Realistically, correctness is only a measure of the consistency between the requirement's specifications, the behavior of the software, and the user documentation, all of which are written at vastly different points in the development cycle.

Software quality can be evaluated to determine its current rating against a set of "goodness" or "badness" criteria. This rating is often reduced to a set of numbers that, when they fall within a particular range, indicate high, medium, or low quality. This may provide a sense of relative quality, but most ratings systems do not suggest how the quality of the software can be improved.

A second approach, that taken by the Ada Analyzer, is to analyze software by locating areas of the code that can affect the quality dimensions cited above. These areas of probable impact are then brought to the attention of the user for further inspection. The user determines whether the code segment is appropriate based on an evaluation of several competing criteria and the full context in which the construct appears.

The Ada Analyzer generates information that supports interactive analysis by project members and quality-assurance personnel. It does not attempt to determine whether a construct is good or bad but to highlight and illuminate code sections that should be evaluated further. The focus is on identifying potential changes that could improve software quality. The user selects the changes to be made after all available information and overall context are considered.

With these goals in mind, Ada Analyzer commands have been organized around a set of quality-improvement objectives. This chapter fully defines each objective and describes how the Ada Analyzer can be used to achieve that objective. These guidelines can be used as the basis for a detailed analysis plan that is specially tailored to project needs.

The following analysis objectives are currently supported by the Ada Analyzer. They are discussed in more depth in subsequent sections of this chapter:

It is important to note that many of these objectives overlap. Some overlap in positive ways. Improving standards conformance, for example, can make a program more consistent, increase its readability, and even improve efficiency when certain prohibited constructs are removed. Some efficiency optimizations, on the other hand, can have negative effects on readability, portability, and reuse. In the following sections, each objective is described somewhat narrowly, with descriptive text outlining how to achieve the stated objective. Competing objectives are sometimes mentioned, but details are often left to the user to evaluate. The Ada Analyzer is intended to provide information to the user, leaving final judgment to the user's understanding of all relevant objectives.

Command descriptions in this chapter are intentionally brief. Although references are made to the output content, actual sample output is not included here. A complete description of all commands along with sample output is provided in Chapter 3, "Command Descriptions."


Analysis Methods

Several basic methods of Ada software analysis are supported by the Ada Analyzer. Online analysis is usually interactive, with users scanning hypertable output and traversing to the actual code when more information or context is required. A more formal quality-validation process is supported by commands that report coding-standard violations, compiler-compatibility problems, and/or potential programming errors. These commands typically are used by developers before a release is made with the objective of reducing or eliminating the number of reported violations. Hard-copy forms of the hypertables can be used to support a code-review process or as general reference information for developers and project managers. This section describes each of these methods and gives hints on how to use Ada Analyzer commands most effectively within these domains.

Online Analysis

Most Ada Analyzer commands generate hypertable reports. Hypertables are essentially read-only editors that can be used interactively to accelerate analysis of large amounts of code. Hypertables display condensed forms of information, with each row of the table containing a separate instance of an Ada construct that matches the analysis criteria. Table columns contain attributes of each construct and help to characterize details or provide the context in which each construct appears. Some columns are connected back to the construct or attribute and can be reached by clicking on the [Visit] button. In this way, tables can be scanned for a quick overview of the contents, with traversal used to gather more information when necessary. If the user decides that a change is necessary, it can be made immediately in the code under inspection since traversal arrives at the exact point in the code where the construct is located.

A specific sort order has been chosen as the default for all Ada Analyzer commands. This order is always left to right by column. (Note that the column order for each command can be seen in Chapter 3, "Command Descriptions.") It is possible to resort a hypertable after it has been generated. Given that only two sort options are available when generating hypertables and that many tables have more than two attribute columns, the user may find that another order would be more optimal for the analysis objective at hand. If the sort order in the existing table is not appropriate, the Sort option on the Format menu can be used to reorder the information and store it in a new preview object. A description of this capability appears on Page 16.

Code Reviews

Code reviews should be an essential part of any large-scale software-development process. In some studies, extensive use of peer review uncovered more programming errors than testing and debugging combined. Code reviews, when used at selective points in the development process, can provide a large return on the time invested. Early code reviews can ensure that a consistent design and structure are used throughout the program. This can reduce implementation costs and avoid the need to restructure in later development phases. Code reviews held after implementation can identify programming errors, ensure a consistent use of the Ada language and the application's meta-language interfaces, and improve the execution efficiency of the code.

Code reviews consist of four phases:

The Ada Analyzer can be used to support the code-review process in several ways. Command output can be used by reviewers before the review meeting to prepare their analysis and comments. Reviewers usually are not the author of the code that they are reviewing and thus are likely to be unfamiliar with its detail before the review. Structural analysis can be used to locate key constructs and increase the reviewers' overall understanding of the code and its design. Other commands can be used to reduce the time required to find potential problems or areas in the code that deserve more attention. Commands analyzing code correctness, portability, and readability can be used to identify areas for improvement. If general structural problems or inconsistencies are found through visual inspection, the Ada Analyzer can assist in finding all occurrences of this pattern within the code. Custom locator commands can be written and integrated into the standards-conformance checking for constructs not already located by the Ada Analyzer (see the section titled "Adding New Compatibility and Standards-Conformance Rules" on Page 252).

Although holding a code review online can be imagined, most reviews are held around a table with discussion centering on hard copies of the code that reviewers have annotated. The effectiveness of hard-copy output used during reviews can be improved in two ways:

The Ada Analyzer can also help with the correction and verification process. If items to be corrected are located in hypertables, the developer can use the built-in traversal to quickly locate the areas of the code requiring correction. Part of the verification process can also be performed by re-executing the Ada Analyzer commands to rapidly verify that corrected problems are no longer present.

Validation of Coding Standards

Most software-development projects have a set of coding standards for developers to follow. Coding standards generally consist of a set of language constructs that are prohibited from use by developers. These standards may be motivated by any combination of style, consistency, or efficiency considerations. Use of host-based development of software for embedded targets may also involve compatibility issues between the two compilation systems used in that process.

The difficulty with such standards is that they often contain a long, complicated list of restrictions that are difficult to remember and follow during day-to-day development. Coding-standards analysis attempts to verify that software does not contain any standards violations or compatibility problems. The existence of entries in a hypertable report indicates that the code under analysis has violated one of these standards. Using the traversal built into hypertables, the user can move to each reported violation and make the necessary changes to correct the problem.

The interactive form of coding-standard validation can be used on a daily basis during software development to ensure that the standards are adhered to from the beginning. This is much more efficient than fixing problems after initial development is complete. Continuous checking by developers helps to ensure standards conformance among the entire development team.

The results of batch analysis can be included as part of the code-review process. Some projects require that standards be verified more often, however. Certain standards may be verified before each release of the software, for example. Compatibility standards should be verified before each transfer of the software to the target compilation system to avoid having to correct and retransmit units that will not compile.

Generation of Project Information

Some Ada Analyzer commands generate useful reference information. Commands in this group are most often of the form Display_*, although the counting commands and some Locate_* commands also belong in this group. Information about Ada units and their interdependencies is one example of information that can be useful to all project members. Location of all key programming constructs such as tasks, generics, exceptions, and subprograms is another. Cross-referenced lists of declarations and their dependencies are a third example. Finally, some of the counting information can be useful at the management level to estimate project-completion milestones.

Although traversal is also built into the hypertable reports containing reference information, the hard-copy form can provide a good cross-reference and/or data dictionary of the program. Setting the Analysis_Switches option Display_Subsystem_Names to True is recommended in this case to fully specify the location of any reported construct.


Analyzing Program Structure and Content

This section discusses analysis of a program's overall structure and its content. These objectives might be pursued most often when the user is unfamiliar with the code and requires an overview of its content. Maintenance projects or any effort that requires some form of reverse engineering would likely employ these techniques as well. The specific objectives in this section are:

Locating Key Constructs

Detailed Objectives

This objective focuses on the basic structure of an Ada program and the location of key constructs within that program. Library units and their with dependencies form the organizational skeleton of any Ada program. All other program entities are embedded within this structure.

Key program structures include:

Applicable Commands and Output Interpretation

Unit Partitioning and Dependencies

Detailed Objectives

This objective focuses on the partitioning of Ada units within the program and the dependencies among those units.

Applicable Commands and Output Interpretation

Note: Note that the List_Closure_To_Ascii_File option will create a list of dependent units and write their pathnames to a file.

Subsystem Partitioning and Dependencies

Detailed Objectives

This objective focuses on the organization of Ada units within Rational Subsystems and subdirectories. Subsystems are an integral part of program design and development on Rational Apex. Understanding the partitioning of units into subsystems, the import relationships between subsystems, and the unit-level dependencies across subsystems is another critical component in understanding an Ada system's organization and design.

Applicable Commands and Output Interpretation

Dynamic Analysis

Detailed Objectives

This objective focuses on the dynamic structure of a program —— that is, the relationship of program entities during execution. Key components of this analysis are:

Applicable Commands and Output Interpretation

Counting

Detailed Objectives

Counting Ada constructs or lines tells little about the semantics or structure of a program, but it can provide a profile of software as it grows over time. A measurement of source lines of code (SLOC) can be used to estimate several attendant development costs —— for example, testing, documentation, and maintenance. Comparison of SLOC at intermediate release points in the development process can also help to gauge progress toward completion.

Applicable Commands and Output Interpretation

With these totals, the user can measure SLOC in whatever way best suits the project requirements.

Construct Location

Detailed Objectives

Construct location often becomes important after a problem or opportunity for improvement is found through other means. Then the objective becomes locating all constructs that have the same pattern as the problem construct. An example might be a construct that is inefficiently implemented by the compiler. If this were the concatenation operator, for example, the Locate_Operators command could be used to find all usages of that operator. When more precise filtering is required, a new rule can be written and easily integrated into the Locate_Coding_Violations command. (See the section titled "Adding New Compatibility and Standards-Conformance Rules" on Page 252 for details on performing this customization.) The following list contains the standard Ada Analyzer commands that provide general construct location.

Applicable Commands and Output Interpretation

For more details on these commands, see their detailed descriptions in Chapter 3, "Command Descriptions."

Metrics Collection

Detailed Objectives

Metrics collection is concerned with quantifying the contents of software with a set of measures or metrics. The significance of a single number that rates a software unit in some dimension is often minimal. It is difficult to interpret the meaning of such isolated numbers or to determine what action to take based on such numbers. If the metric for a particular unit exceeds some threshold, then that unit may warrant further investigation to determine why it has too much or too little of a particular trait. In addition, when metrics collected earlier in the development cycle are compared with one another, the result can be used to recognize trends or to judge project-completion status.

Applicable Commands and Output Interpretation


Analyzing Readability

This section discusses objectives for improving the readability, understandability, and thus maintainability of Ada software. Specific analysis objectives include:

Name Selection

Detailed Objectives

One of the key contributing factors to program readability is the selection of good names. Whether or not a name is good is very difficult to determine automatically, but user-assisted analysis can be employed effectively. Many projects have conventions, using names that imply what an entity is or what it does. Examples of this are the exclusive use of nouns for object declarations, verbs for subprograms, *_Generic for all generic units, and Is_* for all predicate functions. If such a system is used, it should be used consistently to avoid confusion.

Consistency is also critical when renaming is used. If a rename is selected to shorten a long package name, that same name should be used whenever a rename is required.

Names that are too short can be cryptic and offer too little information about the item they represent. Names that are too long or have too many segments can be cumbersome to use and affect formatting negatively. Misspelled words can be very annoying during the development phase when references are created and when trying to read and understand a program. Certain words may be prohibited altogether for reasons of portability or consistency.

This objective focuses on analysis of the name space as defined within an Ada program.

Applicable Commands and Output Interpretation

Use of Use Clauses

Detailed Objectives

The use of use clauses is a very controversial issue. On the positive side, they conveniently provide direct visibility to operators that would otherwise have to be renamed or fully qualified. When they are used to avoid qualification of other names, however, they can obscure where an entity is declared and thus reduce code readability.

Applicable Commands and Output Interpretation

Comment Correctness

Detailed Objectives

When used correctly, comments can greatly enhance the understandability of the program. Although it is as yet impossible to truly "read" comments and check that their content is correct and consistent with the code they reference, comments can be checked for misspellings and the use of prohibited words.

Applicable Commands and Output Interpretation

Program Complexity

Detailed Objectives

Complexity is always the enemy of readability and understandability. This analysis objective focuses on locating various forms of program complexity, including:

Applicable Commands and Output Interpretation

Unit/Subprogram Partitioning

Detailed Objectives

Large library units are generally more difficult to read and understand than smaller ones. Subunits are one method of breaking large units into more manageable pieces. Subprograms that are very large may also attempt to implement too much functionality and might better be broken into smaller subfunctions.

Another aspect of subprogram design is the trade-off between parameters and direct object reference. Many factors, including optimization, safety, and program readability, must be considered when designing object references within a subprogram. This analysis objective focuses on information that can help the user make these decisions.

Applicable Commands and Output Interpretation


Analyzing Portability and Reusability

This section describes objectives to determine when and whether software is portable and/or reusable. It also focuses on locating places where reusability and portability can be improved. Specific analysis objectives include:

Target-Dependent Constructs (Compiler Compatibility)

Detailed Objectives

This objective focuses on locating program entities that are target-dependent and thus
potentially nonportable. Ada defines many of its language constructs as implementation-dependent, which means that each compiler implementation can make its own precise definition. The definition (bounds and precision) of numeric types in the target compiler's package Standard is one example. Supported pragmas, unchecked programming, and available attributes are others.

Incompatibilities generally exist between the Rational compilation system used for host development and the compiler used for target-code generation. Development on Rational Apex can lead to subtle dependencies that do not transfer well to the target. The Rational compiler does not completely support representation specifications or use of System.Address, for example. Because they are not fully supported, they also are not fully checked for correctness.

Some target compilers reserve certain words and prohibit them from use in application programs. No words are reserved by the Rational compiler. This can lead to conflict when an attempt is made to compile these units with the target compiler. 

Applicable Commands and Output Interpretation

Host-Development Dependencies

Detailed Objectives

The use of a more powerful host to develope software targeted for execution on another processor often provides a more productive environment for software development. Some targets are not capable of executing a compiler or other development tools. Use of such hosts, however, can lead to dependencies on the host environment that do not transfer well to the target compilation system. The Rational compiler allows prompts, for example. Code containing prompts will not parse correctly when submitted to any other target compiler. Dependencies on Rational Apex interfaces can also occur, especially when test code that may need these dependencies is mixed with application software.

Applicable Commands and Output Interpretation

Reusable Units

Detailed Objectives

This objective focuses on identifying units that are candidates for reusability and providing information for improving reusability. Although the determination of real reusability is beyond the scope of static analysis tools, indicators such as generic units and the use of private types can identify units with higher probability for reuse. In addition, the following considerations apply:

Applicable Commands and Output Interpretation


Checking for Programming Errors

This section describes methods for locating potential programming errors through both static and dynamic (interpretive) analysis techniques. Static analysis locates constructs that, simply due to their existence, pose a high risk of error. In dynamic analysis, a simple interpreter can walk through the execution paths of a program and recognize problems specific to one or more combinations of code segments and branch decisions. In both cases, the constructs located should not always be considered as errors but as high-risk constructs that should be investigated further. Specific areas of analysis include:

Object Sets and Uses

Detailed Objectives

The use of uninitialized variables is one of the most difficult programming errors to locate. The problem is compounded by the fact that this condition may occur only when a specific path of the program is executed. A traditional approach to finding these errors has been the development of coverage tests —— that is, tests that execute each branch combination of the program in the hope of "touching" all paths and thus each error. Construction of such tests is very expensive and does not always result in identification of the uninitialized variable. Interpretive walks through the program can find these errors without actually having to execute the program.

Problems in the same genre include out parameters that are not updated along a particular path or variables that are set but never used. The objective of the following commands is to locate such problems.

Applicable Commands and Output Interpretation

Subprogram Execution Problems

Detailed Objectives

Additional execution problems that can be found with interpretive analysis techniques are:

Furthermore, recursion can be a very powerful mechanism for implementing certain algorithms. It can have a detrimental impact, however, on programs with limited stack space. Therefore it is important to know where recursion is used within a program.

Applicable Commands and Output Interpretation

Misspellings

Detailed Objectives

Although misspelled words are not truly programming errors, they must at least be considered documentation errors. Standard spelling checkers often report errors incorrectly because of the extra program syntax and name formatting —— for example, underscore ( _ ) characters. Editor-based spelling checkers can be sufficient for single units but cumbersome when checking all units in a release. The command in this section checks spelling of three program constructs within a set of Ada units:

The standard Rational and user-specific dictionaries are used to determine whether a word is misspelled. The user may add words to this dictionary to configure the checking. A file can also be updated to contain abbreviated words that, although not in the dictionary, should not be reported as errors. A second file is used to define any words that, although spelled correctly, are prohibited from use and should always be reported.

Applicable Commands and Output Interpretation

Use of Error-Prone Constructs

Detailed Objectives

The objective of the following set of commands is to locate constructs that have a high probability for error. The following potential problems can be located:

In many cases, no error may exist currently, but the potential for future error is high as modifications are made. Complex programming constructs have an inherently high probability for error. These areas should be scrutinized by the user and possibly broken into smaller, less complicated pieces.

Applicable Commands and Output Interpretation

Representation Specifications

Detailed Objectives

Representation specifications are not fully checked for consistency by the Rational host compiler. The objective of the following command is to locate these inconsistencies:

Note: All size computations are based on configuration parameters stored in the Type_Sizing_File described on Page 40.

Applicable Commands and Output Interpretation

Static Constraint Violations

Detailed Objectives

The Rational host compilation system checks most static constraint violations but does not yet support checking of:

When the appropriate switches are not enabled, it is also possible that no checking is performed. Since these switches have other effects as well, checking may be turned off for parts of the code, resulting in unchecked constraints.

Applicable Commands and Output Interpretation

Use of System.Address

Detailed Objectives

Use of direct memory addressing in high-level programs is an extremely error-prone activity that Ada discourages. Access types, for example, are not addresses but abstract pointers (handles) to dynamically created objects. The operations on access types are limited to assignment and dereferencing, prohibiting the pointer itself from being manipulated to point at some other part of memory.

Direct access to memory addresses was made available for applications that require it to implement low-level interfaces. Address implementations typically are integer-based, allowing their manipulation with numeric operators. This allows access to parts of memory that may be inappropriate (that is, containing instructions, read-only data, or data values that reside outside the original address bounds of the structure being accessed). Because the numeric manipulation of addresses is very error-prone, the use of values of type System.Address should be monitored carefully to ensure correct use.

One particular error that can be located with the command below is the use of the addresses of dynamic variables (that is, subprogram parameters and local variables). Once the address is received through the 'ADDRESS attribute, it must be used immediately and not stored in a variable for later use. The problem is that this address may no longer be valid once the program continues execution. There is no guarantee that the stack will remain in the same state as when the address was taken.

One other potential problem is use of 'ADDRESS on constant values. Constants may have been folded in by the compiler or optimizer and may not be resident in a valid memory address.

Applicable Commands and Output Interpretation

Inconsistencies

Detailed Objectives

When some construct in Ada is declared, it is expected that other parts of the program will use it. Subprograms should be called, generics should be instantiated at least once, and variables should be both set and used. Both exceptions and task entries have somewhat more complicated usage that should be present. User-defined exceptions should be both raised and handled as well. User-defined exceptions without a raise or a handler are likely errors. Task entries must be called but must also be "accepted." Both are required for rendezvous, and the lack of one or the other is an error.

Applicable Commands and Output Interpretation


Checking Standards Conformance

Programming standards are almost always project-specific. The Ada Analyzer provides a framework for incorporation of project-specific checks into an existing command that performs all traversal, sorting, and hypertable management. Custom validation checks can be developed quickly for local needs and incorporated into this framework. Most naming standards can be analyzed with existing commands. This section discusses the following specific objectives:

Formal Coding Standards

Detailed Objectives

This objective focuses on checking units against coding standards that restrict the full use of Ada constructs within a project. Rules in this category may have several motivations:

Applicable Commands and Output Interpretation

Rule libraries are available to check Ada 95 compatibility (provided free in the base release), the Software Productivity Consortium's guidelines on Ada quality and style, and Little Tree Consulting's software-quality guidelines. A list of all checks appears on 299.

Naming Standards

Detailed Objectives

Projects usually choose to define standards for name selection. Naming standards often specify that the names of all declarations of a particular kind have the same form. (For example, the exclusive use of nouns for object declarations, verbs for subprograms, *_Generic for all generic units, and Is_* for all predicate functions.) Other standards require names to have an appropriate meaning and that they be usable. Although no static analysis tool can make this determination, names that are too short, too long, too complicated, or misspelled may be a good starting point for this evaluation. This objective focuses on locating all name declarations in a program and presenting their characteristics to the user so that a determination of "appropriateness" can be made.

Applicable Commands and Output Interpretation


Reducing Compilation Time

The amount of time required to recompile a program can greatly affect the time required to complete a project. Changes to an Ada program mean that at least some part of it must be recompiled. Changes to specifications can force many units to be recompiled. As a project moves into later phases of the development cycle, more emphasis is placed on testing and repairing errors found through testing. The amount of time that it takes to make a change and get the program ready to be retested is directly dependent on the recompilation time required. Since thousands, or even hundreds of thousands, of compilations may occur during the lifetime of a project, keeping compilation time to a minimum should be a priority. The following specific objectives are addressed:

Unused Constructs

Detailed Objectives

When a construct such as a declaration is unused, the compiler must check it for correctness, place its name in the symbol table, and generate code. None of this work adds any value to the execution of the program. Reusable packages may contain subprograms that are not used by a particular application but are present for a complete abstraction. Other items may be intended for future development requirements but are unused at this point in the program's development. Thus it may not always be appropriate to remove unused code even though its presence increases compilation time, can affect performance negatively, and generally is confusing.

Applicable Commands and Output Interpretation

Note: It is important that the specified configuration have entries for all subsystems in the program closure. If a subsystem is omitted, declarations can be reported as unused when they are not.

Dependency Reduction

Detailed Objectives

Unused with clauses can have a major impact on recompilation time. When a unit specification is recompiled, the units that transitively depend on that unit must be recompiled. If a unit withs another unit but does not actually use anything within that unit, it may be recompiled unnecessarily. If the withing unit is also a unit specification, its recompilation may trigger other unnecessary recompilation in the transitive dependency closure. In other cases, a with clause is necessary but can be moved from the specification to the body of the unit. This will reduce the recompilation requirement to the body and any subunits and avoid the transitive impact entirely.

Applicable Commands and Output Interpretation

Redundancy

Detailed Objectives

Like unused items, redundant items must be compiled, even though they do not have a positive impact on the program. The benefit of removing redundancy is perhaps greater for optimization and readability objectives (see the sections that discuss those objectives), but it is included here for completeness.

Applicable Commands and Output Interpretation

Use of Use Clauses

Detailed Objectives

The effect of a use clause is to make the entire name space of a withed package directly visible within the context of that use clause. This can simplify references, especially for operators, but it increases the amount of work that the compiler has to do to resolve any reference, since more names must be searched. Removing use clauses or reducing the context in which they apply can improve compilation speed.

Applicable Commands and Output Interpretation


Optimizing Software

Software optimization is not a high-priority task for all phases of the lifecycle, but every project arrives at the point where, although the software "works", it is too slow or too big. This section describes several techniques for improving the efficiency and/or performance of Ada programs:

Inlining

Detailed Objectives

Object-oriented software defines a set of abstract objects and operations on those objects. These operations typically are subprograms that must be called to initiate some operation on the object. Objects are often composed of lower-level objects, and their operations are implemented, at least in part, by calling lower-level subprograms. This can lead to a large number of subprogram calls, each of which must add and then remove its context from the stack. The ability to inline subprograms thus becomes a critical optimization, allowing the layered structure of the software to exist without the high procedure-call overhead that is often associated with this approach.

One aspect of analyzing where to apply inlining is understanding the calling structure of the program. This is best accomplished with the Display_Call_Tree command, which provides an indented hierarchy of possible calling sequences within a program.

The decision to inline must balance the benefit of eliminating the calling overhead with the resulting code expansion. It also may not be possible to inline some subprograms because they contain exception handlers or tasks, or because they are recursive. The following subprogram attributes are collected to support the decision of whether to inline a subprogram:

Applicable Commands and Output Interpretation

Object References

Detailed Objectives

This objective focuses on object references within a subprogram. Generally, parameter references are more efficient since some number of them can be kept in registers. Many targets also have two addressing modes, one for stack-relative addressing and one for direct-memory references. Thus, references to global variables may be more expensive than parameter or local variable references. Finally, object renaming can be used once to compute one or more address offsets into nested structures, allowing subsequent indexing or selection to be made relative to this base address.

Applicable Commands and Output Interpretation

Object Size

Detailed Objectives

The first objective focuses on objects that require a large amount of storage. Since the referencing of components within large objects may have an impact on the addressing required for access, it can be useful to find all objects that are greater than some threshold size. Some compilation systems even have a limit on the maximum size of an object.

A second objective focuses on analysis of object packing. Clearly there is a trade-off between space reduction with packed objects versus increased time to extract and process packed components. When the highest priority is space, the trade-off may be acceptable; the Ada Analyzer can help make this determination by estimating the difference between packed and nonpacked objects. Three options in the type_sizing_file can be used to support this analysis. They are the Always_Pack_Booleans_Option, the Always_Pack_Enumeration_Types_-Option, and the Always_Pack_Integer_Types_Option. If these options are True, size computation will use The_Minimum_Packed_Boolean_Size, The_Minimum_Packed_-Enumeration_Size, and The_Minimum_Packed_Integer_Size, respectively, when calculating the size of objects or components of these types. The Dont_Straddle_Word_Boundaries_-Option can be used to prevent part of a packed component from appearing in one word and another part in the subsequent word. If this option is set to True, filler size will be added to prevent this straddling.

Applicable Commands and Output Interpretation

Generics

Detailed Objectives

This objective focuses on optimizing the use of generics. On almost all targets, generics are expanded inline when they are instantiated. This can lead to a large amount of memory usage when multiple instantiations are present. One optimization is to make sure that all items in the generic actually depend on the generic formals. If segments of the generic are not dependent, they can be moved to a nongeneric part, removing this code from code that is macro-expanded at each instantiation.

Some compilers may also be less efficient when implementing complicated generic constructions such as nested generics, generics that are instantiated with other generics, and generics that are instantiated in a dynamic scope. The location and analysis of these constructs can lead to optimizations as well.

Applicable Commands and Output Interpretation

Redundancy

Detailed Objectives

Redundant constructs can cause additional code to be generated and included in the load image. In some cases, the compiler can recognize the redundancy and remove (or "fold") them from the code. Redundancy increases confusion and compilation time. These are valid reasons themselves to remove them from the code.

Redundant constants should be eliminated and defined once in a global scope. It is perhaps better to have one constant for pi declared globally than for several code sections to define their own. Two accuracy levels may be required for some algorithms, but their values, once set, are unlikely to change. A global placement therefore will not have potential recompilation impacts.

Redundant implementation of computational algorithms can occur rather naturally when requirements for different functional units are presented separately and then implemented by different developers. Locating these redundant implementations and placing them in one utilities package can save code size and reduce maintenance time.

Applicable Commands and Output Interpretation

Expensive Constructs

Detailed Objectives

This objective focuses on Ada constructs that are generally considered expensive. Although each has a set of semantics that may be advantageous for simple implementation, there may be a trade-off with increased execution time. The commands in this section provide information for evaluating this trade-off. The following Ada constructs are of most interest:

Not all of these constructs may be suspect on all targets. Analysis can focus on those that affect performance given the specific characteristics of the target compiler.

Applicable Commands and Output Interpretation

Operator Selection

Detailed Objectives

Some operators are more expensive than others. Multiplication is typically much less expensive than division, for example. When scaling a value, it is often better to multiply by the precomputed inverse of the value than to perform division. String concatenation is so expensive on some machines that it often must be avoided entirely.

Short-circuit Boolean operators can be more efficient while maintaining semantic equivalence of the conditional expression. These operations allow the evaluation of the second part of an expression to be skipped when the first part completely determines the outcome of the Boolean expression. This can save time when the second expression is complex, requiring many instructions to evaluate.

Applicable Commands and Output Interpretation

Loop Nesting

Detailed Objectives

Loop statements execute the statements within them many times. As the number of iterations increases, the need to optimize the loop contents becomes more important. Nested loops have a multiplier effect with the contents of the inner loop executed M*N times. Identifying such "hot spots" can help the user focus on places where optimization efforts may have the most impact.

Applicable Commands and Output Interpretation

Object Initialization

Detailed Objectives

As discussed in the section on programming errors, an uninitialized variable can be a very difficult error to locate. One strategy for avoiding uninitialized variables is to ensure that all variable declarations have an explicit initial value. This may avoid uninitialized variables, but it can impact performance when the variable is also set by one of the following means before it is ever used:

In these cases, the variable is set twice before it is used. Such cases can usually be eliminated by removing the explicit initial value.

Applicable Commands and Output Interpretation

Use of Text_Io

Detailed Objectives

The use of package Text_Io in programs intended to execute on embedded targets can sometimes be a problem. This package is often very large and can add a significant amount of space to the load image of a program. The use of Text_Io in the early phases of a program's development on the host can greatly help some forms of testing. As the program moves to the target, however, it might be necessary to replace all Text_Io calls with calls to a lower-level set of I/O services.

Applicable Commands and Output Interpretation

Compiler Dependencies

Detailed Objectives

All compilers have constructs that are not implemented as efficiently as other alternative constructs. When these constructs are known (sometimes the compiler documentation provides such a list under "Programming Guidelines"), several Locate_* commands can be used to find all instances of those constructs. The commands Locate_Statements, Locate_Expressions, Locate_Attributes, and Locate_Named_Declarations are likely the most useful for this objective. (See the section titled "Construct Location" for more details on the use of these commands.)


Miscellaneous Objectives

This section describes a few miscellaneous objectives that do not necessarily fit into any of the major categories above. It includes:

Unit-Testing Support

Detailed Objectives

Testing can be a very time-consuming and expensive process. Several forms of testing are, of course, possible during a project. Especially during the early phases of the development cycle, testing is often informal. Programs are executed by the user many times, perhaps under control of a debugger, and repairs are made immediately as errors are found. In later phases, formal test programs are often developed. Their advantage is that the same testing can be repeated many times to ensure that minor changes to other parts of the program do not affect the correct functioning of the tested software. This is typically called regression testing.

Formal unit tests typically set some initial conditions (input), execute the unit (a subprogram), and then compare the result (outputs) against expected values. Inputs include all variables and parameters that the program uses during execution. Outputs include all variables and parameters that the subprogram sets. The setting of inputs and outputs depends on the actual path that execution takes through the subprogram. This objective focuses on identifying each possible path through a subprogram and computing the objects that are set and used along each path. This information can be used to help create unit tests for a subprogram. Coverage tests (that is, a set of tests that execute all paths in a program) can also be developed from this information.

Applicable Commands and Output Interpretation

Documentation Support

Detailed Objectives

In many cases, large segments of detailed documentation can be generated from the software itself. Ada was designed to support code that is self-documenting. Although that goal cannot be completely realized within Ada, the software does contain large amounts of useful information that can be extracted and delivered as documentation to the customer. Commands collecting information about software content and inter-relationships can all be included or delivered separately as system documentation. Two examples, Locate_Objects_Set_And_Used and Locate_Subprograms_Propagating_Exceptions, are listed below.

Comment annotations can increase the information content associated with specific constructs. This information can be extracted and cross-referenced to the Ada units in which they appear or to the specific construct to which they are attached.

Applicable Commands and Output Interpretation

Elaboration Problems

Detailed Objectives

Ada defines an initial phase of program execution called elaboration, where static variables are initialized, tasks are made ready to run, and subprogram and generic bodies are checked to make sure they are also elaborated. This phase occurs before the first statement of the main procedure is executed. Local variables are also elaborated during execution upon entry to all subprograms.

Occasionally, a program will not execute because of some problem during elaboration. Some debuggers may help to illuminate the problem, but sometimes the program simply becomes stuck with no message reported. The Locate_Elaboration_Impacts command can be used to locate all places in the code that impact elaboration. One frequent problem is the use of a function as an initial value expression before the body of the function has been defined. These instances can be found by examining all static objects that are initialized with function calls.

Analysis is also available to determine if a unit is preelaborable or pure as specified by section 10.2.1 of the Ada 95 LRM.

Applicable Commands and Output Interpretation

Null Statements

Detailed Objectives

Null statements may not seem important since they are a simple concept and generate no code. The problem is that null statements are often used as placeholders when the developer wants to defer complete implementation for some reason. Ada requires at least one statement in many places and a null statement is easily supplied to arrive at a unit that will compile and execute. This process is often called stubbing. Prototyping and stepwise implementation strategies often use this technique. In later phases of development, it is expected that all null statements have been replaced with implementation code and that any remaining ones truly mean that the program should do nothing for the case at hand. The existence of null statements in code therefore can indicate unimplemented requirements.

In addition, null statements that do not appear alone in statement lists are superfluous and should be removed.

Applicable Commands and Output Interpretation

Hard-Copy Listings

Detailed Objectives

The Ada Analyzer is intended primarily for interactive use. The traversal built into all hyper-tables greatly accelerates the process of locating areas where improvement is possible. Hard copy of command output does not contain the traversal feature but has the advantage that it can be taken into review meetings or distributed as general reference material.

Applicable Commands and Output Interpretation


Rational Software Corporation  http://www.rational.com
support@rational.com
techpubs@rational.com
Copyright © 1993-2000, Rational Software Corporation. All rights reserved.
TOC PREV NEXT INDEX DOC LIST MASTER INDEX DOC LIST MASTER INDEX