The Lean Developer

Lean software development is an adaptation of lean manufacturing (derived from the Toyota Production System) to software development. I’ll delve into some of its principles based on my experience.

Luís Soares
CodeX
9 min readAug 13, 2021

--

You probably learned about clean code. But what about lean code? What would make you a lean coder?

The Trial movie is based on Kafka’s The Trial

Waste

Design and engineering aim to create a solution that meets the users’ needs while being economically viable. We’re not just blindly applying technology to see if it fits anyone. Product development is applied technology. Therefore, it’s in everybody’s interest to be lean. One of the lean development tenets is to maximize value while minimizing waste. It’s being economically responsible.

Eliminating waste (the first lean principle) is about maximizing the amount of work not done. This doesn’t mean being lazy or neglecting tasks; it only means doing the minimum necessary to get the job done while keeping options open.

There are multiple levels where you can tackle waste: in the processes, in system design, in technology, and in the product itself. I’ll provide my point of view on each.

Processes

A good starting point is acknowledging that everything that doesn’t put value in users or business hands is overhead (e.g., Scrum ceremonies, Git message rules, and spreadsheets to organize everything). It doesn’t mean it’s all bad. It may need to be done, but you must question it and find ways to reduce it. People often do things because “it’s the way it is” or “it’s our process”. Even experienced members don’t question some things for varied reasons. Here are a few examples:

  • Issue trackers: I heard before: “I won’t do it if it’s not in a ticket”, “You gotta have visibility”, and “Please update your tickets” as if the most relevant goal was keeping the tracker up to date. I don’t work for the tracker or upper-management visibility. I prefer to be trusted that I’m doing my best for the end-users. For example, if you have to do something small, do it and skip the tracker overhead. Tell the team (and timebox it) if necessary. Use a tracker solely as a support tool — that means it supports you; it’s not a blocker.
  • Estimations: Will you deliver story points to the client? Estimation is mostly gambling and waste.
  • Dev. metrics: most product development metrics are useless and only serve developer vanity or to please management. Worse: people will game metrics: “We can’t refactor because we’ll change velocity”, “Let’s not create a bug as it will impact metrics”, “Did you hit X% coverage?”. What matters is making your users happy, and you’ll know if you aren’t doing it; if you aren’t, you might need to work on that first. To be fair, the 4 key metrics (from Accelerate) are the only ones you should care about, but you don’t need to track them automatically; you just know if they aren’t in good shape.
  • Alignment meetings are often masked as collaborative work but are usually just handoffs (a type of waste) or throw-over-the-fence kind of work. For example, alignments between backend and frontend teams or between designers and developers are symptoms that the teams' topologies are wrong; teams should rather be multidisciplinary and empowered to deliver value.

To sum it up, remember that you don’t work for any process, tool, or metric; you work for the client, the business, and, mainly, the users. Everything that you do must somehow bring value to them. Don’t lose track of the reasons why you were hired, why the company exists, and why you are a developer in the first place.

A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away. — Antoine de Saint-Exupéry

System design

Why design a full-fledged system (BDUF) if you only need a tiny bit? Why commit to something so big? The world will change, meanwhile. Your understanding of the domain is continuously reshaped. You should design as you go, or you’re doing a waterfall. Be aware that this is not the same as bad design but rather adaptive design. To learn more, check out “Good Enough” Architecture by Stefan Tilkov.

BDUF stands for Big Design Up Front and is used to indicate that the whole design solution is done before execution. This is typical of traditional models of software development, where there is an explicit phase of Analysis prior to the implementation phase. So in short, BDUF is the art of doing things that shouldn’t be done. BDUF

Here are two examples of future-proofing:

  • It’s very tempting to handle all the errors in web handlers. However, if the user has no way to trigger them due to browser-side validation, don’t waste time gracefully handling them; instead, just return a BAD_REQUEST. Done.
  • Let’s say you’re creating a form to create users. Why add all user-related fields to the database, middleware, and UI? Add only the ones that are needed now. This assumes that you properly sliced the story in the first place. For example, “can create an item with a name” is a vertical slice (user-driven); “create item form”, “create item API”, and “create the database schema for an item” are horizontal (tech-driven) slices. Vertical slicing is the way to go.

Rule of Simplicity: Design for simplicity; add complexity only where you must. The Art of UNIX Programming

Stop writing generic code and focus on your users’ actual concrete problems. Just leave your options open so it’s easy to change as you go. You’ll probably learn things that would invalidate your generic solutions anyway, so it’s better to be pragmatic and do what solves the problem now.

Technology

Simple systems have less downtime, so use the least number of libraries, tools, runtimes, languages, design patterns, scripts, etc.

No matter what, don’t fall for hype/resume-driven development. You don’t have the same problems as Google or Netflix so don’t force the same solutions. Don't force tech. Pick tech based on needs while being frugal. Do it with some process. Choose boring technology. Choose radical simplicity.

