Ada 95 Quality and Style Guide                            Chapter 7


Back to sections 7.0 through 7.4.4

7.4.5 Task Scheduling Algorithm

guideline

rationale

The Ada tasking model requires that tasks be synchronized only through the explicit means provided in the language (i.e., rendezvous, task dependence, pragma Atomic). The scheduling algorithm is not defined by the language and may vary from time sliced to preemptive priority. Some implementations provide several choices that a user may select for the application.

notes

The number of priorities may vary between implementations. In addition, the manner in which tasks of the same priority are handled may vary between implementations even if the implementations use the same general scheduling algorithm.

exceptions

In real-time systems, it is often necessary to tightly control the tasking algorithm to obtain the required performance. For example, avionics systems are frequently driven by cyclic events with limited asynchronous interruptions. A nonpreemptive tasking model is traditionally used to obtain the greatest performance in these applications. Cyclic executives can be programmed in Ada, as can a progression of scheduling schemes from cyclic through multiple-frame-rate to full asynchrony (MacLaren 1980), although an external clock is usually required.

7.4.6 Abort

guideline

  • Avoid using the abort statement.

rationale

The rationale for this appears in Guideline 6.3.3. In addition, treatment of the abort statement varies from implementation to implementation, thereby hindering portability.

7.4.7 Unprotected Shared Variables and Pragmas Atomic and Volatile

guideline

  • Do not use unprotected shared variables.

  • Consider using protected types to provide data synchronization.

  • Have tasks communicate through the rendezvous mechanism.

  • Do not use unprotected shared variables as a task synchronization device.

  • Consider using protected objects to encapsulate shared data.

  • Use pragma Atomic or Volatile only when you are forced to by run-time system deficiencies.

example

See Guidelines 6.1.1 and 6.1.2.

rationale

The rationale for this appears in Guidelines 6.1.1 and 6.2.4. In addition, the treatment of unprotected shared variables varies from implementation to implementation, thereby hindering portability.

7.5 EXCEPTIONS

You should exercise care when using predefined exceptions because aspects of their treatment may vary between implementations. Implementation-specific exceptions must, of course, be avoided. See Guidelines 4.3 and 5.8 for further information on exceptions. See Guideline 7.1.6 for further information on vendor-supplied features.

7.5.1 Predefined and User-Defined Exceptions

guideline

rationale

The Ada Reference Manual (1995, §11) states that, among implementations, a predefined exception for the same cause may be raised from different locations. You will not be able to discriminate between the exceptions. Further, each of the predefined exceptions is associated with a variety of conditions. Any exception handler written for a predefined exception must be prepared to deal with any of these conditions.

Guideline 5.6.9 discusses the use of blocks to define local exception handlers that can catch exceptions close to their point of origin.

7.5.2 Implementation-Specific Exceptions

guideline

  • Do not raise implementation-specific exceptions.

  • Convert implementation-specific exceptions within interface packages to visible user-defined exceptions.

rationale

No exception defined specifically by an implementation can be guaranteed to be portable to other implementations whether or not they are from the same vendor. Not only may the names be different, but the range of conditions triggering the exceptions may be different also.

If you create interface packages for the implementation-specific portions of your program, those packages can catch or recognize implementation-specific exceptions and convert them into user-defined exceptions that have been declared in the specification. Do not allow yourself to be forced to find and change the name of every handler you have written for these exceptions when the program is ported.

7.6 REPRESENTATION CLAUSES AND IMPLEMENTATION-DEPENDENT FEATURES

Ada provides many implementation-dependent features that permit greater control over and interaction with the underlying hardware architecture than is normally provided by a high-order language. These mechanisms are intended to assist in systems programming and real-time programming to obtain greater efficiency (e.g., specific size and layout of variables through representation clauses) and direct hardware interaction (e.g., interrupt entries) without having to resort to assembly level programming.

Given the objectives for these features, it is not surprising that you must usually pay a significant price in portability to use them. In general, where portability is the main objective, do not use these features. When you must use these features, encapsulate them in packages that are well-commented as interfacing to the particular target environment. This section identifies the various features and their recommended use with respect to portability.

7.6.1 Representation Clauses

guideline

rationale

