Ada has a set of unique technical features that make it highly effective for use in large, complex and safety-critical projects. But the benefits don’t stop there. We’ll explain how these same technical strengths can also translate into long-term business benefits.
The programming language choice is one of many factors that affect whether a software project will succeed. Other obvious determinants are the skills of the programmers, the planning and management of the development process, and the quality of the development tools (IDE, compilers, etc). The role of the programming language, when viewed in this context, is to offer leverage: developers will do their jobs more effectively and efficiently when the language they are using offers better support for the various “ilities” (readability, maintainability, portability, etc.) These are precisely the goals that Ada was designed to meet, and which it has been achieving successfully for nearly twenty years in large, long-lived systems worldwide.
For most large, long-lived systems the major effort arises not so much in the initial coding stage but rather during testing / quality assurance, functionality upgrades, porting to new platforms, and similar “back end” activities. Ada was specifically designed to address these issues and does so more effectively than other languages. Its many built-in checks allow the compiler or linker to detect errors that in a C-based language would only be caught during run-time debugging, when they are much more expensive to track down. Its reader-friendly syntax makes program maintenance easier. It avoids the trap of allowing programmers to write cryptic code that they may have understood at the time it was written, but which is likely to contain hidden errors, and to be extremely hard to modify or adapt as new requirements emerge. Moreover, with its expressive features and strong checking, Ada encourages a “think first, code later” discipline that translates into fewer bugs and higher productivity.
Ada is an established and low-risk COTS technology. It was originally designed in the early 1980s (Ada 83) and was then enhanced in the mid 1990s (Ada 95), mid 2000s (Ada 2005), late 2000s (Ada 2012), and late 2010s (Ada 2022) with careful attention paid, at every stage, to practical issues such as the effect of proposed language features on run-time efficiency. Vendors have had more than 25 years of implementation experience with Ada 95; high-quality compilers, extensive toolsets, comprehensive libraries, sophisticated IDEs, and interfaces with common third-party tools and technologies are available across a wide range of both native and cross platforms
The Ada language is maintained by a dedicated technical working group under ISO (International Organization for Standardization). The ISO standardization process ensures vendor neutrality, thorough review, and language stability while also permitting periodic upgrades and amendments. Indeed, a revised and upward-compatible version of the Ada standard is expected in 2022, bringing a number of enhancements inspired by user experience with Ada 95, Ada 2005, and 2012. Unlike other languages that have achieved standardization only after implementations have become widespread, Ada was standardized first and implemented later. This avoided the technical and political problems of trying to define the syntax and semantics of features that were implemented in incompatible ways.
It is rare, especially in large systems, for the software to be developed solely in one programming language. Often there is a need for low-level routines written in C or assembly language, GUI components that might be written in C++ or Java, or numerics libraries that might be written in Fortran. Ada is unique in having standard features for interfacing with other languages. This makes it easier to develop multi-language systems — e.g., the way in which C can be called from Ada, or vice versa, is defined by the Ada standard — and also makes it easier to port such systems across different platforms and compilers. The Ada approach is also much more efficient than other styles (e.g. Java’s JNI), in which a middleware layer needs to perform run-time data conversions in order for modules in different languages to communicate with each other.
Ada was designed to support sound software engineering practice, and its features follow consistent principles that are intuitive and easy to learn. Ada is much simpler to master than C++, a complex language with many syntactic and semantic subtleties. Ada is also easier to learn than Java, whose “pure” object-orientation can make simple programs surprisingly complicated and whose concurrency features are rather error-prone.
Several sorts of high-quality resources are available to individuals wishing to learn Ada, including textbooks, online tutorials and reference material, and live instruction. Experience from Ada educators over many years has shown that with a 5-day hands-on course, a programmer familiar with a language such as C can become proficient in Ada. Thus bringing an organization’s staff up to speed in Ada, even if they have little or no previous exposure to the language, is both practical and inexpensive.
Ada has always been an attractive choice in application domains where reliability (versus, say, quickness to market) has been an overriding requirement. Historically this has been most evident in the defense and aerospace industry, with many million of lines of code in use on operational systems. Ada continues to enjoy a strong presence in this domain and is also used commercially in many fields including avionics, shipboard systems, nuclear reactor control, train and subway systems, and communication. Ada is especially attractive in the safety-critical domain, with fielded systems conforming with the most stringent levels of standards such as DO-178B.
Real systems often need to operate on multiple platforms, e.g. for marketing reasons or because of an upgrade to the hardware or operating system. The ease or difficulty of such porting efforts is directly affected by the choice of programming language, and Ada has many qualities that will make this task easier. First, the language was designed to minimize implementation and platform dependences, and where such dependences are necessary Ada makes it easy to isolate them into well-defined modules that can be adapted as needed when the system is to be ported. Second, Ada is a precisely-defined international standard; ambiguities that could interfere with portability are avoided. Questions of interpretation are resolved by a vendor-neutral standardization body. Third, an extensive and openly available conformance test suite is used by compiler vendors to provide confidence that the language features are successfully implemented. Indeed, experience over the years with code portability in Ada has been overwhelmingly positive.
When Ada was first defined, it was ahead of its time. It had ambitious requirements and introduced many features — e.g., packages, exceptions, generics, tasking — that were not used in mainstream languages of the early 1980s. Subsequent developments, both in the computing field in general and in programming language technology in particular, have confirmed that Ada’s design decisions were correct.
As computing power and memory capacity have increased, features found in Ada have proved to “scale up” to meet the needs of ever-demanding applications. Indeed, many of these features have been borrowed and/or adapted by other languages: e.g., Ada’s package construct directly influenced C++’s namespace mechanism; Ada’s exception semantics is basically found also in C++ and Java; Ada’s generics influenced templates in C++; more recently, Ada’s real-time features have inspired some of the decisions in the Real-Time Specification for Java.
Ada is an excellent match for the software demands of modern computing systems. It anticipated these demands when it was first designed, and has kept in synch with new technologies over the years. It has helped advance both the state of the art and the state of the practice.
A modern software system can easily comprise millions of lines of code; developing programs of this size requires language features that “scale up”. Ada was designed to meet this requirement, through features that allow flexible approaches to software development, that avoid “namespace pollution”, that allow a clear separation between a module’s interface and its implementation, and that facilitate writing general-purpose, reusable components.
Flexibility in development is obtained through a general separate compilation facility that supports traditional “bottom-up” methods (usage of existing libraries) as well as top-down techniques (functional decomposition). Ada’s hierarchical library mechanism allows a module to be extended without modifying or recompiling the original, and greatly eases the job of defining an architecture for large systems.
“Namespace pollution” occurs when, due to language semantics, otherwise-irrelevant global names become visible and increase the likelihood of clashes with locally-defined names. This can cause serious problems on large projects, sometimes leading to ad hoc solutions such as lists of names that the programmer needs to avoid using. Ada was designed to minimize the chances for namespace pollution; relevant features include the package construct, the hierarchical library mechanism, the Ada 95 “use type” feature, and the block structure semantics.
The separation between interface and implementation, and the limitations on how a name can be referenced depending on where it is defined, form the basis of the classical software engineering techniques known as data abstraction and information hiding. These principles are essential for large system construction, in order to reduce the potential coupling between modules. Data abstraction and information hiding semantics have been part of Ada since the language’s inception and inspired a software architecture methodology known as Object-Oriented Design.
Reusability, although somewhat of a buzzword, is an important aspect of large system development and is realized in Ada through several major features. Generics (known as “templates” in other languages) allow parameterization and instantiation of a module based on entities such as data types and subprograms. Through this facility the programmer can define generalized components such as containers and math libraries, with strong type checking, performed at compile time. Another relevant facility is the set of features for Object Oriented Programming (“OOP”). Ada offers complete support for this important and popular software development methodology. Concepts such as objects, classes, inheritance, and dynamic binding are modeled directly and reliably within Ada’s semantic framework. Ada provides an inheritance mechanism similar to Java’s: single inheritance of classes, and (coming in Ada 2005) multiple inheritance of interfaces. A basic principle of OOP is that a class is a reusable component, both as a candidate for specialization / extension and as a module that can be included in a program. Ada directly supports this principle.
In many kinds of applications, for example real-time systems, concurrency is an intrinsic requirement. Ada is one of the few languages that includes direct support for concurrent programming; moreover, its features were designed with the goals of reliability (e.g., avoidance of race conditions) and readability, while also allowing efficient implementation. A concurrent program typically comprises active components that interact with each other either directly or through shared resources. Ada has direct linguistic support for each of these elements. An active component is modeled by a task, Ada’s unit of concurrent execution. Direct communication between two tasks is achieved by a rendezvous, which provides synchronous passing of data. A shared resource (which requires some sort of mutual exclusion mechanism) may be realized by any of several features in Ada (including the protected object, introduced in Ada 95), depending on the required generality. These semantic building blocks are high-level enough to be appropriate for modeling the architecture of a concurrent program. They are much easier to use in practice than the low-level facilities found in other languages, such as Java’s synchronized construct and the wait and notify methods. Moreover, Ada’s features may be implemented to achieve the goal of “what you use is what you pay for”: if a tasking program only requires simple communication, then the run-time cost can be kept correspondingly small. Now that multicore has entered all realms of computing, including embedded real-time systems, the newest versions of Ada include growing support for associating tasks with individual processors, and lighter-weight features for automatically dividing a problem up among processors to provide true parallel processing.
Object-Oriented Programming has become an increasingly popular development approach during recent years, and, as noted above, it is completely supported in Ada. However, OOP is not always needed or even desirable for certain kinds of applications. Unlike so-called “pure” Object-Oriented languages such as Java, Ada allows programs to be developed using more traditional methodologies. This can result in simpler programs, and it also avoids the execution time/space unpredictability typically found in OO languages due to memory-management (“Garbage Collector”). (Ada does not require a Garbage Collector even when the OOP features are used, since the language’s definitional power is expressive enough to allow the programmer to define the needed storage management routines on a class-by-class basis.)
Ada offers clear advantages for real-time systems. Beyond its reliable underpinnings and expressive concurrency model, the Ada standard supplies specialized functionality in the Real-Time Annex. This includes a priority model and task dispatching / object locking policies that allow predictable and efficient performance, and also a pragma that allows the programmer to constrain the language features needed and thereby obtain a faster and more compact run-time library. Common real-time idioms such as periodic tasks, semaphores, etc., are easily expressed, and several real-time development methodologies have been designed with Ada features as their basis.
A specialized but extremely important domain is that of safety-critical systems, in which a failure may lead to human injury or loss of life. In such applications reliability is obviously a necessity, and the programming language can either be a help or a hindrance. The dilemma is that the features that make a language expressive and ease software development may also complicate a program’s analyzability, compromise its execution predictability, or add unwanted overhead. Ada addresses this issue in several ways. First, many of the language’s most useful features will assist analyzability, for example by minimizing data coupling across modules, and, since they incur no run-time effects, will not interfere with predictability or degrade performance. Second, as noted in connection with its real-time support, Ada allows the programmer to specify restrictions on the features that will be used: e.g., no exceptions, or a limited set of tasking features. It is not necessary to demonstrate that the full Ada run-time library complies with safety guidelines such as those in DO-178B. Instead, only the restricted libraries for the features actually used need to be considered, thereby reducing certification costs. One of the most exciting developments in safety-critical technology has come from Ada: the definition of a restricted set of tasking features, the so-called Ravenscar profile. This profile is sufficiently general to allow writing real-world real-time tasking applications, but sufficiently simple to allow the program and the run-time support library to be analyzed and certified against safety-critical requirements. Facilitating certification of concurrent programs, as achieved by Ada’s Ravenscar profile, was a major technological breakthrough.