Over the 15+ years of my development career, I have learned several things that significantly increase my effectiveness. In this post, I share those learnings with you.
Structure:
- Foundational Advice -- Important context and motivation for what follows
- Technical Advice -- The main course
- Recommended Reading -- Links to high-quality books and blogs that are great for getting started
This blog post mentions and links valuable concepts that you can explore further as you see fit.
Foundational Advice for Juniors
1. Code is not the Point
As developers, we like writing code. Most of us want to be given a nice unambiguous task. A fun technical puzzle to solve without paying attention to the rest of the world.
Put reasonable effort into making sure that you are solving the right problem. To quote Peter Drucker: There is nothing so useless as doing efficiently that which should not be done at all. Gather feedback early and often, typically by continuous delivery to real users. Be Agile.
Software development is expensive, with the vast majority of the effort of real-world projects typically going into maintenance. Combine this with the goal being user/business outcomes, the best code is often no code. To quote Bill Gates: "Measuring programming progress by lines of code is like measuring aircraft building progress by weight."
See also: YAGNI, KISS, The Last Responsible Moment.
2. Software Design Matters
During the first 5 years of my development career, I thought that software design is for software architects or other people with special roles. I was focused on "getting things done", and saw software design and practices such as writing tests, as a distraction at best. My code worked, and I was getting a lot of things done. Or so I thought.
Then I read Clean Code, by Robert C. Martin. This book motivates caring about software design and contains examples and many technical heuristics. The central takeaway of the book is: "The only way to go fast is to go well". In other words, if you make a mess, it will slow you down. See also: TradableQualityHypothesis, DesignStaminaHypothesis
Learning how to write well-designed clean code of course takes time and effort. And when you start, you will be slower and make mistakes. Simple is not Easy.
3. Use the BEST Practices
Writing tests tends to be beneficial. There are exceptions, but most of the time, it makes a lot of sense to write automated tests. Writing tests is an example of a best practice.
If you are new to writing tests, just follow the best practice and write tests for everything. When starting, blindly following the best practice will be better than following your own underdeveloped judgment. Over time you will learn how to write tests effectively, and be able to tell the difference between you have messed up, and situations where writing a test is not worth it. You will also start to understand the value tests bring on a more visceral level, by having experienced the decrease in debugging sessions and the worry-free refactoring enabled by your tests. After developing your judgment, you will be able to transcend the best practice.
This advice applies to best practices in any area that you are a junior in. Automated tests are just an example.
One big gotcha is that it is not easy to tell the difference between a sensible best practice and something nonsensical or even counterproductive. This is made more complicated by most existing code being a mess, and by most developers, including "experienced" and "senior" ones, not knowing software design basics. This makes having a good mentor extremely valuable. Barring that, one piece of advice based on my own experiences is to be wary of best practices specific to the community of your language or framework. Look for evergreen advice that has been around for decades.
Technical Advice for Juniors
Our focus will be on technical topics. Many other areas are important, such as health, happiness, career, and soft skills. Knowing how to avoid a technical pitfall won't help you if you are sleep deprived and working on the wrong problem for a toxic boss that underpays you.
4. Write Tests
Write automated tests. Perhaps write tests before the code, such as via Test Driven Development (TDD). This makes it easy to verify your code is correct in a repeatable manner, thus saving you from much manual retresting and from debugging sessions.
You think test-first is difficult? Try debug-after.
Perhaps even more importantly, tests give you the safety net to refactor your code. And continuous refactoring is needed to keep your code clean. Without a reliable test suite, it is all the more likely that your code will rot.
Writing tests is difficult if the design of your code is poor, such as when using inheritance for code reuse, or when using static functions. If on the other hand, you have SOLID classes, with no global dependencies, then writing nice tests is not so difficult.
Test design matters because poorly written tests will slow you down. Avoid binding your tests to implementation details of the code under test or to the structure of the system. Avoid overusing Mocks and write better Test Doubles.
5. Do Not Use Inheritance For Code Reuse
This is one of those best practices that bring to mind the "Use the Best Practices" section. My advice: do not use inheritance for code reuse at all when you are starting out. It is rarely the right call and can do a lot of harm. Favor composition over inheritance.
6. Write Object-Oriented code
Write SOLID code that is not STUPID. There is so much value in understanding these principles and anti-patterns.
Actually create objects. Classes with only static methods are not OO. Try to avoid using static code altogether.
See also: my defense of SOLID.
7. Write Functional Code
(Functional programming is not to be confused with imperative structural programming.)
This point is not about fully switching to a functional language. You can benefit from using a functional style in your OO language. Minimize state, especially mutable state, and do one thing in your functions. See also: functional core, imperative shell.
8. Use Informed Duplication
Copy-pasting big chunks of code to multiple places is almost always unwise. Any self-respecting developer soon learns this and starts to follow some form of Don't Repeat Yourself (DRY). Unfortunately, the well-intended pursuit of DRY often leads to overengineering and accidental complexity. This is where the counterpart of DRY comes in: Write Everything Twice (WET). The idea behind WET is to only deduplicate on the third occurrence of duplication.
For a more in-depth look at the costs and benefits of deduplication, see The Fallacy of DRY.
9. Types, Names and Comments
Try to write self-documenting code and avoid comments.
Every time you write a comment, you should grimace and feel the failure of your ability of expression. -- Robert C. Martin
Comments are dangerous because they can lie. The code can change without the comment being updated. New code can be added right under the comment. The comment might have been wrong or inaccurate in the first place. When this happens, the comment not only becomes useless, it becomes misleading.
To write self-documenting code:
-
Do one thing in your functions
- By doing a single thing in a function, you can give it a clear name
- Feel the need to explain what different sections of your function do by adding comments? Instead, extract each section into its own well-named function.
- "Extract till you drop": if you can meaningfully extract a function, then you probably should. Don't be afraid of small functions.
- Command Query Separation
- Similar to the Single Responsibility Principle for classes (The S in SOLID)
- Minimize state
- Use types. Combined with a test suite that executes the code, you can rely on the types telling the truth.
- Avoid mixed types. Avoid parameters or return values that can be an integer, a boolean, or a string. This naturally happens if you write focused functions that only do one thing.
- Write tests. A well-written and comprehensive test suite shows you how the production code can be used, and how it behaves.
Clean Code by Robert C. Martin has some good rules of thumb about naming and comments.
Recommended Reading for Juniors
Books
- Clean Code, 2007 book by Robert C. Martin
- Apprenticeship Patterns: Guidance for the Aspiring Software Craftsman, 2009 book
- Working Effectively with Legacy Code, 2004 book by Michael Feathers
- Refactoring: Improving the Design of Existing Code, 1999 book
- The Software Craftsman, 2014 book
Blogs
- MartinFowler.com - Tons of high-quality articles about all things software development
- EntropyWins.wtf - Clearly the best blog on the internet :)
See also: Recommended Reading for Developers by Jeff Atwood
Bonus links
- Tell Don't Ask -- Encapsulation best practice
- Law of Demeter -- Coupling best practice
- Domain Driven Design -- A sizeable toolbox. More advanced, good to first learn the basics.
- Object Calisthenics -- Rules that restrict what you can do in programming. Nice for learning how to do things differently
- Pair Programming -- A great way to learn from each other
- Code katas -- Simple programming, great for practicing a specific technique or skill such as Test Driven Development
About Jeroen
Jeroen De Dauw is CEO of Professional Wiki, which provides wiki hosting services. He occasionally writes on his software design blog. You can follow Jeroen on Twitter.
Top comments (51)
Awesome post. I've actually read the Clean Architecture book in the last month and I would highly recommend it. It significantly changed the way I think about software and taught me how to take decisions carefully.
Ok not in which ways?
What were you see we the most impactful aspects for you personally?
I'm curious about the interaction between 6 and 7.
6 - OO && don't use static
7 - Functional
In my experience, writing functionally implies lots of static functions (as this is how you declare a "pure" function (one without access to state) in OO languages).
Is 6 more around static objects? e.g. this would mean an object (and its relating data) are global
The "avoiding static" in point 6 is about not writing static functions with side effects or usage of global state. You are absolutely right that pure functions are not a problem.
Nice article! I always recommend Working effectively with legacy code, too. It's one of the best and most applicable books I've read.
What I think what should be added is to structure your code with "package by feature".
This changed a lot for me and I use the same concept now for structuring documentation.
It's much easier to see strange dependencies and to reduce/restrict/avoid them.
Much easier to navigate and find classes and much easier to grasp the code as the classes are much more cohesive (on package/folder level at least).
great post !
Great content...
Many topics, rich resource gathering!
Really nice article, congrats!
Am happy this appeared on my feeds.
This is a lot and this is awesome
Thanks so much
Really really good post.
This post really means a lot to me, thank you so much
Thank you for the resources! I'll take it to heart as a new dev.
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more