In many cases, it is easy to use representation clauses to implement an algorithm, even when it is not necessary. There is also a tendency to document the original programmer's assumptions about the representation for future reference. But there is no guarantee that another implementation will support the representation chosen. Unnecessary representation clauses also confuse porting or maintenance efforts, which must assume that the programmer depends on the documented representation.

Interfaces to external systems and devices are the most common situations where a representation clause is needed. Uses of pragma Import and address clauses should be evaluated during design and porting to determine whether a representation clause is needed.

Without representation clauses, the language does not require two compilations of an unchanged file to result in the same data representation. Things that can change the representation between compilations include:

  • A change in a file earlier in the compilation order

  • A change in the optimization strategy or level

  • A change in versions of the compiler

  • A change in actual compilers

  • A change in the availability of system resources

Therefore, two independently linked programs or partitions should only share data that has their representations explicitly controlled.

notes

During a porting effort, all representation clauses can be evaluated as either design artifacts or specifications for accessing interface data that might change with a new implementation.

7.6.2 Package System

guideline

  • Avoid using package System constants except in attempting to generalize other machine-dependent constructs.

rationale

Because the values in this package are implementation-provided, unexpected effects can result from their use.

notes

If you must guarantee that physical record layouts will remain the same between implementations, you can express record fields by their first and last bit positions as shown in the Ada Reference Manual (1995, §13.5.1). Static expressions and named numbers should be used to let the compiler compute the endpoints of each range in terms of earlier fields. In this case, greater portability can be achieved by using System.Storage_Unit to let the compiler compute the value of the named number. However, this method might not work for all values of System.Storage_Unit.

exceptions

