Clean Engineering

Clean Engineering -- it is a set of rules applied to code/design in general on Engineering level.

The Business Always Wins

Основное правило:

  • The Business Always Wins

  • Business requirements always diverges

Как бы вы не старались справиться с требованиями, решить все задачи на проекте, бизнес подкинет вам еще задач. Требования всегда меняются, расходятся, не согласуются.

  • But if we plan for 100 things, Business will always come up with the 101st thing we never thought of.

  • If we solve 1,000 problems, they will come back with 10,000 problems.

Prefer Isolating Actions than Combining

Делая каждую фичу на проекте, программисты стараются сделать её как можно более универсальной. Они выносят много кода в общую логику, оставляя для каждой фичи только каркас и специфичное поведение. We try to group and generalise logic as much as possible.

The wrong reusability:

Instead we have to write code in isolated manner, reuse just small piece of truly reusable functionality. Isolated code is better to delete (see next #). Shared logic and abstractions tend to stabilize over time in natural systems.

Practices:

  • Try to vertically split user functionality (by flows/features) before splitting horizontally.

  • Code smell: Pick one external-facing action (Endpoint/Page/Job/etc) in your codebase. How many context switches does someone need to understand what’s going on?

Duplication is better than wrong abstraction

Programmers tend to create generic solutions, but this is bad practice of doing wrong things. Instead of trying to solve the business problem, we waste our time trying to find the perfect abstractions. Doing things generic to early often means poor understand of domain model. Reusability or duplication is better than generalization.

  • Duplication is essential for isolation.

  • Carefully reuse code

  • If you decide to create abstraction prefer principle of least abstraction

  • Shared abstractions across services sometimes leads to Microservices ending up as a Distributed Monolith.

Wrappers are an exception, not the norm

Sometimes we also mix up business logic inside wrappers, making it neither a good wrapper nor a good business solution, but some kind of gluey layer in between. Creating an “agnostic” wrapper isn't considered good anymore.

“Swap out library” comes from a mindset of “Configurability” which is covered in detail in the “-ity” section later.

  • Don’t wrap good libraries for the sake of wrapping.

  • You can wrap some solution on top of existing library

Always take a step back and look at the macro picture

Blindly applying Quality concepts (like changing all variables to “private final”, writing an interface for all classes, etc) is NOT going to make code magically better. In the micro-level each class follows SOLID principles, uses all sorts of great Design patterns (factory, builder, strategy, etc) and coding techniques (generics, enums, etc).

It gets high Code quality ratings from CQM tools. But tool can't say that that are testing right thing. A benchmark tool can track performance, but can’t tell whether stuff runs parallel or sequential.

  • Only a Human has to look at the big picture.

  • Concepts need shift in Mindset. Cannot be applied blindly like tools.

  • Learn a different language and try the other mindset of doing things. That makes a fundamentally better developer.

Do not adopt techniques only for adoption

Discovered Generics. Now even a simple HelloWorldPrinter becomes HelloWorldPrinter<String,Writer>. Don’t use Generics when its obvious that a problem handles only specific data types, or when normal type signatures are enough.

Discovered Strategy Pattern. Every “if” condition is now a strategy.

Discovered how to write a DSL. Gonna use DSLs everywhere.

*-ty / Optional Requirements

Configurability, Security, Scalability, Maintainability, Extensibility -- this all is non-functional requirements that create big, often unnecessary challenge. Do not fall in X-ty implementation without clear goal.

  • Don’t let -ities go unchallenged. Clearly define and evaluate the Scenario/Story/Need/Usage.

  • Ask a simple question — “What’s an example story/scenario?” — and then dig deep on that scenario. This exposes flaws in most -ities.

Reuse. Fork. Contribute. Reconsider.

Every your library becomes a legacy in few years. Do not reinvent the wheel. Do not create frameworks/DSL and others.

  • It takes a lot of skills and deep understanding of the problem domain.

  • There is a constant effort required to keeping this going.

  • If you open source it, nobody cares.

Refactoring is part of each and every story. No code is untouchable

Once something is implemented in a certain way, everyone implicitly starts building on top of it. Nobody questions the status quo. Working code is considered “the right way”. Even in cases where it was never intended, people go all the way around to even slightly fit into what’s existing.

How teams iterate vs How they should, Every single day:

  • No code is untouchable

  • Умение писать код без оглядки на ранее написанные строки серьезно облегчает процесс экспериментирования с новыми идеями.

Write code that is easy to delete, not easy to extend

Every line of code is written without reason, maintained out of weakness, and deleted by chance. To avoid paying for a lot of code, we build reusable software. The problem with code re-use is that it gets in the way of changing your mind later on.

  • Every line of code written comes at a price: maintenance.

  • Instead of building re-usable software, we should try to build disposable software.

  • Avoid creating dependencies

  • Split your code: isolate the hard-to-write and the likely-to-change parts from the rest of the code, and each other.

  • Все должно создаваться сверху вниз, за исключением первого раза

  • Быстрее будет слепить десять разных комков грязи и посмотреть, что из этого получится, нежели пытаться довести до блеска одну кучу дерьма.

  • Удалять код целиком проще, чем делать это по частям.

One module for one problem

Несмотря на принцип одной ответственности, который гласит, что «каждый модуль должен решать только одну сложную проблему», на деле гораздо важнее, чтобы «решением каждой сложной проблемы занимался только один модуль».

Handle errors on the ends

Обработка ошибок и восстановление лучше всего выполнять на внешних уровнях вашей кодовой базы. По-другому это называется принципом взаимодействий двух оконечностей. Он гласит, что обрабатывать ошибки легче на двух дальних концах связующей среды, нежели где-либо в ее середине. Это связано с тем, что даже если работа над ошибкой происходит где-то посередине, вам, так или иначе, придется делать проверку и на пограничных уровнях. Если каждому верхнему уровню все равно придется обрабатывать ошибки, то зачем же тогда делать это еще и где-то внутри программы?

Legacy code

Удивительно, но необязательно в проекте должен быть legacy-код. Legacy-код, как грязь, появляется в проекте из за пренебрежения правилами гигиены. На эту тему есть книга «Working Effectively with Legacy Code», где описывается важность тестирования для поддержания кода в актуальном состоянии. Но одних тестов недостаточно. Нужно что-бы код постоянно "чистился" (clean code).

Чистый код -- это противоположность legacy. Legacy даже если и работает, то стоимость его поддержи высока. Чистый код можно с легкостью отлаживать, исправлять, рефакторить, дорабатывать. TDD - это основное средство достижения чистоты кода. TDD нужен вовсе не для того, чтобы находить баги.

TDD — это дисциплина. Это как спорт: все знают, что спорт полезен, но ленятся. Спортсмен, пропускающий тренировки, быстро проигрывает соревнования. Так же и код, написанный не через тесты, моментально становится legacy. Тут всё честно. Мало знать TDD, его надо использовать каждый день, при каждом изменении кода.

Last updated