The policy for what you can use during the exams is as follows:
You may bring and use your own personal hand-written notes, and a printed
copy of the published lecture and section notes. You may not
use any books or any other materials.
(1) Figuring out exactly what the problem to be solved is, is called requirements analysis. This is intrinsically difficult. For complicated tasks, there are always many, many details to be specified. It is difficult to write down a specification that is unambiguous, comprehensive, and an order of magnitude smaller than an actual implementation.
(2) You should think about what your real job is, i.e. what would really make the client happy. If the client hasn't given details in the initial task description, perhaps s/he does not want to. Your job is not to force the client to do something s/he does not want to. Instead, try to understand the wider context, and what the customer really wants. Here, there are at least three different perspectives:
(0) The single entry, single exit property should be preserved: that control can only flow into the command at one point, and can only flow out at one point.
(1) It should be possible to raise exceptions and recover from them for any command, not just loops. There can be reasons to abort any command.
(2) When an exception is handled, it should be possible to execute some recovery commands as an alternative to the regular command which gave rise to the failure. For example if an exception is raised while reading an input value, it should be possible to recover by assigning a default value.
(3) It should be possible to provide exception handlers for many different types of exception.
(4) Providing exception handlers should be optional. If a handler is not provided, the exception should cause an escape from the enclosing block.
(5) Programmer-defined exceptions like "negative age" should be allowed
in addition to system-defined exceptions like "division by zero".
In general exception handlers occur at the end of blocks:
If the exception ei is raised while executing the command C, then C is abandoned and Ci is executed instead.begin
C;
exception
when e1 => C1;
when e2 => C2;
...
end;
If a recovery command is not provided for an exception, then the exception is propagated to the next enclosing block. For example:
In the code above, C1' will never be executed, but C2 may be.begin
begin
C;
exception
when e1 => C1;
end;
exception
when e1 => C1';
when e2 => C2;
...
end;
procedure main is
type
month is (jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec);
rainfall: array (month) of float;negative_rainfall: exception;
procedure input_data is
begin
for amonth in month
loop
begin
get(rainfall(amonth));
if rainfall(amonth) < 0.0 then raise negative_rainfall
exception
when data_error => rainfall(amonth) := 0.0;
when negative_rainfall => rainfall(amonth) := 0.0;
end;
end loop;
end;begin
...
exception
when end_error => put("Insufficient data");
when others => put("Unknown catastrophic error");
end
The language Eiffel enforces a rule of good programming style, that the only ways to leave an exception-handler are
With re-execution (called retry), after raising e1 and executing C1, C would be executed again. With propagation (called reraise), after raising e1 and executing C1, C1' would then be executed.(1) to execute the failed operation again, or (2) to propagate the exception.
The use of exceptions in the Ada example above does not meet the Eiffel criteria.
In general, there are two main opinions on programming style in the presence of errors. The first attitude is that code should be written defensively, and as many errors as possible should be handled, even if they cannot be fixed completely. The second attitude is that errors should be revealed as quickly as possible, so that they can be fixed properly.
For example, according to the first attitude, one should write
for (i = 0; i <= 10; i++) { ... }
According to the second attitude one should write
for (i = 0; i != 10; i++) { ... }
Suppose that the code inside the loop erroneously modifies i
to have a value greater than 10. The first approach will
hide this error, while the second will reveal it to the user, by causing
an infinite loop.
Static type-checking means type-checking done by the compiler, before executing the program.
Dynamic type-checking means type-checking done while executing the program. The compiler must generate extra code to do this checking.
The descriptor strong type-checking is sometimes used to mean
static type-checking. Alternatively, it is used to mean complete
type-checking, i.e. guaranteeing that any mismatch is caught between the
signature of an operation and the types of its actual arguments.
(2) The compiler can use complex type-checking algorithms just once, with no run-time speed penalty.
(3) Errors can be reported at compile-time instead of run-time.
(3)(a) Experts on software engineering and management say that the
later an error is caught, the more it costs to fix the error.
(3)(b) Some run-time type conflicts may not be caught at all during
testing, because they only happen for un-tested input values.
This last reason (3) is the most important.
(2) At run-time, type-checking can know the actual values of variables, not just their initial value or declared type.
In practice all languages combine run-time and compile-time type-checking. Verifying that index variables are within range can often only be done at run-time.
Question to think about: when can it be done at compile-time?
(3) Less verbosity. Example: the awk language.