Do use package System constants to parameterize other implementation-dependent features (see Pappas (1985, §13.7.1).

7.6.3 Machine Code Inserts

guideline

  • Avoid machine code inserts.

rationale

The Ada Reference Manual (1995, Annex C) suggests that the package that implements machine code inserts is optional. Additionally, it is not standardized so that machine code inserts are most likely not portable. In fact, it is possible that two different vendors' syntax will differ for an identical target, and differences in lower-level details, such as register conventions, will hinder portability.

exceptions

If machine code inserts must be used to meet another project requirement, recognize and document the portability decreasing effects.

In the declarative region of the body of the routine where machine code inserts are being used, insert comments explaining what functions inserts provide and (especially) why the inserts are necessary. Comment the necessity of using machine code inserts by delineating what went wrong with attempts to use other higher level constructs.

7.6.4 Interfacing to Foreign Languages

guideline

  • Use the package Interfaces and its language-defined child packages rather than implementation-specific mechanisms.

  • Consider using pragma Import rather than access-to-subprogram types for interfacing to subprograms in other languages.

  • Isolate all subprograms employing pragmas Import, Export, and Convention to implementation-specific (interface) package bodies.

example

This example shows how to interface with the following cube root function written in C:

double cbrt (double x);

------------------------------------------------------------------------------



package Math_Utilities is



   Argument_Error : exception;



   function Cube_Root (X : Float) return Float;



   ...



end Math_Utilities;

   

------------------------------------------------------------------------------

with Interfaces.C;

package body Math_Utilities is



   function Cube_Root (X : Float) return Float is



      function C_Cbrt (X : Interfaces.C.Double) return Interfaces.C.Double;

      pragma Import (Convention    => C,

                     Entity        => C_Cbrt,

                     External_Name => "cbrt");



   begin

      if X < 0.0 then

         raise Argument_Error;

      else

         return Float (C_Cbrt (Interfaces.C.Double (X)));

      end if;

   end Cube_Root;



   ...



end Math_Utilities;



rationale

For static interfacing to subprograms in other languages, the pragma Import provides a better solution than access to subprograms because no indirection is required. The pragma Interface (Ada Reference Manual 1983) has been replaced by pragmas Import, Export, and Convention. Annex B of the Rationale (1995) discusses how to use these pragmas in conjunction with the access-to-subprogram types in interfacing to other languages.

Access to subprogram types is useful for implementing callbacks in a separate subsystem, such as the X Window system.

The problems with interfacing to foreign languages are complex. These problems include pragma syntax differences, conventions for linking/binding Ada to other languages, and mapping Ada variables to foreign language variables. By hiding these dependencies within interface packages, the amount of code modification can be reduced.

exceptions

It is often necessary to interact with other languages, if only an assembly language, to reach certain hardware features. In these cases, clearly comment the requirements and limitations of the interface and pragma Import, Export, and Conventions usage.

7.6.5 Implementation-Specific Pragmas and Attributes

guideline

  • Avoid pragmas and attributes added by the compiler implementor.

rationale

The Ada Reference Manual (1995) permits an implementor to add pragmas and attributes to exploit a particular hardware architecture or software environment. These are obviously even more implementation-specific and therefore less portable than an implementor's interpretations of the predefined pragmas and attributes. However, the Ada Reference Manual (1995) defines a set of annexes that have a uniform and consistent approach to certain specialized needs, namely, real-time systems, distributed systems, information systems, numerics, interfacing to foreign languages, and safety and security. You should always prefer the facilities defined in the annexes to any vendor-defined pragmas and attributes.

7.6.6 Unchecked Deallocation

guideline

  • Avoid dependence on Ada.Unchecked_Deallocation (see Guideline 5.9.2).

rationale

The unchecked storage deallocation mechanism is one method for overriding the default time at which allocated storage is reclaimed. The earliest default time is when an object is no longer accessible, for example, when control leaves the scope where an access type was declared (the exact point after this time is implementation-dependent). Any unchecked deallocation of storage performed prior to this may result in an erroneous Ada program if an attempt is made to access the object.

This guideline is stronger than Guideline 5.9.2 because of the extreme dependence on the implementation of Ada.Unchecked_Deallocation. Using it could cause considerable difficulty with portability.

notes

Ada.Unchecked_Deallocation is a supported feature in all Ada implementations. The portability issue arises in that unchecked storage deallocations might cause varying results in different implementations.

exceptions

Using unchecked deallocation of storage can be beneficial in local control of highly iterative or recursive algorithms where available storage may be exceeded.

7.6.7 Unchecked Access

guideline

  • Avoid dependence on the attribute Unchecked_Access (see Guideline 5.9.2).

rationale

Access values are subject to accessibility restrictions. Using the attribute Unchecked_Access prevents these rules from being checked, and the programmer runs the risk of having dangling references.

7.6.8 Unchecked Conversion

guideline

  • Avoid dependence on Ada.Unchecked_Conversion (see Guideline 5.9.1).

rationale

The unchecked type conversion mechanism is, in effect, a means of bypassing the strong typing facilities in Ada. An implementation is free to limit the types that may be matched and the results that occur when object sizes differ.

exceptions

Unchecked type conversion is useful in implementation-dependent parts of Ada programs where lack of portability is isolated and where low-level programming and foreign language interfacing are the objectives.

If an enumeration representation clause is used, unchecked type conversion is the only language-provided way to retrieve the internal integer code of an enumeration value.

7.6.9 Run-Time Dependencies

guideline

  • Avoid the direct invocation of or implicit dependence upon an underlying host operating system or Ada run-time support system, except where the interface is explicitly defined in the language (e.g., Annex C or D of the Ada Reference Manual [1995]).

  • Use standard bindings and the package Ada.Command_Line when you need to invoke the underlying
    run-time support system.

  • Use features defined in the Annexes rather than vendor-defined features.

rationale

Features of an implementation not specified in the Ada Reference Manual (1995) will usually differ between implementations. Specific implementation-dependent features are not likely to be provided in other implementations. In addition to the mandatory predefined language environment, the annexes define various packages, attributes, and pragmas to standardize implementation-dependent features for several specialized domains. You enhance portability when you use the features declared in the packages in the Annexes because you can port your program to other vendor environments that implement the same Annexes you have used. Even if a majority of vendors eventually provide similar features, they are unlikely to have identical formulations. Indeed, different vendors may use the same formulation for (semantically) entirely different features.

When coding, try to avoid depending on the underlying operating system. Consider the consequences of including system calls in a program on a host development system. If these calls are not flagged for removal and replacement, the program could go through development and testing only to be unusable when moved to a target environment that lacks the facilities provided by those system calls on the host.

Guideline 7.1.5 discusses the use of the package Ada.Command_Line. If an Ada environment implements a standard binding to operating system services, such as POSIX/Ada, and you write POSIX-compliant calls, your program should be portable across more systems.

exceptions

In real-time, embedded systems, making calls to low-level support system facilities may often be unavoidable. Isolating the uses of these facilities may be too difficult. Comment them as you would machine code inserts (see Guideline 7.6.3); they are, in a sense, instructions for the virtual machine provided by the support system. When isolating the uses of these features, provide an interface for the rest of your program to use, which can be ported through replacement of the interface's implementation.

7.7 INPUT/OUTPUT

I/O facilities in Ada are not a part of the syntactic definition of the language. The constructs in the language have been used to define a set of packages for this purpose. These packages are not expected to meet all the I/O needs of all applications, in particular, embedded systems. They serve as a core subset that may be used on straightforward data and that can be used as examples of building I/O facilities upon the low-level constructs provided by the language. Providing an I/O definition that could meet the requirements of all applications and integrate with the many existing operating systems would result in unacceptable implementation dependencies.

The types of portability problems encountered with I/O tend to be different for applications running with a host operating system versus embedded targets where the Ada run-time is self-sufficient. Interacting with a host operating system offers the added complexity of coexisting with the host file system structures (e.g., hierarchical directories), access methods (e.g., indexed sequential access method [ISAM]), and naming conventions (e.g., logical names and aliases based on the current directory). The section on Input/Output in ARTEWG (1986) provides some examples of this kind of dependency. Embedded applications have different dependencies that often tie them to the low-level details of their hardware devices.

The major defense against these inherent implementation dependencies in I/O is to try to isolate their functionality in any given application. The majority of the following guidelines are focused in this direction.

7.7.1 Name and Form Parameters

guideline

rationale

The format and allowable values of these parameters on the predefined I/O packages can vary greatly between implementations. Isolation of these values facilitates portability. Not specifying a Form string or using a null value does not guarantee portability because the implementation is free to specify defaults.

notes

It may be desirable to further abstract the I/O facilities by defining additional Create and Open procedures that hide the visibility of the Form parameter entirely (see Pappas 1985, 54-55).

7.7.2 File Closing

guideline

  • Close all files explicitly.

rationale

The Ada Reference Manual (1995, §A.7) does not define what happens to external files after completion of the main subprogram (in particular, if corresponding files have not been closed).

The disposition of a closed temporary file may vary, perhaps affecting performance and space availability (ARTEWG 1986).

7.7.3 Input/Output on Access Types

guideline

  • Avoid performing I/O on access types.

rationale

The Ada Reference Manual (1995, §A.7) does not specify the effects of I/O on access types. When such a value is written, it is placed out of reach of the implementation. Thus, it is out of reach of the
reliability-enhancing controls of strong type checking.

Consider the meaning of this operation. One possible implementation of the values of access types is virtual addresses. If you write such a value, how can you expect another program to read that value and make any sensible use of it? The value cannot be construed to refer to any meaningful location within the reader's address space, nor can a reader infer any information about the writer's address space from the value read. The latter is the same problem that the writer would have trying to interpret or use the value if it is read back in. To wit, a garbage collection and/or heap compaction scheme may have moved the item formerly accessed by that value, leaving that value "pointing" at space that is now being put to indeterminable uses by the underlying implementation.

7.7.4 Package Ada.Streams.Stream_IO

guideline

  • Consider using Sequential_IO or Direct_IO instead of Stream_IO unless you need the low-level, heterogeneous I/O features provided by Stream_IO.

rationale

Sequential_IO and Direct_IO are still well suited for processing homogeneous files. Additionally, in cases where the intent is to process homogeneous files, the use of Sequential_IO or Direct_IO has the advantage of enforcing this intent at compile time.

Stream_IO should be reserved for processing heterogeneous files. In this case, a file is not a sequence of objects of all the same type but rather a sequence of objects of varying types. To read a heterogeneous sequence of objects in the correct order requires some application-specific knowledge.

7.7.5 Current Error Files

guideline

  • Consider using Current_Error and Set_Error for run-time error messages.

example


with Ada.Text_IO;



...



begin

   Ada.Text_IO.Open (File => Configuration_File,

                     Mode => Ada.Text_IO.In_File,

                     Name => Configuration_File_Name);

exception

   when Ada.Text_IO.Name_Error =>

      Ada.Text_IO.Put_Line (File => Ada.Text_IO.Standard_Error,

                            Item => "Can't open configuration file.");

      ...

end;



rationale

The package Text_IO includes the concept of a current error file. You should report errors to the user through the associated subprograms Current_Error and Set_Error instead of the standard output facilities. In interactive applications, using the Text_IO error facilities increases the portability of your user interface.

notes

In a program with multiple tasks for I/O, you need to be careful of two or more tasks trying to set Current_Input, Current_Output, or Current_Error. The potential problem lies in unprotected updates to the "shared" state associated with a package, in this case, the package Text_IO. Guidelines 6.1.1 and 6.2.4 discuss the related issues of unprotected shared variables.

7.8 SUMMARY

fundamentals

  • In programs or components intended to have a long life, avoid using the features of Ada declared as "obsolescent" by Annex J of the Ada Reference Manual (1995), unless the use of the feature is needed for backward compatibility with Ada 83 (Ada Reference Manual 1983).

  • Document the use of any obsolescent features.

  • Avoid using the following features:

    • The short renamings of the packages in the predefined environment (e.g., Text_IO as opposed to Ada.Text_IO)

    • The character replacements of ! for |, : for #, and % for quotation marks

    • Reduced accuracy subtypes of floating-point types

    • The 'Constrained attribute as applied to private types

    • The predefined package ASCII

    • The exception Numeric_Error

    • Various representation specifications, including at clauses, mod clauses, interrupt entries, and the Storage_Size attribute

  • Make informed assumptions about the support provided for the following on potential target platforms:

    • Number of bits available for type Integer (range constraints)

    • Number of decimal digits of precision available for floating-point types

    • Number of bits available for fixed-point types (delta and range constraints)

    • Number of characters per line of source text

    • Number of bits for Root_Integer expressions

    • Number of seconds for the range of Duration

    • Number of milliseconds for Duration'Small

    • Minimum and maximum scale for decimal types

  • Avoid assumptions about the values and the number of values included in the type Character.

  • Use highlighting comments for each package, subprogram, and task where any nonportable features are present.

  • For each nonportable feature employed, describe the expectations for that feature.

  • Consider using only a parameterless procedure as the main subprogram.

  • Consider using Ada.Command_Line for accessing values from the environment, but recognize that this package's behavior and even its specification are nonportable.

  • Encapsulate and document all uses of package Ada.Command_Line.

  • Create packages specifically designed to isolate hardware and implementation dependencies and designed so that their specification will not change when porting.

  • Clearly indicate the objectives if machine or solution efficiency is the reason for hardware or implementation-dependent code.

  • For the packages that hide implementation dependencies, maintain different package bodies for different target environments.

  • Isolate interrupt receiving tasks into implementation-dependent packages.

  • Refer to Annex M of the Ada Reference Manual (1995) for a list of implementation-dependent features.

  • Avoid the use of vendor-supplied packages.

  • Avoid the use of features added to the predefined packages that are not specified in the Ada language definition or Specialized Needs Annexes.

  • Use features defined in the Specialized Needs Annexes rather than vendor-defined features.

  • Document clearly the use of any features from the Specialized Needs Annexes (systems programming, real-time systems, distributed systems, information systems, numerics, and safety and security).

  • Do not write code whose correct execution depends on the particular parameter passing mechanism used by an implementation (Ada Reference Manual 1995, §6.2; Cohen 1986).

  • If a subprogram has more than one formal parameter of a given subtype, at least one of which is [in] out, make sure that the subprogram can properly handle the case when both formal parameters denote the same actual object.

  • Avoid depending on the order in which certain constructs in Ada are evaluated.

numeric types and expressions

  • Avoid using the predefined numeric types in package Standard. Use range and digits declarations and let the implementation pick the appropriate representation.

  • For programs that require greater accuracy than that provided by the global assumptions, define a package that declares a private type and operations as needed; see Pappas (1985) for a full explanation and examples.

  • Consider using predefined numeric types (Integer, Natural, Positive) for:

    • Indexes into arrays where the index type is not significant, such as type String

    • "Pure" numbers, that is, numbers with no associated physical unit (e.g., exponents)

    • Values whose purpose is to control a repeat or iteration count

  • Use an implementation that supports the Numerics Annex (Ada Reference Manual 1995, Annex G) when performance and accuracy are overriding concerns.

  • Carefully analyze what accuracy and precision you really need.

  • Do not press the accuracy limits of the machine(s).

  • Comment the analysis and derivation of the numerical aspects of a program.

  • Anticipate the range of values of subexpressions to avoid exceeding the underlying range of their base type. Use derived types, subtypes, factoring, and range constraints on numeric types.

  • Consider using <= and >= to do relational tests on real valued arguments, avoiding the <, >, =, and /= operations.

  • Use values of type attributes in comparisons and checking for small values.

  • In information systems, declare different numeric decimal types to correspond to different scales (Brosgol, Eachus, and Emery 1994).

  • Create objects of different decimal types to reflect different units of measure (Brosgol, Eachus, and Emery 1994).

  • Declare subtypes of the appropriately scaled decimal type to provide appropriate range constraints for application-specific types.

  • Encapsulate each measure category in a package (Brosgol, Eachus, and Emery 1994).

  • Declare as few decimal types as possible for unitless data (Brosgol, Eachus, and Emery 1994).

  • For decimal calculations, determine whether the result should be truncated toward 0 or rounded.

  • Avoid decimal types and arithmetic on compilers that do not support the Information Systems Annex (Ada Reference Manual 1995, Annex F) in full.

storage control

  • Do not use a representation clause to specify number of storage units.

  • Do not compare access-to-subprogram values.

  • Consider using explicitly defined storage pool mechanisms.

tasking

  • Do not depend on the order in which task objects are activated when declared in the same declarative list.

  • Do not depend on a particular delay being achievable (Nissen and Wallis 1984).

  • Never use knowledge of the execution pattern of tasks to achieve timing requirements.

  • Do not assume a correlation between System.Tick and type Duration.

  • Do not depend on the order in which guard conditions are evaluated or on the algorithm for choosing among several open select alternatives.

  • Do not assume that tasks execute uninterrupted until they reach a synchronization point.

  • Use pragma Priority to distinguish general levels of importance only.

  • Avoid using the abort statement.

  • Do not use unprotected shared variables.

  • Consider using protected types to provide data synchronization.

  • Have tasks communicate through the rendezvous mechanism.

  • Do not use unprotected shared variables as a task synchronization device.

  • Consider using protected objects to encapsulate shared data.

  • Use pragma Atomic or Volatile only when you are forced to by run-time system deficiencies.

exceptions

  • Do not depend on the exact locations at which predefined exceptions are raised.

  • Do not rely on the behavior of Ada.Exceptions beyond the minimum defined in the language.

  • Do not raise implementation-specific exceptions.

  • Convert implementation-specific exceptions within interface packages to visible user-defined exceptions.

representation clauses and implementation-dependent features

  • Use algorithms that do not depend on the representation of the data and, therefore, do not need representation clauses.

  • Consider using representation clauses when accessing or defining interface data or when a specific representation is needed to implement a design.

  • Do not assume that sharing source files between programs guarantees the same representation of data types in those files.

  • Avoid using package System constants except in attempting to generalize other machine-dependent constructs.

  • Avoid machine code inserts.

  • Use the package Interfaces and its language-defined child packages rather than implementation-specific mechanisms.

  • Consider using pragma Import rather than access-to-subprogram types for interfacing to subprograms in other languages.

  • Isolate all subprograms employing pragmas Import, Export, and Convention to implementation-specific (interface) package bodies.

  • Avoid pragmas and attributes added by the compiler implementor.

  • Avoid dependence on Ada.Unchecked_Deallocation.

  • Avoid dependence on the attribute Unchecked_Access.

  • Avoid dependence on Ada.Unchecked_Conversion.

  • Avoid the direct invocation of or implicit dependence upon an underlying host operating system or Ada run-time support system, except where the interface is explicitly defined in the language (e.g., Annex C or D of the Ada Reference Manual [1995]).

  • Use standard bindings and the package Ada.Command_Line when you need to invoke the underlying
    run-time support system.

  • Use features defined in the Annexes rather than vendor-defined features.

input/output

  • Use constants and variables as symbolic actuals for the Name and Form parameters on the predefined I/O packages. Declare and initialize them in an implementation dependency package.

  • Close all files explicitly.

  • Avoid performing I/O on access types.

  • Consider using Sequential_IO or Direct_IO instead of Stream_IO unless you need the low-level, heterogeneous I/O features provided by Stream_IO.

  • Consider using Current_Error and Set_Error for run-time error messages.


In This Guide:
Table of Contents
Chapter 1
Chapter 2
Chapter 3
Chapter 4
Chapter 5
Chapter 6
Chapter 7
Chapter 8
Chapter 9
Chapter 10
Chapter 11
Appendix
References
Bibliography
Index