Programming like an investor

by Alexandros Marinos I’ve run a development team for the last three years- and I’ve recently gotten a sense that while I have a fairly crystalised notion of what I like and don’t like in a developer’s approach to programming- I haven’t put it down in words. This is my attempt at doing that- and…

by Alexandros Marinos

I’ve run a development team for the last three years, and I’ve recently gotten a sense that while I have a fairly crystalised notion of what I like and don’t like in a developer’s approach to programming, I haven’t put it down in words. This is my attempt at doing that, and I feel the picture it paints is different enough from most material I read that it’s worth publishing here, if only to have somewhere to point people when I’m asked if we do “agile or scrum”.

If there’s an overarching theme in the way we work at resin.io, it is that instead of trying to reduce the developer to a manageable cog, we deliberately look for developers that can break out of the box and apply their intelligence at different abstraction layers. We are looking for hackers that take responsibility for the value output of their work, not just the time input.

When starting to write code, or in fact create anything at all, your first instinct is to keep working until you make it perfect. After a string of failed attempts at achieving perfection, most realise that there may be a slight flaw in that plan. You hit problems that are too hard to solve, or you need too much time to complete the project, or even if you had infinite coding skill and time, you simply don’t know enough to even describe what perfection would look like.

Perhaps the desire to achieve perfection is in vain, perhaps you should just resign and see programming as just a job, get your kicks elsewhere. Many make that choice. But this is not the path of the craftsman. Perhaps instead of gearing yourself to improve a result, you can turn your energy to improving your process for writing the code. The good news is that you’ve already learned your first lesson: trying to get everything right the first time, effectively going about programming depth-first, doesn’t work for anything non-trivial. Engineering is a constant struggle with tradeoffs, the better programmer being the one that chooses strategically how to trade off various currencies to achieve the best result possible within a resource budget. Otto von Bismarck said that ‘politics is the art of the possible’, perhaps he should have looked closer at his engineers.

Perfection in the art of tradeoffs is not achievable either, but it is an art where the master is many orders of magnitude better than the beginner, and it shows in the results. Looking at an code written by Peter Norvig, it’s striking how much he does with how little code, and how readable that code is. There are many mental heuristics and steps of enlightenment that allow you to produce a better result in a shorter amount of time. And the better you are at this art, the more powerful you are as a programmer, but also as an agent-in-general.

One of the meta-realisations you’ll make along the way is that many times the solution to a programming problem is outside the realm of programming. Many times the solution comes from thinking together with other people. Where the beginner spends inordinate amounts of time programming furiously, trying any possible solution, the master finds the right person, describes the problem concisely, and gets pointed in the right direction without writing a line of code.

Tighten your development cycle

Before we get into the more philosophical and abstract concepts, one thing that will keep paying dividends is to learn your tools well, and have zero tolerance for things that delay your development cycle. Developer time is expensive, and computer time is cheap. If there is a way to have a computer save you time, use them. This is the principle we sell to the users of our software, but I’m surprised by how many developers don’t fully apply that within their own way of work. All the principles discussed below apply to the end result, but many of them also apply to the development cycle itself.

Incremental Development

Given that the problem you want to solve — best result with minimal effort — is a two-dimensional space, you often don’t know how much effort you’re justified to budget if every additional unit of effort improves the result. Whereas the beginner makes a complex plan that needs a considerable amount of time to produce any result at all, the master arranges their work in a way that produces a working result as fast as possible, and then incrementally improves it until the right balance of result vs. time is found. In other words the master tries to have a result to show at any given point of time. Once you master that technique, you can work back and identify large improvements in time to delivery by merging milestones, and only then, knowing what you’re giving up, can you return to large (but not quite as large) monolithic pieces of work.

This naturally leads you to building incrementally as much as possible. If a big new feature or project requires a basic skeleton to even be considered to exist, then build nothing else until that skeleton exists. Don’t optimise and don’t add anything else until the basic skeleton is there. In other words, don’t improve what doesn’t exist. Once it does exist, you will more easily be able to see how to improve it, and how to prioritise the improvements in order of pain they cause to their user.

Feedback loops

Getting something working fast is a special case of the general principle of adding feedback loops. Other instances are ‘eating your own dogfood’ and ‘customer validation’. The goal is to extract as much information as you can from what you have, rather than pushing further to adding more and more, that might even obscure what you want to find out. More code means more commitment to assumptions you’d prefer to validate while they’re still raw and you haven’t committed a ton of work and emotion to.

Feedback loops can also help you maintain your motivation, as seeing a user’s reaction, or even better, having real people use the thing, can truly focus you on what matters. It’s critical here to work with people that will be careful to not pile negative feedback too high, as this can make things worse instead of better. Early feedback leads us to yet another benefit — you quite often can’t understand what you are building until you see it in action. When you do, you’ll have all sorts of new ideas about what to do next and which of your ideas were worth prioritising.

Think like an investor

Prioritising is another word for ranking potential work items by ‘return on investment’. Like any good investor, a developer must consider every potential course of action as an investment option: It requires resources and promises a return in product value. Resources must be spent on the course of action that is estimated to be the most rewarding in terms of the end result. That something is a ‘good idea’ is not enough of a justification to get it actioned. It must stand out from all the other good ideas as the most powerful use of your time.

