Re: Discuss changing the behaviour of append()
- Posted by DerekParnell (admin) Jun 12, 2014
- 4757 views
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.
At some point here I am losing you.
Yes, that you have.

