Hey there! I'm Catalin Dragutescu, a Full Stack Engineer at Railsware.
Over the past six years, I've had the opportunity to work with engineering teams of various sizes. I've developed different growth strategies, tackled challenges, and prioritized tasks. I've seen both successes and setbacks. So I'm sure the topic of speeding up the development process interests everyone—from interns to seasoned developers.
If you've encountered situations where hiring new developers doesn't increase productivity or large projects take too much time—keep reading. I'll share my own experience, discuss what strategies work and which don't, and talk about the aspects we considered when choosing a path to optimize development. So even if your primary focus isn't backend, you'll find this material useful.
More people =/= more capacity
Improving team productivity is often easier said than done. Let me illustrate this with an example from my own experience.
My team and I were working on a large product that already had a market presence. At that time, our strategy focused on two main areas:
- Maintaining the quality and availability of existing services to ensure stable growth.
- Experimenting with new business directions.
Our team was relatively small: four developers, a product manager, and a QA engineer. We decided to bring in additional specialists to handle new projects while maintaining the quality of work and control over minor issues. However, hiring new people didn't solve our problem.
We noticed that even with the addition of new specialists (mid-level and senior), the overall productivity of the team did not increase. Initially, we assumed the issue was the time it took for newcomers to familiarize themselves with the project. And while that's partly true, we later discovered a more significant problem: a lack of clear documentation. This led to constant interruptions as colleagues sought help, which frustrated them and delayed the work of other developers, who had to spend more time onboarding.
Such situations don't only occur with new hires; they are also common in established teams working on various products. So, if you're planning to expand your team, keep in mind that the process might be slower and more complex than you expect.
How to ease the onboarding process
Previous experience has ended positively, as it allowed us to identify conditions that can ensure a smooth start. Here’s what I’ve learned:
1. Accessible Process Documentation. New functionality or changes should be described on a platform that is convenient for you. When all team members have access to it, they can quickly use the guide. Try to do the same for existing functions to avoid the situation of “It probably just works this way.”
For creating README files, you can use GitHub. There, you can detail and concisely describe all the necessary steps for developers to “run” new features. I also recommend using Swagger/API documentation, which should be shared among the entire team.
For describing functionality to clients, you can use Zendesk. This will help your users better understand how to use a particular feature through the user interface, ensuring that information is fully accessible and clear to both your development team and clients.
2. Simplify Project Setup. The most common method is containerizing the application, but there are other options. The key is to ensure that the project runs on any operating system without conflicts with existing services. This setup should also include all external service accounts if possible, or steps for creating them.
3. Stay Updated. Unfortunately, when you are already familiar with the project, it can be easy to overlook and ignore updates. Remember, the person reviewing the documentation has never used your product before. So, pay attention to updates in tools, requirements, and services. No one wants to waste time on outdated libraries. To manage updates, consider using tools like Dependabot, AIKIDO, Depfu, or Snyk. They will help you track security issues and monitor general dependency updates.
4. Don’t Reinvent the Wheel. One example — If there is a convenient and popular method for managing environment variables that has no major drawbacks, it’s best to use it. This will make it easier for new colleagues, as they might have used it before. Generally, I try to follow familiar configurations unless it impacts my work.
These are not magical solutions, but they will ease the start of working on a project, bring long-term benefits, and enhance reliability.
Optimizing the Development Process
Let's look at development in general. Whether you are fixing bugs or developing a new feature, your process will roughly look like this:
- Defining workflow: Familiarization with the task description and acceptance criteria.
- Developing the solution.
- QA/Testing.
- Deployment.
- Measuring results.
- Monitoring errors.
You might say that I’ve simplified things quite a bit. That’s true, but from experience, I can tell you that there is no universal formula. However, these basic steps can easily guide you. You can then tailor your development process to your specific needs and requirements.
Moreover, remember that even Agile is just a set of ideas and principles that can genuinely help you, but won’t solve all your problems. Therefore, I recommend optimizing each process as much as possible and seeking tools and strategies that will best support your team.
Defining workflow
Let's check the approach using user stories. This is indeed a useful tool that can greatly simplify understanding tasks. I believe it's worth trying to describe tasks through them. It's important to have clear acceptance criteria but remember that user stories add a personal touch to the task. They allow the developer to notice if something seems off and make small decisions without restarting the process each time.
Here's a very simple example. You have an interface with a table displaying user information. You need to add a button to export this data. You could describe this task as "add an export button to the table" and perhaps list one or two requirements or constraints.
But you could also phrase it as "As a data administrator, I want to be able to export information from table X." In this case, the story better conveys who the user is and what they want to do. This allows the developer more freedom in choosing how to implement the task, rather than just focusing on creating a button.
How to improve the coding process
There are a few tools that you can integrate at this step that improve the code’s and repository’s quality as a whole and in the long term make maintaining the project and improving it much faster:
1. Set Up Automated Testing: The main advantage here is the ability to quickly notice if something in your code isn’t working. Tests should run quickly, especially those checking individual parts of the code. It’s important to define their types with your team:
- Tests for individual functions
- For controllers
- For interaction between different parts of the code
- For the interface, etc.
The best option is to run them with each pull request or commit. However, you can also perform nightly builds for more complex tasks, like running interface scenarios. There are very practical tools for running your tests—CircleCI or Jenkins. Since they will be used and stored in the cloud after launch, you can just define a set of rules and forget about them.
Regarding testing tools, each framework should have something useful and common that you can use. For example, Rails has RSpec. It’s a powerful tool with many additional features, and we mostly use it for unit tests. But it can also be extended with features for interface testing. It helps you create descriptions for tests conveniently. So, when a test fails, you can precisely understand what went wrong in the format: functionality description as a context explanation, how it should behave.
For front-end and end-to-end testing, I would choose something like Playwright. It simplifies running tests across different browsers and configurations and helps write scripts for them. It’s a convenient tool that both developers and QA engineers can use.
2. Include Static Analysis Tools: There are several categories, although sometimes a single tool can perform multiple functions:
- Code style analyzers: ensure your code style is consistent. Most tools should allow flexibility so you can define your own rules.
- Vulnerability scanners: try to find possible attack vectors using common patterns. Although such scanners are not infallible, they can help you know in advance if you’re doing something wrong.
- Type checking: we all love freedom, but it quickly turns into chaos. Therefore, I recommend including a tool that helps avoid random errors.
Ideally, these tools should run automatically — if possible on the developer box, or if not on a server. The whole idea is to create small feedback loops – so you shouldn’t go to QA and an engineer discover that you have an SQL injection threat when most vulnerability scanners can warn about that.
QA, Deploy, Measure
In testing, nothing is set in stone. However, having a test environment that can be easily created and deleted plays a crucial role. This brings us back to my earlier point about easy project setup. Initial data is also important. You should have standard data for any combinations of features. Once created, you can use them in the future, saving you time. Additionally, the ability to conduct tests with multiple testers simultaneously is important.
In several projects, I've noticed that for some developers, deployment resembled a mysterious ritual. As I mentioned earlier, this process should be predictable and, importantly, easy to roll back. There are many components to consider here: gradual deployment, auto-scaling, and feature flags for new functionality. What exactly to include depends on the team. But processes like automated deployment should be mandatory.
Although a custom script offers many advantages, using a recognized framework can provide better confidence and clarity for new colleagues. So again: if there are proven solutions available, don’t reinvent the wheel.
I can suggest a few such frameworks. For example, AWS Beanstalk, AWS CloudFormation, or infrastructure integration tools like CircleCI, since your end goal is to define your infrastructure as code, rather than using a list of commands or manually taking actions. Therefore, the "infrastructure as code" approach should include documentation on what each step does. This makes it easier to reduce the number of errors or defects.
Depending on your skill level and the need to control each step, you might choose Railway.app. It provides a more user-friendly interface for deployment but follows the same principles of repeatable processes or rollback in case of problems.
Regarding measuring results, I like to look at it from two perspectives: what you use and how you use it. Let’s start with the latter to make it easier to describe the former. Any of your work should have an expected outcome. This could be increasing users on a page, improving conversion rates in a funnel, or enhancing performance. Measuring each of these outcomes likely requires different tools. However, the most important thing is how you use them.
Try to minimize the number of variables and choose just one. If you want to improve performance and measure latency, ensure that you select benchmarks with similar load, data volume, or other parameters that remain constant during the experiment. The results you obtain should be repeatable with each measurement.
Monitor errors
Tracking errors requires more time investment than checking software functionality after release. Balancing this can be quite challenging. On one hand, it's important to stay informed about any issues that arise. On the other hand, too many notifications can lead to overload and missing important messages.
In a previous company, we faced a situation where we received a large volume of emails daily, which caused us to miss critical messages. Even worse, sometimes we only learned about problems when customers reported them. This indicated a shortcoming in our error monitoring system.
What can be done? Set up "cooldown periods" for notifications. You can use tools like Honeybadger, Datadog, or ScoutAPM, which offer various ways to send notifications. Additionally, consider creating separate Slack channels for notifications based on the severity of the error. This will help focus on what’s important and facilitate easier information processing.
Moreover, fixing errors is not always straightforward. Sometimes, instead of actually addressing the root cause of an error, one might simply hide it if it’s not critical. For instance, errors resulting from incorrect user data should ideally be avoided through validation checks rather than just marking them as errors. When all errors look similar, distinguishing significant ones can become a problem.
Conclusion
Finally, I would like to emphasize that our goal is consistent, reliable results, not quick fixes. In this article, I have attempted to explore a wide range of ways to improve performance that can be applicable to any team. I encourage you to experiment and refine your process using these tips.
Remember: for skilled professionals, there is no limit to improving results.