Contributor: Dmitry Masley, front-end developer in Archer Software.
I’ve got a little fairy tale for you.
Once upon a time there was a big, awesome project, and in that big, awesome project there was a feature. It was a complicated feature with a lot of user actions and information to display, and required the work of a brave and noble team of front-end developers. This is the story of how a great Class was born.
The Class about which I am talking could do anything the developer needed in order to implement the feature I mentioned earlier. The Class handled everything – user actions, getting and synching data with the server, displaying information, checking user permissions, etc. It was highly configurable, and thus extremely flexible. The Class even had common functions which could be used in other parts of the application, and were therefore moved to a separate file – a stroke of pure genius on the part of our heroes! The new file meant that there was no need to create a dependency to the Class in order to use its logic.
The feature was implemented and everything seemed fine ... at first. The developers ended up with only three files – the Class definition, common functions, and files with a few instances of the Class with different configurations. The feature requirements changed from time to time, but that all seemed o.k. After all, we had a powerful Class to handle all of this – all you have to do is add a new configuration option, right?
Not exactly. As the product managers introduced changes, the new configurations needed to make them became more and more complicated. When the file with Class instance grew larger than 5000 lines of code, our Front-end heroes realized that something had gone wrong.
O.k. Now we know. Building a monolithic Class was a bad idea. A single module meant to handle all tasks is extremely hard to maintain. What’s more, as the number of user stories available to us grew, the more complicated that module became, and thus more difficult to understand or even test.
In order to fix the above-mentioned issues with the front-end architecture, we made three main decisions.
- Use MVC
- Reduce the front-end dependency on the server
- Use TDD
Let’s dig a little deeper.
1. Use MVC
MVC, or model-view-controller, is a type of front-end architecture in which the ‘model’ stores and synchronizes data, the ‘view’ handles user input, and the ‘controller’ manages the interactions between models and views, in addition to doing a bunch of other stuff I won’t mention here.
What are the benefits of this approach? At the top of the list is standardization. Once you open a model, you already have a really good idea of what it should do. Each module has its own unique purpose and style. Having single purpose modules also helps because they tend to be both smaller and simpler than other modules.
All that’s great, but there’s a downside. Having lots of small, simple modules creates dependency problems that developers have to watch closely.
Assuming you’re using some form of the MVC framework, the models will be the simplest part. Generally, all you need to do is give a model a URI to sync with. The Views should handle everything related to DOM manipulations and events, and the controller should handle the actual business logic, as well as the interactions between views and models. In terms of testing, views are usually the priority for UI tests, while unit tests are more concerned with the controller part of the equation.
You can find a good example of MVC architecture here, at dos.sencha.com. For our project we used backbone.js + marionette.js + require.js, with the whole application being split into separate, highly independent require.js packages.
Each package includes a main file that initializes a feature, as well as models, collections, views, and feature controllers.
2. Reduce the front-end dependency on the server.
So, when we use a nice MVC architecture we get simple, easily-tested modules. The downside, though, is that writing unit tests can be a big problem. Front-end is always highly-dependent on back-end. All templates are generated on the back-end, and to initialize our application we needed the whole context available. It turned out that it was impossible to test our front-end without using the server.
Our solution was to get rid of all the back-end logic from the templates, and make the application service oriented. Everything we needed from the server could be attained with Ajax call. For all intents and purposes, the front-end became a separate application, independently constructed and tested. This is another big step from having a monolithic application to one that has modular architecture.
3. Use TDD.
TDD, or Test Driven Development, is a relatively new practice that is rapidly gaining popularity in the world of IT. The main idea of this practice is to write unit tests before code, then write code that will pass the test. This approach improves code coverage, and more importantly improves feature coverage. At their best, unit tests are working examples for invoking code, and thereby provide working specifications for it. If feature specifications are changed, then tests should be updated. Only then should the code be changed to pass the new tests.
TDD is not a “silver bullet“, but it can improve your tests, and, paradoxically, improve your code. When a developer writes tests before the code, he thinks about the feature, not about the particular lines of code or functions that go into it. Because of this, he tries to split the feature into smaller parts in order to more easily pass the test. That forces the developer to write smaller, single purpose functions and avoid “god functions” that do everything. The result is flexible and expandable code that is easy to understand. Developers need to remember that writing tests is a major part of the development process, rather than a routine. Well written tests help us understand the feature in development and invariably save us time in the future.
Our team implemented all three of these decisions over the course of one year, and in the process dramatically improved our code quality and made the software development process much simpler. Code became more standardized and unit tests helped reduce the number of issues. I hope this article will help you improve your development process too.
Archer Software has experience developing effective front-end solutions. Feel free to reach out to us at email@example.com if you have any questions.