Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

When using an event based component I often feel some pain at maintenance phase.

Since the executed code is all split around it can be quite hard to figure what will be all the code part that will be involved at runtime.

This can lead to subtle and hard to debug problems when someone adds some new event handlers.

Edit from comments: Even with some good practices on-board, like having an application wide event bus and handlers delegating business to other part of the app, there is a moment when the code starts to become hard to read because there is a lot of registered handlers from many different places (especially true when there is a bus).

Then sequence diagram starts to look over complex, time spend to figure out what is happening is increasing and debugging session becomes messy (breakpoint on the handlers manager while iterating on handlers, especially joyful with async handler and some filtering on top of it).

//////////////
Example

I have a service that is retrieving some data on the server. On the client we have a basic component that is calling this service using a callback. To provide extension point to the users of the component and to avoid coupling between different components, we are firing some events: one before the query is sent, one when the answer is coming back and another one in case of a failure. We have a basic set of handlers that are pre-registered which provide the default behavior of the component.

Now users of the component (and we are user of the component too) can add some handlers to perform some change on the behavior (modify the query, logs, data analysis, data filtering, data massaging, UI fancy animation, chain multiple sequential queries, whatever). So some handlers must be executed before/after some others and they are registered from a lots of different entry point in the application.

After a while, it can happens that a dozen or more handlers are registered, and working with that can be tedious and hazardous.

This design emerged because using inheritance was starting to be a complete mess. The event system is used at a kind of composition where you don't know yet what will be your composites.

End of example
//////////////

So I'm wondering how other people are tackling this kind of code. Both when writing and reading it.

Do you have any methods or tools that let you write and maintain such code without to much pain ?

share|improve this question
    
You mean, besides refactoring out logic from event handlers? – Telastyn Jul 16 '12 at 13:18
    
Document what goes on. – user1249 Jul 16 '12 at 13:29
    
@Telastyn, I'm not sure to fully understand what do you mean by 'besides refactoring out logic from event handlers'. – Guillaume Jul 16 '12 at 13:33
    
@Thorbjoern: see my update. – Guillaume Jul 16 '12 at 13:41
2  
It sounds like you are not using the correct tool for the job? I mean, if the order matters, then you should not be using plain events for those parts of the application in the first place. Basically if there are limitations on the order of events in a typical bus system, it's not a typical bus system anymore, but you are still using it as such. Which means you need to address that issue by adding some extra logic to handle the order. At least, if I understand your problem correctly.. – stijn Jul 16 '12 at 14:11
up vote 4 down vote accepted

I've found that processing events using a stack of internal events (more specifically, a LIFO queue with arbitrary removal) greatly simplifies event-driven programming. It allows you to split the processing of an "external event" into several smaller "internal events", with well-defined state in between. For more information, see my answer to this question.

Here I present a simple example which is solved by this pattern.

Suppose you are using object A to perform some service, and you give it a callback to inform you when it's done. However, A is such that after calling your callback, it may need to do some more work. A hazard arises when, within that callback, you decide that you don't need A any more, and you destroy it some way or another. But you're being called from A - if A, after your callback returns, cannot safely figure out that it was destroyed, a crash could result when it attempts to perform the remaining work.

NOTE: It's true that you could do the "destruction" in some other way, like decrementing a refcount, but that just leads to intermediate states, and extra code and bugs from handling these; better for A to just stop working entirely after you don't need it anymore other than continue in some intermediate state.

In my pattern, A would simply schedule the further work it needs to do by pushing an internal event (job) into the event loop's LIFO queue, then proceed to call the callback, and return to event loop immediately. This piece of code it no longer a hazard, since A just returns. Now, if the callback doesn't destroy A, the pushed job will eventually be executed by the event loop to do its extra work (after the callback is done, and all its pushed jobs, recursively). On the other hand, if the callback does destroy A, A's destructor or deinit function can remove the pushed job from the event stack, implicitly preventing execution of the pushed job.

share|improve this answer
    
Thanks for the link, it's a great answer. – Guillaume Jul 30 '12 at 10:42

I think proper logging can help quite a big. Make sure that every event thrown/handled is logged somewhere (you can used logging frameworks for this). When you're debugging, you can consult the logs to see the exact order of execution of your code when the bug occurred. Often this will really help narrow down cause of the problem.

share|improve this answer
    
Yes, that's an helpful advise. The amount of produced logs can then be frightening (I can remember having some hard time to find the cause of a cycle in the event processing of a very complex form.) – Guillaume Jul 16 '12 at 14:35
    
Maybe one solution is to log the interesting information to a separate log file. Also, you can assign a UUID to each event, so you can track each event more easily. You can also write some reporting tool that allows you to extract specific information from the log files. (Or alternatively, use switches in the code to log different information to different log files). – Giorgio Jul 29 '12 at 8:07

