Contents
Short answer: Thatโs simply not possible. Weโre all humans so itโs hard to make something good on the first try. But itโs possible to minimize the number of mistakes and effectively fix everything later.
I’ve recently completed writing my latest program, and I must say, it’s one of the most decent ones I’ve ever created. However, it wasnโt without its challenges and I hit the wall quite a few times. This experience taught me several valuable lessons, which I’ll be sharing in this article. Here, you wonโt find a bunch of cliche advice from ChatGPT but rather practical insights gained from my mistakes.
Thoroughly think over everything before writing any code
The program I was working on is a habit-tracking app. Not a simple one though. The matter is, many habit trackers and planner apps out there do not work as Iโd expect. When I miss completing the habit on a due date, I expect the program to tell me about this backlog later. And vice versa, when I complete a habit on a non-due day, I want the next scheduled occurrence to be canceled. So I created my own that does exactly that.
It also calculates a habit streak. And after all calculations, it should look something like this:
Sounds simple in theory but in reality, youโre gonna hit some major pitfalls. To determine habit dueness, itโs not enough to check whether itโs due on the current date or not. If the habit has been over-completed previously, we want to cancel the current occurrence. And if the habit was failed on a certain date, we canโt surely say itโs failed because this backlog may have been sorted out later.
So how do we determine whether itโs actually due or not? By calculating the number of times it shouldโve been completed and then comparing it with the actual number of completions? How do we calculate these numbers? By checking whether each date is due or not? Is it performant? How do we store all of these in a database? And remember that the results will be different depending on whether the given date is in the future or in the past. Finally, how do we then calculate streaks?
So, instead of thinking over all of those, I decided to write the code first. And, honestly, it didnโt turn out well. I ended up creating an architecture where
- it wasnโt possible to compute habit status for a certain date without affecting other dates;
- I had to compute everything up to the current date;
- and the worst part, all this was stored persistently so I had to somehow update everything every time something changed.
I wonโt go into the details of how awfully everything was implemented, but trust me, it was simply unmanageable.
Thatโs why we have to thoroughly think through every aspect of a project before touching the keyboard. While it might seem like a slower approach, I now understand that it leads to cleaner, more efficient code that is far easier to maintain and extend. Because itโs usually a lot more complicated than we think it is. Rushing to write the implementation will often lead to a huge amount of time spent for refactoring later. For this very reason, seniors spend a lot more time thinking than juniors.
Also, our thoughts do not extend to every single edge case. Thatโs where AI comes into play! Itโs a good thing to ask AI about that, especially if you donโt have anyone else to discuss it with. I discovered that just recently when I asked about my next projectโs implementation. And it dropped some really good points, which Iโd otherwise not consider. Check this out.
Adding features is easy, but maintaining them is hard
Some functionality may not be as useful as it seems. I had to drop some of my app’s features because they were not worth the effort. I learned to avoid adding or expanding features on a whim or without a clear purpose. Be a lazy developer!
The thing is, even if some functionality seems easy to add, you have to remember that you will also have to maintain it, fix bugs, test and enhance it, integrate it with the rest of the app, optimize its performance, make sure it works on different devices, and so on. It will be frustrating if you eventually realize that you have to drop this functionality because it is not needed by the user or it actually hurts the user experience.
Follow git best practices
When I started working on my project, I didn’t have a clear plan in mind. I was jumping from one task to another, resulting in a messy codebase because everything was done all at once. I ended up building on top of functionality that wasnโt working properly and wasnโt tested thoroughly. Debugging such a structure was also quite challenging. Not only could I not identify what wasn’t working, but I also struggled to easily revert to the latest working state.
What I’ve learned is that it’s crucial to have a clear goal for each part of the project and to focus on one task at a time. After completing a small chunk of work, test it thoroughly โ either manually or automatically โ to ensure it functions correctly. Only then should you move on to the next task.
This approach aligns perfectly with git best practicesโmaking your commits small so that you can easily pinpoint the exact change that caused the issue. With this method, youโll also be able to roll back to a previous commit without losing unrelated work.
I also discovered the power of pull requests. They are not only useful for team projects but also for personal ones. While you would typically want to keep your commits as small as possible, you can gather them in a pull request to form a feature. This way, youโll have a clear separation of what youโre working on.
Whenever you start working on another feature, create a new branch and commit to it instead of committing directly to the main branch. And hereโs the important part: you have to be confident in your code each time you hit the merge button. This ensures you donโt build further on top of broken code.
Adopting this workflow has other benefits. For example, you can leverage AI code reviews by setting up a GitHub action that will automatically review your pull requests. This way, you can discover things you might otherwise be unaware of because, after all, we donโt know what we donโt know.
This approach will also make it less tempting to make changes to unrelated code and instead focus on building the planned functionality. Follow the single responsibility principle!
Additionally, I suggest documenting your changes by writing descriptive commit messages. Youโll thank yourself in the future because future you wonโt necessarily remember those details. You can also provide more context in the pull request details than you can in commit descriptions including images and videos.
What is actually an MVP
Finally, I made it work. And now Routine Trackerโs code is clean and works properly. But here comes the last insight from building this program.
When it was time to write the readme, I realized one important thing. I actually havenโt achieved the functionality I wanted to make initially. Apart from other habit-tracking apps, my app was supposed to display the habitโs progress and the estimated completion date. You see, I thought that I would first create a minimum viable product and then fill it with cool features.
However, what I discovered too late was that an MVP doesnโt presume a project with minimum features. It presumes a project with minimum functionality that is required to test whether people find your idea useful. So what kind of feedback can users give you if you provide an app that copies features from already existing projects and the only thing that differentiates your project is that it is immature and buggy? Instead, the MVP should contain those very features that set your app apart. In my case what I shouldโve built first is habitโs progress and completion date estimation functionality.
How do you build those cool features if the foundation isnโt ready yet? The answer is in this tweet:
To give you another example, in my next project, I want to make an algorithm that will predict which products the user will run out of soon. I’ve thought over the implementation but again, this will most likely be much more complicated than I imagine. Therefore, it makes sense to first, build a version of the app where users will manually choose how long it takes for a certain product to run out and only then automate it.
I was so focused on making that core functionality work that I forgot about the very purpose of the app. That was fine though because its core functionality is also kinda non-trivial.
Also, while pet projects are a playground for skill development, applying such business principles to them brings a whole new set of benefits. From faster development and preparation to a real-world scenario to these projects bringing more chances to give you a job.
Write the Readme first
You may have noticed that I gained the last insight while writing the readme. Why? Because the readme is where we have to outline the mission of our project, how it achieves it, and what makes it unique. So I had an opportunity to better think about that in the final stage of development. Cool, but itโs not very smart. What if we turn things around and write the readme on the planning stage of the project before writing any code? This is called Readme Driven Development and itโs not a new idea.
This also makes it possible to receive some initial feedback by sending this readme to other people and asking what they think.
Hi, Iโm Daniel
Iโm a native Android developer looking for my first dev role. Iโm currently focused on building a strong portfolio, improving my skills, and establishing an online presence. If you want to collaborate with me in any way, please let me know.
[fluentform id="8"]