Last update June 10, 2009

Doc Comments /
Exception Safe



Difference (last change) (no other diffs, normal page display)

Added: 63a64,104
=== RAII transactions (by Dragan Milenkovic) =

I agree what you said "The RAII approach involves the creation of dummy classes, and the obtuseness of moving some of the logic out of the abc() function". But there are better designs than the one you presented in the article. I'll use C++ syntax (at least until I'm more familiar with D). "dofoo" and "dofoo_undo" can be encapsulated in a class that represents one transaction unit. Depending on the design, it might inherit an abstract class Transaction_unit that has some of work(), success(), failure(), exit() methods. We encapsulate all our transaction units, and then invoke the function execute_transaction(list_of_transactions). If you don't want to write a separate class for each piece of work, you can design a concrete Func_transaction_unit class that you can pass "dofoo", "dofoo_undo" as functions or lambda functions.

The resulting code would look like this (just a simplified version with only do/undo):

<pre>

Transaction abc() {
Foo f;
Bar b;

Func_transaction_unit u1([&]{ f = dofoo(); }, [&]{ dofoo_undo(); });
Func_transaction_unit u2([&]{ b = dobar(); });

execute_transaction({ u1, u2 });

return Transaction(f, b);
}

</pre>

Or in another design we might have:

<pre>

struct Foo_transaction_unit : public Transaction_unit {
Foo result;

virtual void work() { result = dofoo(); }
virtual void failure() { dofoo_undo(); }
};

</pre>

... or instead of inheritance we could use templates and concepts...

In both designs we still have do and undo code close together, but this time not only by moving lines up and down - it's encapsulated as a transaction unit. Dummy classes exist only in the second design, however they are minimal and clean, since there is no "if (commit)" or "finally" to make a mess. It scales very nicely. I personally like these two approaches better. If needed, you can combine these units into another transaction by reusing, instead of copying the exact amount of lines needed.

A very nice thing is that I could still use these designs in D.


Exception Safe Programming    

Table of contents of this page
Exception Safe Programming   
Comments   
Intro typo   
Mailer example issue   
Response (by WalterBright)   
RAII transactions (by Dragan Milenkovic)   
Links   

Comments    

I have an issue with the Mailer example as well. Some scope locations don't make sense to me, could you explain them? I would write the example this way:


class Mailer {
    void Send(Message msg)
    {
	{
	    char[] origTitle = msg.Title();
	    msg.SetTitle?("[Sending] " ~ origTitle);
	    scope(exit) msg.SetTitle?(origTitle);   // Do this only if the first SetTitle? succeeded
	    Copy(msg, "Sent");
	}
	scope(failure) Remove(msg.ID(), "Sent");
	SetTitle?(msg.ID(), "Sent", msg.Title);   // Does not need to go in scope(success)
	SmtpSend?(msg);	// do the least reliable part last
    }
}


Intro typo    

There is a typo in the first sentence: "... if any piece of code that might throw an exception does throw an exception, the the state of the program is not corrupted and resources are not leaked."

Mailer example issue    

There is an error in the Mailer example. The inner block should restore the message title only on scope(failure), not scope(exit). Otherwise, during the duration of the SMTP send (potentially minutes), the message would not have [Sending] in the title, which contradicts the problem description. Also, I suggest that the example would be more clear if an enum were used for the sent folder instead of a string. Corrected code:


class Mailer
{
    void Send(Message msg)
    {
	{
	    char[] origTitle = msg.Title();
	    scope(failure) msg.SetTitle(origTitle);
	    msg.SetTitle("[Sending] " ~ origTitle);
	    Copy(msg, SENT_FOLDER);
	}
	scope(success) SetTitle(msg.ID(), SENT_FOLDER, msg.Title());
	scope(failure) Remove(msg.ID(), SENT_FOLDER);
	SmtpSend(msg);	// do the least reliable part last
    }
}

Response (by WalterBright)    

The mailer example in the article is correct, it should be scope(exit). The reason is that there are two messages, the original and the copy in the sent folder. The original should always get it's title set back to the original value, hence scope(exit).

RAII transactions (by Dragan Milenkovic)    

I agree what you said "The RAII approach involves the creation of dummy classes, and the obtuseness of moving some of the logic out of the abc() function". But there are better designs than the one you presented in the article. I'll use C++ syntax (at least until I'm more familiar with D). "dofoo" and "dofoo_undo" can be encapsulated in a class that represents one transaction unit. Depending on the design, it might inherit an abstract class Transaction_unit that has some of work(), success(), failure(), exit() methods. We encapsulate all our transaction units, and then invoke the function execute_transaction(list_of_transactions). If you don't want to write a separate class for each piece of work, you can design a concrete Func_transaction_unit class that you can pass "dofoo", "dofoo_undo" as functions or lambda functions.

The resulting code would look like this (just a simplified version with only do/undo):


Transaction abc() {
    Foo f;
    Bar b;

    Func_transaction_unit u1([&]{ f = dofoo(); }, [&]{ dofoo_undo(); });
    Func_transaction_unit u2([&]{ b = dobar(); });

    execute_transaction({ u1, u2 });

    return Transaction(f, b);
}

Or in another design we might have:


struct Foo_transaction_unit : public Transaction_unit {
    Foo result;

    virtual void work() { result = dofoo(); }
    virtual void failure() { dofoo_undo(); }
};

... or instead of inheritance we could use templates and concepts...

In both designs we still have do and undo code close together, but this time not only by moving lines up and down - it's encapsulated as a transaction unit. Dummy classes exist only in the second design, however they are minimal and clean, since there is no "if (commit)" or "finally" to make a mess. It scales very nicely. I personally like these two approaches better. If needed, you can combine these units into another transaction by reusing, instead of copying the exact amount of lines needed.

A very nice thing is that I could still use these designs in D.

Links    

Corresponding page in the D Specification

FrontPage | News | TestPage | MessageBoard | Search | Contributors | Folders | Index | Help | Preferences | Edit

Edit text of this page (date of last change: June 10, 2009 14:12 (diff))