Home > Archive > Software Engineering > January 2007 > exception handling patterns?
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
exception handling patterns?
|
|
| Timasmith 2006-10-17, 9:59 pm |
| Hi,
Does anyone know of some good articles on exception handling. From my
own experience this is where I am at:
System (as in the application) defined exceptions should
a) Extend something like a system.lang.xxxException when you want to
defend but you dont want to force the method user to catch/throw the
exception
e.g. NullArgumentException extends NullPointerException
b) Extend Exception when you need the method user to handle the
specific exception
e.g. InvalidUserNameException extends Exception
c) Extend your own exceptions when you have a commonality that the
user could protect against
e.g. SQLException extends DataAccessException
e.g. DataParameterException extends DataAccessException
Not sure what else to do around Exception Handling other than handle
them gracefully.
| |
| Oliver Wong 2006-10-17, 9:59 pm |
|
"Timasmith" <timasmith@hotmail.com> wrote in message
news:1161087273.372100.30830@h48g2000cwc.googlegroups.com...
> Hi,
>
> Does anyone know of some good articles on exception handling. From my
> own experience this is where I am at:
>
> System (as in the application) defined exceptions should
>
> a) Extend something like a system.lang.xxxException when you want to
> defend but you dont want to force the method user to catch/throw the
> exception
> e.g. NullArgumentException extends NullPointerException
>
> b) Extend Exception when you need the method user to handle the
> specific exception
> e.g. InvalidUserNameException extends Exception
Your rule "A" should be changed so that the exceptions of interest
extend RuntimeException, or one of its subclasses. NullPointerException is a
subclass of RuntimeException, so the example still works.
Similarly, in "B" you could extend any exception which is not a
RuntimeException (Exception itself is an exception, but not a runtime
exception).
Also, don't be afraid to occasionally throw one of Sun's built-in
exceptions, instead of defining your own.
- Oliver
| |
| H. S. Lahman 2006-10-17, 9:59 pm |
| Responding to Timasmith...
> c) Extend your own exceptions when you have a commonality that the
> user could protect against
> e.g. SQLException extends DataAccessException
> e.g. DataParameterException extends DataAccessException
I basically agree with Wong, so I'll take a different tack here.
These examples sound suspiciously like somebody is throwing an exception
whenever /anything/ goes wrong when accessing a database. [If the
exceptions you have in mind are just for preconditions like improperly
formed SQL strings or improper API arguments (i.e., things the caller is
responsible for ensuring never happen), you can stop reading this
response. B-)]
Is so, then I think you need a different interface to your DB.
Exceptions should only be used when the software is in an unstable state
because something unexpected has happened and continued processing
cannot provide correct results. For example, divide by zero or a null
pointer when a valid pointer is always expected will yield undefined
results.
But simply not finding data requested by a user is not unexpected in any
environment that admits to human fallibility. Nor is a timeout for some
service in a distributed environment. IOW, if the requirements
implicitly or explicitly deal with the possibility of certain errors,
then those errors should be dealt with in the normal flow of control of
the application rather than with exceptions.
There are several reasons for this. Exception processing is not
portable across languages and sometimes across platforms in the same
language. Exception processing, especially in languages where a serious
attempt is made to make it bulletproof, tends to be very expensive.
Because exception handling is normally invoked when the application is
already in an unstable state, it is very difficult to make it truly
bulletproof, so exception processing is inherently fragile.
But perhaps the most important reason lies in the nature of the sideways
exit from current scope. That implies that processing in the normal
flow of control in incomplete, which has nasty implications for data and
referential integrity. So even if the exception is handled "gracefully"
and processing continues, it is difficult to ensure that the problem has
been completely fixed. So one could encounter a more subtle problem
later that triggers more exceptions or, worse, simply leads to incorrect
results with no symptoms.
Bottom line: don't use exception processing unless you have to.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
|
|
"H. S. Lahman" <h.lahman@verizon.net> wrote in message
news:Kq6Zg.4880$IW6.955@trndny01...
> Exceptions should only be used when the software is in an unstable state
> because something unexpected has happened and continued processing cannot
> provide correct results.
Well how is stack unwinding (and running destructors) not continued
processing? It seems that the only time a program can exit gracefully
is when an error has been detected before it wreaks any havoc.
> For example, divide by zero or a null pointer when a valid pointer is
> always expected will yield undefined results.
If you can detect it, like the precondition "ptr cannot be null", one can
handle that with an assertion. Those are development-time errors.
You can catch divide by zero exceptions because the hardware/OS will
inform you of those (detection after an error has already occurred). Log
an error and exit seems the only appropriate thing to do. Then one has
to get the software update or patch that fixes the error before using the
program again or avoiding the situation in which that error occurs.
Can one say something in general about error detectability and exceptions?
Such as: "when an error has already occurred and been detected (GPF for
example), that is an exception". Which seems like you'd have to have
hardware and OS support to inform you of those things always.
What are some good examples of exceptions raised in one library and
handled at a higher level (user of the library)? What exceptions does
the standard library raise?
> But perhaps the most important reason lies in the nature of the sideways
> exit from current scope. That implies that processing in the normal flow
> of control in incomplete, which has nasty implications for data and
> referential integrity. So even if the exception is handled "gracefully"
> and processing continues, it is difficult to ensure that the problem has
> been completely fixed. So one could encounter a more subtle problem later
> that triggers more exceptions or, worse, simply leads to incorrect results
> with no symptoms.
>
> Bottom line: don't use exception processing unless you have to.
Sounds reasonable. Though I think the language guys promote using them
for general error handling ("exception handling is error handling") (?).
Tony
| |
| H. S. Lahman 2006-12-02, 6:59 pm |
| Responding to Tony...
>
>
> Well how is stack unwinding (and running destructors) not continued
> processing? It seems that the only time a program can exit gracefully
> is when an error has been detected before it wreaks any havoc.
Not necessarily; it depends on the language. In a language like C++
there is essentially no attempt made to make handling exceptions
bulletproof. So, as you indicate, it is problematic whether exception
handling will even work if the software is in an unstable state (e.g.,
the stack is trashed).
However, other languages (e.g., PL/I, Ada, BLISS, etc.) have made at
least some attempt to isolate exception processing from normal
processing (e.g., providing a separate call stack in protected memory,
putting the handler itself in protected memory, reserving resources so
simple dialogs can be displayed even when the resource space is full,
etc.). The downside is that such bulletproofing usually has substantial
overhead. Worse, things like maintaining a separate call stack can
result in overhead for all processing unless there is also hardware support.
>
>
> If you can detect it, like the precondition "ptr cannot be null", one can
> handle that with an assertion. Those are development-time errors.
>
> You can catch divide by zero exceptions because the hardware/OS will
> inform you of those (detection after an error has already occurred). Log
> an error and exit seems the only appropriate thing to do. Then one has
> to get the software update or patch that fixes the error before using the
> program again or avoiding the situation in which that error occurs.
>
> Can one say something in general about error detectability and exceptions?
> Such as: "when an error has already occurred and been detected (GPF for
> example), that is an exception". Which seems like you'd have to have
> hardware and OS support to inform you of those things always.
Clearly one has to be able to detect an unstable condition in order to
invoke exception handling. Exceptions signaled from the OS are
convenient to use, but the developer could provide the same facilities,
albeit tediously, before the hardware sees a problem (e.g., checking an
input for 0 before using it in an arithmetic expression).
So DbC assertions are typically the primary tool for detecting unstable
situations for application-specific problems when other sources of such
signals for those conditions are lacking. IOW, the basic premise of a
DbC assertion is that correctness requires that the assertion should
never be violated, so a violation indicates an unstable software
condition by definition even if one doesn't know exactly what went wrong.
> What are some good examples of exceptions raised in one library and
> handled at a higher level (user of the library)? What exceptions does
> the standard library raise?
IMO, a well-formed library will only employ exceptions for DbC
violations of the client contract. Thus a SQL function generating an
exception for an input SQL string that cannot be parsed properly would
be a valid exception if the DbC contract says the client is supposed to
always provide a parsable SQL string.
OTOH, failing to find a record identified in the SQL string in the
database is not a proper basis for an exception because in the vast
majority of situations, such as CRUD/USER processing from user input.
That's because failing to find the record would be an expected condition
(albeit hopefully unusual) _in most client contexts_. Therefore it is
not always a DbC contract violation with the client.
[The SQL library could provide a separate function to check if a record
exists. Then the client could be expected to always request reads of
existing records in the example, in which case an exception is
justified. But the problem is that one now needs two DB transactions
rather than one for every read, which can lead to serious performance
problems. So it is unlikely anyone is going to design the library that
way.]
>
>
> Sounds reasonable. Though I think the language guys promote using them
> for general error handling ("exception handling is error handling") (?).
I agree that is clearly the case for languages like C++. There
exception processing seems to be viewed as simply a neat way to exit
processing from within nested blocks. IMO, that view opens almost
infinite opportunities for foot-shooting.
[In fairness to C++, it was developed in the mid-'70s. At that time the
notion of a single exit point for every block had not been widely
recognized as a Good Practice. (It had actually been enshrined in a
methodology, Warnier-Orr, but it wasn't commonly used.)]
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
|
|
"H. S. Lahman" <h.lahman@verizon.net> wrote in message
news:y5ich.1004$g_3.425@trndny02...
> Responding to Tony...
>
>
> Not necessarily; it depends on the language. In a language like C++ there
> is essentially no attempt made to make handling exceptions bulletproof.
> So, as you indicate, it is problematic whether exception handling will
> even work if the software is in an unstable state (e.g., the stack is
> trashed).
Ah yes. I forgot there were other languages (I'm a C++ user, exclusively).
And yes, though I don't know the implementation details of EH in C++,
I did indeed just somehow think that the EH was tied into the rest of the
program and couldn't be relied on _after_ a critical error happened (as
compared to detecting an error before it trashed something).
> However, other languages (e.g., PL/I, Ada, BLISS, etc.) have made at least
> some attempt to isolate exception processing from normal processing (e.g.,
> providing a separate call stack in protected memory, putting the handler
> itself in protected memory, reserving resources so simple dialogs can be
> displayed even when the resource space is full, etc.).
I do some of that kind of stuff in my own error handling (not exceptions
because they are a black box to me) C++ code.
> The downside is that such bulletproofing usually has substantial overhead.
You mean space overhead I assume. Not a problem for my desktop and
server programs. Indeed a problem (?) for embedded programs, though
nowadays I'd assume the constraints are also less than they used to be.
> Worse, things like maintaining a separate call stack can result in
> overhead for all processing unless there is also hardware support.
That's stuff for the compiler and language implementors (read, "beyond me").
>
> Clearly one has to be able to detect an unstable condition in order to
> invoke exception handling.
I'm still wondering where that "detection stuff" fits in the definition of
"exception", if anywhere. I remember reading threads about it years ago,
but apparently it didn't stick in my mind if there was definitive conclusion
as to whether being able to detect .. no actually, not 'whether', but _when_
an
error condition is detected categorizes it as being an exception or not. I'm
not sure if that train of thought has language-specific implications.
> Exceptions signaled from the OS are convenient to use, but the developer
> could provide the same facilities, albeit tediously, before the hardware
> sees a problem (e.g., checking an input for 0 before using it in an
> arithmetic expression).
Indeed. But is it then NOT an exception? Perhaps in doing so, one moves
the condition from the exception handling domain to the "common error"
handling domain (hence no fancy EH mechanisms required). That sounds
really plausible and even recommendable as I write that (but requires a
guru to say yeah or nay).
I'm hoping someone will say that exceptions are not only rare, but that
they are well-defined in any specific hardware/software environment.
For example, Windows GPFs: those exceptions can be "caught". Of
course (?) those are just hardware things presented through the OS
API similar to interupts producing key vals or mouse movements.
I would be one chompin at the bit to reduce exceptions to hardware things
("software exceptions" to be deprecated. Well, but then there's that
"error within a constructor thing").
> So DbC
(aside: I "hate" that moniker because it seems to want to pretentiously
make an invention out of common practice. It seems too language-specific
or wanting to promote a language that does something in that area. In C++,
we have all sorts of developer-created assertions to test, say, function
args. Just like I think error handling design is part of the application
program design space (one size cannot fit all), similar with the "DbC"
product-specific-thing-trying-to-shoehorn-its-way-into-glory-without-
being-substantive).
> assertions are typically the primary tool for detecting unstable
> situations for application-specific problems when other sources of such
> signals for those conditions are lacking.
I'd say, no, use them before using the complex machinery. Afterall, mostly,
argument validity checking is mostly development-time stuff. The software
should be using the functions and classes correctly in the production code.
I remember developing on Win98: what a nightmare without doing some
assertion checking cuz if one got by, I'd end up having to reboot the
machine
for the next edit-compile-debug cycle. NT and XP, the latter especially,
were
a boon to development for me: when debugging programs, there is not much
worry of hanging the machine anymore (I actually develop on my business
desktop now where before I wouldn't consider my data safe on a development
machine).
> IOW, the basic premise of a DbC assertion is that correctness requires
> that the assertion should never be violated, so a violation indicates an
> unstable software condition by definition even if one doesn't know exactly
> what went wrong.
Yes, and is more often than not, just a programming aid in using a specific
library. My assertions popup a window that displays the stack trace when
stuff like that happens.
>
> IMO, a well-formed library will only employ exceptions for DbC violations
> of the client contract.
Sounds like stuff that should be fixed before it turns into production code.
To me, that sounds like "assertion" (a developer-defined "exception").
> Thus a SQL function generating an exception for an input SQL string that
> cannot be parsed properly would be a valid exception if the DbC contract
> says the client is supposed to always provide a parsable SQL string.
But that should be worked out in development. "Real" exceptions are ones
that are in the production code, yes?
>
> I agree that is clearly the case for languages like C++. There exception
> processing seems to be viewed as simply a neat way to exit processing from
> within nested blocks. IMO, that view opens almost infinite opportunities
> for foot-shooting.
Well the "error within a constructor" thing requires it apparently. EH and
templates get a lot of verbage (unfortunately) in C++ groups. I'd rather
talk about higher level design than language-specific implementation
details. One way to avoid all that is to minimize the use of those
"advanced"
features.
Tony
| |
| Bjorn Reese 2006-12-03, 7:05 pm |
| H. S. Lahman wrote:
> [In fairness to C++, it was developed in the mid-'70s. At that time the
> notion of a single exit point for every block had not been widely
> recognized as a Good Practice. (It had actually been enshrined in a
> methodology, Warnier-Orr, but it wasn't commonly used.)]
That "Good Practice" is not supported by empirical evidence. Studies
show that people write more correct programs when they are allowed to
exit from the middle of a block. Examples are:
Soloway, Bonar, and Ehrlich, "Cognitive Strategies and Looping
Constructs: An Empirical Study", Communications of the ACM, vol. 26
(11), pp. 853-860, 1983.
Buhr, "A Case for Teaching Multi-exit Loops to Beginning Programmers",
ACM SIGPLAN Notices, vol. 20 (11), pp. 14-22, 1985.
Roberts, "Loop Exits and Structured Programming: Reopening the
Debate", Proceedings of the twenty-sixth SIGCSE technical symposium on
Computer science education, ACM Press, pp. 268-272, 1995.
--
mail1dotstofanetdotdk
| |
| H. S. Lahman 2006-12-03, 7:05 pm |
| Responding to Tony...
>
>
> You mean space overhead I assume. Not a problem for my desktop and
> server programs. Indeed a problem (?) for embedded programs, though
> nowadays I'd assume the constraints are also less than they used to be.
Both space and performance. All exception handlers need to save the
contents of all registers at some point (e.g., where the 'try' is in
C++). That's necessary to restart processing after the handler executes
with the correct application state. That copy is additional processing
overhead and that is the minimum one needs. For more capability and
bulletproofing the compiler needs to insert additional executable code.
>
>
> That's stuff for the compiler and language implementors (read, "beyond me").
You young whippersnappers have it easy today! B-)
Back in the day 3GL compilers weren't very bright and you had to help
them out, which required understanding how they worked internally. For
example, I can remember when one always wrote "x = a + a + a" rather
than "x = 3 * a" in FORTRAN because the compiler wasn't smart enough to
know that one multiply instruction took several times as many machine
cycles as two add instructions.
Another fun project was an application we did on PDP11s that was too big
for RSX so we had to provide our own customized overlay manager.
(PDP11s had no hardware support for virtual memory so the RSX OS had a
segmented architecture, like the early PCs.) That overlay manager
needed to write into the compiler's call stack. That, in turn, meant
the application had to run at root privilege. So we could crash
computers in the next building if they were on the network. It didn't
get any better than that!
<apocryphal anecdotes>
A college classmate of mine and I were commiserating over the state of
software development at 3AM at a New Year's Eve party a few years ago.
My buddy was one of the authors of the software for the Apollo Lunar
Lander. As I recall the tirade it went something like, "These kids
today are &^%$#! soft! They don't know what &*%^$#! development is! I
landed that &*%$#! thing with four &*^%$#! registers and a &*^%$#! fifty
element stack! Back then programmers were Real Men!" [I forget the
exact numbers, but they were pretty close -- close enough so most
developers today can't even image doing it. The Apollo computer had
less power than a cheap hand calculator today.]
Back in the days of drum memories there was a legendary BAL programmer
who optimized for the drum having multiple heads to reduce rotation
time. He reordered his BAL program so that statements for the heads
were interleaved. That meant he had to map each statement so he could
"fill in" with short statements to minimize the "dead" time between drum
head reads. [The drum diameter was designed to allow the longest
Assembly statement to exactly fit between heads, but shorter statements
in a serial program would have empty space behind them that wasn't used
if the next statement was too long. (The BAL loader did the padding for
a serial program.) When clock speeds are measured in ms, that was
significant to performance.] In effect he was doing concurrent
processing at the Assembly statement level by alternating statements for
two different threads in the code. The mind boggles; this guy was in
another league. I had a hard enough time debugging BAL that was coded
serially!
</apocryphal anecdotes>
>
>
> I'm still wondering where that "detection stuff" fits in the definition of
> "exception", if anywhere. I remember reading threads about it years ago,
> but apparently it didn't stick in my mind if there was definitive conclusion
> as to whether being able to detect .. no actually, not 'whether', but _when_
> an
> error condition is detected categorizes it as being an exception or not. I'm
> not sure if that train of thought has language-specific implications.
The existence of the condition and whether it is detected are orthogonal
issues. It is the condition that is unexpected. A correct program is
not supposed to have divisions by zero. If it is somehow broken and the
divide-by-zero occurs, then that condition is unexpected in a correct
program. That unexpected condition exists whether it is detected or not.
One uses hardware interrupts or provides DbC assertions to detect
unexpected conditions _if they arise_ even though one does not expect
them to arise in a correct program. That's because one wants to know if
the program is incorrect even though it isn't supposed to be. Otherwise
the program may produce incorrect results without anyone knowing they
are incorrect.
Also note that what one detects as an incorrect application state may be
very tenuously related to the real correctness problem. If one has a
DbC precondition that checks for 0 in an input that will be a divisor,
one detects the incorrectness before the hardware does via an interrupt.
But the real problem was assigning a value of 0 to the input in the
first place. The application was in an incorrect state as soon as that
assignment was made. (Technically, it could also have been a failure to
reset it to non-zero prior to providing it as an input.)
That condition could have arisen anywhere in the processing up to the
DbC assertion. More to the point, the processing leading to that
incorrect assignment could have been very semantically complex compared
to what the hardware sees as divide-by-zero or the DbC assertion sees as
an invalid data domain value. IOW, one needs to distinguish between the
nature of the incorrect state and how that incorrectness is /manifested/
for detection. One also needs to distinguish between when an incorrect
condition prevails and when it is detected.
>
>
> (aside: I "hate" that moniker because it seems to want to pretentiously
> make an invention out of common practice. It seems too language-specific
> or wanting to promote a language that does something in that area. In C++,
> we have all sorts of developer-created assertions to test, say, function
> args. Just like I think error handling design is part of the application
> program design space (one size cannot fit all), similar with the "DbC"
> product-specific-thing-trying-to-shoehorn-its-way-into-glory-without-
> being-substantive).
It certainly wasn't common practice in the '50s and '60s. B-)
All of today's Good Practices were learned the hard way. They were then
formalized as guidelines and methodologies. DbC formalizes a number of
good ideas and packages them so that they play together well as part of
a design methodology. But there was a lot of pain and suffering before
those good ideas were recognized and formalized.
Nor is DbC just about 3GL assertions; it is a sophisticated overall
approach to design. 3GL assertions are just a mechanism for detecting
contract failures. The DbC lies in a sophisticated approach to defining
the contracts. One can employ DbC as a design methodology without any
3GL assertions. For example, R-T/E people figured out many moons ago
that DbC was a very useful mechanism for defining interactions between
state machines. (In fact, like many other good practices we take for
granted today, DbC has its roots in R-T/E.)
Every state action has some set of preconditions that must prevail in
the application before it can be executed. Those preconditions are
determined by the overall problem solution context. An event needs to
be generated to trigger the transition to cause the execution of the
action. One can use DbC to determine where that event needs to be
generated. The preconditions will prevail when some other state action
completes and has modified the state of the application so that those
preconditions prevail. So the event should be generated in whatever
state action has a postcondition that exactly matches the required
preconditions. That matching of preconditions to postconditions is the
essence of a DbC contract.
Exactly the same sort of DbC philosophy can be used in creating a UML
Interaction Diagram for OOA/D after the objects and their
responsibilities have been identified.
[Caveat. In practice, a formal DbC approach to OOA/D is usually not
done unless one has tricky synchronization issues. That's because the
OO developer already had some vision of the solution in mind when
abstracting the problem space. So normally one use that vision to
"know" where the DbC postconditions and preconditions match up.]
>
>
> I'd say, no, use them before using the complex machinery. Afterall, mostly,
> argument validity checking is mostly development-time stuff. The software
> should be using the functions and classes correctly in the production code.
> I remember developing on Win98: what a nightmare without doing some
> assertion checking cuz if one got by, I'd end up having to reboot the
> machine
> for the next edit-compile-debug cycle. NT and XP, the latter especially,
> were
> a boon to development for me: when debugging programs, there is not much
> worry of hanging the machine anymore (I actually develop on my business
> desktop now where before I wouldn't consider my data safe on a development
> machine).
Assertions have two benefits over the predefined OS and hardware
signals. One is that one can check things like particular business
rules that are specific to the problem. The other is that one can check
for manifestations of incorrectness at a much higher level of abstraction.
In the divide-by-zero example, by the time one gets to the hardware
interrupt one may be long past the context where the actual correctness
failure occurred. That makes the real problem difficult to diagnose.
It may also be too late to recover by taking corrective action.
Assertions judiciously placed throughout the application can greatly
mitigate these problems.
Generally I think it is a good idea to litter the application with
assertions during development. And I wouldn't remove them in the
production version unless someone could /prove/ there was a performance
problem because of them.
>
>
> Sounds like stuff that should be fixed before it turns into production code.
> To me, that sounds like "assertion" (a developer-defined "exception").
>
>
>
>
> But that should be worked out in development. "Real" exceptions are ones
> that are in the production code, yes?
I would say they need to be worked out /before/ development. One view
of DbC is that it simply formalizes requirements. To use the library,
there is an implied contract between the client and the library
function. DbC just forces a more explicit treatment of what the
contract requirements are for both the client the library function to
use it properly.
Those requirements need to be specified somewhere before the library is
written and the client accesses it. So for something like a third party
library, it is incumbent on the library vendor to explicitly define the
contract and, consequently, the requirements.
>
>
> Well the "error within a constructor" thing requires it apparently. EH and
> templates get a lot of verbage (unfortunately) in C++ groups. I'd rather
> talk about higher level design than language-specific implementation
> details. One way to avoid all that is to minimize the use of those
> "advanced"
> features.
The constructor problem is endemic for all OOPLs. That's why the Golden
Rule of Constructors is: don't do anything in a ctor that isn't
absolutely, positively essential for referential and data integrity _in
the solution context_. That essentially means doing nothing but
assignments (no computation of values) of knowledge and referential
attributes. IOW, ctors create instances; they don't apply business
rules and policies.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| S Perryman 2006-12-03, 7:05 pm |
| H. S. Lahman wrote:
> Nor is DbC just about 3GL assertions; it is a sophisticated overall
> approach to design. 3GL assertions are just a mechanism for detecting
> contract failures. The DbC lies in a sophisticated approach to defining
> the contracts. One can employ DbC as a design methodology without any
> 3GL assertions. For example, R-T/E people figured out many moons ago
> that DbC was a very useful mechanism for defining interactions between
> state machines. (In fact, like many other good practices we take for
> granted today, DbC has its roots in R-T/E.)
<quote>
Origin of Design by Contract
According to Tony Hoare [i]:
"An early advocate of using assertions in programming was none other
than Alan Turing himself. On 24 June
1950 at a conference in Cambridge, he gave a short talk entitled
"Checking a Large Routine" which explains
the idea with great clarity. "How can one check a large routine in the
sense that it's right? In order that the
man who checks may not have too difficult a task, the programmer should
make a number of definite
assertions which can be checked individually, and from which the
correctness of the whole program easily
follows."
The notion of assertions was further developed by Hoare [ii, iii], Floyd
[iv] and Dijkstra [v].
i Hoare, C.A.R.. The Emperor's Old Clothes. (1980 Turing Award lecture).
Communications of the ACM, vol.
24, no. 2, February 1981, pp. 75-83.
ii Hoare, C.A.R. An Axiomatic Basis for Computer Programming.
Communications of the ACM, vol. 12, no.
10, October 1969, pp. 576-583.
iii Hoare, C.A.R. Proof of Correctness of Data Representations. Acta
Informatica, vol. 1, 1972, pp. 271-281.
iv Floyd, Robert F. Assigning Meanings to Programs. Proc. American
Mathematical Society Symp. in Applied
Mathematics, vol. 19, 1967, pp. 19-31.
Page 21
v Dijkstra, Edsger W. A Discipline of Programming. Prentice Hall,
Englewood Cliffs, New Jersey, 1976.
</quote>
So what you have stated is in fact utter rubbish.
Some of us thought your claim that the term "parametric polymorphism"
was first used in IBM Fortran circles years before Stracheys' seminal
paper in the 1960s was a laugh. But little did we know there was (is ??)
more to come ...
Maestro : we salute you !!!
Steven Perryman
| |
|
|
"H. S. Lahman" <h.lahman@verizon.net> wrote in message
news:6sEch.333$ne3.147@trndny03...
>
> You young whippersnappers have it easy today! B-)
I wish I was young again!
| |
| H. S. Lahman 2006-12-04, 7:01 pm |
| Responding to Perryman...
>
>
> <quote>
>
> Origin of Design by Contract
> According to Tony Hoare [i]:
> "An early advocate of using assertions in programming was none other
> than Alan Turing himself. On 24 June
> 1950 at a conference in Cambridge, he gave a short talk entitled
> "Checking a Large Routine" which explains
> the idea with great clarity. "How can one check a large routine in the
> sense that it's right? In order that the
> man who checks may not have too difficult a task, the programmer should
> make a number of definite
> assertions which can be checked individually, and from which the
> correctness of the whole program easily
> follows."
> The notion of assertions was further developed by Hoare [ii, iii], Floyd
> [iv] and Dijkstra [v].
>
> i Hoare, C.A.R.. The Emperor's Old Clothes. (1980 Turing Award lecture).
> Communications of the ACM, vol.
> 24, no. 2, February 1981, pp. 75-83.
> ii Hoare, C.A.R. An Axiomatic Basis for Computer Programming.
> Communications of the ACM, vol. 12, no.
> 10, October 1969, pp. 576-583.
> iii Hoare, C.A.R. Proof of Correctness of Data Representations. Acta
> Informatica, vol. 1, 1972, pp. 271-281.
> iv Floyd, Robert F. Assigning Meanings to Programs. Proc. American
> Mathematical Society Symp. in Applied
> Mathematics, vol. 19, 1967, pp. 19-31.
> Page 21
> v Dijkstra, Edsger W. A Discipline of Programming. Prentice Hall,
> Englewood Cliffs, New Jersey, 1976.
>
> </quote>
>
> So what you have stated is in fact utter rubbish.
The fact that assertions were used at the 3GL level before DbC was
formalized as a design methodology does not invalidate the notion of
modern DbC as a design methodology. Software development has come a
long way since the '50s.
Given your penchant for reducing discussions to ad hominem attacks, Ta-ta.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| H. S. Lahman 2006-12-04, 7:01 pm |
| Responding to Reese...
>
>
> That "Good Practice" is not supported by empirical evidence. Studies
> show that people write more correct programs when they are allowed to
> exit from the middle of a block. Examples are:
>
> Soloway, Bonar, and Ehrlich, "Cognitive Strategies and Looping
> Constructs: An Empirical Study", Communications of the ACM, vol. 26
> (11), pp. 853-860, 1983.
>
> Buhr, "A Case for Teaching Multi-exit Loops to Beginning Programmers",
> ACM SIGPLAN Notices, vol. 20 (11), pp. 14-22, 1985.
>
> Roberts, "Loop Exits and Structured Programming: Reopening the
> Debate", Proceedings of the twenty-sixth SIGCSE technical symposium on
> Computer science education, ACM Press, pp. 268-272, 1995.
Alas, since I retired I have cut back on subscriptions so I have no
convenient access to these papers. I can only speculate that they are
referring to original development, in which case such a conclusion would
not surprise me.
Like using GOTOs, the big problems do not lie in original development;
they lie in subsequent maintenance. The problem for
PROCEDURE
code block A
if (condition1) return
code block B
if (condition2) return
code block C
return
END PROCEDURE
lies in maintenance to code block C that changes what it does. The
problem is recognizing that the change may also apply to, say, code
block A. Note that one can trivially convert this to a single exit
point by introducing GOTOs:
PROCEDURE
code block A
if (condition1) GOTO L:
code block B
if (condition2) GOTO L:
code block C
L: return
END PROCEDURE
but the maintenance problem still exists. That's because the problem
really lies in the way the conditions are formulated. The tests are
checking if the condition context for exit exists _given the original
flow of control_.
When code block C is modified in a manner that now requires execution of
some part of the change before exiting after code block A is executed,
there is no obvious way to determine that. The state variables one
checks for condition1 and condition2 are still valid indicators of
context. What has changed is the original flow of control for what
needs to be processed in a particular context. The answer in both cases
is to modify the way conditions are checked so that the scope of what
must be processed is properly captured:
PROCEDURE
code block A
if (condition2A)
code block B
if (condition3)
code block C
return
END PROCEDURE
Now the tests are not based on when to exit within the original flow of
control. Instead they are based on what processing should be done for
the condition. IOW, the condition is explicitly tied to the processing
block and only the processing block.
The maintainer can still screw up but it is less likely for two reasons.
One is the way the structure explicitly defines scope in terms of
fundamental processing blocks (e.g., {}). One is using block
structuring to explicitly highlight flow of control issues.
IMO more important, the nature of the conditions will have to change
because one has to test a state variable that is directly related to
executing the relevant code block rather than a state variable that is
tied to the original flow of control. These things conspire to make it
easier for the maintainer to recognize changes to the flow of control by
inspection.
One way to see this is by recasting the original conditions:
PROCEDURE
code block A
if (NOT condition1)
code block B
if (NOT condition2)
code block C
return
END PROCEDURE
I submit that the block structuring itself will provide a major clue to
the maintainer. The maintainer should be able to see that the code
change to code block C now applies in some cases when condition2 does
prevail. To make that evaluation the maintainer does not have to look
at anything except the condition for executing code block C.
I further submit that if condition1 and/or condition2 are anything more
than a simple boolean state variables, then the NOT itself is
suspicious. One is generally better off testing positively than
negatively. If one provides state variables that allow a positive check
for when code block c should execute, one will be less likely to
encounter maintenance problems as exceptions are introduced. That's
because positive checks essentially enumerate the valid situations but
exceptions are transparent to a negative check. Any such recasting
makes it much more likely that the maintainer will recognize the flow of
control problem when evaluating just the condition on code block c.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Richard Harter 2006-12-04, 7:01 pm |
| On Mon, 04 Dec 2006 17:10:55 GMT, "H. S. Lahman" <h.lahman@verizon.net>
wrote:
>Responding to Reese...
>
>
>Alas, since I retired I have cut back on subscriptions so I have no
>convenient access to these papers. I can only speculate that they are
>referring to original development, in which case such a conclusion would
>not surprise me.
>
>Like using GOTOs, the big problems do not lie in original development;
>they lie in subsequent maintenance. The problem for
>
>PROCEDURE
> code block A
> if (condition1) return
> code block B
> if (condition2) return
> code block C
> return
>END PROCEDURE
>
>lies in maintenance to code block C that changes what it does. The
>problem is recognizing that the change may also apply to, say, code
>block A. Note that one can trivially convert this to a single exit
>point by introducing GOTOs:
>
>PROCEDURE
> code block A
> if (condition1) GOTO L:
> code block B
> if (condition2) GOTO L:
> code block C
>L: return
>END PROCEDURE
>
>but the maintenance problem still exists. That's because the problem
>really lies in the way the conditions are formulated. The tests are
>checking if the condition context for exit exists _given the original
>flow of control_.
>
>When code block C is modified in a manner that now requires execution of
>some part of the change before exiting after code block A is executed,
>there is no obvious way to determine that. The state variables one
>checks for condition1 and condition2 are still valid indicators of
>context. What has changed is the original flow of control for what
>needs to be processed in a particular context. The answer in both cases
>is to modify the way conditions are checked so that the scope of what
>must be processed is properly captured:
>
>PROCEDURE
> code block A
> if (condition2A)
> code block B
> if (condition3)
> code block C
> return
>END PROCEDURE
>
>Now the tests are not based on when to exit within the original flow of
>control. Instead they are based on what processing should be done for
>the condition. IOW, the condition is explicitly tied to the processing
>block and only the processing block.
>
>The maintainer can still screw up but it is less likely for two reasons.
> One is the way the structure explicitly defines scope in terms of
>fundamental processing blocks (e.g., {}). One is using block
>structuring to explicitly highlight flow of control issues.
>
>IMO more important, the nature of the conditions will have to change
>because one has to test a state variable that is directly related to
>executing the relevant code block rather than a state variable that is
>tied to the original flow of control. These things conspire to make it
>easier for the maintainer to recognize changes to the flow of control by
>inspection.
>
>One way to see this is by recasting the original conditions:
>
>PROCEDURE
> code block A
> if (NOT condition1)
> code block B
> if (NOT condition2)
> code block C
> return
>END PROCEDURE
>
>I submit that the block structuring itself will provide a major clue to
>the maintainer. The maintainer should be able to see that the code
>change to code block C now applies in some cases when condition2 does
>prevail. To make that evaluation the maintainer does not have to look
>at anything except the condition for executing code block C.
>
>I further submit that if condition1 and/or condition2 are anything more
>than a simple boolean state variables, then the NOT itself is
>suspicious. One is generally better off testing positively than
>negatively. If one provides state variables that allow a positive check
>for when code block c should execute, one will be less likely to
>encounter maintenance problems as exceptions are introduced. That's
>because positive checks essentially enumerate the valid situations but
>exceptions are transparent to a negative check. Any such recasting
>makes it much more likely that the maintainer will recognize the flow of
>control problem when evaluating just the condition on code block c.
I don't agree with your proposed code. The issue is that it hides the
essential structure of the flow control. Multi-exit blocks are what I
call filters. A filter is a block with a series of alternating
sub-blocks and tests. After a sub-block (except for the last one) is
executed we execute the test. If the test is passed we continue on to
the next sub_block; if it failed we perform whatever cleanup may be
needed and abort the block. The last sub-block contains the "real"
code, i.e., the code that is to be executed if all tests are passed.
Few languages provide an explicit fltered block construct. However the
pattern is clear. To make it more explicit consider this style.
begin multi
begin
code block A
end
if (not test1) escape
begin
code block B
end
if (not test2) escape
begin
code block C
end
end multi
You wrote:
>When code block C is modified in a manner that now requires execution of
>some part of the change before exiting after code block A is executed,
>there is no obvious way to determine that.
I don't see this as making sense, at least not in the context of the
discussion. Blocks B and C are not to be executed at all if the test
after A fails. I'm guessing that what you mean is there is some code to
be executed regardless of whether we reach C or not. This is
problematic because the code to be executed can be different depending
on whether we go through C or not; it becomes particularly problematic
if the proposed modification is in the middle of C's code. Some kinds
of modifications should not be made.
What happens frequently enough is that we need an epilog (typically
cleanup code) before making the escape. This is not insuperably
difficult, e.g.
if (not test1) begin
epilog code
escape
end
One thing that I think is a real no-no is having the escape
(return/break) buried in nested code. I also think that one should not
mix the filter pattern with other patterns.
My two cents.
| |
| S Perryman 2006-12-04, 7:01 pm |
| H. S. Lahman wrote:
> Responding to Perryman...
[color=darkred]
[color=darkred]
[color=darkred]
[color=darkred]
[color=darkred]
[color=darkred]
> The fact that assertions were used at the 3GL level before DbC was
> formalized as a design methodology does not invalidate the notion of
> modern DbC as a design methodology. Software development has come a
> long way since the '50s.
Who is "invalidating" the notion of modern DbC as a design method ??
Not me.
I am disputing your statement the following statement from you :
"(In fact, like many other good practices we take for granted today, DbC
has its roots in R-T/E.)"
In fact, the prevailing evidence is that DbC has its roots in CS academia.
> Given your penchant for reducing discussions to ad hominem attacks, Ta-ta.
Given your penchant for :
1. N * 10 page Usenet postings (poor Netiquette in itself) and
2. the inability to cite documented references to your more absurd claims
I thank you for sparing Usenet point 1, and similar thanks IMHO are in
order for sparing you Usenet scrutiny in having to actually undertake
point 2 ...
Regards,
Steven Perryman
| |
| H. S. Lahman 2006-12-05, 7:00 pm |
| Responding to Harter...
>
>
> I don't agree with your proposed code. The issue is that it hides the
> essential structure of the flow control. Multi-exit blocks are what I
> call filters. A filter is a block with a series of alternating
> sub-blocks and tests. After a sub-block (except for the last one) is
> executed we execute the test. If the test is passed we continue on to
> the next sub_block; if it failed we perform whatever cleanup may be
> needed and abort the block. The last sub-block contains the "real"
> code, i.e., the code that is to be executed if all tests are passed.
> Few languages provide an explicit fltered block construct. However the
> pattern is clear. To make it more explicit consider this style.
>
> begin multi
> begin
> code block A
> end
> if (not test1) escape
> begin
> code block B
> end
> if (not test2) escape
> begin
> code block C
> end
> end multi
It seems to me you still have the potential maintenance problem when you
change what code bloc c does. Suppose all or part of that change should
also be executed whenever code block A executes under the new
requirements. How can you know that? To determine that you need to
look at all of the conditions, not just the test2 condition.
The basic problem is that the tests are based on the /original/ flow of
control, not what specific processing must be done in a particular
problem context. Therefore such tests only indirectly determine whether
code blocks are executed. If one restricts the conditions specifically
to whether each code block should be executed, one ties the individual
block execution to a single condition on the block itself. That allows
the maintainer to evaluate whether the original flow of control still
applies without looking at anything but the immediate context of the
code block.
I believe that problem is a direct result of organizing flow of control
around cascaded filters because...
>
> You wrote:
>
>
>
> I don't see this as making sense, at least not in the context of the
> discussion. Blocks B and C are not to be executed at all if the test
> after A fails. I'm guessing that what you mean is there is some code to
> be executed regardless of whether we reach C or not. This is
> problematic because the code to be executed can be different depending
> on whether we go through C or not; it becomes particularly problematic
> if the proposed modification is in the middle of C's code. Some kinds
> of modifications should not be made.
The problem exists because a requirements change can affect both /what/
gets processed in a code block and /when/ it gets processed in the
overall flow of control. Those are quite different concerns so the
rules and policies governing them should be isolated so that they can be
managed (maintained) separately. Using multiple returns "hard-wires"
the original flow of control and the conditions that dictate the return
are based on the original assumptions about flow of control.
That's due to the "filter" effect you mentioned. Whether the last block
gets executed depends on the /cumulative/ affect of all the conditions.
It is exactly that cascaded filtering that is "hard-wiring" the
assumptions of the original flow of control.
Eliminating that "hard-wiring" cascade allows one to use block
structuring to tie the When directly to the /current/ problem context.
That is, the order in which blocks should execute is decoupled from
whether they should execute. That localizes the evaluation of whether
the requirements change modifies the original flow of control.
Let's try a more concrete example:
PROCEDURE compute_employee_benefit
// code block to compute base benefit
if (Employee.type != SALARIED) return
// code block to adjust for any part time work
if (Employee.type != 1099) return
// code block to adjust for the employee contribution
return
Now suppose somebody changes the rules for the way the employee
contribution is computed for all employees except 1099s. The maintainer
is going to focus on the last code block where the computation is
actually done. However, the employee contribution now applies to
salaried employees as well (i.e., only 1099s are excluded from employee
contributions now).
The check for 1099s is still correct but the check for salaried
employees is broken (i.e., the contribution adjustment block will never
be reached for SALARIED employees). Whether the maintainer will
recognize the problem is problematic. That's because the conditions
must be evaluated as a group since they eliminate processing
cumulatively via cascaded filters. Now compare that to:
PROCEDURE compute_employee_benefit
// code block to compute base benefit
if ((Employee.type = HOURLY) OR
(Employee.type = 1099))
// code block to adjust for any part time work
if (Employee.type = HOURLY)
// code block to adjust for the employee contribution
return
One glance at the condition on whether the employee contribution
computation should be done will indicate that there is a problem because
the condition does not include SALARIED employees.
Also note that the ordering of blocks does not depend upon the
conditions. The conditions only determine /whether/ the block should be
executed. Determining /when/ a block should be executed depends solely
on the order of the blocks (e.g., the employee contribution must be
based on the net after part time adjustments). In this example it is
not plausible, but in other contexts the blocks could be independent
(i.e., requirements do not specify an order). So the blocks could be
put in any order. In such situations the conditions are not affected by
modifying the order of the blocks. I submit that such decoupling is
what makes it easy for the maintainer to realize that the condition on
executing the employee contribution adjustment is no longer correct.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Bjorn Reese 2006-12-13, 7:03 pm |
| H. S. Lahman wrote:
> Alas, since I retired I have cut back on subscriptions so I have no
> convenient access to these papers. I can only speculate that they are
> referring to original development, in which case such a conclusion would
> not surprise me.
Yes, the studies refer to original development.
> Like using GOTOs, the big problems do not lie in original development;
> they lie in subsequent maintenance. The problem for
>
> PROCEDURE
> code block A
> if (condition1) return
> code block B
> if (condition2) return
> code block C
> return
> END PROCEDURE
[...]
> One way to see this is by recasting the original conditions:
>
> PROCEDURE
> code block A
> if (NOT condition1)
> code block B
> if (NOT condition2)
> code block C
> return
> END PROCEDURE
Apart from the fact that two procedures are not functionally equivalent
(if conditions1 is true and condition2 is false), I have no problem if
you prefer the second procedure over the first.
However, the examples you use are fairly trivial. I do not think there
is much difference the two solutions, neither from a constructional nor
a maintenance point of view. Discussion the merits of these examples
would be futile.
Single versus multiple exit points becomes more interesting when we talk
about loops.
One issue is that in the loop-and-a-half problem the price of a single
exit point is redundant code.
Another issue is that you have to aggregate all conditions into the
looping condition. In simple cases that poses no problem, but in more
complex case the resulting looping condition can become quite unreable,
and it can be difficult comments the conditions appropriate.
Both the loop-and-a-half and the aggregated looping condition can lead
to maintenance problems, and it is not uncommon to find defects that
were due to these issues.
The point I want to bring across is that the single exit point solution
is not "widely recognized as a Good Practice". In fact, it is highly
disputed legacy of the Structured Programming paradigm, which indicates
that it may not be such a good practice after all.
Instead, I consider it good practice to use either single exit or
multiple exit points depending on what makes your code most readable.
--
mail1dotstofanetdotdk
| |
| H. S. Lahman 2006-12-13, 7:03 pm |
| Responding to Reese...
>
> [...]
>
>
>
> Apart from the fact that two procedures are not functionally equivalent
> (if conditions1 is true and condition2 is false), I have no problem if
> you prefer the second procedure over the first.
Good catch. That was careless of me. Because of the cumulative nature
of the exit conditions the second condition in the second exit should
have been
if ((NOT condition1) AND
(NOT condition2))
>
> However, the examples you use are fairly trivial. I do not think there
> is much difference the two solutions, neither from a constructional nor
> a maintenance point of view. Discussion the merits of these examples
> would be futile.
The code in the subblocks, the conditions, and the number of exits in
the examples can be arbitrarily complex. It is the /structure/ of the
overall block that is at issue.
> Single versus multiple exit points becomes more interesting when we talk
> about loops.
I don't think that changes anything; it is still a block within single
scope. The issue is how easy it would be to recognize when a change
also affects the flow of control when a requirements change applies to
code within a subblock. With multiple exits one has to look at /all/ of
the conditions to determine that but with a <properly formed> single
exit one only needs to look at the condition for executing the modified
code.
> One issue is that in the loop-and-a-half problem the price of a single
> exit point is redundant code.
I'm not sure I buy that. There are the same number of condition checks
either way. The issue is that in one case they are cumulative and in
the other they aren't.
What might be redundant are some phrases within compound conditions.
(There will always be redundant phrases is one quite literally recasts
the positive exit conditions to positive execution conditions via
negation as above, but that would normally not be the way one would
construct positive execution conditions.) [One can also address that by
nesting condition blocks (though that has its own maintainability issues).]
The maintainability gain lies in having to evaluate only one condition
to recognize changes in flow of control. Whether that condition is
compound with some phrases the same as in other conditions really
doesn't matter. It is the correctness of the condition as a whole that
matters.
> Another issue is that you have to aggregate all conditions into the
> looping condition. In simple cases that poses no problem, but in more
> complex case the resulting looping condition can become quite unreable,
> and it can be difficult comments the conditions appropriate.
I'm afraid I don't see that at all. Each individual exit from the loop
requires a condition and branch embedded /within/ the loop. Those are
the ones one must deal with. The overall loop conditions only apply to
the exit from the end of the loop.
LOOP (condition1)
BEGIN
// code block A
if (condition2) break
// code block B
if (condition3) break
// code block C
END // condition1 only applies here for iteration
I see no difference (other that the number of executions of the main
block itself) with
PROCEDURE
BEGIN
// code block A
if (condition2) break
// code block B
if (condition3) break
// code block C
END // always exit
There are three exits in either case.
> Both the loop-and-a-half and the aggregated looping condition can lead
> to maintenance problems, and it is not uncommon to find defects that
> were due to these issues.
>
> The point I want to bring across is that the single exit point solution
> is not "widely recognized as a Good Practice". In fact, it is highly
> disputed legacy of the Structured Programming paradigm, which indicates
> that it may not be such a good practice after all.
>
> Instead, I consider it good practice to use either single exit or
> multiple exit points depending on what makes your code most readable.
I see defect prevention as the dominant concern. Every exit must be
evaluated whenever any code is modified during maintenance because their
flow of control implications are cumulative. Since multiple exit
conditions are dispersed in the overall block, one has to look at
multiple places and mentally link those contexts. That reduces focus
and presents multiple opportunities for inserting a defect (albeit by
omission) when doing maintenance. That will always be true whenever
there are multiple exits from a block. So from my perspective, that
makes elimination of multiple exits universally beneficial. B-)
[BTW, there is a whole other debate about readability. One can argue
that whenever there are multiple styles used for the same basic
construct, that introduces a readability problem all by itself. That
is, maintainers will be less prone to make mistakes if the same style is
always used for a basic code construct. That's why beautifiers were
invented on the cosmetic end and it is one reason for using design
patterns on the high end.
Apocryphal anecdote. Back in the early '80s I was programming in BLISS.
BLISS has three ways to map an address. REF is equivalent to a C
pointer. BIND is a direct alias to the memory address that requires no
intermediate storage. The third way isn't relevant to the anecdote. I
and another developer were maintaining a suite of modules running about
250 KLOC. I went directly from PL/I to BLISS so I had no preconceptions
about pointers and I found BIND quite intuitive while the REF seemed
somewhat pointless. My accomplice came to BLISS from C and found the
REF to be more intuitive.
Whenever I went into the code and encountered REFs, I would refactor
them to BINDs because that made the code more readable for me. Whenever
she went into the code and found BINDs, she would refactor them into
REFs because those were more readable for her. Because of the code size
it took us about six months to realize that we were constantly
refactoring each other's code! Moral: pick a style and learn to live
with it.]
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Bjorn Reese 2006-12-13, 7:04 pm |
| H. S. Lahman wrote:
>
>
> I'm not sure I buy that. There are the same number of condition checks
> either way. The issue is that in one case they are cumulative and in
> the other they aren't.
I was not refering to redundant condition checks. The canonical example
of the loop-and-a-half problem is reading and processing data as long as
there is data.
The single exit point version:
read data
LOOP (there_is_data)
BEGIN
process data
read data
END
The multiple exit points version:
LOOP (true)
BEGIN
read data
IF (NOT there_is_data) break
process data
END
The 'read data' part occurs twice (i.e. redundancy) in the single exit
point version, but only once in the multiple exit points version.
While it is possible to remove the redundancy in the single exit point
version, it requires either redundant condition checks or interdependent
condition checks.
>
>
> I'm afraid I don't see that at all. Each individual exit from the loop
> requires a condition and branch embedded /within/ the loop. Those are
> the ones one must deal with. The overall loop conditions only apply to
> the exit from the end of the loop.
>
> LOOP (condition1)
> BEGIN
> // code block A
> if (condition2) break
> // code block B
> if (condition3) break
> // code block C
> END // condition1 only applies here for iteration
How would you rewrite this without the multiple exit points?
>
>
> I see defect prevention as the dominant concern. Every exit must be
Defect prevention depends on program comprehension, and thus how
readable the code is.
> evaluated whenever any code is modified during maintenance because their
> flow of control implications are cumulative. Since multiple exit
> conditions are dispersed in the overall block, one has to look at
> multiple places and mentally link those contexts. That reduces focus
> and presents multiple opportunities for inserting a defect (albeit by
> omission) when doing maintenance. That will always be true whenever
> there are multiple exits from a block. So from my perspective, that
> makes elimination of multiple exits universally beneficial. B-)
I agree with the above (execept for the conclusion). However, as I was
trying to explain in my previous reply, the alternative (single exit
point) requires compound condition checks. In some cases compound
condition checks are more readable, and in other cases nested condition
checks with exit points are more readable. Trying to coerce all cases
into one solution tends to result in code that is difficult to read
for some of the cases.
--
mail1dotstofanetdotdk
| |
| S Perryman 2006-12-13, 7:04 pm |
| Bjorn Reese wrote:
> I was not refering to redundant condition checks. The canonical example
> of the loop-and-a-half problem is reading and processing data as long as
> there is data.
> The single exit point version:
> read data
> LOOP (there_is_data)
> BEGIN
> process data
> read data
> END
> The multiple exit points version:
> LOOP (true)
> BEGIN
> read data
> IF (NOT there_is_data) break
> process data
> END
> The 'read data' part occurs twice (i.e. redundancy) in the single exit
> point version, but only once in the multiple exit points version.
there_is_data := true
WHILE there_is_data
DO
read data
IF there_is_data
DO
process data
END
END
> While it is possible to remove the redundancy in the single exit point
> version, it requires either redundant condition checks
One additional test of there_is_data.
> or interdependent condition checks.
This is true in general.
A point of note is that a lot of work on the single-entry single-exit
paradigm was done in the CS community (Dijkstra etc) for correctness
purposes. For any statement S, the classic {pre} S {post} specification
can be compromised with jumps out of anywhere. This is often the case
with so many loop constructs written by programmers - an appropriate
{post} would fail miserably.
As for condition inter-dependency, IMHO this relates to (cyclomatic)
complexity. If the termination condition to achieve single exit is too
complex, re-consider what you have.
Regards,
Steven Perryman
| |
| H. S. Lahman 2006-12-13, 7:04 pm |
| Responding to Reese...
>
>
> I was not refering to redundant condition checks. The canonical example
> of the loop-and-a-half problem is reading and processing data as long as
> there is data.
>
> The single exit point version:
>
> read data
> LOOP (there_is_data)
> BEGIN
> process data
> read data
> END
>
> The multiple exit points version:
>
> LOOP (true)
> BEGIN
> read data
> IF (NOT there_is_data) break
> process data
> END
>
> The 'read data' part occurs twice (i.e. redundancy) in the single exit
> point version, but only once in the multiple exit points version.
>
> While it is possible to remove the redundancy in the single exit point
> version, it requires either redundant condition checks or interdependent
> condition checks.
But one can still eliminate the sideways exit without redundancy or that
sort of complexity:
EOF_flag = false
LOOP (EOF_flag = false)
BEGIN
read data
if (NOT there_is_data)
EOF_flag = true
else
process data
END
However, it seems to me that this sort of example is more about the
mechanics of iteration constructs than about multiple exits within a
block. For example, in most 3GLs one could do:
WHILE (read_data())
process data
That is, multiple exits from a block are a maintenance issue because of
the implied conditional processing within the block, not the mechanics
of iteration control.
>
>
>
> How would you rewrite this without the multiple exit points?
LOOP (condition1)
BEGIN
// code block A
if (condition2A)
// code block B
if (condition3A)
// code block C
END
Now one could literally recast the original conditions:
condition2A -> NOT condition2
condition3A -> (NOT condition2) AND (NOT condition3)
but, as I indicated, it would usually be better to define new conditions
that explicitly expressed the business rules for executing each of the
subblocks.
>
>
> Defect prevention depends on program comprehension, and thus how
> readable the code is.
Only in part. It primarily depends on program structure, such as
localizing where changes need to be made. The old Cut & Paste Days are
a classic example where each fragment might be quite readable but using
cut & paste, rather than isolation and encapsulation to eliminate
redundancy, was a disaster from a reliability perspective.
I would also rank things like the application of consistent good
programming practices, good modularity, message-based interfaces,
encapsulation, implementation hiding, and a bunch of other stuff as much
more important to both defect prevention and comprehension than simple
readability. IOW, code readability is way down on the my list of
priorities, so we are on different planets here.
[As a translationist, I am biased. When one programs in a 4GL and uses
a full code generator, one doesn't care about 3GL readability at this
scale at all. (I know of one tool vendor who deliberately made the
generated code difficult to read to discourage the users from mucking
with it rather than the models during maintenance.) At the 4GL level
readability pretty much comes for free because of the more compact
notation, use of graphic representation, the platform-independent view,
and methodological guidelines. But defect prevention and comprehension
are still a top priority.]
>
>
>
> I agree with the above (execept for the conclusion). However, as I was
> trying to explain in my previous reply, the alternative (single exit
> point) requires compound condition checks. In some cases compound
> condition checks are more readable, and in other cases nested condition
> checks with exit points are more readable. Trying to coerce all cases
> into one solution tends to result in code that is difficult to read
> for some of the cases.
Multiple condition checks are only needed if one tries to recast the
original cumulative sequencing checks. As I indicated a couple of
times, that is usually not a good idea. Instead one should define
conditions that directly reflect the business rules for doing the
processing.
Those rules may result in compound conditions, but they are directly
related to the problem space and one is much better off expressing them
directly for the maintainer than indirectly through cumulative flow of
control conditions.
There is also no rule that requires one to put the detailed processing
conditions explicitly in the block. In fact, it probably isn't a good
idea in many cases because the owner of the block may not logically have
any business knowing what those rules are. IOW, in the example above
condition2A and condition3A might be simple booleans whose value should
be computed elsewhere.
So, resurrecting my example from another part of the thread:
PROCEDURE compute_employee_benefit
// code block to compute base benefit
if (Employee.type != SALARIED) return
// code block to adjust for any part time work
if (Employee.type != 1099) return
// code block to adjust for the employee contribution
return
should not be recast in terms of SALARIED and 1099. Instead one might
have something like:
PROCEDURE compute_employee_benefit
// code block to compute base benefit
if (NOT Employee.isPartTime) return
// code block to adjust for any part time work
if (Employee.makesContribution) return
// code block to adjust for the employee contribution
return
Since the characteristic is owned by Employee, whoever instantiates
Employee can evaluate the business rules properly and set the attributes
correctly. That decouples the computation of benefits from determining
the characteristics of Employee, which will clearly improve
maintainability. Those business rules may be evaluated in terms of
Employee.type, but that isn't relevant to compute_employee_benefit; the
Employee attribute being tested will have different semantics than
Employee.type.
So there are a couple of points here...
The first is that multiple conditions are probably symptomatic of
another problem: the owner of the benefit computation probably should
not need to know such details. That could apply to the original flow of
control conditions (i.e., they might be compound also) so one would s
to simplify that in the same way.
The second is that the semantics of such a boolean attribute will be
different for the execution conditions than for the flow of control
conditions.
The third is that from a canonical perspective for multiple exits there
is only one condition. One can always reduce the compound conditions to
a single boolean attribute for the computation of benefits. How or
where that condition is defined isn't relevant to the maintainability
problem of multiple, cumulative exit conditions in the benefits
computation. One way that is manifested is validating whether the
processing is still done under the right conditions during maintenance.
Whether the conditions are simple or compound or whether the business
rules are evaluated locally or externally doesn't matter. If the
condition is based on processing context, one only needs to look at one
condition. If the conditions are based on filtering the sequence of
execution _within the block_ one must look at multiple conditions and
determine if they still play together.
Another way this is manifested is separation of concerns. The
maintainer changing the way, say, employee contributions are done
doesn't really care what the detailed conditions are. That maintainer
can take it as an act of faith that Employee.makesContribution is set
properly. (Obviously it would be prudent for the maintainer to make a
note to check the computation later, but that is orthogonal to modifying
compute_employee_Benefit.) With the filter approach the maintainer
can't make that act of faith because the conditions play together
_within the block_ to ensure proper computation. So the maintainer
/can't/ determine if they still play together unless the maintainer
looks at exactly how each condition is evaluated (wherever those
business rules are applied).
Bottom line: I think the only way one gets in trouble with respect to
your concerns is when one tries to literally recast the filter approach
into a single exit approach rather than constructing the single exit
approach from scratch.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Bjorn Reese 2006-12-13, 7:04 pm |
| H. S. Lahman wrote:
[...][color=darkred]
>
>
> But one can still eliminate the sideways exit without redundancy or that
> sort of complexity:
>
> EOF_flag = false
> LOOP (EOF_flag = false)
> BEGIN
> read data
> if (NOT there_is_data)
> EOF_flag = true
> else
> process data
> END
This is an interdependent condition check (EOF_flag depends on
there_is_data).
Do you think that you have you improved readability with the last
example?
> block. For example, in most 3GLs one could do:
>
> WHILE (read_data())
> process data
This solution is not available in the general case of a loop-and-a-half,
only in simple cases such as the read-and-process example.
>
>
> LOOP (condition1)
> BEGIN
> // code block A
> if (condition2A)
> // code block B
> if (condition3A)
> // code block C
> END
First, the two loops are not functionally equivalent. Code block A is
executed more times in the rewritten procedure than in the original.
That may cause different results.
Second, you assume that condition1 will eventually cause the loop to
terminate. That is not the case for "LOOP (true)" used in the multiple
exit version of the loop-and-a-half example. Now we have an infinite
loop.
This is the second time you have introduced defects when trying to
convert multiple exit points to a single exit point. That does not bode
well for defect prevention ;-)
Furthermore, one could raise concerns about the execution speed.
> Multiple condition checks are only needed if one tries to recast the
> original cumulative sequencing checks. As I indicated a couple of
> times, that is usually not a good idea. Instead one should define
> conditions that directly reflect the business rules for doing the
> processing.
And what if the business rules calls for multiple exit points?
I have read the rest of your reply several times, and I have difficulty
understanding how it relates to single versus multiple exit points. It
seems to me that you are trying to contrast orthogonal issues.
--
mail1dotstofanetdotdk
| |
| H. S. Lahman 2006-12-13, 7:04 pm |
| Responding to Reese...
>
>
> This is an interdependent condition check (EOF_flag depends on
> there_is_data).
Then use Perryman's example.
>
>
> This solution is not available in the general case of a loop-and-a-half,
> only in simple cases such as the read-and-process example.
True, which is why I qualified with "most". But that's a language
problem for how iteration constructs are defined in the language syntax,
which is why I don't think this sort of example is very relevant. All
this demonstrates is that iteration in some languages is defined in a
manner that can invite maintainability problems.
>
>
>
> First, the two loops are not functionally equivalent. Code block A is
> executed more times in the rewritten procedure than in the original.
> That may cause different results.
No, code block A is always executed exactly the same number of times in
both examples (i.e., the number of iterations for which condition1 is true).
> Second, you assume that condition1 will eventually cause the loop to
> terminate. That is not the case for "LOOP (true)" used in the multiple
> exit version of the loop-and-a-half example. Now we have an infinite
> loop.
Again, you are postulating different scenarios for designing 3GL
iteration constructs. To keep focus on the real issue of multiple exits
I think we should just examine blocks that are executed without iteration.
> This is the second time you have introduced defects when trying to
> convert multiple exit points to a single exit point. That does not bode
> well for defect prevention ;-)
>
> Furthermore, one could raise concerns about the execution speed.
I don't follow that. The number of conditions tested, the code blocks
executed, and the number of branches are the same in both examples.
Even if true, I think that is an orthogonal issue. We make lots of
trade-offs between conflicting goals like performance and
maintainability. But when we do so, it should be an informed decision.
Where performance is concerned we always favor maintainability until
there is a demonstrated need to sacrifice it for performance. IOW,
achieving high maintainability is a Good Practice that we only sacrifice
when we must for other reasons and when we do so we should realize that
we are making that sacrifice.
>
>
> And what if the business rules calls for multiple exit points?
I don't think it matters. 3GL procedural block structuring is a
computing space abstraction based on hardware computational models that
the business rules know nothing about. So one always needs to map the
business rules into computing space abstractions at the 3GL level. I
submit that one can always do that without multiple exits from a block.
> I have read the rest of your reply several times, and I have difficulty
> understanding how it relates to single versus multiple exit points. It
> seems to me that you are trying to contrast orthogonal issues.
OK, let me summarize:
(1) I believe readability of 3GL code is a relatively minor concern
compared to other maintainability issues like localization, separation
of concerns, and overall comprehension.
(2) using the filter approach to organizing flow of control _within a
block_ combines two orthogonal issues: order of execution within the
block and the conditions that must prevail for executing subblocks.
That failure to separate orthogonal concerns is the root of the
maintainability problem for multiple block exits.
Basically what I am arguing is that one should organize the sequence of
execution for subblocks as a simple Turing sequence rather than with
test & branch constructs that also determine /whether/ the subblocks
should be executed. That allows one to separate the concern for
/whether/ a given subblock is executed by making the individual block
conditional. IOW, the order in which blocks are executed is driven by
dependencies between the blocks based on business rules (e.g., code
block B uses values computed in code block A). Those business rules are
different than the ones that define the context for /whether/ a given
subblock should be executed (e.g., whether an Employee contribution
needs to be computed). Since they are different business rules, the
overall block will be more robust if they are implemented separately.
(3) when designing blocks, one should design single exit blocks
differently than one would design filtered blocks. That is, one should
not mechanically recast a filter view solution into a single exit
solution. The point of the GOTO example I used early on was to
demonstrate that the problem lies in the intrinsic structure of
filtering, not the mechanics of exits. The GOTOs converted the flow of
control to single exit, but the maintainability problem was still there
because of the marriage of ordering context and execution context that
made the conditions cumulative. IOW, one solves the overall problem
differently.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Robert Maas, see http://tinyurl.com/uh3t 2006-12-14, 10:04 pm |
| > From: "H. S. Lahman" <h.lah...@verizon.net>
> the basic premise of a DbC assertion is that correctness requires
> that the assertion should never be violated, so a violation
> indicates an unstable software condition by definition even if
> one doesn't know exactly what went wrong.
I agree. If the calling module violates a before condition in the
contract with the called module, that's a bug in the overall
program, and it should be sent "back to the drawing board". In the
context of a program being debugged, where it's the programmer
testing the program who encounters the bug, the Lisp approach of
landing the user in a debug ReadEvalPrint loop in the context where
the violation was first detected sounds near to best. A stack
backtrace per Java standard practice is not so good, but still
better than the IBM OS approach of an ABEND COREDUMP on
lineprinter.
> failing to find the record would be an expected condition (albeit
> hopefully unusual) _in most client contexts_. Therefore it is not
> always a DbC contract violation with the client.
I agree, but the what should be done?
> [The SQL library could provide a separate function to check if a
> record exists. Then the client could be expected to always request
> reads of existing records in the example, in which case an
> exception is justified. But the problem is that one now needs two
> DB transactions rather than one for every read, which can lead to
> serious performance problems. So it is unlikely anyone is going to
> design the library that way.]
I agree, too inefficient to require two DB transactions. But
furthermore, there's a race condition, if record appears or
disappears between the p and the actual read, due to some other
process updating the same shared database. If a p causes a lock
of the existing record so it can't be deleted by anyone else, and a
lock of the failed query so the record can't appear later, that
would be a disaster!
It seems to me the right thing to do is simply to return an object
of ResultSet class, but an empty one if there was no match. Then
the caller can simply look at the record-count in the ResultSet
object and proceed accordingly. It's been a while since I used
jdbc/odbc/MS-Access, so I don't remember. Is that what actually
happens, or does it throw an exception, when no record matches?
And what happens in C++ and other languages which I've never used
in conjunction with a RDBS.
Note: Please forgive me if my questions sound overly beginnerish. I
discovered this newsgroup, via a cross-post from comp.programming,
just within the past day. So-far the discussions here sound really
high-level interesting.
P.S. IMO the really important thing is to be consistent. Pick a
philosophy about which runtime situations should invoke each of the
two kinds of exceptions (must-handle, and can-ignore), and which
should be handled by inline program flow, and stick to that
decision within any one module, and as much as possible within
all/most of the modules you personally have authored. And if your
employer has already decided this question, follow suit usually.
I personally hate the must-handle exceptions, where every level of
calling code must either handle or declare&re-throw, basically
defeating the idea of non-local control flow to avoid the need for
painful handling and re-throwing at each level. And also
must-handle exceptions don't work when passed through an
intermediary module that is parameterized per module called hence
can't possibly declare all exceptions that might ever be thrown by
any parametric module that might ever be called through it.
For example, trying writing Lisp's MAPCAR in Java!
| |
| Robert Maas, see http://tinyurl.com/uh3t 2006-12-15, 4:07 am |
| > From: "Tony" <rdnewsNOSPAM2...@sbcglobal.net>
> I forgot there were other languages (I'm a C++ user, exclusively).
Ah, you're the kind of person I want to ask:
> I would be one chompin at the bit to reduce exceptions to
> hardware things ("software exceptions" to be deprecated. Well,
> but then there's that "error within a constructor thing").
From what I learned in one C++ programming class a couple years
ago, it's very important (to avoid memory leak) to release exactly
the memory you allocated up to the point of failure. That implies
if the constructor allocates memory in multiple pieces then it must
release exactly the memory already allocated successfully up to
that point. If it calls other constructors to allocate some of the
memory (implicit in building sub-objects), then when one of those
bombs out it must call the destructors of all the earlier pieces.
Only after all the memory is back into the state it was before this
constructor was called, *then* this constructor can finally release
control (by throwing its own exception, or by returning NULL
pointer, per its contract with caller). You agree?
> I'd say, no, use them before using the complex machinery.
> Afterall, mostly, argument validity checking is mostly
> development-time stuff. The software should be using the functions
> and classes correctly in the production code.
I agree. Let a failed assertion throw a "program bug" exception,
detailing explicitly what bug condition was detected (NULL pointer
passed instead of array for mumble parameter, or ran off end of
list before finding expected mumble, or such-and-such object passed
as parameter has a NULL pointer in slot mumble, etc.), rather than
fall through to a generic machine or null-pointer exception where
you have to look at the backtrace and guess what really went wrong
to cause that class of exception somewhere inside that method. Now
if the built-in assertion system can't generate meaningful "program
bug" exceptions, then you have to handwire them, IF NOT
(assertedCondition) THEN THROW("Text...etc."), infinitely better
than hitting a generic NULL POINTER exception when you try to call
some API function from inside your method.
| |
| Robert Maas, see http://tinyurl.com/uh3t 2006-12-15, 4:07 am |
| > From: Bjorn Reese <bre...@see.signature>
Phooey!
[color=darkred]
> That "Good Practice" is not supported by empirical evidence.
> Studies show that people write more correct programs when they are
> allowed to exit from the middle of a block.
Hey, I'm vindicated, by you anyway. For any loop more complicated
than a simple iteration over a range guaranteed to complete, I
prefer this programming template:
Setup loop constants and initial values of loop variables.
WHILE TRUE {
Do some every-time-thru-loop pre-processing.
Test first exit condition, maybe BREAK out here, or RETURN already.
Do some more every-time-thru-loop processing.
Test second exit condition, maybe BREAK out here, or RETURN already.
Do some final every-time-thru-loop post-processing to get
initial constraints back into compliance.
}
I had a narrow-minded single-exit instructor who didn't like that,
preferred this:
Setup loop constants and initial values of loop variables.
First copy of: Do some every-time-thru-loop pre-processing.
WHILE (boolean combination of first and second exit conditions) {
Do some more every-time-thru-loop processing.
Do some final every-time-thru-loop post-processing to get
initial constraints back into compliance.
Second verbatim copy of: Do some every-time-thru-loop pre-processing.
}
IF (test for first exit condition) {..}
ELSE (test for second exit condition) {...};
I hated her insistance on that ugly code, for one additional reason
that it's unmaintainable if that verbatim copied stuff ever changes
and you forget to make identical edits in each copy. This was a C
class, and another classmate suggested making all the copied code
into a preprocessor MACRO, so when you edit the MACRO both calls of
it will simultaneously change. But that macro definition is located
somewhere else in the source file, so you can't even see it
on-screen when you're debugging this program loop.
| |
| Robert Maas, see http://tinyurl.com/uh3t 2006-12-15, 4:07 am |
| > From: S Perryman <q...@q.net>
> there_is_data := true
> WHILE there_is_data
> DO
> read data
> IF there_is_data
> DO
> process data
> END
> END
That's stupid! You're checking there_is_data twice each time around
the loop, once at the very top of the explicitly top-tested loop,
and once as condition for the second block of code within the loop,
despite the fact that it can't change from the time you test it as
block2condition until the time you get back at the top and test it
again. Every new programmer worth his salt looking at this code
will spend a lot of time trying to figure out why the double
testing is needed, like does "process data" somehow modify the
value of the boolean flag so the extra test really is necessary? If
"process data" can't possibly change the boolean, then why the
double test, but if it *can* change it the I must be missing
something and need to spend another hour looking at the code to try
to figure out what's happening.
And if that's not a boolean, but a SYSTEM CALL, then not only is
the double test expensive, but you can't set it to TRUE yourself
anyway, so there!! Even moreso, what if the SYSTEM CALL resets it,
i.e. there_is_data semantically means new data has been read since
the last time you checked that system flag.
> a lot of work on the single-entry single-exit paradigm was done
> in the CS community (Dijkstra etc) for correctness purposes. For
> any statement S, the classic {pre} S {post} specification can be
> compromised with jumps out of anywhere.
That's one reason I prefer multiple RETURNs right from inside the
loop, instead of multiple BREAKs to fall out to one finale-RETURN
combo. It's pretty easy to check that each RETURN in fact returns
some appropriate value per contract with caller, and all local
variables are discarded immediately upon RETURN so you don't have
to worry if they are in some inconsistent state at the RETURN
point, and any good algorithm will keep the referenced data
structure valid at *all* times so you don't have to worry about
that post condition. So the only complicated thing to test is that
once around the loop transforms local variables in a consistent
way. That's actually easier to do with the multiple exit style
(loop compute1 test1-RETURN compute2 test2-RETURN compute3) where
once-around the loop *always* executes *all* the code in a circular
program-flow loop, compared to (loop test1-EXIT test2-compute2
test3-compute3 test4-compute4) where all 8 possible combinations of
various compute{1/2/3} blocks need to be consistent together.
> If the termination condition to achieve single exit is too
> complex, re-consider what you have.
Consider binary search, where there are two possible exit
conditions, failure return when sub-array is empty due to indexes
passing each other, and success return when exact match of array
element occurs. I'd rather see it my way, first check subarray
empty, and return immediately if that's true, else fall through to
computing midpoint index, then fall thruough to doing lookup there,
then fall through to comparing, then if match return immediately
else two other cases that modify the sub-array bounds before
looping. Trying to combine the two tests (empty array, exact match)
would depend on lazy-OR to avoid array index out of bounds, and
involve complex assignment inside the test condition itself, an
ugly mess. If you tried single-test single-exit, and it's messy,
yes reconsider what you have, and consider switching to multiple
exit points, and see if that makes the code become legible.
By the way, I notice we've gotten off-topic from this thread.
How to exit a loop per se has nothing to do with exception handling.
| |
| H. S. Lahman 2006-12-16, 10:03 pm |
| Responding to Maas...
>
>
> I agree, but the what should be done?
The software still needs to check for the condition; it is just done in
the normal flow of control (e.g., IF...ELSE...) and the response should
be dictated by requirements.
>
>
> I agree, too inefficient to require two DB transactions. But
> furthermore, there's a race condition, if record appears or
> disappears between the p and the actual read, due to some other
> process updating the same shared database. If a p causes a lock
> of the existing record so it can't be deleted by anyone else, and a
> lock of the failed query so the record can't appear later, that
> would be a disaster!
Good point. One would need two such functions: simple check for
existence, to be used just to ping; and check-and-lock, to be used for a
later read. Even then one is just begging for deadlocks.
> It seems to me the right thing to do is simply to return an object
> of ResultSet class, but an empty one if there was no match. Then
> the caller can simply look at the record-count in the ResultSet
> object and proceed accordingly. It's been a while since I used
> jdbc/odbc/MS-Access, so I don't remember. Is that what actually
> happens, or does it throw an exception, when no record matches?
> And what happens in C++ and other languages which I've never used
> in conjunction with a RDBS.
Alas, I never used Java and the only OODB I used was so long ago I
forget what it did. Access /looks/ like exception processing because of
the on-event callback structure, but that could be deceptive since the
actual read check is hidden in the bowels of Access itself. All one
really knows is that the code the developer provides will be executed if
the record isn't there and that could be done inline.
> Note: Please forgive me if my questions sound overly beginnerish. I
> discovered this newsgroup, via a cross-post from comp.programming,
> just within the past day. So-far the discussions here sound really
> high-level interesting.
Generally this forum is at the OOA/D level.
> P.S. IMO the really important thing is to be consistent. Pick a
> philosophy about which runtime situations should invoke each of the
> two kinds of exceptions (must-handle, and can-ignore), and which
> should be handled by inline program flow, and stick to that
> decision within any one module, and as much as possible within
> all/most of the modules you personally have authored. And if your
> employer has already decided this question, follow suit usually.
Bernard Berenson ("Consistency requires you to be as ignorant today as
you were a year ago") notwithstanding, consistency in software
development is a Good Thing. The tricky part lies in deciding what to
be consistent about.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
|
|
| Bjorn Reese 2006-12-16, 10:03 pm |
| H. S. Lahman wrote:
>
>
> Then use Perryman's example.
It uses a redundant condition check.
Actually, there is a single exit point solution that does not require
redundancy or interdependency, but it does require multiple entry
points, which I do not think any of us favours.
[... versus ...][color=darkred]
>
>
> No, code block A is always executed exactly the same number of times in
> both examples (i.e., the number of iterations for which condition1 is
> true).
Code block A in the original loop is executed at most (not exactly) the
number of iterations for which condition1 is true.
So if condition1 becomes true after 10 iterations, and condition2
becomes true after 4 iterations, then code block A will be executed 5
times in the original loop, and 10 times in the modified loop.
> Again, you are postulating different scenarios for designing 3GL
> iteration constructs. To keep focus on the real issue of multiple exits
> I think we should just examine blocks that are executed without iteration.
3GL iteration constructs are an essential part of mainstream software
development. Maybe this concern will disappear one day, but until then
it remains topical.
If we restrict our discussion to simple blocks, then we will arrive at
"good practices" that are of little practical value in the general case
and that may cause us to extrapolate such practices to cases where they
are ill-suited.
> Even if true, I think that is an orthogonal issue. We make lots of
> trade-offs between conflicting goals like performance and
> maintainability. But when we do so, it should be an informed decision.
> Where performance is concerned we always favor maintainability until
> there is a demonstrated need to sacrifice it for performance. IOW,
> achieving high maintainability is a Good Practice that we only sacrifice
> when we must for other reasons and when we do so we should realize that
> we are making that sacrifice.
I completely agree.
> OK, let me summarize:
Thank you for making this summary.
> (2) using the filter approach to organizing flow of control _within a
> block_ combines two orthogonal issues: order of execution within the
> block and the conditions that must prevail for executing subblocks. That
> failure to separate orthogonal concerns is the root of the
> maintainability problem for multiple block exits.
I am not sure whether you consider the maintainability problem to be
due to difficulty of understanding the code, or due to difficulty of
modifying the code. Let me return to one of your first examples.
PROCEDURE
code block A
if (condition1) return
code block B
if (condition2) return
code block C
return
END PROCEDURE
This was rewritten as:
PROCEDURE
code block A
if (NOT condition1)
code block B
if ((NOT condition1) AND (NOT condition2))
code block C
return
END PROCEDURE
Concerning the ability to understand the code, I think that they are
roughly on par in the above examples.
Concerning the ability to modify the code, I think that the filtered
approach is easier to work with. Let us consider two common types of
modification where they differ: fixing an error in a condition, and
reordering the sub-blocks.
Fixing an error in condition1 requires one change in the filtered case,
and 2 (or N in the general case) in the single exit case.
Reordering code block B and C is a simple cut'n'paste operation in the
filtered case. In the single point case we must also modify the check
conditions for code block B and C.
So, there seems to be more maintainability concerns regarding check
condition and order of exection for the single exit solution than for
the filtered solution.
> (3) when designing blocks, one should design single exit blocks
> differently than one would design filtered blocks. That is, one should
> not mechanically recast a filter view solution into a single exit
> solution. The point of the GOTO example I used early on was to
> demonstrate that the problem lies in the intrinsic structure of
> filtering, not the mechanics of exits. The GOTOs converted the flow of
> control to single exit, but the maintainability problem was still there
> because of the marriage of ordering context and execution context that
> made the conditions cumulative. IOW, one solves the overall problem
> differently.
Let us say that we have a business rule that filtered by nature. Would
you implement this by a filtered block that reflects the business rule
directly, or by a single exit point block that has been does not reflect
the business rule directly?
--
mail1dotstofanetdotdk
| |
| H. S. Lahman 2006-12-17, 7:03 pm |
| Responding to Reese...
>
>
> It uses a redundant condition check.
OK, then try:
there_is_data = TRUE
WHILE TRUE
DO
read data
IF NOT there_is_data break
process data
END
One check; one exit
>
> [... versus ...]
>
>
>
> Code block A in the original loop is executed at most (not exactly) the
> number of iterations for which condition1 is true.
How can it be executed fewer times than the number of block iterations?
Code block A is always executed on entry to the block in both cases.
The condition for entering the block is condition1 in both cases.
>
>
> 3GL iteration constructs are an essential part of mainstream software
> development. Maybe this concern will disappear one day, but until then
> it remains topical.
But it is still about exiting the /iteration/, not about conditional
processing /within/ the block.
To put it another way, the price of awkward language iteration
constructs is that one may have to write awkward body code when dealing
with other problems like maintainability. IOW, one cannot site awkward
code (e.g., my IF...ELSE...) to provide a single exit in an iteration as
a reason for not ensuring a single exit.
> If we restrict our discussion to simple blocks, then we will arrive at
> "good practices" that are of little practical value in the general case
> and that may cause us to extrapolate such practices to cases where they
> are ill-suited.
Then we have to agree to disagree. I see the real issue here as being
conditional processing within the block. That issue applies equally to
filters, single exits using GOTOs, cascaded IF...ELSE... structures, and
complex switch statements. The maintainability problem exists because
one must evaluate the entire structure when making changes rather than a
single condition where the change is made.
>
> Thank you for making this summary.
>
>
>
> I am not sure whether you consider the maintainability problem to be
> due to difficulty of understanding the code, or due to difficulty of
> modifying the code. Let me return to one of your first examples.
>
> PROCEDURE
> code block A
> if (condition1) return
> code block B
> if (condition2) return
> code block C
> return
> END PROCEDURE
>
> This was rewritten as:
>
> PROCEDURE
> code block A
> if (NOT condition1)
> code block B
> if ((NOT condition1) AND (NOT condition2))
> code block C
> return
> END PROCEDURE
>
> Concerning the ability to understand the code, I think that they are
> roughly on par in the above examples.
I disagree in practice. In the first case one must look at both
conditions to determine if code changed in code block C still executes
at the right times. In the second case one only needs to look at the
second condition. This is important for three reasons.
The first is that the conditions may not be just one line away from each
other in the block. If the maintainer is focused on an editor screen,
the other condition may not even be visible in the current scroll area.
So the maintainer may not even realize it is there.
The second is that even if the maintainer looks at it, it may be
evaluated in isolation rather than in conjunction with the local
condition. (In my 1099 example one could make exactly that mistake
because all the individual checks were valid; it was the combination
that was wrong.)
The third is that the maintainer is focused on the business rules that
need to change /within/ code block C. The flow of control within the
block was an original design decision that was probably based one
servicing other requirements. So the maintainer is in the wrong mindset
for even recognizing a flow of control problem spread over multiple
conditions. However, the condition on code block C is intimately tied
to that block so it is much more difficult for maintainer to ignore it
or misinterpret it.
Now one can argue that the solution is to hire a better grade of
maintainers. The problem is that nobody is perfect and human
fallibility is a given in software development. So we need to write
software in a manner that reduces the opportunities for human fallibility.
>
> Concerning the ability to modify the code, I think that the filtered
> approach is easier to work with. Let us consider two common types of
> modification where they differ: fixing an error in a condition, and
> reordering the sub-blocks.
Modifying the code was not my concern. The tricky part is recognizing
that there is a problem that needs to be fixed. However,...
>
> Fixing an error in condition1 requires one change in the filtered case,
> and 2 (or N in the general case) in the single exit case.
I don't see that. The only condition that would need to be modified in
the single exit case would be the local one around the block being
changed. In theory (albeit unlikely) one might have to modify every
condition in the multiple exit case. IOW, it is the /combination/ of
checks that will be wrong, which could involve changes to each one.
Note that in my Employee benefits example all one needed to change in
the single exit version was the condition on computing the contribution.
But in the filter case the blocks would have to be reordered or both
conditions would have to change.
>
> Reordering code block B and C is a simple cut'n'paste operation in the
> filtered case. In the single point case we must also modify the check
> conditions for code block B and C.
In practice, I think one will have to almost always do this sort of
thing whenever there is a change in flow of control. IME the most
likely situation is that a subset of the code (usually all or part of
what changed in the maintenance of a subblock) will have to be moved to
another block.
In any case, I don't see modification as being a big deal (at least in
an OO environment) because methods are designed to be small with limited
responsibilities. (In most of the AALs used to describe methods in
OOA/D there isn't even a construct for blocks smaller than a method
because methods aren't supposed to be complex.) IOW, there isn't going
to be a whole lot to move around.
>
>
> Let us say that we have a business rule that filtered by nature. Would
> you implement this by a filtered block that reflects the business rule
> directly, or by a single exit point block that has been does not reflect
> the business rule directly?
I don't see that arising. Remember that 3GL block structuring is an
abstraction designed to make the hardware computational models more
manageable for developers. But the basic Turing sequence, branch, and
iteration are still hard-wired and those are pure computing space
constraints. In addition, a block is a pretty low level construct in a
very verbose 3GL environment. Thus I would expect that any filtering in
the customer space is much more likely to be expressed at a higher level
of abstraction than individual methods.
OTOH, if it did get to that level, I would still use the single exit.
That's because at the 3GL level one must address orthogonal requirements
like maintainability. Since the filter sequence can always be recast
into a single exit sequence, I think the maintainability issues outweigh
the problem space mapping at the level of an individual block.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Bjorn Reese 2006-12-18, 8:05 am |
| H. S. Lahman wrote:
[...][color=darkred]
>
>
> How can it be executed fewer times than the number of block iterations?
> Code block A is always executed on entry to the block in both cases.
> The condition for entering the block is condition1 in both cases.
You seem to forget that we exit the loop in the original example if
condition2 is true.
--
mail1dotstofanetdotdk
| |
| Bjorn Reese 2006-12-18, 7:06 pm |
| This discussion is starting to turn into a useless marathon, and I am
pulling out that this stage; not because I believe that my position is
weakened in any way, but because I do not have the time to proceed at
this level of detail.
Let me try to recapitulate the discussion so far.
I have argued in favour of multiple exit by pointing out problems with
single exit, whereas you have argued in favour of single exit by
pointing out problems with multiple exit.
I acknowledge that single exit may be preferable in certain
circumstances, whereas you seem to insist that single exit is
preferable in every circumstance.
If the above interpretation is true, then logically I would have
expected you to demonstrate that there are no severe problems with
single exit. This has not been done (e.g. empirical evidence about
correctness of construction, and redundancy/dependency of conditions
in loops, errors introduced in converting multiple to single exit
loops).
--
mail1dotstofanetdotdk
| |
| H. S. Lahman 2006-12-18, 7:06 pm |
| Responding to Reese...
>
> [...]
>
>
>
> You seem to forget that we exit the loop in the original example if
> condition2 is true.
Yes, but that exit occurs /after/ code block A has been executed.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| H. S. Lahman 2006-12-18, 7:06 pm |
| Responding to Reese...
> This discussion is starting to turn into a useless marathon, and I am
> pulling out that this stage; not because I believe that my position is
> weakened in any way, but because I do not have the time to proceed at
> this level of detail.
>
> Let me try to recapitulate the discussion so far.
>
> I have argued in favour of multiple exit by pointing out problems with
> single exit, whereas you have argued in favour of single exit by
> pointing out problems with multiple exit.
>
> I acknowledge that single exit may be preferable in certain
> circumstances, whereas you seem to insist that single exit is
> preferable in every circumstance.
My objection is to "in certain circumstances". Multiple exits /always/
have a potential maintainability problem for the reasons I gave. Since
that problem involves a higher probability of inserting defects during
maintenance, it is a serious problem IMO. So the issue rests on whether
single exists have an even more serious problem in at least some
situations...
> If the above interpretation is true, then logically I would have
> expected you to demonstrate that there are no severe problems with
> single exit. This has not been done (e.g. empirical evidence about
> correctness of construction, and redundancy/dependency of conditions
> in loops, errors introduced in converting multiple to single exit
> loops).
I see no significant problems with single exit. The problems you have
cited are focused on mechanisms for exiting block-level iterations,
which are language-dependent. I consider those issues cosmetic because
when the language doesn't support
WHILE (do something)
do something else
the workarounds involve, at most, slightly more verbose code. You have
suggested that those workarounds reduce comprehension or introduce
unnecessary condtion checks. I don't really buy the notion of better
comprehension:
time_to_exit = FALSE
WHILE (time_to_exit)
DO
read data
IF (there_is_data)
process data
ELSE
time_to_exit = TRUE
END
To me this is more comprehensible /because/ there are two condition
checks. The check on time_to_exit is semantically a check on whether it
is time to exit the iteration. The check on there_is_data is a check on
whether the 'process data' block should be executed. Those are
semantically different concerns and, IMO, comprehension is improved by
making that clear.
That both exiting the block iteration and executing 'process data'
ultimately depend on whether data was read is serendipity of the
particular example. (That dependence is, itself, captured in the
IF...ELSE... dichotomy.) Thus in another <fairly common> problem
context we might have:
time_to_exit = FALSE
WHILE (time_to_exit)
DO
read data
process data
IF (something process data did)
time_to_exit = TRUE
END
Note that in this situation there is no alternative to having two
conditions checked to exit the loop even though there is no conditional
subblock processing. This difference in semantics becomes even more
clear when we consider:
time_to_exit = FALSE
WHILE (time_to_exit)
DO
read data
process data
END
What if time_to_exit is a state variable that 'read data' sets in the
normal course of its processing? Clearly this is broken when 'process
data' should not be processed when there is no data. To fix that
conditional processing one must introduce another condition check,
whether it is your multiple exit check or my IF. So I really can't buy
the notion of multiple condition checks being a significant problem.
Similarly, I can't buy comprehension as a significant problem with
single exit precisely because block iteration exits and conditional
subblock processing are orthogonal issues. To the contrary, I submit
such orthogonal issues /should/ be made clear to improve comprehension.
So if neither multiple conditions checks or comprehension are
significant problems for single exit and maintainability is a serious
problem for multiple exits, the choice seems clear to me.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| H. S. Lahman 2006-12-18, 7:06 pm |
| Responding to Lahman...
> OK, then try:
>
> there_is_data = TRUE
> WHILE TRUE
> DO
> read data
> IF NOT there_is_data break
> process data
> END
>
>
> One check; one exit
Substance abuse alert 1: this just begs the point. There are still
multiple exits to the block, which should not happen from my perspective.
> responsibilities. (In most of the AALs used to describe methods in
> OOA/D there isn't even a construct for blocks smaller than a method
> because methods aren't supposed to be complex.)
Substance abuse alert 2: AALs must have block structures for at least IF
constructs. Most also have block structure for iteration constructs as
well (though AALs severely limit what can be iterated and some use set
operations rather than element-by-element iterations).
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Bjorn Reese 2006-12-19, 8:07 am |
| H. S. Lahman wrote:
> Responding to Reese...
>
>
>
> Yes, but that exit occurs /after/ code block A has been executed.
*Sigh*
Let me try with a simple example. I am going to use C syntax to be more
precise.
The original multiple exit version:
for (i = 0; i < 10; ++i) { // LOOP (condition1)
sum += i; // code block A
if (i > 4) break; // if (condition2) break
Whatever(); // code block B
}
Your modified single exit version:
for (i = 0; i < 10; ++i) { // LOOP (condition1)
sum += i; // code block A
if (!(i > 4)) { // if (NOT condition1)
Whatever(); // code block B
}
}
The original multiple exit version will execute code block A 5 times,
which gives a sum of 15. The modified single exit version will execute
code block A 10 times, which gives a sum of 45. Can we agree that the
two versions are not functionally equivalent?
--
mail1dotstofanetdotdk
| |
| Bjorn Reese 2006-12-19, 8:07 am |
| H. S. Lahman wrote:
> Responding to Lahman...
>
>
>
> Substance abuse alert 1: this just begs the point. There are still
> multiple exits to the block, which should not happen from my perspective.
Yes, I know. I listed the very same example as a multiple exit example
in my Dec 10 reply.
So, I assume that we can agree that the loop-and-a-half can only be
solved with added complexity in the form of redundancy, dependency,
multiple exit, or multiple entry, even if we cannot agree which of the
beforementioned solutions is preferable?
--
mail1dotstofanetdotdk
| |
| H. S. Lahman 2006-12-19, 7:09 pm |
| Responding to Reese...
>
>
> *Sigh*
OK. I forgot to also exit the loop when organizing the conditional
processing. (My excuse is that as a retired translationist I haven't
written any 3GL code in years.)
However, I would argue that just demonstrates my point about not
separating the concerns of iteration exit vs. conditional processing in
the filter format. B-)
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| H. S. Lahman 2006-12-19, 7:09 pm |
| Responding to Reese...
>
>
> Yes, I know. I listed the very same example as a multiple exit example
> in my Dec 10 reply.
>
> So, I assume that we can agree that the loop-and-a-half can only be
> solved with added complexity in the form of redundancy, dependency,
> multiple exit, or multiple entry, even if we cannot agree which of the
> beforementioned solutions is preferable?
I'm afraid not because
WHILE (read_data())
process data
works fine in most common 3GLs. For those languages where one can't
handle loop-and-a-half this way one still has the conditional processing
problem to deal with and the language is just making that more awkward.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
| |
| Bjorn Reese 2006-12-19, 7:09 pm |
| H. S. Lahman wrote:
>
>
> I'm afraid not because
>
> WHILE (read_data())
> process data
>
> works fine in most common 3GLs. For those languages where one can't
> handle loop-and-a-half this way one still has the conditional processing
> problem to deal with and the language is just making that more awkward.
The reading/processing example is a simplified example of the
loop-and-a-half problem. The general case of the loop-and-a-half
problem can have many exit points, and therefore cannot be fitted
into the above template. We have already covered that earlier in
this thread (on Dec 11).
--
mail1dotstofanetdotdk
| |
|
|
"H. S. Lahman" <h.lahman@verizon.net> wrote in message
news:y5ich.1004$g_3.425@trndny02...
> Responding to Tony...
>
>
> However, other languages (e.g., PL/I, Ada, BLISS, etc.) have made at least
> some attempt to isolate exception processing from normal processing (e.g.,
> providing a separate call stack in protected memory, putting the handler
> itself in protected memory, reserving resources so simple dialogs can be
> displayed even when the resource space is full, etc.). The downside is that
> such bulletproofing usually has substantial overhead. Worse, things like
> maintaining a separate call stack can result in overhead for all processing
> unless there is also hardware support.
>
Excellent observation. May I add that, when exceptions are based
on type-based errors, Ada has added a unique feature that, in many
cases (not all) can avoid a lot of errors that used to require an
exception handler: the X'Valid option.
Consider,
type Number is range -320..800; -- an integer type with an invariant
range
...
-- declare an instance of Number
X : Number;
...
-- in an algorithm, somewhere
Get(X);
if X'Valid then ... end if;
-- instead of raising an exception, the 'Valid option traps the error
-- and allows corrective action
When the 'Valid option is exercised prior to using the value in another
operation, it eliminates the need for an exception handler.
Not everyone likes to use this since it requires an if statement for every
pass through this code -- extra overhead -- where the exception only
happens when an actual error occurs. This is the classic trade-off of
doing 100% checking for a possible 0.0001% occurrence. However,
for safety-critical software, the 'Valid option is usually applied.
Richard Riehle
|
|
|
|
|