2

Traditional programming could be done quite readable. Like this:

FUNCTION do_HTTP_request(url) {
    if(!ask_user_if_he_wants_to_connect()) return;
    if(!network_is_enabled()){
        enable_network();
    }
    var content = download(url);
    // … do something with ‘content’
}

However, nowerdays frameworks are rich in callbacks. This splits up the above lines over various fuctions:

var listener;
var url;

FUNCTION async_completed(content) {
    switch (content.which) {
        case async_http.SUCCESS:
            // … do something with ‘content’
        case bar.CLOSED:
            // unrelated stuff
        case bar.LAST_ORDER:
            // unrelated stuff
        case bar.OPENED:
            // unrelated stuff
        case foo.SUCCESS:
            // unrelated stuff
    }
}

FUNCTION async_progress(progress) {
    // unused, but must be there to satisfy the compiler
}

// 8 unrelated functions between ‘a’ and ‘d’

FUNCTION do_HTTP_request(url) {
    this.url = url;
    ask_user_if_he_wants_to_connect();
}

FUNCTION do_the_request() {
    new async_http(url).execute();
}

FUNCTION enabled() {
    remove_listener(listener);
    do_the_request();
}

// 26 unrelated functions between ‘e’ and ‘o’

FUNCTION on_OK_button_press() {
    if(network_is_enabled()) do_the_request();
        else {
            enable_network();
            listener = new network_enabled_listener(this);
            set_listener(listener);
        }
}

In separate class (file / editor window) network_enabled_listener:

FUNCTION on_receive(data) {
    if("enabled" in data) my_main.enabled();
}

This code is far more complex and totally obfuscates the control flow, which is merely the same as the first code block. Are there good programming practices, design patterns, or code style recommendations that target the challenge to reveal the control flow in code that is rich in callbacks?

  • 2
    You are facing the very same that we faced when jumped from structural programming to OOP. Semantically, these 2 blocks you exposed may seem to do the same thing. But there're huge differences. One is sequential and the other one is not. You are trying to figure out if there's any way to design event driven code as structural code. That is for sure a bad strategy if you are trying to approach to event driven paradigms – Laiv Aug 30 '16 at 7:53
  • Agree. To make the question broader: Are there good programming practices, design patterns, or code style recommendations to follow in event driven environments? (Aside from, or on top of those that already apply to OOP, as speaking variable names, good indent style practice, documenting the code, …) – Matthias Ronge Aug 30 '16 at 8:48
  • 2
    Use language and framework that support async/await pattern. There is reason why is it such A Big Deal™, that languages race who implements it first. – Euphoric Aug 30 '16 at 9:17
  • 1
    In addition to what @Euphoric said, standards or conventions for the use of finite state machines is useful, primarily because in event driven systems you don't control the flow. Using a state diagram, you can specify the valid states and valid transitions, so there are no run time surprises. – Frank Hileman Aug 31 '16 at 1:45
7

There are a bunch of design patterns for event-driven code out there, some of which were already mentioned in the comments. I'll write a short introduction to each of the ones I'm familiar with.

Promises/Futures

These two terms are generally used for essentially the same concept: a value which will be available in the future, but isn't necessarily ready yet. (I'll let Wikipedia cover the differences between these concepts.)

Promises/Futures are used in cases where you might have used a callback to receive a value. They often will provide functionality to let you know when the value becomes available and let you retrieve it:

Future<X> doSomething() {
    ...
}
...

Future<X> fx = doSomething();

while (!fx.isAvailable()) {}

x = fx.getValue();

This would probably be the closest thing to the structured code of your first example and, while doable, isn't in the best style.

The preferred way to do things is to give the Promise/Future a callback function to call with the promised value when the value becomes available:

doSomething().then(x => print("Got X: " + x));

The callback passed to the then() function will be called and given the value of the Promise/Future that was returned by doSomething() once it has been resolved (i.e. becomes known).

"But wait!" I hear you say, "this is still using callbacks!"

Yes, it's still using callbacks, but in a cleaner way than just assigning a callback function. The then() method in this case also returns a Future for a value of the same type as the return value of the callback function. If this return type happens to be a Future itself, then the value of that Future becomes the value of then()s future, and you can chain these asynchronous callback functions:

Future<Y> doSomethingElse(X) { ... }
Future<Z> doAnotherThing(Y) { ... }
void output(z) { print("Got a Z: " + z) }

doSomething()
.then(x => doSomethingElse(x))
.then(y => doAnotherThing(y))
.then(z => output(z));

The control flow of this asynchronous Promise/Future chain is readily apparent from all the then()s: doSomething(), then doSomethingElse() with the result of the first step, then doAnotherThing() with that result, then output the final result.

