Re: Discuss changing the behaviour of append()

new topic     » goto parent     » topic index » view thread      » older message » newer message
gimlet said...
DerekParnell said...

One of the better techniques in designing applications, is to always provide validity checking on data that comes from outside the program, as opposed to data that is generated by the program.

This can (and should) be applied to functions.

I hold a contrary opinion. In the general case, a function should not be required to validate its arguments. I understand that this sounds almost heretical to many, but let me explain.

A function can not know if the values in its arguments originated from outside the program or internally. In a well-behaved program, data coming into the application will be validated prior to it being used, and usually at the point at which it can give the most meaningful error/warning message to the end user. Calling a function with arguments is defined as data usage. Also, in a well-behaved program, data generated by the program will be correct, and this will be proven by appropriate logic testing during the development stage prior to its live release.

I believe that a routine should assume that it is operating inside a well-behaving program. Thus it will not be required to ensure that its arguments, or any shared program data, are valid. The program will have already done that so there's no need to do it again. However, any data that a function creates and makes available to other code, should be validated by that function. This needs only to be done while in development mode.

At this point I need to talk a bit about "what is valid data?" In general terms, its the set of values that a routine can reasonably be expected to operate on.

For example, consider this function to calculate the real 'X' value for a quadratic equation, which is defined as ... aX2 + bX + c = 0 . For any given quadratic equation, the X can have zero, one or two values which solves the equation. Thus the function should return a sequence of 0, 1, or 2 elements.

The formula that the function needs to evaluate is ...

X1 = (-b - sqrt(b2 - 4ac)) / 2a and X2 = (-b + sqrt(b2 - 4ac)) / 2a

The first thing to note is what should the function do if a=0? If it doesn't check then the program will fail with a divide-by-zero error. If it does check, then its also admitting that the calling program is trying to use the function in an illogical manner. By definition, if a=0 then the equation is no longer a quadratic one but a linear one, and this function is only supposed to be used for quadratics.

I maintain that it's the calling program's responsibility to do the checking of a before calling the function, because it knows when is the most efficient time to do that. The function would be nearly always be duplicating or wasting effort if always checked for a=0.

So, one version of this function could be ...

 
ifdef DEVEL_MODE then 
  include std/error.e 
  procedure fail_if(integer x, sequence t) 
    if x then 
        crash("** FAILURE DETECTED **\n" &  t & '\n') 
    end if 
  end procedure 
end ifdef 
 
-- Compares two floating point numbers allowing for a tolerance. 
function compare_real(atom f, atom g, atom e = 0.000_000_000_000_009) 
 
	atom r 
	r = f-g 
	if r > e then 
		return 1 
	elsif r < -e then 
	    return -1 
	else  
	    return 0 
	end if 
end function     
 
----------------------------------- 
public function solve_quad_eqn(atom a, atom b, atom c) 
----------------------------------- 
    sequence res 
 
    atom descrim 
 
    descrim = b * b - 4 * a * c 
    -- Adjust result for limitations in floating point math. 
    if compare_real(descrim, 0) = 0 then 
    	descrim = 0 
    end if 
     
    if descrim < 0 then 
        res = {} 
         
    elsif descrim = 0 then 
        res = {-(b / 2 * a)} 
         
    else 
        descrim = sqrt(descrim) 
        res = - { b - descrim, b + descrim } / (2 * a)  
         
    end if 
 
    ifdef DEVEL_MODE then 
    	-- Check that results are sane. 
        for i = 1 to length(res) do 
            fail_if(not atom(res[i]), "The results must be real numbers") 
        end for 
 
    	atom temp = compare_real( (b*b), (4*a*c) ) 
    	atom vx = -(b / 2 * a) 
    	atom vy = -( b*b - 4*a*c) / (4 * a) 
    	 
        if length(res) = 0 then 
           fail_if(temp >= 0, "Should have one or two Real results") 
           fail_if(a > 0 and vy <= 0, "Vertex too low") 
           fail_if(a < 0 and vy >= 0, "Vertex too high") 
            
        elsif length(res) = 1 then 
           fail_if(temp != 0, "Should have zero or two Real results") 
           fail_if(compare_real(vy , 0) != 0, "Vertex Y should be zero") 
           fail_if(compare_real(vx , res[1]) != 0, "Vertex X should be result") 
            
        elsif length(res) = 2 then 
           fail_if(temp <= 0, "Should have zero or one Real results") 
           fail_if(compare_real(-(b / a), res[1] + res[2]) != 0, "Invalid symetry") 
           fail_if(a > 0 and vy >= 0, "Vertex too high") 
           fail_if(a < 0 and vy <= 0, "Vertex too low") 
            
        else 
           fail_if(1, "Only a maximum of two results are allowed") 
        end if 
 
    end ifdef 
 
    return res 
 
end function 
 
ifdef UNIT_TESTING then 
procedure test_solve_quad() 
	for i = -20 to 20 do 
	for j = -4 to 4 do 
	for k = -5 to 5 by 0.3 do 
 
	if i != 0 then 
		solve_quad_eqn(i/10, j, k) 
	end if 
	end for 
	end for 
	end for 
end procedure 
test_solve_quad() 
end ifdef 


So only during development would the results of the function be validated. Once in production, the function is known to be behaving as specified so we no longer have to make sure its results are good; they will be. And we don't check its arguments because the calling program should have already checked those.

gimlet said...

At some point here I am losing you.

Yes, that you have.

new topic     » goto parent     » topic index » view thread      » older message » newer message

Search



Quick Links

User menu

Not signed in.

Misc Menu