This probably rings familiar to any developer who has worked on a sizable project:
In the beginning, anything is possible. New features and content is added at a rapid pace. Months pass. The project grows. Developing new features becomes slower. More and more time is spent testing, handling problematic special cases, and fixing interaction problems between different features, content and subsystems.
There are many reasons why development slows down. Two major reasons are increased complexity and accrued technical debt. We want to avoid this slowdown; in fact, a significant portion of our development philosophy can be summarized as “maintain high development velocity”. All the tools in our toolboxes should be evaluated in the eternal struggle against slow and frustrating situations when working on a project.
We have experienced it many times before. Fortunately, we have learned from our mistakes. Here are some guidelines of ours. We rely on these to keep the situation at work painless. Some of these might also apply to your project.
Only do something if it is really worth doing
A given bit of work can be valuable if one of these holds true:
- It introduces new features, content or fix bugs
- It improves processes
- It removes technical debt
- You learn something from doing the work
If it does not add a lot of value compared to the time it will take, perhaps there is something else you could do, that would give more bang for the buck?
Only do something when you know how to do it
… or, when you can formulate a plan for how you will learn how to do it. Often, if something needs to be done, but you don’t know how, tackling a related problem or a part of the problem can both provide value and also help you gain insight into the bigger problem.
Organize your work so that you demonstrate value often
Break down tasks into small bits of work before you begin with hands-on development. This helps both you and others understand how things are going. It is common that the results of a task can only be assessed toward the end; long periods of “in-development” means unmanaged risk, and risks should only be taken when the potential benefits are justified. Avoid long-winded rewrites/restructurings if possible; perhaps you can reorganize such a rewrite/restructurings into several phases, where it is possible to assess the value and work remaining after each phase?
Balance short term vs long term
When faced with a choice of what to do next, or two different ways of reaching the same goal, think both about “how will this affect our velocity in the next week, next month, and next six months?” Sometimes the short-term results are the most important; sometimes you need to focus on the long term.
Use the programming language(s) to your advantage
You should learn the programming languages that you use well. Use the language itself wisely to reduce the complexity of the code.
C# has functions/methods. These allow you to name a set of related operations and define inputs/outputs. Create functions whose names describe both what they do, and what they do not do. If you can find short names, you have probably also defined the functions well.
C# has a rich type system with built-in types, structs and classes. Use the type system to describe the data that you are working with. Create extra types when possible to disambiguate uses; a Vector3 can hold three floats, but its name also conveys that the three floats within form a vector, and nothing else.
C# has block scoping of variables. This allows you to control accessibility and lifetime of data. Use this to make any mistyped variable names or copy/paste errors result in compilation errors.
C# has higher-order functions (LINQ). Use these to describe your problems in terms of data and operations on the dataset, wherever runtime performance is not a serious problem.
Minimize stateful logic
Many programming bugs have to do with mistakes when handling state in the program. Functional programming can be effective in C++. Do the same in C#, wherever the runtime performance is not a serious concern.
Use automated tests
Code that has not been tested is likely wrong. The bigger a project becomes, the more time consuming it is to perform a manual test of newly-written or newly-modified code. Create automated test cases in tandem with writing or modifying code; this can reduce the number of full application test cycles you need to go through, and it also helps you discovering regression problems in the future.
It is easy to write test cases for functional logic. It is easy to write test cases for code where state manipulation is reduced to a minimum.
Automate recurring work
Manual work that needs to be done several times over can be scripted. Manual work that needs to be done on a schedule can be fully automated.
The more you have automated it, the more frequent you can perform the work.
Is it difficult and time consuming to build a release version of the product? Build a release version every day. You will be forced to automate it, and by that point it will no longer be difficult nor time consuming.
The team is usually more important than you alone
What counts in the end is what goes into the product. Who does what is less important – rather, how can you together with your colleagues maximize your results?
Empower others to do their own jobs
Sometimes the best way for you to spend your time is to teach someone else something, or to build something for someone else on the team, or to convince someone else to do this for you. This can remove bottlenecks and increase the overall velocity of the team.
Programmers are in a unique position to not only understand the inner workings of the tools, but they are also able to modify and create new tools. Teaching and improving tooling can improve the overall quality of the product while freeing up programmer time in the long run.