Software has the tendency to devolve into disorder over time – Also known as "software rot".
From my own experience, over time, and through the combination of new feature requests and bug fixes, applications slowly become such a bloated tangled mess that of code that one inevitably wants to just burn them down and start again. Couple this with changes in technology and design trends and the desire is only magnified.
The first problem can be partially mitigated through better upfront planning and better documentation. It's been a hard lesson learned, but spending the extra few minutes on good documentation pays off over the long-term. No matter how self-explanatory the code seems at the time of writing it, when you revisit it a year later (or even a month later!), it isn't always clear what it does, why it was added, or how it integrates into the larger project scope.
To the second problem, it's all too easy to avoid improving something that already works. Technology doesn't change overnight, but it does over years. Often you can see problems such as outdated libraries miles down the road problems. If you don't spend the time to fix them now – when they don't need fixing - you'll eventually have to fix them when they become an emergency. And the problem with emergencies is, as a rule, they always happen at the worst possible times.
As a policy, I try do make small fixes and cleanups to code any time I open a file. It just takes minutes a day, but those minutes add up and keep your code up-to-date and constantly improving.
Perfect is the enemy of the good.
There is a diminishing return on time invested. Sometimes (usually) it's better to get something out the door rather than to make it 'perfect'. It's not even clear what perfect is. Minimum viable products, and 'failing fast', is the idea that it's better to get a product out there, and improve upon it based on real user feedback, rather than spending time developing features users may not want at all.
The internet moves fast. 20 years experience means 3 years of relevant experience and 17 years of experience in outdated technologies and practices.
Being a software developer means constantly learning new skills and abandoning old technologies. That's not to say that the old knowledge is without value, however. Skills such as problem-solving and even the ability to learn new skills will aid you in your path of constant growth.
This section contained helpful suggestions on ways to foster the learning habit such as setting measurable goals for reading books or learning new languages (such as OCaml!).
Additionally, there's the often overlooked and undervalued knowledge investment of 'who you know'. In an age where all the information you need is at your fingertips, it's even more important to create processes for investing in relationships – it's all too easy to stay head-down in the code and not make the social investments
Know your audience and know what you want to say.
Jargon can be an efficient way of summarizing complex ideas into a single word, but it's not efficient if it's not understood by the listener. Additionally, in all forms of communication (emails, meetings, technical documentation, etc...) organized, well-structured ideas are far more efficient and persuasive than sprawling streams of consciousness.
I've always thought of DRY (Don't repeat yourself) in terms of being an efficiency model – If you're going to do something more than once, you should automate it to save time. DRY has values beyond time savings, notably that without it, changes have to be made in multiple locations, so we should be mindful of the ways duplication sneaks in.
One practice I'm guilty of is verbose code documentation (something I've considered a positive but which can be a form of duplication). The risk of over-documenting code is that when the code is updated, the documentation may fall out of sync. Good code should document itself.
The second type of duplication I often succumb to is when I want to make a modified version of a function that already exists. Under the guise of expediency, I just copy the old function and change a line or two to add the functionality I'm looking for. This creates a lot of duplication and, potentially, the need two find and update two instances of code when a future modification is needed.
In a system, components should be independent. Non-orthogonality can be found in organizations where people have overlapping responsibilities, or in code where components rely on other components to work.
Two areas in my work where I should be mindful of non-orthogonality are the expectation of dependencies being present, and of the reliance on a global state.
Ideally, components should be stand-alone pieces of code. This allows them to be tested independently and to be more reusable. If a component relies on state, it should be set internally.
A very clear sign that code is non-orthogonal is when modifying one component requires making change across many other components. This can be seen if fixing a bug requires working across many files.
Decoupling systems makes it easier to make changes in the future. An example that comes to mind is data handling. If an application relies on data to be formatted a certain way and that data structure changes, every component would have to be rewritten. Separating data handling into a dedicated component or interfacing with an API rather than directly with a database would allow you to only make changes in one place if the data source was to change.
Tracer-code is a means of progressive development. By having non-dependant components, very early on you can string together an application using real components and then build each one out in time to handle more complex cases. This approach allows you to user-test early and frequently without having to write disposable code (or at least not too much).
Tracer-code is a better solution than prototyping for its ability to be continually built upon, and for when discrete aspects of an app need to be tested in tandem, such as UI and data output when dummy data just won't do.
Prototypes are quick to develop and change. They can be UI mockups in image editors, whiteboard planning of architecture, quick and dirty code, etc. They allow for rapid planning and testing but lack detail.
One caveat to prototypes is that it should be made clear that these are disposable. Don't build from your prototypes or you'll have a weak foundation.
Coding in a domain's language means using coding in plain language that can be understood by end-users. This may require parser handlers to translate terms into language-specific commands, but being able to understand the code makes for easier development and maintenance.
Consider config files, it's far easier to define a program in terms of natural language rather than obscure parameter names. Another example of the benefit of having code in a natural language is making error messages easier to read.
- Clearly define the scope
- Don't be too specific with your estimate
- Ask someone who's done a similar project
- Break the project into smaller components and estimate each of those
If you develop incrementally, you can track how long one feature takes to develop and continually readjust from there.
The only right answer when asked how long something will take is "I'll get back to you".
When backed against the wall, my rule of thumb is to take everything I know, make the most informed estimate I can, then double that number.