Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Advanced CORBA Programming wit C++ - M. Henning, S. Vinoski.pdf
Скачиваний:
65
Добавлен:
24.05.2014
Размер:
5 Mб
Скачать

IT-SC book: Advanced CORBA® Programming with C++

typedef

unsigned long prime;

 

prime

next_prime(in long n);

 

void

next_prime(in long n, out prime p); // Error

void

next_prime(inout long n);

// Error

};

Unfortunately, this is not legal IDL. Operation names are scoped by their enclosing interface and must be unique within that interface, so overloading of operations is impossible. This restriction was introduced because overloading makes it difficult to map IDL to a non-OO language such as C. For C, overloaded functions would have to use some form of name mangling (which is fine for a compiler but not very nice for a human developer).

Anonymous Types

Parameters and return values for operations must be declared using a named type. Anonymous types are illegal as a return type and in parameter declarations:

sequence<long> get_longs();

//

Error,

anonymous

type

void get_octets(out sequence<octet> s); //

Error,

anonymous

type

Because anonymous types create awkward language mappings, you should make it a habit always to use named types, even when anonymous types are legal. (They are legal as sequence and array elements and as structure, union, and exception member definitions.)

Constant Operations

Unlike C++, IDL does not distinguish between operations for read and write access. The following is in error:

SomeType read_value() const;

// Error, illegal const qualifier

As a consequence, if a client has a reference to an object, it can invoke all operations on that object whether or not they modify object state. (On ORBs that provide it, you can use the CORBA Security Service to create read-only access for specific operations.)

4.9 User Exceptions

IDL uses exceptions as a standard way to indicate error conditions. An IDL user exception is defined much like an IDL structure, and that allows an exception to contain an arbitrary amount of error information of arbitrary type. However, exceptions cannot be nested. Here is an example:

exception Failed {}; exception RangeError {

unsigned long supplied_val;

89

IT-SC book: Advanced CORBA® Programming with C++

unsigned long min_permitted_val; unsigned long max_permitted_val;

};

Exceptions, like structures, create a namespace, so the exception member names need be unique only within their enclosing exception.

Exceptions are types but cannot be used as data members of user-defined types. For example, the following is illegal:

struct ErrorReport {

 

Object

obj;

// Error, exception as data member

RangeError

exc;

};

 

 

An operation uses a raises expression to indicate the exceptions it may possibly raise:

interface Unreliable {

void can_fail() raises(Failed);

void can_also_fail(in long l) raises(Failed, RangeError);

};

As you can see, an operation may raise more than one type of exception. Operations must indicate all the exceptions they may possibly raise. It is illegal for an operation to throw a user exception that is not listed in the raises expression. A raises expression must not be empty.

IDL does not support exception inheritance. This means that you cannot arrange error conditions into logical hierarchies (as you can in C++) and catch all exceptions in a subtree by catching a base exception. Instead, every user exception creates a new type that is unrelated to any other exception type. This restriction exists because exception hierarchies using multiple inheritance are difficult to map to languages that do not support the concept directly. (Because exceptions have data members, the target language would have to support implementation inheritance.) However, single inheritance for exceptions could have been mapped quite easily, even to target languages that lack support for implementation inheritance.

Unfortunately, even single inheritance for exceptions did not make it into the initial OMG IDL specification, so we are stuck without it. (It is unlikely that exception inheritance will ever be added to OMG IDL because it would be disruptive to some language mappings.)

4.9.1 Exception Design Issues

When designing your interfaces, keep in mind that it is harder for a programmer to deal with exceptions than ordinary return values because exceptions break the normal flow of control. You should take some care in deciding whether something is an exception or a

90

IT-SC book: Advanced CORBA® Programming with C++

return value. Consider the following interface, which provides a database lookup operation:

interface DB {

ResultSeq;

typedef sequence<Record>

typedef string

QueryType;

exception NotFound {

// Bad approach

QueryType failed_query;

};

ResultSeq lookup(in QueryType query) raises(NotFound);

};

The lookup operation in this interface returns a sequence of results in response to a passed query. If no matching records are found, it raises NotFound. There are a number of things wrong with this interface.

When searching a database, it is expected that a search will occasionally not locate anything. It is therefore inappropriate to raise an exception to indicate this. Instead, you should use a parameter or return value to indicate the empty result.

In the preceding example, raising an exception is redundant because you can indicate the empty result by returning an empty sequence. The NotFound exception complicates the interface unnecessarily.

The NotFound exception contains the failed_query member. Because only one query is passed to the operation, there is only one possible query that can fail—namely, the one that was passed to lookup. The exception contains information that is already known to the caller, and that is pointless.

The DB interface does not allow the caller to find out why a query failed. Was it because no records matched the query, or was it because the query contained a syntax error? Compare the preceding version with this one:

interface DB {

ResultSeq;

typedef sequence<Record>

typedef string

QueryType;

exception SyntaxError {

 

unsigned short position;

};

ResultSeq lookup(in QueryType query) raises(SyntaxError);

};

This version is almost identical to the previous one. However, the flaws are eliminated. A search that returns no results is indicated by returning an empty sequence instead of raising an exception.

An exception is raised if the query itself is unacceptable. This enables the caller to distinguish between a bad query and a query that merely did not return any results.

91

IT-SC book: Advanced CORBA® Programming with C++

The exception contains useful information. In this case, it contains the index of the character position in the query string at which a syntax error was found.

The DB example highlights some lessons that many designers still refuse to heed. They can be summarized as follows.

Raise exceptions only for exceptional conditions.

Operations that raise exceptions for expected outcomes are ergonomically poor. Consider the programmer who needs to call such an operation. The C++ mapping maps IDL exceptions to C++ exceptions. C++ exceptions are harder to deal with than normal return values or parameters because exceptions break the normal flow of control. Forcing the programmer to catch an exception for expected behavior is simply bad style.

Make sure that exceptions carry useful information.

It is worse than useless to tell the caller something that is already known. Make sure that exceptions convey precise information.

An exception should convey precisely one semantic error condition. Do not lump several error conditions together so that the caller can no longer distinguish between them.

Make sure that exceptions carry complete information.

If exceptions carry incomplete information, the caller will probably need to make further calls to find out what exactly went wrong. If the initial call did not work, there is a good chance that subsequent calls will also fail, and that can make precise error handling impossible for the caller.

Design interfaces so that they cater to the needs of the caller and not the needs of the implementer.

Computing abounds with difficult-to-use APIs that provide poor abstractions of functionality. Typically, such APIs come into existence because they are written by the implementer of the functionality and not its user. But good tools are built for the convenience of the tool user; the effort required by the tool maker to create the tool is usually considered irrelevant (within reason). APIs are tools, and you should build them to suit their users.

Do not use normal return values or parameters to indicate errors.

As you will see in the next section, operations can raise exceptions even if they do not have a raises expression. If you use error codes instead of exceptions, callers end up with inconsistent and convoluted error handling because they must check for exceptions as well as an error return code.

92