Graceful Degradation for low value code, tricky YAGNI

The business value of the developed application is not evenly distributed throughout the code base. This statement is true for the amount of development effort. Until recent time,  IE6 support was an example of a huge waste of resources on code with potentially low value.

We can’t eliminate the cost of writing code with a low value indicator, but we can reduce them: do not write unit tests, do not refactor, score on the quality of the code in certain parts of the system. For this we will have to give our support in the future. Unless of course it is this piece of the system that will really need to be supported and developed in the future.

And if not? And if not, then we are well done and were able to save.

Not a single person in the world can reliably know which part of the system will remain unchanged, and what will have to be rewritten from scratch or thrown away altogether. However, there is a possibility of a change in one or another requirement. For example, the processing of financial transactions with a high probability will not change over the long term. In addition, the cost of error in this part of the software is extremely high. In this part of the application there is a place for DDD, TDD, BDD and any other cool * DD that help you in solving problems.

But if the company decided to arrange a promotion and you were asked to make a landing page for your online bank – this is a completely different story. Most likely, it’s easier to outsource this functionality and make a page in PHP. Fence off such a code using the Facade pattern. Keep the system core clean.

Sending too much effort to the frequently-modified part of the code base is unprofitable.

Application update mechanism

If you are developing a desktop application, take care of your users and provide an auto-update mechanism from the Internet. For win exists, for example, ClickOnce.

Change of employees (Bus Factor)

Key employees can leave the company, go on vacation, become pregnant or even die, taking with them important knowledge both from the subject area and about the application code.

Methodological risk mitigation

Joint code ownership, pair programming, code review.

Additional mitigation methods

Use well-known design patterns, name classes in an obvious way (Repository, Specification, DAL, DTO, ValueObject, Entity). It will not be superfluous to leave links to resources on the network directly in the code, if some concept is not too well known, and the material successfully reveals it.

Use the Contentions Over Configurations principle, functional programming elements (especially for working with collections) and Side-effect free programming, if your platform allows.

Minimize the number of “especially made” code parts. If they appear, add a comment explaining the presence of a non-obvious code and why such an implementation was chosen. If you still really need, put it in open-source or use a separate product. Let someone support the new technology.

For complex subject areas, it is highly recommended to use DDD concepts, especially Ubiquitous Language. This will help new employees quickly “diveinto” the project. It is much easier to understand the code if it looks like a natural language from the specification that you just learned.

Use annotations / attributes to help intellisense and code analyzers.

Use standard application deployment methods as much as possible. Modern frameworks come with a database migration mechanism. If the configuration is very complicated, it makes sense to write a deployment script. For severe cases, it may be advisable to use things like vagrant.

Simplify the development configuration as much as possible. If you use Memcached to speed up the application, consider implementing a MemcachedDummy that will work without a server installed and will always return a cache miss. The faster a new developer can deploy a working, if not entirely, version, the better.

Try to use generally accepted methods for specifying and describing bugs: user stories, use cases, UML, expected / actual behavior, fix the basic business rules of the system in unit tests. Use style guides for UI and server code.

Create support tests for at least basic business rules. Use bdd notation in test names to make their purpose obvious.

Decomposition specification

The specification is incomplete and / or contains conflicting requirements

Methodological risk mitigation

Using the SpecByExample methodology, ability to exclude low business value requirements from the sprint plan, formal consistency check before starting work.

Criticism 

Typically, risk is paired with a “change in requirements.” If the first has come, then the second comes almost automatically. For large systems, the formal task of checking the consistency of requirements is at least nontrivial.

Additional mitigation methods

If a nonsense is written in task or another change request is crazy, then it is best to raise this issue at an early stage and make corrections. Until a decision is made, you just need to postpone this task and take the next most important one from the backlog.

If you do not have enough materials to get started and you know for sure that they will not appear by the deadline, you do not need to play Counter Strike. Try to start working without the necessary materials. No graphics? Use squares and circles. No visual design, but prototypes? Take Bootstrap. Even slow motion is better than a complete stop.

Having tests for business logic helps to mitigate this risk as well: you will learn about a conflict of requirements from a dropped test, and not from a floating bug on production.

Technological risks

Will the technology stack satisfy the task or no? Do you have to change the programming language, database management system due to load or lack of interoperability? Will the chosen “architecture” / framework fit, won’t they become too expensive to support? How bad is someone else’s legacy code?

Attenuation method

Use the possibility of horizontal scaling at an early stage, Provide Persistence Ignorance.

Use the Data Mapper or determine the place where it can appear during the refactoring process – this will help to flexibly change the data source if necessary. Modern mappers are equipped with Assert’s, which will help you remember to map all the fields correctly, regardless of the data source.

Follow the Low Coupling – High Cohesion principle during designing. Clearly define the responsibilities of each class, do not allow the appearance of God Objects.

Prefer QueryObject to Repository. QueryObject has no drawbacks compared to the Repository, but there is an advantage – the ability to quickly move from a monolithic architecture to a distributed to ensure full horizontal scaling.

Use defensve programming to protect invariants (an invariant is a consistent internal state of an object). Protective structures should be placed in the constructor, then the post-conditions will not have to be checked, since the result of the function will also be an object protected in its constructor.

Use the Composite and Specification patterns to organize code and break complex sections into smaller and simpler ones. Use the Producer-Consumer pattern to organize multi-threaded applications, if possible.

Prefer Rich Domain Model over Anemic. Even when using ORM in most cases, you can transfer Entity to another layer without resorting to creating a DTO. Responsibility for creating the DTO should be assigned to the Data Mapper.

Provide at least minimal application self-healing and self-diagnosis systems: installation, filling out the necessary directories, creating configuration files if they were deleted, restarting the service / daemon in case of an error.

Low productivity

The intensity of work is directly proportional to the proximity of the deadline. While the delivery dates are far, there is a temptation to procrastination.

Methodological risk mitigation 

Short iterations, stand-up rallies, practice demonstrations: after each iteration, the team presents the work done to the client’s representative or internal Product Owner.

Additional mitigation methods 

Investments of times at the beginning of the project in infrastructure and meta-programming. If your project has many forms, think about the form generator, instead of riveting the same type of View-files. If you need CRUD functionality for a large number of entities, one well-designed controller with all the virtual methods will help to get rid of writing tons of routine code. Virtual methods will allow you to override the behavior, if necessary.

Performance problems with meta-programming (especially reflection) can be solved by dynamic compilation or code generators.

The less code, especially of the same type of routine, you write, the easier it will be to support. In addition, the routine code is the nastiest. For the client it doesn’t matter, you were interested in the process of solving his problems or bored. You, as a developer, can set yourself the task of optimizing your routine work and solving problems, which appear.