2

I'm trying to find a better way to execute the following functions. I have a series of steps that need to be completed, and if any fail, I need to undo the previous step like so:

try:
    A = createA()
except:
    return None

try:
    B = createB(A)
except:
    deleteA(A)
    return None

try:
    C = createC(B)
except:
    deleteB(B)
    deleteA(A)
    return None

try:
    D = createD(C)
except:
    deleteC(C)
    deleteB(B)
    deleteA(A)
    return None

return D

I would prefer not to repeat myself if possible. How can I improve this? Is there a known pattern to follow?

One thing I have considered would be adding deleteB() to deleteC(), and deleteA() to deleteB(). Is that the best possible way to do it?

Johnny
  • 8,939
  • 2
  • 28
  • 33
user60749
  • 21
  • 1
  • 1
    One pattern to use could be the *context manager*, which you can `__enter__` and `__exit__` - you may have used this like `with open(...) as file_:`, for example, where it's used to close the file for you. – jonrsharpe Jan 31 '19 at 16:11
  • 1
    Since Python 3.3 there is `contextlib.ExitStack` in combination with context managers for such things. – Michael Butscher Jan 31 '19 at 16:12
  • One way to do is you could write an undo function that wraps around all your `deleteX()` functions and call it like `undo([C, B, A])`, where it parses your objs to delete and call `deleteX()` accordingly. Though this is probably not the optimal approach. – r.ook Jan 31 '19 at 16:13
  • I dont' know python but some equivalent to goto is actually a good solution this even though its frown upon. See [here](https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources) for a general standard – zar Jan 31 '19 at 17:05

4 Answers4

0

If you look some design patterns, check following:

Command pattern

It is probably what you are looking. Also, it commands can have "undo" action. Check the following question also, if it contains a similar problem that you have. best design pattern for undo feature

T.Nylund
  • 617
  • 6
  • 11
0

As the comments point out, this is what the context manager protocol is for. However, if you don't want to dig into what is a fairly advanced feature yet, then you can define lambda anonymous functions as you go, to remember what to tidy ...

try:
    deleters = []
    A = createA()
    deleters.append( lambda: deleteA(A) )
    B = createB( A)
    deleters.append( lambda: deleteB(B) )
    C = createC( B)
    deleters.append( lambda: deleteC(C) )
    D = createD( C)
    return D
except:
    for d in reversed( deleters):
        d()
    return None
nigel222
  • 7,582
  • 1
  • 14
  • 22
0

It depends on what exactly "undo" means. E.g. if A, B, C, D etc are arbitrary user commands and there may be a large number of them in any order, then likely you need to write some sort of abstraction around do/undo.

If alternatively A, B, C, D etc are resources that need tidying up (database connections, files, etc), then context managers may be more appropriate.

Or maybe A, B, C D are some other sort of thing altogether.

A brief bit of code using context managers:

class A:
    def __enter__(self):
        set things up
        return thing
    def __exit__(self, type, value, traceback):
        tear things down

class B, C and D are similar

def doSomethingThatNeedsToUseD():
    try:
        with A() as a:
        with B() as b:
        with C() as c:
        with D() as d:
            d.doSomething()
    except:
        print("error")
junichiro
  • 5,282
  • 3
  • 18
  • 26
0

What you are looking for is called memento pattern. It is one of the GoF design patterns:

Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.

The one way how it could be implemented using python could be find here.

Johnny
  • 8,939
  • 2
  • 28
  • 33