Am 18.07.2012 11:06, schrieb Lipska the Kat:
On 18/07/12 01:46, Andrew Cooper wrote:
Take for example a Linux system call handler.  The general form looks a
little like (substituting C for python style pseudocode)

if not (you are permitted to do this):
     return -EPERM
if not (you've given me some valid data):
     return -EFAULT
if not (you've given me some sensible data):
     return -EINVAL
return actually_try_to_do_something_with(data)

How would you program this sort of logic with a single return statement?
  This is very common logic for all routines for which there is even the
remotest possibility that some data has come from an untrusted source.

Eeek! well if you insist (type bound)

someType -EPERM
someType -EFAULT
sometype -EINVAL
someType -EDOSOMETHING

//method
someType checkSomething(data){

someType result = -EINVAL //or your most likely or 'safest' result

if not (you are permitted to do this):
       result = -EPERM
if not (you've given me some valid data):
       result = -EFAULT
if not (you've given me some sensible data):
       result = -EINVAL
else
       result = -EDSOMETHING

       return result
}
//cohesive, encapsulated, reusable and easy to read

This is a classic discussion topic, whether single exit (SE) functions should be used or not. There are two things I consider as problematic with them: 1. In the presence of exceptions, every function has at least two possible paths that can be taken out, one returns a value (or None, in Python), the other throws an exception. For that reason, trying to achieve SE is a dangerous illusion. The syscall handler above is C, which doesn't have exceptions like Java, C++ or Python, so it doesn't suffer those two paths. 2. The biggest problem with SE functions is that you often need to skip over lots of code before you finally find out that the fault at the very beginning causes nothing else to happen inside the function before it is finally returned to the caller. A typical symptom is deeply nested if-else structures. Another symptom is result variables that are checked multiple times to skip over effectively the rest of the function, which "unrolls" the nested if-else structures. Yet another symptom is a very fine granularity of microscopic functions, which is effectively a distributed nest of if-else structures.

Coming back to Python, this would look like this:

   if not /you are permitted to do this/:
       raise NotPermitted("go awai!")
   if not /you've given me valid data/:
       raise TypeError("unexpected input")
   if not /you're given me sensible data/:
       raise ValueError("invalid input")
   # do stuff here...

If you shoehorn this into an SE function (which you can't do if something in between might throw), then it probably looks like this:

   error = None
   if not /you are permitted to do this/:
       error = NotPermitted("go awai!")
   elif not /you've given me valid data/:
       raise TypeError("unexpected input")
   elif not /you're given me sensible data/:
       raise ValueError("invalid input")
   else:
       # do stuff here...
   if error:
       raise error
   else:
       return result


//later

if(checkSomething(data) == EDOSOMETHING){

     actually_try_to_do_something_with(data)
}
else{
     //who knows
}

Interestingly, you suggest to divide the original function into one that verifies some conditions and one that does the actual work. Using an early return is to me like drawing a big red line inside a function by which it can be split into two sections. This makes it IMHO equally clear, even clearer since I don't have to locate and read that other function. My bioware parses this so that if the first part succeeds, the second part can be read independently thereof, which reduces the amount of info to keep in mind at a time.

Also, when changing code, I don't have to locate other places where the utility function (checkSomething) is called (Python allows local functions, which can be very(!!) useful). Since the code is inline, I know that only this one function is affected. Yes, this is in direct contrast to the reusability you mentioned. Neither ease of change nor reusability are goals in and of themselves though, so this is not a black-or-white question and a compromise can be good enough. It's a question of taste, experience, phase of the moon, coffeination levels etc.

:)

Uli
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to