I suspect that each new technicality builds up exponential complexity because it will impact and be impacted by everything that was in place. You might say that you isolate and abstract technology but there’s the law of leaky abstractions so you can’t get away with it. Finally, since technology is a detail, avoid tech stories on the board.

Sure, it’s important to balance reducing technology and using what makes sense for the job. However, when in doubt, go for the less-technology option. Why? Because it’s much easier to add stuff than remove. But when you add, you create a dependency on many levels: one more point of failure, one more commitment and buy-in, and more to install, learn, and maintain. More moving parts. It can be a daunting task to remove something that was incorrectly added. It can turn out as painful and long-lasting tech debt. On the other hand, if you postpone the decision, you haven’t created any tech debt in between. You’ll maximize the amount of work not done. You’ve allowed yourself more learning time in between. Maybe YAGNI. Who knows, there are better options now. This corresponds to the third lean principle — deciding in the last responsible moment.

We get it, you’re clever. But the business doesn’t need clever. It needs code that is only as complicated as absolutely necessary to get the job done. Anything else is just wasted effort. Code runs on people

Seniority in Software Engineering

Product

“Extra features” is one of the lean wastes. It’s expensive to do the wrong things, so make sure you build exactly what’s needed and no more. Besides the development and training costs, maintenance costs will pile up forever. Even worse, there are the costs of lost opportunities because you could be building the correct things, but you weren't. That said, focus on the user story: make sure it’s validated, it’s small, and it brings user value. This is the MVP (minimum viable product) mindset applied to features. It can be mapped to the build, measure, learn cycle, one of the principles of the Lean Startup.

Every time you add a feature to your product, everything gets harder. Everything gets slower. And everything gets more expensive, from that point and forever more. As a core business strategy, shared on all levels of the organisation, it is crucial that you are ruthless with the feature set of your application. Why your huge tech team isn’t delivering

If you’re not careful with the scope, your product might suffer from featuritis. This will make your software quite hard to evolve, maintain, and learn, among other chronic problems. “Number of features” is an awful metric. Also, deciding not to have a feature with value can be valid, considering the number of edge cases arising from it.

Complexity probably increases as the square of the features: double the number of features, quadruple the complexity. Provide ten times as many features, multiply the complexity by one hundred. The Design of Everyday Things

the art of product management

Return on investment

For everything I do, from adding a library to fixing an issue, automating something, or creating a new team policy, I always ask, “Is it worth it?”. This is closely related to the return on investment (ROI) or the benefit-cost ratio from economics. It’s as simple as doing it if the benefit divided by the cost is at least 1. Within benefits, consider the added value for the team and the product (that can add up forever). For the cost, consider the fixed costs (e.g., the effort to implement it) and the variable costs (e.g., the impact it will have on the maintenance, the pipeline, and the performance) that will add up forever.

Each piece of design infrastructure added to a system, such as an interface, argument, function, class, or definition, adds complexity, since developers must learn about this element. In order for an element to provide a net gain against complexity, it must eliminate some complexity that would be present in the absence of the design element. A Philosophy of Software Design

Automation

Ensure you calculate the benefit-cost ratio for preexisting policies, processes, technologies, etc. For example, in my previous team, we had a code linter creating annoyance. Why not resort to the power of defaults? You need to challenge that.

There’s also the law of diminishing returns to be aware of. Often, adding more of something gradually produces fewer returns until the point where the costs outweigh the benefits. For example, in usability testing, the returns quickly diminish after five users. As another example, there’s a moment after which adding more tests will turn them into a burden.

Outside-in approach

I like to start by picking a story scenario, building its UI, the API (if there’s one), the business logic, and finally, the data storage. Had I started with the API, I’d probably be doing too much as real needs didn’t guide me. I could over-engineer. I’d make assumptions. APIs are implementation details unless you’re exposing them to the outside.

A top-down approach, coupled with outside-in TDD, is the best way to ensure I only build the exact minimum needed and no more. It helps me control scope creep and over-engineering. It imposes constraints on what should be done in every sense. It moves the focus from the technology to the users. Why is TDD so relevant here? TDD locks you to the minimum, implementation-wise and functionality-wise — you can’t add functionalities you don’t have tests for, and you can’t add tests for unrequested functionalities. This works better on teams that are fully able to deliver value (with skills in product management, UX, frontend, backend, database, etc.).

Begin with the end in mind

To put it bluntly, don’t do anything without an end in sight. If you don’t know the goal, ask why or don’t do it. This applies to user stories, meetings, policies, etc. It allows you to draw a line between the why and the how and think about each separately. Start with the why (never lose track of it); only then worry about the how.

Further reading

--

--

Luís Soares
CodeX

I write about automated testing, Lean, TDD, CI/CD, trunk-based dev., user-centric dev, domain-centric arch, coding good practices,