Refactoring using an event-driven approach: part 1

Chirag, a senior developer working on a project

In programming, it’s inevitable that you will end up looking at a codebase that you wrote a long time ago or at someone else’s old code. Programming, just like in any other profession, the professional in that trade improves over time. The professional’s skills are sharpened and an ability to solve problems in their field becomes much easier and faster.

The professionals can then look back and evaluate how they would have approached a certain problem differently. If possible, changes can be made. This process of evaluating the old and making changes is called Refactoring in computer science.

There are many ways of refactoring a codebase but in this article, we will limit ourselves to using an event-driven approach.


The What and Why

Event-driven programming according to Wikipedia is defined as the following:

Event-driven programming is a programming paradigm in which the flow of the program is determined by events such as user actions (mouse clicks, key presses), sensor outputs, or messages from other programs/threads.

Event-driven refactoring utilizes event-driven programming techniques to achieve the following:

  1. Reduce amount of code written
  2. Reduce/eliminate duplication of code
  3. Reduce complexity (scenario-based)
  4. Improve performance (scenario-based)
  5. Improve coherence
  6. Improve readability
  7. Decouple code

In general, a program can be broken down into an event-based program if it fulfills the following three main criteria:

Note: This is not absolute!

  1. It’s modular. There are various modules that communicate with each other.
  2. It’s interactive. Requires user interaction such as use of input devices to carry out functions. An example is in a game.
  3. It’s coupled. Classes and modules extensively rely on each other in order to function.

If your codebase satisfies the above criteria, follow the steps outlined below.

A Super Mario clone built on the Open Source Hello Mario Engine

Steps to refactoring the codebase using an event-based approach

  1. Breakdown — Breakdown the codebase and define the main functions/modules/classes.
  2. Determine functionality — Clearly define the main functionalities of each module/class.
  3. Determine level of coupling — Establish the extent of which each module depends on the other. In this step, identify what is shared between the various functions/modules/classes. This may be messages, an object, an array etc.
  4. Identify events — Determine actions that can be considered as events.

Examples of actions include:

Note: => indicates progression to next action

i) When a key is pressed => move player in given direction => check if collided with a collectable item => add to score => remove item from screen. The code below shows this in action:

A simple HTML5 Canvas game to illustrate keyboard actions

In the next part, we shall refactor the above game to an event-driven architecture that will make the code less verbose and more testable.

ii) When user signs up =>validate data=>save the data=> send a confirmation email

ii) When user signs up =>validate data=>save the data=> send a confirmation email

iii) When a request is received=>evaluate request=>save to audit log

iii) When an error occurs=>save the error to database or file=>send error to client frontend or other channel (mail, social media etc.)

iv) When user requests for a loan=>validate request=>evaluate eligibility=>send response

There are many more examples of actions which may be considered as events.

Events can be generally considered to fall in one or more of the following categories:

  1. Notification — like sending emails and showing error/success alerts
  2. Logging — like errors and activity tracking
  3. Input/Output — like keyboard, mouse and gamepad events

It is very common in codebases to find these events implemented in a very coupled fashion. Code that could have remained in a specific module ends up duplicated in multiple modules.

Using Event Emitters

An Event emitter is a program that registers an event and it’s respective handler/listener. The event emitter can then be used to dispatch those events. Using an Event Emitter can easily make the code more succinct and maintainable. An event can therefore be dispatched from any point in the code base.

Most Event Emitters utilize three main methods:

on(), off() and dispatch() or fire() in some libraries. 

The most common syntax (shown below in JavaScript) used by Event Emitters is quite simple to follow and takes the following forms:

EventEmitter.on(event, callback, target|context?)
EventEmitter.off(event, callback, target|context?)
EventEmitter.dispatch(event, [arg1, …, argn])

There are other ways of implementing an Event system such as pub/sub which we shall explore in the next article. We shall also explore relationships between events, queues and jobs and how you may take advantage of them in your next project.

Conclusion

Refactoring is a skill that every programmer needs to have under their belt. Learning different refactoring techniques helps you decide on the best one to use based on your situation. Libraries and packages for event management are available in many languages and for different frameworks.

In JavaScript, NodeJs has a native Event system that you can take advantage of. You may also use other 3rd party packages if you also want to use events in the browser. Other languages like PHP do have packages for event management. A popular one is the Doctrine Event Manager. The Laravel event system is also a great implementation that you can use to simplify your codebase.

Feel free to fork and try out an implementation of a simple event emitter I developed from GitHub. We shall explore how it’s implemented and used in the next article.


Do you need to hire top developers? Talk to Andela to help you scale
Are you looking to accelerate your career as a developer? Andela is currently hiring senior developers.
Apply now.