But of course you often don’t know how long something will take. This unpredictability is a fundamental characteristic of software development and a master knows many tricks to manage it. One is to simply get better at estimating. When you give yourself a feedback loop as to your predictions, you’re bound to get better over time, but this is hard to teach. An advanced form of prediction is to estimate how much time to invest in researching the problem before committing on a solution. In my experience, people (including me) reliably underestimate how many shortcuts research can uncover. Because research doesn’t feel like “productive” work, we have a tendency to cut it short and just start coding. When you think you have researched enough, consider spending some more time and see if you can improve your plan.

Use others’ code

One of the things research often produces, is that someone else has built what you want to build, or has built a key part of it so you can re-use it, often the hardest part. Finding one of those before you invest the time is one of the most pleasurable occasions, as you’ve saved yourself a lot of time, and, if the code you discovered is well-maintained, it’s probably a better result than the one-off solution you would have built. If you make improvements to the code, contributing back is the considerate thing to do, thanking the author for the free work they just gave you.

The less code we have to maintain the better, but if we do have to maintain a piece of code, let’s make the most of it by modularising, re-using, and open sourcing where it makes sense.

It’s more painful when you discover a module after you’ve written your own, and more painful still when someone else in your team discovers it. In both cases, you should simply use it if it’s good. Less code for you to manage, more time to focus on the unique parts of your project.

An advanced form of this skill is to develop a sense for what tasks would likely have been solved by someone else. This gives you impetus to look harder for an existing module, and likely an idea of what keywords to look for. But if this thing doesn’t exist, you’ve now got a chance to become that someone by open-sourcing your solution!

Use others’ knowledge

Even when code isn’t available, knowledge often is. IRC rooms, issue trackers, and communities like StackOverflow are commonly found on a search, and they have good answers there. Even with so many venues, you’ll be surprised how many times people keep banging away at a problem without asking a question. A key problem there is knowing how to ask good questions.

Asking good questions

A good question gives the person who’s trying to help you all the information they need to understand what you’re ultimately trying to do, specifically what your problem is, what other approaches you’ve tried and why you’ve rejected them, and any other details you have. An incredibly interesting observation is that in the process of writing out a question in the sufficient level of detail, many times you will actually solve your problem, just by clearing up your thinking. This is related to another counter-intuitively effective technique, rubber-duck debugging.

Update on new information

As you proceed with trying to complete a task, you also understand the problem better, both in the small and in the large. Perhaps you realise that your current task will take a lot longer than you thought. Or you have enough information to infer that a different approach to the problem would be more beneficial. Unfortunately our brain isn’t wired to integrate information across abstraction levels in this way, but it can be trained. If you’re able to recognise that the new information makes your current course no longer the optimal one, you can be a lot more effective than the alternative, which is to simply continue dumping resources on a course of action that was chosen under different assumptions.

Challenge the requirements

The highest level of strategic coding is one where you understand the requirements and exactly why you need to do something. When that is the case, you can actually rethink the parameters of your current task as you go, due to the improved understanding of the problem space you develop. There is huge resource waste that happens on the line between business goals and technical implementation. If you follow a task without understanding its business significance, you don’t know which requirements are soft, and which are hard, meaning you can’t do a proper return on investment calculation. At the end of the day you will either compromise a hard requirement, or spend a huge amount of resource on a soft requirement. A very common case in my work with external clients is when I’ve been asked to develop something that sounds simple but is actually quite hard, and I can replace that either with repurposing what exists, or by substituting with something that sounds complex but is actually quite simple to implement. Business users are not aware what primitives developers have access to, and which ones have to be built from scratch, even if they sound simple.

Avoid coding at all costs

All in all, the one thing you’ll learn as a developer, is that code is inherently messy. We don’t have the languages that will allow us to express pure thought while being able to evolve a program over time. While we strive for purity and elegance, reality will always find its way in. At the end of the day, the most elegant solution is to avoid coding at all costs, exhausting other possibilities before hitting the code editor.

Find a team that expects you to work this way

If I can write an epilogue, it’s the sad realisation that this way of working is the opposite of the typical enterprise code-monkey. Having written this I realise that someone wanting to work this way will not have a smooth ride in many code shops. While the ideas above fall out of first principles, most companies work by analogy, in particular the factory analogy: The Sysyphean task of reducing the developer to a repeatable, manageable cog. But of course this makes no sense when development by its very nature is the art of continually solving new challenges. Once we know a development task well enough to repeat it blindly, we know enough to automate it, and it therefore stops being a development task.

Resin.io can work in these broad strokes today, but we are not the innovators here. Other teams and companies have adopted similar principles, “flat structure” is particularly trendy these days, and Automattic and Valve are well-known thought leaders in the space. We will also try to contribute to the structures being tried, possibly with some inspiration from Raikoth, and hopefully you’ll see a more concrete form of these ideas here soon.

Any questions? or you’d just like to say hi, come find us on our community chat.


Posted

in

Tags: