Design by contract in tinymail

About

Prescribes that software designers should define precise checkable interface specifications for software components based upon the theory of abstract data types and the conceptual metaphor of a business contract. You can find more about design by contract here.

In tinymail

Tinymail uses the the same "require" and "ensure" concepts as the original design by contract features of Eiffel. When developing in tinymail, however, you can only strengthen the contracts. You can't weaken them. Tinymail has API 'launchers' that should always be used in stead of the function pointers of the class structs. Each type in the GObject type system has such a class struct with a function pointer for each method in it. An interface is such a type with empty function pointers. An implementation of such an interface fills in the function pointers with pointers to functions that implement the methods in a meaningful way.

Tinymail's API, however, consist of normal C functions. You don't call those function pointers but in stead you call the C functions. It's those C functions that will call the function pointer for you.

When you enabled design by contract, those functions will wrap the function pointer call with a require and an ensure section. This effectively checks the implementation of the method of an interface against the contract of it (it ensures that the implementation's result or output will be conforming to the contract, in case the input requirements were fulfilled).

Enabling design by contract

In tinymail you must enable design by contract before you compile the framework. Software developers are encouraged to indeed enable it. The reason why it's not always enabled is performance reasons: some contracts require certain actions that decrease performance. Usually your users only care about correct software. While developing, however, the developer cares about getting the software to be correct. There are situations where you might want the software to constantly self check itself. A mail user agent typically isn't that kind of software.

This is how you can enable it:

CFLAGS="-DDBC -DDEBUG" ./autogen.sh --prefix=/opt/tinymail --enable-unit-tests
make
sudo make install

When writing a new API, how do I add the design by contract sections?

This is for example the entire API launcher of TnyDevice's tny_device_force_offline method. When writing your own API launchers, please stick to the conventions and naming as used here. Who knows might somebody someday want to automate a massive change, like a huge search and replace. We want your code to be easy to handle in that case too.

  • Always check the type of "self"
  • Always check for the function pointer not being NULL
  • Always check for all the parameters their types
  • Always check for the type of the return value (keep in mind NULL, if NULL is allowed as return type)
  • Try to check as much of your contract as possible in the ensure. The more you check, the more use the DBC clauses have.
  • But never check things that are sometimes true, unless you can know (from within the interface) that it'll be true. By this I mean that you can't just make any assumption. Although slower, even with DBC enabled must all of the code still work exactly as before.
  • Don't let the DBC clauses change the state of the instance unless this is contractually intended behavior or behavior that the implementer must cope with.
/**
 * tny_device_force_offline:
 * @self: a #TnyDevice object
 * 
 * Force offline status
 * 
 * Example:
 * <informalexample><programlisting>
 * TnyDevice *device = ...
 * tny_device_force_offline (device);
 * if (tny_device_is_online (device))
 *      g_print ("Something is wrong\n");
 * tny_device_reset (device);
 * </programlisting></informalexample>
 **/
void 
tny_device_force_online (TnyDevice *self)
{
#ifdef DBC /* require */
	g_assert (TNY_IS_DEVICE (self));
	g_assert (TNY_DEVICE_GET_IFACE (self)->force_online_func != NULL);
#endif

	TNY_DEVICE_GET_IFACE (self)->force_online_func (self);

#ifdef DBC /* ensure */
	g_assert (tny_device_is_online (self) == TRUE);
#endif

	return;
}

Thread safety

Clever readers of above code already know this: Tinymail's DBC checks aren't thread safe. If assertions happen while doing things with a lot threads on the same instances, it might not be a bug but rather a race condition. If some hard worker comes by and adds mutexes within the #ifdef/#endif blocks, I would very much welcome his or her huge patch.

Getting the contracts in the API reference documentation

This ain't done yet. I would be very interested in gtk-doc patches and/or work that makes it ease, possible or actual that the require and ensure contract clauses are automatically put in the API reference documentation.

Handling contract violations

The glib g_assert API is used for checking the contract. If you use -DG_DISABLE_ASSERT in your CFLAGS variable, you can let violations get ignored. You can probably also provide your own g_assert macro implementation to handle contract violations your way.

References to related other documentation