One of the changes that I experienced is this huge change which occurred from ember.js 1.0 to 2.0. They rewrote the framework more component oriented than before. And suddenly community also turned “this will be fixed in 2.0 version!”, duh… There was an app which was developed into a monster code chunk of ember.js 1.0 and it was giving pain so much. The data down action up concept was causing too many constraints and the definition of controllers, components were like wild in the community. So how did we embrace the change? Is it a rewrite or updating the same code base?
The first option is a very very risky decision as the many apps are business oriented and can’t risk the value of the time to market in business decisions. With a platform already in a prod environment, a blooming startup can’t risk its reputation. But we cannot go towards the “Elephant in the room” state as the startup was going towards more co-operate hands and decisions were so strict in a sense.
Next, comes the upgrading the same code base in gracefully with the new features introduced(hoping the constraints would go away, but trust me it won’t happen!). Though this seems as the most favourable decision, migrating to a new framework was not so easy in a way as the previous code had too many layers which would be needed to change in a full manner. Following are the mistakes which could possibly occur in when migrating an existing code base towards a new framework level enhancement.
- No clear separation of the logic applied inside the code.
- No clear separation of the data layer and the view components.
- View and Controllers(or Components) are so tied that the change in a single layer would affect the whole functionality of a subsystem or a module.
And also let’s say somebody did separate the logic and creates the underlying app in a very robust manner. At the initial design phase, they will come up with a clear abstraction between logic, data, components and implement it from the scratch. Is it a good practice…? Well… hear this out.
The abstraction becomes evil
This time the whole app is like crystal and everybody is happy. The app has used react.js with redux and the requests are called asynchronously to fetch data and actions are dispatched like tiny ants at work. They built their own underlying framework for the app. And all the new features defined in the whole wide web at the moment is in our code base. Everyone is happy about it and then adds features one by one each day without updating it underlying app framework. And the code also grows exponentially. So what could go bad with this?
One day they face another issue introduced by the underlying app in such a way that it affects the whole code base. But what happens is the company is now thriving from the place of startup to being a co-operate management and they don’t want stop adding new features as well. So they found a new theme called “Technical Debt”. They start to pick and push every framework level changes to debt registry and don’t think of addressing them.
When time goes by, the debt becomes a serious problem to all and debts starts to speak for themselves. But devs try to hide it under the bed by introducing monkey patches to validate and do all sort of things to hide the smoke which comes up. And all the people will pretend like there is no issue at all in the app and try hide over and over again. This adds more weight if the project management also hasn’t been following proper SCRUM or DevOps practices. And especially deployment procedures. If the deployment units are scattered and there is no continuation of integration and delivery, there will be more fumes in dev teams to get the product enhancements to the market.
But finally, there is a day where all the people who come to an agreement as they cant proceed further enhancements with the current implementations, wondering how they even get this far with this code base, which now has become a legacy app.
So how can we embrace this without giving up the velocity of market-ready production releases?
The most trivial thing in the evolution theory is the adaptation. Species who could quickly be changed to the environment variables were the ones to remain for future. The ones who neglected to change, perished eventually when they cannot withstand what the environment was forcing them to do. Just like in our context, js world is changing rapidly and ones choose “What” and “When” are the ones who succeed the development and release cycle in most appreciative manner.
How? Is the question that most people want answers. So I think the most important thing is to use rapid iterative development cycles and repetitions in the code and introduce abstractions with the implementations. To simplify this we should start with a small startup who tries to develop their app with current technology available
- Get a good knowledge of the best-suited frameworks and libraries that could be used to develop the app with the projection for 2 years from the current position. Consider scalability, security, performance, availability as top pillars. And when I mean 2 years, don’t go with the technologicle buble now on the web. Go with a more reliable and best suited one. But do your homework as much as you can. Then you will see lots of things that you would have been wished to see before in a future perspect.
- Design data structures properly. Minimize data redundancy. Think of algorithms to shrink data. Think of more suitable js structures which would suit the best performance. Use single depth data structures without going into a more deeper level. Understand how V8 tries to optimize just in time compilation and avoid mixing the data types. Understand how immutable data structures should work and implement them correctly so that you won’t blame on the creators of immutable data libraries rather than understanding what happens in your own code.
- Algorithms. Even for a for loop, just focus on how much iterations are necessary and please do consider the complexity. If the 2nd point is crisp and clear in your framework, I guarantee you won’t have to consider this too much.
- Separation of logic and memoisation. You don’t have to waste your CPU cycles on resource-hungry operations over and over again. Use dynamic algorithms and memoisation to overcome the delays in rendering the view.
- Build your own underlying framework necessary for the app without using all the things shipped and sent from a framework. This was even encouraged by Dan Abramov who came up with redux.js framework. The idea is to customize your code to a level that you won’t be in a position to wait till the framework providers give you solutions. You are the captain of your ship and train your crew to craft the ship too so that you don’t need to dock in next harbour when you need to change the floor wood.
- Repetition and separation. This can be related to the DRY method(Don’t Repeat Yourself) in rails. First, code without using so many different files and module structures in your code base. For practice, just start with one file (don’t make it bigger more than 3000 lines of codes!). And when you see a repetition just separate it. And if you want more modular context, then introduce abstractions. Here without sticking into features provided by the frameworks, try some design patterns as well. Sometimes it could be very frustrating at first, but trust me, frustrations would give you the best solution afterwards.
- Don’t mutate the state and manage the state very carefully and selectively update the views so that the rendering is minimum. This is more redux oriented and this is very important when designing your app. Sometimes you might not notice how many renderings are happening in the app while you are updating the state. ALWAYS keep your eyes on it.
- Use pure functions and PureComponents as much as possible. Pure functions are so easy to manage and its a good practice. Sometimes you may need to use stateful components but it will be the best if you can use it in the places where it is necessary only.
- Iteration. When developing features, always develop with more atomic functionalities and get them done right. Identify the mistakes you did to your underlying framework and attack it as soon as possible. In this manner, you will be able to adapt to the requirements and environment of your app and enhance it without losing the grip.
- Consider synchronous and asynchronous actions and data requests wisely without using them blindly. If you are going to give framework level support to async actions, please take some time to realize whether you need it and what possible issues could arise in future.
- Minimize deployment time rather than development time. Most of the startups tend to lose the deployment automation and testing capabilities. It is vital to adapt into continuous principles without leaping forward. Because when you become a more co-operate business, you will feel the time is more valuable to push new features to production.
These steps may vary for your context, but the basic idea is valid. Without making a legacy code base, please adapt into more safer grounds as time goes by. Have a cleaner code and always try to clear up the debts no matter how high is your impact on existing production app.