The gap between learning code and producing usable software
One of the unspoken areas about software development is building usable software. Learning how to code, coding something, and having it used by thousands is no easy task. In this article, I’ll be discussing the most important pillars that you need to address before launching your product.
My first app ever made was a social media, if you exceed 100 comments/post the app becomes crazy slow, the database over fetches, authentication was broken, and the whole world was burning down. Not a great spot to be in, but this goes without saying that paying close attention to some little details will save you months of technical debt and headache.
Note: Each point can be open for debate, this is what will get you started, the more you learn, the more reliable your app becomes.
Think of this as a checklist for your next product:
1- Code Safety
While coding (or when finished) the app, you want to make sure that the environment is safe, no user data is leaking, and the app is reliable and ready to scale for any spikes of usage.
Avoid unwrapping values the unsafe way (aka check if the value is nil) then execute your logic. Surprisingly, this isn’t talked about that much and as a fresh software developer, you never know what will crash your app. You should never be 100% sure that a value won’t be nil, weird stuff happens.
Avoid retain cycles: While these are caused unintentionally and not easy to detect, you will need to run a check for your app to detect if memory is retained for some unused objects. Each IDE has tools to help you check for retain cycles and you need to make use of them. Xcode for example has a tab where you can detect memory spikes, deallocations, and CPU usage. There are many ways to detect if something is wrong with your app, hardware-wise, make sure to search on the net depending on your platform.
Protect your secret keys! More often than not, you’ll find yourself using 3rd party services. These services provide a “secret key” and a “test key” to get access to their API. In production you use the “secret key” and it’s secret for a reason. If leaked, another person will use it and you’ll pay the bill :) not so fun. Even worse, this might leak your users’ data or even shut down the servers. That’s why it’s important to make use of “environment variables”. As the name applies, these are variables that live in the local machine. Say when a script wants to have access to that secret key, it can get it from the local machine. This way, you aren’t hardcoding anything into the script and at the same time keeping your code clean and secure.
Passwords: Now this is an underrated problem that isn’t talked about and can bring down a whole corporation. In some cases, when you aren’t using reliable 3rd party services for authentication such as AWS & Firebase, you need to be hashing the passwords yourself. Personally, I always keep the hashing function as part of the API in a separate file. I include a random environment variable that I can keep track of which dictates how the password will be generated for a specific user depending on the information provided. And we are set! I don’t need to know my users’ password, I trigger the “sign-in” by triggering the same hashing function using the environment variable I stored before and everything is secure.
Don’t keep “Logs” (similar to print) when building for production! This is a great way to shoot yourself in the leg, keeping logging functions extends to the build and if you are logging sensitive information you’ll get in trouble. Just stick with classic print() statements; these are safe for usage in production and aren’t logged (from an iOS point of view)
2-Help your project help you
After building a couple of apps in different areas with moderate difficulty, you’ll wish you’ve done many things differently, note these down and use them in your next project! My projects went from utter spaghetti mess to a clean hierarchy.
In my case, I found it hard to keep track of the placement of my files in Xcode, so for each screen, I had a folder. The folder contains front-end logic, backend-logic, and unique utilities. As for general utilities, I’d keep them in a global folder to refer to them from every other file.
Think of factorizing the most. When coding, you want to have a file work as a global factor for other scripts that use the same functionality.
At some point, you’ll realize you are dealing with many folders, and your project is huge, but this is a thousand times better than having everything crammed into one folder and you waste 30seconds to just find a file
You don’t need to follow my pattern. Find yours and keep experimenting.
3-Your servers will make you pay for what you don’t know (Literally)
Sometimes it’s tempting to use 3rd party services since they can make life easier. I’m all down for using these services and in fact, I use them a lot. However, you need to know the inside outs of their billing policies and know what you are billed for.
Rule of thumb:
Add a service only when it can save you costs and/or enhance your UX. Yes, I just said you need to add a service that will save you money. For example; AWS has a free-tier usage per service. So instead of putting the pressure on just 1 service, you can make use of multiple, you take the roundabout but you end up saving costs early on.
Algorithmically speaking, you can save Database costs on your client-side. The intuitive way is by making fewer calls, caching user information, and preloading data for future usage. This obviously adds more work client-side, but it’s getting easier to integrate offline capabilities than ever.
Note: by experience, I noticed the more you save money client-side, the better is your UX, and the higher the performance gets. I’m aware this is a bold point to make so I’ll leave diving into it for another episode.
Scalability wise, make sure to keep your code usable for huge traffic spikes, these are basic algorithm analysis techniques and they are often shorter to code. For example: Use built-in sort functions, don’t’ iterate every time on a list of 100 items and above, cache your results, make use of dynamic programming (in short the previous result helps with the next result; read more on this here)
4- Just when you think you’re done…. Layout Issues
By now, you might think you’re done, but there is a bit more to the story. You’re very close though cheer up!
Often when building your projects, checking tutorials, StackOverflow, etc… you find hardcoded pixel values, typically: (width: 100, height: 100) or similar. These hardcoded are made for simplicity’s sake. No, your app won’t scale because each device has different # of pixels, etc.. That’s why:
When coding, avoid using hardcoded values and make use of a percentage. You can have 2 static variables in a separate class from which you get the height and width of the current screen. This way while coding my layout I have fast access and scalable way of presenting my layout.
Typically, you ‘ll be calling the class as : SomeClass.screenWidth * 0.5, this means “make my button’s width half of the super view’s width”
Same applies for the height
Test on different physical devices, simulators aren’t that bad but sometimes screw up layout-wise, they also have more performance since they run on your computer. Sometimes when a layout or animation looks smooth, you might want to test it on a physical device and double-check for yourself.
Don’t stress over the layout too much, have a fair looking one, make use of 3rd party packages/plugins/libraries for animation, there are many free services that help make your app look neat for the least effort possible. Remember, we are all about 20% investment 80% return in this newsletter :)
5-Client Error Handling: You can get away with this, but don’t overlook it
Error handling is fun, right? Or you might think you don’t need it so you just use a standard popup to show any “Error, try again later” message. DON’T. Bad coding, bad bad bad. Every app will crash/fail at some point, you want to provide a targetted message to what happened. While you can’t know every reason for failure, you know the error code. To get my projects up and running, I embed the error code with a standard failure popup, this way, when a user reports the problem during beta/production, I check the logs for the code. Fast and easy debugging!
At a later stage, you want to remove the codes and move to “natural language explanation”. You’ll start working on this when the time comes for enhancing your UX further.
6-Cross Services Error Handling
Same as in the previous case, but one important matter to take into consideration, if you are dealing with a financial backend (aka Stripe, etc..) you want to have your webhooks (API that receives transaction calls from Stripe, Paypal, etc… and here you update your database) react the right way. Error handling here is critical because you don’t want to flag a user as non-paying because they had their payment attempt succeed but you failed to call the database and make the necessary updates.
7-Test Cases: You can get away with this too
Writing test cases is indispensable for reliable software, especially when scaling and adding more features as a team. You never know who coded what and how that reacted with a piece of code you’ve written. If you are a solo developer and about to ship a basic product, you might get away with skipping this step but you need to keep in mind you’ll have to write test cases to avoid basic bugs in the future.
Well, this was a lengthy one. I hope you found this article helpful; sorry I had to make it this long but the process had to be detailed and practical. Consider sharing with your friends and anyone who might find this article helpful; and if you aren’t subscribed, consider doing so!
Until the next time, take care, byeeeeeee :)