I wrote my first program around 2006 or thereabout – it was some toy program for the FORTRAN course at school. Ever since then, I have been given the opportunity to take on more challenging programming tasks. It’s been a heck of a journey for me and I am very grateful for the learning, the mentoring from rockstars and the chance to do exciting work.
Looking back, I have learnt a couple of lessons the hard way and wanted to share some of these so that other engineers know what to avoid. These are my rules of thumb currently (in no particular order). Enjoy and let me know your thoughts in the comments!
1. Write plain simple code
Code is meant to be read by computers. Computers and compilers could care less. So if you are feeling super smart then write in 0s and 1s; otherwise, please write extremely simple code.
When exceptions arise necessitating unorthodox or clever approaches; please do add comments and also create documentation. This would help the poor fellow who’s going to maintain it in the future (most likely you).
But why should we avoid clever code? After all, how else does a programmer show off his cool tricks?
if all you have is a hammer, everything looks like a nail – Abraham Maslow, 1966.
The problems associated with smart code are plenty –
- They require a higher level of concentration to understand
- They are prone to bugs since the smart fluff can hide bugs
- They can nearly always be simplified leading to smaller code and bug footprints
Suppose you have a pounding headache and your doctor orders you to buy any non-steroidal anti-inflammatory drug (NSAID). Imagine trying to decipher what NSAID stands for while coping with the raging headache. Not fun eh… What if the doctor told you to buy Panadol (substitute with Tylenol or Ibuprofen etc.)? Do you want to sleep well at night? That’s the similitude of simple vs smart code.
One way to simplify code is by avoiding surprises and establishing consistent patterns. For example, consistent naming patterns, predictable branching styles or standardized design patterns.
Why does this matter? Well it allows someone else to pick up your code, see the structural pattern and then extend it. There is a common language that everyone understands which makes deviations and buggy approaches easy to spot.
But writing dumb plain code is not so easy; so let’s go on.
2. Make it work then clean it up
Most authors do free-flow writing while trying to get their thoughts together; afterwards, the editing and clean up occurs. Why do you think we have book editors? Coding is similar too; rather than rewrite that 3-line function 20 times, why not get the app working and then start refactoring? It’s much faster and code reviews would help catch coding issues you might have missed (just like editors with books).
Typically I just write and get a feature out, then I take a fine sieve to it. All the rules now come into play: DRY, YAGNI, code simplication etc. I do try my best to make it read like poetry (yes I am serious).
3. Never make the same mistake twice
Yes, once is good enough. Twice is bad.
You shouldn’t be shy of making mistakes, no human is infallible but what should scare you is making the same mistake more than once. Do a retrospective after completing each feature to learn what could have been done better and learn from missteps.
I recently took a long amount of time to implement some core software in an unfamiliar area, why? My initial assumptions about existing data structures were wrong and I found out a while later. It wasn’t so difficult to pivot to the right structures and then I ran into another issue – the helper functions I relied on didn’t behave as I expected they would.
Looking back, I could have written tests earlier (instead of later) to validate expected behaviours and also clarified assumptions with senior engineers. Lesson learnt.
A respected senior engineer explained it thus to me:
Junior developers might take a day to realize they are on a wrong path while senior engineers have enough experience to avoid such drains.
Good judgment comes from experience which involves making mistakes and learning. It’s OK to stumble and make mistakes, but please review each one and learn from that.
4. Know where you want to go
How do you get to China? Start heading east. Once you have a target, it is easier to calibrate your efforts and see if you are moving in the right direction. Unfortunately, most software developers never set goals and then get caught up.
The software engineering profession is remarkable for having rapidly changing and moving targets. Currently, the big areas include cloud computing, security, big data, artificial intelligence and AR/VR. Are your skills stagnating or positioning you to shine?
Having a general blanket target (e.g. I want to be a 10X programmer) doesn’t work; there has to be some specificity to it. So for the one who aspires to be a 10X programmer, the next question would be in what areas do you want to excel? Are there examples of such 10X programmers to copy?
Think again; do you want to extend the frontiers of computer science? Design the next Hadoop? Or author a widely popular book / open source software piece.
Know where you want to go and then start walking.
5. Task Breakdown and Estimation
You are to build a brand new calculator app; your manager wants it delivered as fast as possible. How do you estimate the amount of work required and when it’ll be ready for the customer?
How do you eat an elephant? One bite at a time…
The calculator project can be decomposed into modules like the core engine, the user interface and the data storage mechanism for calculation history. Each component should be broken down into smaller chunks. After breakdown, the engineer typically knows the pieces of work that needs to be done before he completes the project and can start implementing each phase.
This quickly becomes tricky when there are multiple people working on the same project and you need to coordinate integration efforts, release dates and project quality.
Always keep in mind the end goal of shipping the software and prioritize tasks accordingly. You want to maximize your return on investment so pick tasks that move you closer to the end goal.
Nope nope nope, testers shouldn’t find simple bugs in your code. You only want them to find bugs that occur when you press 10 buttons simultaneously while doing one-handed push ups standing upside down.
If you don’t find and fix your bugs; your customers will. And they won’t be happy.
Do the right thing; test the happy path first. Then, verify that there are no monsters lurking in the dark corners. Take some time, maybe 30 minutes, to rigourously exercise the code.
I typically write unit tests while completing the feature. Then once it’s ready, every discovered bug gets its own unit test. This helps to protect against regressions in the future. Aside, those unit tests provide active ‘documentation’ of the behaviour of the software.
Don’t compromise on quality – it’s the sign of good software and a great programmer.
Have been writing programs for some time, made mistakes and learnt lots of lessons.
Do share your thoughts in the comments – would love to hear them!
18 thoughts on “10 years of programming: Lessons Learnt”
Good points. The only one which I think is misleading is the point headed “Quality”. Reading the paragraph, it seems that “Quality” is equated with “Testing at the level of program code”. Actually, “Quality” is an attribute of the whole development process, starting somewhere with a customer requirement, then being developed into some sort of design, and then being implemented by programmers, database designers and others.
“Testing” is the process of making sure that EACH LEVEL meets the requirements AT THAT LEVEL:
– Unit testing of individual programs
– Integration testing to ensure that programs and databases perform correctly together (this also validates the design)
– Customer acceptance testing to ensure that the customer is getting what was originally ordered
– And there’s also volume testing needed (high throughput, large database sizes and so on) and security testing where necessary
When all these tests have been done (and various compromises negotiated!), the team will know something about the “quality” of their work.
LikeLiked by 1 person
Yes you are right however I was trying to emphasize the need for developers to pay some more attention to testing code.
All too often, we have the case of handing code over the wall to testers. That shouldn’t be the case.
I do agree with your points though. Thank you for sharing.
I wrote my first programs in about 1982 at school, and started production software engineering about 1988. A lot has happened since then, but the first thing they told me at work still holds: “We try not to say the same thing twice.” This prompts me to say something about point #1, which is not necessarily a disagreement but could be seen as one, depending on style. For me, simple code adheres to that rule: Don’t replicate. I always prefer a template (in C++) over function overloading or other techniques, because I end up with less code and fewer ways for it to vary. Template specialisation, null patterns and a few other techniques deal with exceptional cases and lead to very spare code with few decision paths. It is my experience that many other engineers do not regard this as simple code, however, and prefer to write the same hand-written loop or null-test again and again in overloaded functions, something that makes me steam at the ears, but I seem to be in a minority.
A corollary to the above leads to a comment on point #2, also not necessarily a disagreement but not really agreement: It’s my experience that starting out with a good design will get you to a useful result faster, where trying to hand-code a procedural solution often will not get you there at all. The philosophy of “make it work, then make it good” underlies the methodology of agile programming, which in my mind is a mixed good, or possibly an evil. In embedded systems much of agile method can mostly not be applied at all, as there is no release cycle and there is often regulatory validation to go through before the product can go out. In PC- and web-based consumer software the method is more appropriate, but it has some traps of its own and this is one of them. A good knowledge of design patterns, algorithms and standard libraries will usually ensure that a well-structured solution presents itself out-of-the-box. I find that coding something that “just works” is a false economy compared to a good foundational design, as it is just a messier route to the same destination..
LikeLiked by 1 person
Thanks Elliott and I am amazed to know you have been writing code for nearly 35 years! Amazingly awesome!!
Regarding #1, maybe you could enlighten others to see the benefits of your approach – developers sometimes don’t want to change and need to be convinced that indeed there are better approaches. I do agree that things should not be repeated – it just makes code much more painful to maintain; could also be an indicator of functions that do too much.
I do agree with you that good design is critical to getting software right (I have been burnt a couple of times and am still learning). However, I was referring to style there – sometimes I have the design right and know what I want to do however I lose time fretting over the style (minor issues like trailing commas, brackets style etc). I have found that generally, it’s faster to just get the design implemented and then polish the code. Furthermore too, there are times when it’s all new territory and building prototypes might be faster than theorizing about which of 3 untested approaches is the best.
Excellent points you have raised and I am glad you shared these. Thank you.
I might add a point: If your code contains an assumption, make it an assertion. I would add a corollary that one should always code so that unwanted conditions CANNOT arise. wherever possible. The null pattern is an example of this, as using a reference to a null pattern ensures that a null pointer can never physically exist. However, this is not always possible, so assertions are possibly the most valuable tool in our armoury.
Yes I definitely agree with this one. Assertions make life easier for everyone, they document contract interfaces and make it very easy to trace contract mismatches. One other thing I have learnt is to also gracefully handle scenarios involving assert failures.
For example, in web development, development versions typically have assertions while this is a no-op in production code. Adding a graceful handler ensures that this issue does not cause a bad user experience while still making it possible to catch such issues early in development.
Thank you for your excellent points!
Thanks, all you have said apply to life generally.
LikeLiked by 1 person
1: I hear tell that people who read the code for the Mix assembler (written by Knuth to implement the algorithms in his famous books) said the code was ugly. He was great at algorithms, but coding, not so much.
The surprise thing has been referred to as The principle of least surprise.
2: That’s also why people use code profilers. Don’t waste time on everything, only on the parts where optimization/elegance will actually make a significant difference.
Other: The biggest trap that beginning programmers and managers miss is to clearly specify “what” they want rather than “how” it should be done. What is the goal – in as close to natural language (e.g. English) as possible. If you tell any technician what to do, most often, they’ll do it well – even if it doesn’t solve the actual problem. Don’t start coding until you understand the problem to be solved.
It’s very important to have an overview of where a particular program or system fits in the grand scheme of things. The perfect mouse trap will be useless if all you have are porcupines. Is the product for single or limited use or will it be used thousands of times a minute for years? What can the users be expected to know/understand? What aspects are most important to them and what models do they think in when addressing the issue?
Things like functions/libraries are lovely for isolating system dependencies. Defining them separately and explicitly makes adapting code to changing environments much easier.
Although it may not always be explicitly recognized, things like these are a big part of the reason why ads for programmers (and other skilled professions) often emphasize years of experience rather than grades or expertise. Learning how to think and analyse is usually harder and more time consuming than learning how to construct pieces of software or apparatuses.
LikeLiked by 1 person
Thanks a lot Joseph – I do appreciate your deep insight and the ‘what’ vs ‘how’ analogy.
Your comment is very inspiring and got me deeply thinking. Ideally, I do try to delay coding until I know the problem space well enough however finding out the right problem is a difficult task.
Thanks a lot for sharing.
Phew! 36+ years! I am quite awed to hear this! You must have a lot of interesting experiences over the long period.
Thank you very much for the kind words.
Nice article, i think we share the same approach with respect to writing unit test for every discovered bug to avoid regressions later.