1. Проектируем снизу - вверх (начинаем с максимального простого, негибкого дизайна, потом, где нужно, вводим "Ось вариативности", то есть добавляем абстракцию, чтобы легче было менять)
2. Когда принимаем архитектурные решения - концентрируемся на важных для конкретного проекта POV (точках зрения)
3. Сразу принимаем решения (на уровне Architecture Guideline), что делать в случае неопределенности требования (например, если неизвестно, будет меняться, или нет - делаем в лоб, максимально просто)
4. Ключевые внутренние атрибуты качества:
* Понимаемость (насколько понятно, что происходит)
* Тестопригодность (насколько просто писать тесты)
* Возможность повторного использования
5. Контракты. Контракт пишется на уровне абстракции. Он определяет не только сигнатуры (вход-выход), но и другие необходимые требования (например, наличие сайд-эффектов, ожидаемый перфоманс, работа в многопоточном окружении). Контракт можно описывать в виде документации, или писать тест. Писать тесты хорошо, но долго.
6. Если требование непонятно, инкапсулируем его в компонент (накрываем абстракцией). Например, если непонятно, где храним какие-нибудь данные, выставляем интерфейс с нашими ожиданиями и откладываем это решение на потом.
7. Жесткая система типов - хорошо, но не надо упарываться. Смотреть на трейдоффы. Если ради того, чтобы убрать один свитч нужно написать 20 классов - ну нахер.
8. Смотреть базовые объектные метрики (cyclomatic complexity, coupling, cohesian). Важны не конкретные цифры, а динамика + экстремальные точки. Например, если цикломатика в среднем по системе - 2, а в каком-то методе - 94, то, возможно, стоит обратить на этот метод внимание.