Software development is an area, which is always at risk. In our sphere, when one or several risks occur, the delivery time for the working version may change not by the usual and comfortable 10-20%, but by all 150-300%. And we must admit that this is far from the limit.

We can either cross our fingers and hope that success will accompany the project in everything, or admit that, according to statistics, most of the software development projects “fail” and take additional efforts to mitigate possible risks.

My practice shows that customers do not want to work according to the T&M scheme and more often prefer Fixed Price. At a fixed cost, the occurrence of a risk event means an automatic decrease in the project’s profitability: employees receive a salary on a monthly basis, and not for completed projects.

Before Agile and XP, all the responsibility for working with risks was on managers. In agile methodologies, developers are much more involved in the process and share responsibility with managers. However, the principles of XP and Agile are more methodological than technological. I think that it is more efficient to work with risks comprehensively at all levels, including at the lowest level, i.e. while designing and writing code.

Why should a developer think about it if there is a manager?

It’s no secret that if the fail happens, the management will make the only “super-smart” decision: “let’s work overtime on the weekend”

Employees also receive bonuses usually for delivered on time, and not for failed projects.

As well t is much more pleasant to pass the project on time and see the client’s smile than to get rid of the “difficult child” six months later.

From my point of view, a quiet working environment instead of rumors and bonuses is a good motivation to start taking care of this.

Life hack

So, we will consider the main risks in software development.

Schedule Error

Evaluating labor costs were estimated incorrectly.

Methodological Risk Mitigation

The team evaluates the complexity of the task using Planning Poker.

Criticism

In programming, we are faced with a large number of unique tasks. Despite previous experience, you can’t expect all unaccounted details.

Additional mitigation techniques

Proof of Concept Development

Do you have a description of the client API, but it’s not clear how it will behave in a working environment? The easiest prototype will help to test your worst fears. The prototype code you better throw out after. As a bonus, designing a system the second time will probably work better than the first.

If there are concerns about the load, you should conduct load testing at the very beginning on the array of generated data to check the viability of the architecture under load.

Research testing

The method is especially good for working with unknown APIs, inherit code, and new frameworks. Before you start, you only have guesses about someone else’s code. Perhaps you have documentation, but it can simply lie. Writing research tests helps to confirm or refute your guesses.

Clear acceptance criteria for assessing tasks

Quite often, the client and the programmer have a different understanding of the meaning of the word “done”. For the programmer, “ready” – this is the code written, but for the client “ready” – this is all configured, uploaded to server and system. If the team has formal criteria for the readiness of work, then it will be more difficult to ignore them during evaluating.

New requirements or changes to existing ones

In process of work more and more requirements appear or current requirements change.

Methodological Risk Mitigation

Short iterations with fixation of requirements (scrum) or continuous delivery (kanban).

Criticism

While working in small iterations, we are constantly forced to refactor and remodel existing code. The team has requirements only two weeks in advance and doesn’t see the full picture. The price of failure is reduced, but the efficiency also becomes less, because without complete information it is not known how much the “exclusive” architecture of the project will need.

Additional mitigation techniques

Changing requirements is a risk with an almost 100% probability of occurrence, therefore it is worth considering that it has already occurred before the start of the project and put it in the schedule and estimate labor costs. However, it what is most painfully perceived by programmers. It is psychologically difficult to throw out code that worked well, because the business model did not work. Unfortunately, you just have to accept the fact that we will regularly send part of the code to the trash. Moreover, the more old code we throw out, the better.

Lean and the progressive jpeg method

Focus on the most important functionality first. All the thing that can be dispensed with should be implemented at the end of the project. Need to show and hide the panel? For the beginning, a simple hide and show will be enough. Complex animation can be added later.

Weak connectivity and modular design, onion architecture

Fortunately, today there are many ready-made solutions that provide poor connectivity. Sing the mantra S.O.L.I.D, daily at work. Think of interfaces, not implementations. Potentially, any implementation can go to the trash. Make it a rule to use the IOC / DI principle. If you have a lot of JavaScript code, be sure to use RequireJS or one of the frameworks, otherwise you will be list between jQuery, DOM calls, selectors and logic.

Give up the idea of ​​designing the entire application in a single style. Divide it into subsystems. You will have to pay for this with some code duplication, but the ability to throw and rewrite an entire application subsystem from scratch without breaking other parts is much more important.

Evolutionary refactoring with classic top-down design

There are very, very few people who really can be called System Architect. A true architect should participate in three or four really large projects, fail a couple of them, write and throw away a lot of code. If you have such a person – you are lucky. The trouble is that he will not have time to write code. His full-time job will be designing at the level of large blocks and system contexts. Designing, sometimes, entire subsystems will be left to Team Lead’s and senior developers. Some of them will cope with the task, and some will not, so it is important that all modules are independent.

Establish developer-client relationships between related teams, for example, the UI team — the client of the Backend team.

This approach provides the following benefits:

  • You get rid of the sacred idol and are ready to constantly refactor and improve the quality of the code. Some “naughty” pieces may appear, but only within a specific module. The overall level of code quality will be constantly maintained at a high level.
  • It’s easier for you to interact with the client and management, because to explain that this week we have +3 new features is much easier than we have +100500 new classes.
  • You save time, including tine for coordination and integration. I have repeatedly observed such story in three parts:

Part. 1 The UI team doesn’t like the API and everything needs to be done differently and in general the fields are named completely differently.

Part. 2 The backend team believes that the UI team are idiots and don’t understand how the kernel works.

Part. 3 The managers of both teams are trying to do something for work, otherwise they will fire everyone. Final

If communication between the teams is established, then each of them receives valuable feedback. Surely there is a way to modify the file a bit to change the specification on both sides, which will allow to get an architecture that works in every sense of the word.

Really reused code should be taken to the kernel. Each module (subsystem) may depend on the functions of the kernel, but the modules themselves should not know anything about each other. If you want to ensure the interaction of the two modules, use event-oriented programming. Subscribe to events only through the kernel. If you have to duplicate some functionality in two or three modules, this is a signal to include it in the kernel or to be allocated as a separate external dependency. Such organization of the code will allow you to throw out any module or rewrite it from scratch, possibly even using a different programming language.