I will write this example in Eiffel to make it simple to understand.
my_code
-- Calls the `do_something' routine
do
set_table ("my_table")
do_something
end
do_something
-- Something to do
require
valid_table: is_valid_table (table_name)
do
sql_list := execute_sql_on_table (table_name)
ensure
has_result: sql_list.count > 0
end
sql_list: ARRAYED_LIST [STUFF]
table_name: STRING
set_table (a_name: STRING)
-- Set `table_name' to `a_name'
require
has_name: not a_name.is_empty
valid_table: is_valid_table (a_name)
do
table_name := a_name
ensure
table_name_set: table_name.same_string (a_name)
end
delete_table (a_name: STRING)
-- Delete `a_name' from the database.
require
valid_table: is_valid_table (a_name)
do
execute_sql ("DROP TABLE " + a_name)
ensure
table_gone: not is_valid_table (a_name)
end
- The feature `do_something' is a command, where the array sql_list is to be loaded with STUFF from the table "my_table".
- The precondition contract on
do_something' makes it the responsibility of the client
my_code' to provide table_name' before making the call to
do_something'.
- In return, the post-condition ensure contract makes it the responsibility of the supplier
do_something' fill the array
sql_list' with instances of STUFF.
- The feature `sql_list' is a query, returning a reference pointer to an array of STUFF.
- Similarly, the feature
table_name' is a query returning a reference pointer to a STRING, which is set with a "setter" command called
set_table'.
In this case, the "contracts" of Design-by-Contract take care of ensuring appropriate separation of concerns and who is responsible for what in this small bit of code above. Notice the distinct lack of TRY-CATCH constructs in the code. In this case, the data source is expected to have "my_table". The presence of the contracts means the software will raise an exception when the contract fails. A require failure says the caller is broken, while a failure in an ensure post-condition points at the supplier feature.
Finally, this code exhibits clear Command-Query-separation and the application of quality assurance gained from Design by Contract. Thus, the original question can be answered:
"But how can we understand if something in command DoSomething() went wrong? What about sql command (ex: void Delete(Table))? How can we know if that table existed?"
While it might be true that a call to delete_table ("my_table") might be injected into some ancestor or might happen on another thread, this is what the contracts are for in do_something'. As long as those contracts stand guard over calls to
do_something', the process will be appropriately handled. An injected call to `delete_table' will simply cause the contract to fail.
All of this is presuming it is NOT OK to DROP TABLE on "my_table" and that doing so is tragically accidental. However, if it becomes OK to DROP TABLE on "my_table" then a retry mechanism or other "handler" is needed to manage this use case and the code above will not work.