In the past you would develop a software system and as your business grew, you would upgrade it if the computer system running your software was having trouble keeping up; add memory, hard-drives, even processor cores.
We call it scaling vertically, or scaling-up. Hardware companies would tout the scalability of their servers in terms of additional memory and processor slots, but there is always a limit as well as diminishing returns. A single server, no matter how upgraded, is still one machine.
With the advent of cloud computing, a major shift in thinking occurred around software architecture. Suddenly we could turn on any number of servers out on the cloud, but our software systems had to be re-designed to spread the load over multiple servers. Instead of scaling up individual servers, we scale OUT by adding additional servers. In theory, you can scale out horizontally limited only by your budget. The diminishing return problem of vertical scaling also lessened; if your software is designed to distribute work over many computers, each one can be less powerful.
What does this have to do with scaling your development team?
As business grows and the demands for change and stabilization on a software system grow, too often development teams are making the same mistakes we made in software architecture before the cloud. They scale production up by working developers for longer hours, taking short-cuts and delaying features until the team can get around to them. These solutions are not good for business long term, but that’s just the software biz, right?
When you head down this route, your business becomes dependent on the ability of a few key developers to keep running. I know these guys – I’ve been that guy – and they can be found in every organization. They are overclocked, running hot and kept running with some combination of caffeine, bribery, manipulation and emotional blackmail – and they don’t last for more than months like that at best. The consequences of a key developer burning-out can be massive. I’ve consulted with many businesses trying to pick up the pieces after the exit of a key developer, and it is never pretty. Much like a server crash without backups.
The solution is obvious, hire some help! But, a common problem is only the developers who have been working on the software system since the “kitchen table days” can read the code. All the postponed work and short-cuts – “technical debt” – has built-up and now your code is a mess. Only senior developers and consultants with years of experience untangling spaghetti code like this can make any kind of meaningful improvement to your software in a reasonable time-frame… and we’re not cheap.
It comes down to this: you will eventually need to scale your team out, and there is far more demand than supply for good developers in every market. So, just like with software architecture, you need a software system that was designed to be horizontally scalable so you can add more, smaller servers AND so you can add more, possibly less-experienced developers.
How do you prepare for horizontally scaling your development team?
There are many steps you can take to prepare your code base for new developers of all skill levels. Most of it comes back to the basics, low coupling, high cohesion, unit testing and adherence to object-oriented design principles such as the SOLID principles, but here are some tips that might not be obvious:
Use abstraction to reduce Required Reading.
When coming into a new system and adding functionality, a developer needs to understand the code their new code will interact with. This includes interfaces they are consuming or implementing, both internal to the project and external third parties. If your system is highly coupled with poor abstractions, just understanding a basic module can quickly spider-out into a reading of the entire code base.
Think of abstractions as summaries to free readers from having to dig into the implementations. At every opportunity you should be defining interfaces such that you can say, “This is what these kinds of things do, don’t worry too much about how they do it.”
Finish your refactors to eliminate History Lessons.
Software systems change over time. All too often developers refactor some aspect of their code base to the point of achieving the desired change and getting tests to pass but stop short of going back through the code and making it readable.
If you’re finding yourself explaining that the property called “UserId” actually refers to a customer account ID now and that back in 2012 these were one concept, but then Bob split them… Bob didn’t finish his refactor and now the invalidated assumptions of the ancient past are tripping up the developers of the present.
Design for extension via new code.
Your system should be designed from the beginning so that likely changes and points of extension can be accomplished via new implementations of well-defined abstractions (see also Open-closed principle, the O in SOLID). More than that, you need to set expectations for implementations via documentation on the interface by comments and name choices.
A great way to document this is to write unit tests against an abstraction that establish the expected minimal behavior (see also Liskov Substitutability Principle, the L in SOLID).
Code like everyone else is an idiot.
And, be cool about it when the idiot is you three months later. Imagine a world where if your system compiles, you can be about 70% sure it works (sorry duck-typed languages). This is an excellent boon for scaling a development team and an important consideration when choosing a programming language.
If you need a reference to something, require it on the constructor or at least check that it is set and throw as soon as you can if it is not; do not assume anyone knows what they are doing! Use the least-capable yet valid abstractions for arguments and properties – use IEnumerable! And finally, write code documentation that will appear in your IDE of choice with code completion.
How do I bring a new developer “on-line”?
When the time comes to initiate a new developer, it’s like teaching a class. With a solid understanding of the material, you need to have confidence that the material the student will encounter on their own will be consistent with what you’re teaching and a lesson plan that builds up their knowledge bit by bit until they can see the big picture.
Assignments to add code.
If you’ve built for extension instead of modification then your assignments should often be of the form, “Make a new implementation of abstraction ‘A’ that does ‘B’. You should review modules ‘P’ and ‘Q’ as they will help you accomplish ‘B’, and you might also review implementation ‘X’ and ‘Y’ which is similar to what you will be doing.” Then point them at the abstraction-level tests their implementation will need to pass and work out a point-list of the high-level unit tests their implementation should pass to be considered complete. Let them work out the “how”.
Prepare your lesson plan.
The initial forays into a new software system can be overwhelming and if things don’t go as expected, it can confuse and slow down a new developer’s progress. Before you make an assignment, look ahead. Make sure the system jives with what you are about to tell them. It’s your last chance to clean up after old unfinished refactors to avoid giving confusing history lessons.
Give a brief briefing.
Initially there is need for a long conversation about what your team does, what the system does and the general 30,000ft view of system architecture. However when it comes time to turn a developer loose within a system, keep the lectures short. For most developers, they need to figure things out on their own; you cannot talk it into their heads. Just make sure you cover the nomenclature they need to understand, and make the required reading list of code they should review before designing. That said, review any needed language features, frameworks or third-party libraries they will need to ensure they are up-to-speed on them, and if not, add it to the required reading list.
Have them Check-in.
Now they run off, read up, maybe play with the code to learn via the code completion feature of their IDE, and now they think they know what they are doing and are anxious to write some code. Stop. Have them come back and tell you what they are going to do. This is where you catch if they are way off. If they are only a little off, maybe pursuing a sub-optimal, but more involved solution, just keep that to yourself. You can catch it in review and this is how we learn.
Once the assignment is done, read through their code with them. Ask questions about the places where they interacted with the system to ensure they understand what they did and did not simply cut and paste from elsewhere. Also focus on sub-optimal approaches they may have taken – perhaps because they were ignorant of something in the system you chose to hold back – and have them do a refactor. This is also a checkpoint to ensure that they doing their part to make things easier for the next guy.
Anyone can do this!
The personality of the individuals play a lot into how well a team can grow. As an extrovert, it has been easier for me, but I’ve had the opportunity to mentor brilliant introverts who are often uncomfortable with the leadership and mentorship positions they eventually earn (despite their own protests). If you take it one newbie at a time, it’s less overwhelming, and if you look at this more as an engineering scalability problem then regardless of your personality type, you can work through the steps to scale-out your team and save yourself a lot of needless face-time down the road.
BlogSlayer is the official blog of FrogSlayer, a custom software product development shop in Bryan/College Station, Texas. Our specialty is getting your product to market in 90 days or less. If you would like a free consultation for your project or big idea, email us at firstname.lastname@example.org. You can also connect with us on Twitter, Facebook, or LinkedIn.