It is instructive to compare Slice and CORBA IDL because the different feature sets of the two languages illustrate a number of design principles. In this section, we briefly compare the two languages and explain the motivation for the presence or absence of each feature.
Slice is neither a subset nor a superset of CORBA IDL. Instead, Slice both removes and adds features. The overall result is a specification language that is both simpler and more powerful than CORBA IDL, as we will see in the following sections.
As a result, CORBA applications typically use a plethora of exception handlers following each call or block of calls or, at the other extreme, use only generic exception handling at too high a level to still yield useful diagnostics. The trade-off imposed by this is rather draconian: either you have good error handling and diagnostics, but convoluted and difficult-to-maintain code, or you sacrifice error handling in order to keep the code clean.
Knowing that an operation is idempotent permits the Ice run time to transparently recover from transient errors that otherwise would have to be handled by the application. This makes for a more reliable and convenient platform. In addition, idempotent operations (if they do not modify the state of the object) can be mapped to a corresponding construct (if available) in the target language, such as C++
const member functions. This improves the static type safety of the system.
2. Redundant features are unergonomic and confusing. A single feature is both easier to learn (for programmers) and easier to implement (for vendors) than two features that do the same thing. Moreover, there is always a niggling feeling of unease, especially for newcomers: "How come I can do this in two different ways? When is one style preferable over the other style? Are the two features really identical, or is there some subtle difference I am missing?" Not providing more than one way to do the same thing avoids these questions entirely.
Unsigned integers add very little to the type system but make it considerably more complex. In addition, if the target programming language does not support unsigned integers (Java does not), it becomes almost impossibly difficult to deal with out-of-range conditions. Currently, Java CORBA programmers deal with the problem by ignoring it. (See
[9] for a very cogent discussion of the disadvantages of unsigned types.)
Whether or not identifiers should contain underscores is largely a matter of personal taste. However, it is important to have some range of identifiers reserved for the language mapping in order to avoid name clashes. For example, a CORBA IDL specification that contains the identifiers
T and
T_var in the same scope cannot be compiled into valid C++. (There are numerous other such conflicts, for C++ as well as other languages.)
CORBA IDL offers both arrays and sequences (with sequences further divided into bounded and unbounded sequences). Given that a sequence can easily be used to model an array, there is no need for arrays. Arrays are somewhat more precise than sequences in that they allow you to state that precisely
n elements are required instead of at most
n. However, the minute gain in expressiveness is dearly paid for in complexity: not only do arrays contribute to code bloat, they also open a number of holes in the type system. (The CORBA C++ mapping suffers more from the weak type safety of arrays than from any other feature.)
#pragma version directives in CORBA IDL have no purpose whatsoever. The original intent was to allow versioning of interfaces. However, different minor and major version numbers do not actually define any notion of backward (non-)compatibility. Instead, they simply define a new type, which can also be achieved via other means.
CORBA IDL allows you to initialize a constant with a constant expression, such as
X * Y. While this seems attractive, it is unnecessary for a
specification language. Given that all values involved are compile-time constants, it is entirely possibly to work out the values once and write them into the specification directly. (Again, the number of constant expressions in published IDL specifications can be counted on the fingers of one hand.)
2
The cost of adding fixed-point types to CORBA in terms of code size and API complexity is considerable. Especially for languages without a native fixed-point type, a lot of supporting machinery must be provided to emulate the type. This penalty is paid over and over again, even for languages that are not normally chosen for financial calculations. Moreover, no-one actually does calculations with these types in IDL—instead, IDL simply acts as the vehicle to enable transmission of these types. It is entirely feasible (and simple to implement) to use strings to represent fixed-point values and to convert them into a native fixed-point type in the client and server.
While there is a genuine need for extended floating-point types for some applications, the feature is difficult to provide without native support in the underlying hardware. As a result, support for extended floating-point types is widely unimplemented in CORBA. On platforms without extended floating-point support, the type is silently remapped to an ordinary
double.
IDL typedef has probably contributed more to complexity, semantic problems, and bugs in applications than any other IDL feature. The problem with
typedef is that it does not create a new type. Instead, it creates an alias for an existing type. Judiciously used, type definitions can improve readability of a specification. However, under the hood, the fact that a type can be aliased causes all sorts of problems. For many years, the entire notion of type equivalence was completely undefined in CORBA and it took many pages of specification with complex explanations to nail down the semantic complexities caused by aliased types.
4
IDL allows you define types inside the scope of, for example, an interface or an exception, similar to C++. The amount of complexity created by this decision is staggering: the CORBA specification contains numerous and complex rules for dealing with the order of name lookup, exactly how to decide when a type is introduced into a scope, and how types may (or may not) hide other types of the same name. The complexity carries through into language mappings: nested type definitions result in more complex (and more bulky) source code and are difficult to deal with in languages that do not permit the same nesting of definitions as IDL.
5
As most OO textbooks will tell you, unions are not required; instead, you can use derivation from a base class to implement the same thing (and enjoy the benefit of type-safe access to the members of the class). IDL unions are yet another source of complexity that is entirely out of proportion to the utility of the feature. Language mappings suffer badly from this. For example, the C++ mapping for IDL unions is something of a challenge even for experts. And, as with any other feature, unions extract a price in terms of code size and run-time performance.
IDL allows the use of #pragma directives to control the contents of type IDs. This was a most unfortunate choice: because
#pragma is a preprocessing directive, it is completely outside the normal scoping rules of the language. The resulting complexity is sufficient to take up several pages of explanations in the specification. (And, even with all those explanations, it is still possible to use
#pragma directives that make no sense, yet cannot be diagnosed as erroneous.)
IDL permits an operation to be tagged with a oneway keyword. Adding this keyword causes the ORB to dispatch the operation invocation with "best-effort" semantics. In theory, the run time simply fires off the request and then forgets all about it, not caring whether the request is lost or not. In practice, there are a problems with this idea:
• The semantics of oneway invocations were poorly defined in earlier versions of CORBA and refined later, to allow the client to have some control over the delivery guarantees. Unfortunately, this resulted in considerable complexity in the application APIs and the ORB implementation.
• Architecturally, the use of oneway in IDL is dubious: IDL is an
interface definition language, but
oneway has nothing to do with interface. Instead, it controls an aspect of call dispatch that is quite independent of a specific interface. This begs the question of why
oneway is a first-class language concept when it has nothing to do with the contract between client and server.
Slice does not have a oneway keyword (even though Ice supports oneway invocations). This avoids contaminating Slice definitions with non-type related directives. For asynchronous method invocations, Slice uses metadata directives. The use of such metadata does in no way affect the client–server contract: if you delete all metadata definitions from a specification and then recompile only one of client or server, the interface contract between client and server remains unchanged and valid.
IDL contexts are a general escape hatch that, in essence, permit a sequence of name–string pairs to be sent with every invocation of an operation; the server can examine the name–string pairs and use their contents to change its behavior. Unfortunately, IDL contexts are completely outside the type system and provide no guarantees whatsoever to either client or server: even if an operation has a context clause, there is no guarantee that the client will send any of the named contexts or send well-formed values for those contexts. CORBA has no idea of the meaning or type of these pairs and, therefore, cannot provide any assistance in assuring that their contents are correct (either at compile or run time). The net effect is that IDL contexts shoot a big hole through the IDL type system and result in systems that are hard to understand, code, and debug.
CORBA has the notion of supporting multiple codesets and character sets for wide strings, with a complex negotiation mechanism that is meant to permit client and server to agree on a particular codeset for transmission of wide strings. A large amount of complexity arises from this choice; as of CORBA 3.0, many ORB implementations still suffer interoperability problems for the exchange of wide string data. The specification still contains a number of unresolved problems that make it unlikely that interoperability will become a reality any time soon.
The IDL Any type is a universal container type that can contain a value of any IDL type. The feature is somewhat similar to exchanging untyped data as a
void * in C, but improved with introspection, so the type of the contents of an
Any can be determined at run time. Unfortunately, type
Any adds much complexity for little gain:
• The API to deal with type Any and its associated type description is arcane and complex. Code that uses type
Any is extremely error-prone (particularly in C++). In addition, the language-mapping requires the generation of a large number of helper functions and operators that slow down compilations and take up considerable code and data space at run time.
• Despite the fact that type Any is self-describing and that each instance that is sent over the wire contains a complete (and bulky) description of the value’s type, it is impossible for a process to receive and re-transmit a value of type
Any without completely unmarshaling and remarshaling the value. This affects programs such as the CORBA Event Service: the extra marshaling cost dominates the overall execution time and limits performance unacceptably for many applications.
Slice uses classes to replace both IDL unions and type Any. This approach is simpler and more type safe than using type
Any. In addition, the Slice protocol permits receipt and retransmission of values without forcing the data to be unmarshaled and remarshaled; this leads to smaller and better performing systems than what could be built with CORBA.
Earlier versions of the CORBA specification permitted the use of anonymous IDL types (types that are defined in-line without assigning a separate name to them). Anonymous types caused major problems for language mappings and have since been deprecated in CORBA. However, the complexity of anonymous types is still visible due to backward compatibility concerns. Slice ensures that every type has a name and so prevents any of the problems that are caused by anonymous types.