|
|
Programming by Contract is a very usefull feature of Eiffel, the programming language envisioned and implemented by Bertrand Meyer. If you want to know more details on what it is and what you gain by using it, there is a nice introduction by the people who wrote Eiffel. With regard to the Toolserver Framework For Python, programming by contract has three parts (this feature is available starting with version 0.3.3 of the Toolserver Framework For Python):
Every API designer gains a lot by using programming by contract paradigms: your API users know in much more detail what your API expects. This goes far beyond type declarations in statically typed languages. Your users will know much more detail about your API results, too. And you will have some mechanism by which you can decide wether your internal state is out of order - in which case you should accept no API calls any more, as you can't provide correct results. Of course this adds to performance. So usually you don't want to run your toolserver with enabled contracts. But while debugging a system, developing a larger system or when problems show up, you can enable contract checking by just adding the -c switch to your tsctl command. Contracts are checked directly around the inner API call - so postconditions are checked after all REST wrappers did their work! And postconditions are checked before any REST wrapper has a chance to look at the result! So you can't actually define contracts for the REST wrappers themselve, but as they only pass their data to or fetch their data from the API call, they are included in checks. TooFPy processes an API call as follows, if it matches a REST request:
If you have SOAP or XMLRPC calls, a call looks like this:
The _invariant is called without additional arguments - it just checks on inner state of the tool, usually checks instance variables and maybe database content (although that might slow down the system massively - better use referential integrity checks or other database means to check your database for consistency). The _pre_condition (the actual method called is prefixed with the original method name) is called with the identical parameters as the original method. You should only check parameters and inner state. The _post_condition is called with the result as the only parameter. Of course it is only called if there was no exception in the original computation - if there is an exception, only the invariant is checked again! All condition methods must never change anything internally, never try to repair stuff or do other weird things. They should only d a bunch of assertions and other tests and throw exceptions if something is not as expected! Now on to some code. I give a short snippet that shows how an API method with pre- and postconditions looks like. This example is quite silly, but shows the syntax. It's a list of numbers. Numbers must be between 1 and 100 (inclusive) and the list can take at most 100 elements. It has a bug: the appendNumber doesn't check for the length and happily adds numbers 101 and above. But if you run the toolserver with tsctl -c, you will get an exception as soon as the invariant is broken!
class SampleTool(StandardTool):
def _initparms(self): self.lst = [] def _invariant(self): assert len(self.lst) < 100 def addNumber(self, number): self.lst.append(number) def addNumber_pre_condition(self, number): assert type(number) == type(1), "Type must be integer" assert (number < 100) and (number > 0), "Number must be less than 100, but greater than 0" def length(self): return len(self.lst) def length_post_condition(self, result): assert (result < 100) and (result >= 0), "Result must be less than 100, but greater or equal 0" The idea to implement all this is that remote APIs are much more in need of correct specifications than internal APIs. With internal APIs, you can usually debug directly both the library and your code. With remote APIs, you can only debug one side. The provider will only be able to debug his server code. The consumer will only be able to debug his client code. But both can do wrong and code on both sides can do weird stuff. Programming by contract adds a safety net to services and a great debugging aid. If your checks are very light and fast, you can even run your production system under contract setting, as usually the RPC encoding/decoding takes up much more time than your little condition checks! last change 2004-02-23 19:27:12 |
This is the Python Desktop Server weblog.
|