Is Done Better than Perfect? — Part 1
The challenge of maintaining clean code and good architecture when implementing a complex given algorithm
Background
In the last semester during my Bsc. in Electrical Engineering, we were assigned the project of implementing the WFQ-GPS routing algorithm as part of computer networks course. Other than implementing this as an online scheduling method (which had an impact on the processing of the input packets), we weren’t given with any other restrictions. Efficiency was a bonus.
I saw this project as a great opportunity to kick my software engineering skills into gear and write this algorithm in a clean and efficient manner. Unfortunately, my aspirations didn’t meet reality, and due to deadlines, my partner and I compromised, and ultimately handed a “done” rather than a perfect project.
When the semester ended, I decided to fulfill my goal and complete this project the right way.
Decisions
- I needed to learn C++. The original project was written in C, but I understood that C++ contains more useful data structures (STL library for example), and is more flexible in terms of memory allocations and implementing design patterns.
- I’m a C developer, I already know the main concepts of C++. So what’s the best way to become a C++-er? There are some guides that provide overviews about the difference between these two languages. However, what I found most useful was to consult with one of my co-workers who is a senior C++ developer. He provided me with a wider perspective on the language and its best practices. He also agreed to mentor me throughout this project, which was priceless.
Getting Started
Improving my Drawing Skills
I started with drawing my code’s architecture. One of the best lessons I’ve learned through several previous projects, is that this step is crucial and very helpful. It has so many advantages, that it’s worth highlighting them:
- It helps you better understand the functionality you are willing to implement, since the design should first and foremost serve the correctness of the algorithm.
- It is the map of your code— this is where you decide about your objects, the correlation between them , shared resources and data structures. Writing (not in an imaginary document in your head) it all down in advance, forces you to think about all of the above, before the actual writing. I usually use draw.io or a simple pen and paper (would love to get recommendations for more good tools).
- It is the safe zone to come back to when things are getting dirty (and things do get dirty when you start writing and/or have due dates) or during the debugging process. It reminds you exactly what you intended on doing. Of course it will undergo modifications along the way, but it’s like the compass of your code.
- It is extremely valuable for collaboration with peers.
Test. Everything.
From my past experience and together with the fact that I’m new with C++, I tested almost everything along the way. It was the first time I’ve worked that way and it proved itself.
One good example would be how I learned that I have to use “new” when creating objects inside functions (which makes them allocated in the heap and not in the function’s stack) in case I wish to use them outside of the function (and of course free this memory afterwards). It was after I exported creation of an object to a separate function from main, even though it was “just” taking out the same lines and creating a function out of it — I tested it and found the fault. I was able to learn this practice thanks to my mentor.
(I will elaborate on when, how, and what, to debug in such projects — in my next post)
One Step at a Time
Once I had a solid skeleton of the code that I was happy with in terms of efficiency, architecture and good enough use of the C++ features, I started with a basic example I had. It resembled the way the algorithm works. I then dove into the first “real” input I had.
The basic example worked almost flawlessly on the first attempt. My thoughts were:
I’ve done everything right, my code is great, everything should work now.
Not. I mean, it did bring me to a really good point, but the first real input worked only partially. I wanted it to work badly. It almost made me entirely drop the architecture I drew, and micro-debugged the code to understand it’s faults with no documentation on new functions and features. Not good.
Thanks to Git, and me reminding myself of the goal of this project, it didn’t take long until I was back to my architecture, the “compass”, trying to figure out what was missing.
At the moment, 2 out of the 3 inputs worked fine, which is very satisfying. These days, I’m working on understanding why the third isn’t.
Gotta go back to my drawings. See you in part 2.
BTW, you can find my code here, so feel free to suggest your input, and help me become a better software engineer.