(A side note: Yes, I could have just put method references instead of lambda expressions in the then() calls (e.g. .then(doSomethingElse) instead of .then(x => doSomethingElse(x)), but there are cases where this can lead to issues in some languages.)

Some languages have basic Promises/Futures baked in as part of the language and provide keywords for using it; e.g. C# has async for declaring an asynchronous method and await to use when calling that method (result), as a way to wait for and unwrap the promised value:

async Task<X> doSomething() { ... }
...

X x = await doSomething();

Other languages have flexible-enough type systems and syntax to accommodate this sort of thing via monads; e.g. Haskell with do-notation:

do
  x <- doSomething ()
  y <- doSomethingElse x
  z <- doAnotherThing y
  output z

or F# with its async "workflow":

async {
    let! x = doSomething ()
    let! y = doSomethingElse x
    let! z = doAnotherThing y
    output z
}

Functional Reactive Programming

In functional programming, loops are abstracted over -- instead of looping over a data structure (like a list or a tree or a set) explicitly, you pass a function that you would like to e.g. be applied to each element of that structure and the abstraction takes care of the looping for you:

List<Int> ints = [1,2,3,4]
List<String> strings = ints.map(i => i.toString());
// strings == ["1", "2", "3", "4"]

There are abstractions for changing every element of a data structure (aka mapping), filtering values from it, reducing the elements to a single value (e.g. doing a sum), etc.

This sort of abstraction can be done to streams too:

InputStream<byte> byteStream = readFileBytes(path);
InputStream<char> charStream = byteSream.map(b => b.toAsciiChar());
printFromStream(charStream);

In this way you can transform streams on-the-go, without having to explicitly consume the whole stream to loop over it.

In Functional Reactive Programming (FRP), an event source can be treated as a stream of values (that forms over time) which can be transformed as time progresses.

Take downloading a file as an example. Suppose the downloader class supported events like:

  • Progress to broadcast a change in completion status (e.g. passing the % completed)
  • Done to broadcast when the download had completed
  • Errored to broadcast when an error had occurred

During the process of downloading a file, the Progress event would likely be fired multiple times to inform the application of how much of the file had been downloaded. The Done and Error events would likely only be emitted once a piece, and only if the other one wasn't, when the file had completed downloading or an error had occurred while downloading.

This gives us three events to listen to, but using FRP we could combine them into a single stream to listen to the progress of our download:

abstract class Status
{
    int getPercentDone() { return 0; }
    bool isDone() { return false; }
    byte[] getValue() { return null; }
    bool errored() { return false; }
    Error getError() { return null; }
}

class ProgressStatus extends Status
{
    private int progress;

    ProgressStatus(int progress) { this.progress = progress; }

    @Override
    int getPercentDone() { return this.progress; }
}

class DoneStatus extends ProgressStatus
{
    private byte[] value;

    DoneStatus(byte[] value)
    {
        super(100);
        this.value = value;
    }

    @Override
    bool isDone() { return true; }

    @Override
    byte[] getValue() { return this.value; }
}

class ErrorStatus extends Status
{
    private Error error;

    ErrorStatus(Error error) { this.error = error; }

    @Override
    bool errored() { return true; }

    @Override
    Error getError() { return this.error; }
}

...

Downloader downloader = new Downloader();

EventStream<Status> progressStream =
  downloader.Progress.map(percent => new ProgressStatus(percent));
EventStream<Status> doneStream =
  downloader.Done.map(payload => new DoneStatus(payload));
EventStream<Status> errorStream =
  downloader.Errored.map(error => new ErrorStatus(error));

EventStream<Status> compositeStream =
  EventStream.merge([progressStream, doneStream, errorStream]);

downloader.download(url);

Once everything is setup, you can listen to compositeStream to get a comprehensive view of the download's progress over time using a single callback.

State Machines

I've used state machines many times to control event-driven systems; I'll just quickly give a template that I've found handy for dealing with simple machines in code:

class StateMachine
{
    private StateEnum currentState;

    void transition(input)
    {
        StateEnum nextState = getNextState(currentState, input);
        output(currentState, nextState, input);
        currentState = nextState;
    }

    StateEnum getNextState(currentState, input)
    {
        // determine the next state given the current state and input
    }

    void output(previousState, nextState, input)
    {
        // generate output for the transition
    }
}

This template breaks up the different parts of operation orthogonally into separate methods making for nice coding and debugging. For more complex machines (e.g. with nesting) you'll probably want something like UML State Machines.

Actors

I'll only briefly touch on Actors since I don't think they're what you're looking for.

An Actor is, in essence, a thread coupled with a message queue. The thread reads a message from its queue, performs some action, then loops to read the next message. Actors send each other messages by enqueuing them in each others' queues.

This may sound like a very simple mechanism, and it is, but it's a nice way to split responsibilities or distribute operations. There are many popular libraries for utilizing Actors in different languages, and the Erlang programming language was designed around this concept.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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