Command Query Separation violation
What do you think about
if(!D开发者_运维知识库oSomething()) return;
In Clean Code this is viewed as violation of Command Query Separation. 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?
Thanks.
I agree with the comments from rObiwahn that you should check CanDoSomething
before issuing a command of DoSomething
. In a pure CQRS environment, DoSomething
would not return anything and if anything prevented Something from happening (not due to an exception, but a race condition or something else changing between CanDoSomething
and DoSomething
), your domain would issue a DoSomethingWasInvalid
event (or something like that) which would allow your application to eventually become consistent.
It may sound complex, but it really becomes pretty simple once you start breaking down the logic into small chunks and allow your application to embrace eventual consistency.
There are a lot of good resources on the DDD/CQRS group in google groups. A question like 'How do you tell the sender that a command failed?' is similar to your question a bit. People like Udi Dahan, Greg Young, Rinat Abdullin and others monitor that group and provide some really great answers. I'd recommend checking that out every now and then, too.
Hope this helps!
If something went wrong, DoSomething()
should probably throw an exception, if you as the caller must handle it.
E.g.:
try
{
DoSomething();
// .. do more after success
}
catch(SomeException ex) // maybe focus on a special error
{
// maybe do something special or just clean up!
}
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 providetable_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.
i really had this thing came to my mind now, [why we don't think of "concerns of separation" rather than "separation of concerns"!] i know its off topic but too much push for standards will lead to NO WHERE. from history of mankind it easy to see how much of practices/standards proven wrong with time! it's all about your perspective of excellence.
so in such case i always try to think of the opposite to stay REAL. i want to say more but i think that's enough from my perspective of answering ;)
good luck!
精彩评论