I started my software engineer career in an artificial intelligence (AI) startup, followed by, currently in a Singapore technology company. There are certain things that I wish I would know during my early career as a Software Engineer as it shapes me to be an engineer with more perspectives.
Speed is not the end game
Having to complete your JIRA [1] tickets as fast as possible can be a good thing in terms of meeting the timeline. BUT, if you have completed your JIRA tickets fast with bad implementation, this means that you may not be completing the task effectively. There are always a lot of things to consider when it comes to implementation. With bad implementation, this will potentially lead to more technical debts and it is difficult to scale the system in the long run. It is important to plan your implementation and think of the system more holistically to reject potential hyperbolic discounting.
Plan your implementation
Plan your implementation before writing your code is important but why? It is like solving a puzzle game such as Chess, you need to strategise what is your next move, once you have planned out and acted on your next move, it is impossible to revert your change. This will affect the outcome for the rest of the puzzle game unless you are to start over or to salvage your game in the subsequent moves.
In coding context, if you do not plan ahead, you may write code implementation that may not be desirable and miss out certain considerations such as:
Coding principles
- Keep It Simple, Stupid (KISS)
- Don’t Repeat Yourself (DRY)
- Single Responsibility (S)
- Open-Closed (O)
- Liskov Substitution (L)
- Interface Segregation (I)
- Dependency Inversion (D)
Non-functional considerations
- Availability
- Installability
- Integrity
- Performance
- Reliability
- Robustness
- Safety
- Security
- Usability
- Efficiency
- Modifiability
- Portability
- Reusability
- Scalability
- Verifiability
Note that the above are just some considerations.
You need not to follow all considerations but you can find relevancy when you are implementing features.
One simple example of not planning is having multiple nested for loops in your code implementation. This will affect the performance in terms of time complexity. There are a few ways that such issues can be resolved. One way can be using a map data structure to reduce nested for loops, for example, from O(n²) to O(n) which is a significant improvement.
When you are not planning your implementation, there is a tendency that you will be doing a bandage to your implementation as you move along when new features are implemented due to missing considerations. This should not be the way to approach implementation.
This leads to the next point where the rejection of hyperbolic discounting comes in place.
Rejection of hyperbolic discounting
Hyperbolic discounting is a cognitive bias, where people choose smaller, immediate rewards rather than larger later rewards and this occurs more when the delay is closer to the present than the future. [2]
Having to understand what hyperbolic discounting means by the above definition, planning out the code implementation definitely helps with it as it reduces technical debts and the time taken to refactor code implementation. It can potentially affect the business, especially when rolling out high priority projects. Having to avoid this can save the engineers’ time in the long run and avoid any business-related impacts. For example, there is a project feature that can be implemented within a day but it can take up weeks due to the implementation that missed out certain considerations which can lead to delay.
Importance of testing
There are a few different types of testings — unit testing, integration testing, system testing, functional testing, acceptance testing, smoke testing, regression testing, performance testing, security testing and user acceptance testing. In this post, I will be focusing mainly on unit testing and integration testing.
Testing often has been deemed as unimportant or neglected as it is not part of software features. This conception is WRONG! So what is so special about testing? Why do we need testing?
Unit testing ensures individual code blocks are tested and working as expected. Integration testing involves different modules integrated together and ensures the correctness of communication among them. In general, testing serves as a way of demonstrating the implementation is correct which can be served as a part of documentation and engineers should hold ownership of.
With that being said, testing is an important step in the development stage as you can identify bugs earlier in the code, which sometimes can be difficult to be identified later in the testing stage, especially when there are many code logics. This prevents bringing buggy software features to end users which can affect your credibility, helps with enhancing code quality and reducing bug fixing costs such as time and money of product and engineers.
Testing consideration
A testing consideration that could help with the approach of writing tests is to determine whether the test case is meaningful to be written. As high coverage coverage can be a metric that determines how extensive each logic has been written and tested, if the test case is not meaningful, it is pointless to have them as you are writing tests for the sake of coverage. An example of a meaningless test case is a logic that performs an initialisation (note that this depends on your use case, it may not always be necessarily meaningless all the time). In the test case, you are probably going to write it as initialising the logic with the respective values. If you think about it, what could happen here? It could potentially cause a panic. A meaningful test case should be comprehensive enough to test its happy path — achieve desired results without any exceptions and unhappy path — the opposite of happy path. After determining that the test case is meaningful to be written, find the edge cases of the logic and proceed to increase the code coverage!
Note that when you are adding a new piece of code logic into the current implementation, the existing tests should be working as expected and NOT break, if it breaks, this shows that the newly added code logic has bugs. You could also utilise the generation of code coverage report to identify how much code logics are being covered by unit tests and integration tests, ideally, you should be writing unit tests and integration tests to cover all the meaningful code logics.
Better code, productive engineers — happy engineers. :)
Find mentor
Every experienced engineer has been through the phase of being a junior engineer. They probably have made mistakes and that is what makes engineers learn and grow in the field. It is always good to find a mentor as they can give you advice and share their mistakes that they have made, so that you can learn through on behalf without having to make the same mistake again. My advice is do not be afraid to reach out to people as people in this field are always very friendly and willing to give you advice. Through this, it is where you will learn a lot faster because you will receive newer perspectives that you may not have thought of and in return there may be constructive discussions formed!
Pair programming
Pair programming is when both engineers come together and solve coding problems together. Usually one person will act as a driver to write the code and another person will act as a passenger to give advice or check on the code. The idea behind this is to retain knowledge between both engineers and help the other engineer who requires advice to write the code and understand the codebase better.
Likewise, pair programming does not mean that you are bad at programming. Pair programming allows you to understand the concept and understand your service repositories quicker, especially if a service consists of many logics and having many downstream and upstream services. These are the things that you will need to figure out in order to make your contributions in the codebase. Having to reach out to engineers who are already familiar with the repositories help you to bridge the gaps faster instead of figuring out things yourself that can lead to feeling overwhelmed and can potentially delay project timeline.
Don’t rely on AI tools
With respect to the recent technology trends, sure, AI tools such as the chatbots — ChatGPT/ Bard/ Bing AI chatbot and the coding assistants — GitHub Copilot/ Tabnine are powerful tools, they can be used for code implementation and many people use them. It is not wrong to use the AI tools, however, they are web scrapers that scrape data off the internet and use AI to generate respective results. The results generated are NOT 100% accurate as of yet. My perspective is that it still does require human intervention to verify the logic as the results generated may not be suitable for use cases or it can produce incorrect results. I see the AI tools as a mechanism that assists people in optimising their tasks on a day to day basis, you can use what the AI tools produce as a reference but not relying on it fully for the actual implementation.
Read up on resources!
Read up on resources such as books, publications and articles from engineers and technology companies. For example, technology companies will always share how they implement their systems on their blog articles.
This will help you to gain new perspectives and knowledge from it. You can gather and learn useful tips and techniques from it, create your own thoughts and apply it to your work not just to improve your technical skills, as well as soft skills and/ or specific topics that you are reading based on it. Similarly, you can always be innovative and design your own way of doing things after understanding these theories.
Writers of such usually have been into the domain for a long period of time, they have done tons of research and experiments before they have published them. Since there are resources readily available everywhere, I don’t see why we can’t put them into good use.
The real value of reading up — application
Having to mention the ‘whys’ to read up above, however, the real value of reading up only comes in when you put what you have read, the knowledge, into practice. This is when you understand the concepts which in return are able to achieve desired results, gain more practical experiences and benefit the people around you through the course of application.
For example, if you find yourself or the codebase having issues to maintain clean codes, you can read up on resources on writing clean code. You will gain knowledge and tips such as writing readable function/ variable names and adding necessary comments to functions, etc from the resource. Having to apply it, you are contributing and making improvements to your codebase, which allows you to understand the concepts better, at the same time, you are sharing your knowledge with the contributors of the codebase. This is where the real value — improvements and knowledge sharing — comes in.
So read up on resources to expand your knowledge and find the right scenario to apply them into your work!
Special mentions
This post is reviewed by Roman Volkov and Jazz Peh who have played a part in expanding my perspectives and growth as a software engineer. Hope this post can benefit people who just started their career as a software engineer.
References
[1] Atlassian, “Jira: Issue & project tracking software,” Atlassian. [Online]. Available: https://www.atlassian.com/software/jira. [Accessed: 02-May-2023].
[2] M. Team, “Hyperbolic discounting: Behavioural Science in banking,” Moneythor, 02-Sep-2020. [Online]. Available: https://www.moneythor.com/2020/09/02/hyperbolic-discounting-behavioural-science-in-banking/#:~:text=What%20is%20hyperbolic%20discounting?,the%20present%20than%20the%20future.%E2%80%9D. [Accessed: 02-May-2023].