I have been working as an R&D Engineer for most of my professional life; it has only been in recent years that I have started to work with full-fledged product development for startups.
Within the R&D, most of the code that I have written had a lifetime of at most 2-3 months; once the paper or the technical document has been published, it becomes obsolete, or in the case of industry, the PoC is taken over by software team who rewrote it in their system. It was exceedingly rare for a need to revisit such codes. Furthermore, given the nature of prototyping in research, rarely was consideration given for long-term maintenance of the code.
The R&D approach does not scale well in the industry; when working on production-level products. Through much of my work, I have learned some MUST follow rules that have served me well, namely:
Within the startup, it is common to have either external people working on the code or people working on their part/free time. This generally means that there are always multiple people on the same code base, which forms the basis of a significant problem!
If you are busy with your full-time work or even taking time off (startups can be exhausting!) and there is a critical bug that needs a fix, then other people available would need to manage it. This would pose a major issue if they either do not fully know the programming language (over-optimized code, library, etc.) or cannot understand the underlying logic.
Always write the specification and a justification over the choice of implementation (if any)
- Smart coding
It is always tempting to write optimized code that seems a clever workaround. Although this may sound to be great, there is a significant trade-off that is sometimes not considered, namely, the maintainability and clarity of the code.
Always follow the KISS (Keep it simple stupid!) principle. Start with simple code while focusing on optimization once the base functionalities are implemented and working as intended.
- Dependency hell
Tempting to always use that small library that allows you to implement the logic you are looking at quickly. It is a double-edged sword…
Using many libraries leads to dependency hell that may even make you susceptible to security exploits (See: Dependency Attack).
Periodically review the dependencies, write the code snippet that would help avoid importing too large dependencies.
- Enforce tests and good practices
Harmful code patterns and lack of proper integration tests can lead to significant issues, especially at later stages of the development.
Enforce good code patterns that would prevent bad code smells. Many tools allow this (e.g., SonarCloud), integration tests (via GitHub actions), and active endpoint monitoring (Sentry).
- Data types and structures
It is vital to use the right data structures for the code for the program flow to be smooth without increasing the complexity of converting the data types from one to another. Similarly, functions that are too restrictive in terms of assumptions would boggle down further down the line.
Start with a well-documented structure and clarify the type to be used while keeping modularization in mind.