So I'm wondering how other people are tackling this kind of code. Both when writing and reading it.

The model of event driven programming simplifies coding to some extent. It has probably evolved as a replacement of big Select (or case ) statements used in older languages and gained popularity in early Visual development environments such as VB 3 (Don't quote me on the history, I did not check it)!

The model becomes a pain if the event sequence matters and when 1 business action is split across many events. This style of process violates the benefits of this approach. At all costs, try to make the action code encapsulated in the corresponding event and don't raise events from within events. That then becomes far worse than the Spaghetti resulting from the GoTo.

Sometimes developers are eager to provide GUI functionality that requires such event dependency, but really there is no real alternative that is significantly simpler.

Bottom line here is that the technique is not bad if used wisely.

share|improve this answer
    
Is their any alternative design to avoid the 'worse than the Spaghetti' ? – Guillaume Jul 16 '12 at 15:45
    
I am not sure there is an easy way without simplifying the expected GUI behavior, but if you list all your user interactions with a window/page and for each determine exactly what your program will do, you could begin grouping code into one place and direct several events to a group of subs/methods that are responsible for the real processing of the request. Also separating code that only serve the GUI from code that does the back end processing may help. – NoChance Jul 16 '12 at 15:50
    
The need to post this message came from a refactoring when I moved from a inheritance hell to something based on event. On some point it is really better, but on some other it is quite bad... Since I've already had some trouble with 'events gone wild' in some GUI, I'm wondering what can be done to improve maintenance of such event sliced code. – Guillaume Jul 16 '12 at 16:09
    
Event-driven programming is far older than VB; it was present in the SunTools GUI, and before that I seem to remember that it was built into the Simula language. – kevin cline Jul 16 '12 at 17:43
    
@kevincline, thanks for the clarification. – NoChance Jul 16 '12 at 20:10

What has worked for me is making each event stand on its own, without reference to other events. If they are coming in asynchroniously, you don't have a sequence, so trying to figure out what happens in what order is pointless, besides being impossible.

What you do end up with are a bunch of data structures that are getting read and modified and created and removed by a dozen threads in no particular order. You've got to do extemely proper multi-threaded programming, which is not easy. You've also got to think multi-threaded, as in "With this event, I am going to look at the data I have at a particular instant, without regard to what it was a microsecond earlier, without regard to what just changed it, and without regard to what the 100 threads waiting for me to release the lock are going to do to it. Then I will make my changes based on this even and what I see. Then I am done."

One thing I find myself doing is scanning for a particular Collection and making sure that both the reference and the collection itself (if not threadsafe) are locked correctly and synchronized correctly with other data. As more events are added, this chore grows. But if I was tracking the relationships between events, that chore would grow a lot faster. Plus sometimes a lot of the locking can be isolated in its own method, actually making the code simpler.

Treating each thread as a completely independent entity is difficult (because of the hard-core multi-threading) but doable. "Scalable" may be the word I'm looking for. Twice as many events take only twice as much work, and maybe only 1.5 times as much. Trying to coordinate more asynchronious events will bury you quickly.

share|improve this answer
    
I have updated my question. You are totally right about sequence and async stuff. How would you handle a case where event X must executed after all the others event are processed ? It is looking like I have to create a more complex Handlers manager, but I also want to avoid to expose too much complexity to the users. – Guillaume Jul 16 '12 at 16:28
    
In your set of data, have a switch and some fields that are set when event X is read. When all others are processed, you check the switch and know you have to handle X and you have the data. The switch and the data should stand on their own, actually. When set, you should think, "I have to do this job," not "I have to hande X." Next problem: how do you know the events are done? and what if you get 2 or more events X? Worst case, you can run a looping maintenance thread that checks the situation and can act on its own initiative. (No input for 3 secs? X switch set? Then run shutdown code. – RalphChapin Jul 16 '12 at 16:43

Event driven code is not the real problem. I fact I have no problem following logic in even driven code, where call-back are explicitly defined or in-line call-backs are used. For example generator style callbacks in Tornado are very much easy to follow.

What is real hard to debug are dynamically generated function calls. The (anti?)pattern which I would call the Call-back Factory from Hell. However, this kind of function factories are equally hard to debug in traditional flow.

share|improve this answer

Centralize the Side Effects

The difficulty of reasoning about the correctness of event-driven systems for me, and of eventually running into that "series of unfortunate events", is the level of decentralization of code resulting from it combined with side effects. Ideally the code executed when an event triggered has hardly any risk of ever doing something wrong, or at the wrong time.

When you try to visualize the control flow of an event-driven system, the code doesn't even tell you what the paths of execution are, since what functions are called as a result of an event being triggered are often largely unknown (and subject to change at runtime), unless you trace through it. The mental image might be something like this:

enter image description here

... but worse (simplified diagram), and this would only be a snapshot in time since what functions are called by an event will change as the system changes state. The difficulty here is the unpredictable control flow combined with the fact that each event is doing something "complex".

Is the event even being triggered from a particular thread? In some systems, that too might not even be clear. When/where/how becomes a total mystery in an event-driven system.

"Complex"

"Complex" to me practically always implies some form of external state changes. An event might occur which then resizes a widget which then triggers a cascade of subsequent events on the descendants of that widget. An event might occur which removes an item from a scene graph with another that reorders the hierarchy... Things of this sort.

Combining something that complex, resulting from state changes, with the unpredictable nature of the control flow of event-handling is a recipe for systems whose correctness are really, really difficult to reason about, even when all of our tests pass. I'm talking about the ease of comprehending our systems, and how they will respond to changes in advance. Tests are a critical part of ensuring reliability which is a critical step to not being at the mercy of our systems, but I'm talking about staying in the driver's seat and not becoming a passenger, to stay in total control of our systems, to shape it to our will.

The problem is that each event triggered forms like a little "branch" of functionality. If you have a boatload of little branches of functionality that each do something potentially complicated inside such as changing external, fragile state (and I'm not talking about global variables but simply calling methods which modify a widely-accessible object central to the software, e.g.), then we're in for potentially a lot of pain.

The solution to me: centralize and limit the number of places in your codebase where these complex state changes can occur.

Centralization

One strategy to centralize the complexity is to simply have all these disparate events push to a central event queue, like so:

enter image description here

What happens in this case is that all this maze of winding and twisting event-triggered functionality is no longer causing any external side effects besides pushing to some central queue to be processed later. This is very simple, symmetrical behavior for each event and never bound to fail no matter how you shuffle up the order in which events occur, since each one is just immediately pushing to a central queue.

This "Central Event Processor" then pops from the queue and does the actual critical and error-prone work of updating this kind of central, external state and causing side effects.

While this might just seem like it's transferring complexity to a central place, that's kind of the whole point. It's hard to reason about software if complex things are happening in all kinds of event-triggered functions which are called "who knows when?", "from who knows where?", and "who knows how?".

This central event processor is easy to understand in terms of when it is executed, and how. It also becomes easier to thread both safely and efficiently, to avoid doing redundant updates of data for two or more events which could be consolidated to yield a single update of data in response, etc. etc. It just becomes easier in general for humans to reason about exactly what is happening, when, and how.

It also becomes easier to reason about the correctness of this central state when there's only one entire place in the system that can modify it (the central event processor).

Side Effects

As much as it's generally true that many simpler things are easier to comprehend that one complex thing, if the decision is between many disparate places in your codebase that can cause complex side effects vs. one central place that can do that, the central place generally wins if you want an easier software to maintain and reason about. You want to limit the number of places that can mutate external state to an absolute minimum. We might not be able to use pure functional programming for everything, but we can definitely limit the number of places in the system that can trigger complex side effects, and centralize them and make it easy to understand exactly when/where/how those side effects are caused.

There are other strategies to centralize the processing that become even simpler than an event queue if the order of processing events doesn't have to be in the incoming order, and your events are being generated from entities (in which case your design starts to take on an entity-component system with systems processing local state encapsulated by components).

In any case, the broad strategy, when applicable, is minimize the places in your codebase where complex side effects can occur. Event-driven systems without special care, and in very large-scale and complex codebases, can really exacerbate that and maximize the number of places where side effects can occur while simultaneously making it difficult to predict when/where/how those side effects will occur. The easiest way to fight against that is to then defer the processing once more to a centralized place.

share|improve this answer

It sounds like you are looking for State Machines & Event Driven Activities.

However, you might also want to look at State Machine Markup Workflow Sample.

Here you are a short overview of state machine implementation. A state machine workflow consists of states. Each state is composed of one or more event handlers. Each event handler must contain a delay or an IEventActivity as the first activity. Each event handler can also contain a SetStateActivity activity that is used to transition from one state to another.

Each state machine workflow has two properties: InitialStateName and CompletedStateName. When an instance of the state machine workflow is created, it is put into the InitialStateName property. When the state machine reaches the CompletedStateName property, it finishes execution.

share|improve this answer
2  
Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference. – Thomas Owens Jul 16 '12 at 14:00
    
But if you have dozens of handlers attached to each state, you'll be not so fine when trying to understand what is going on... And every event based component may not be described as a state machine. – Guillaume Jul 16 '12 at 15:27

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.