Чистый код. Создание, анализ и рефакторинг. Роберт Мартин (В ПРОЦЕССЕ НАПОЛНЕНИЯ)

5jtCeReNx12oajmW7KKJNdG

1.Чистый код

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

1. **Важность чистого кода**: Первый фрагмент подчеркивает, что несмотря на развитие технологий и возможное увеличение уровня абстракции в программировании, потребность в написании кода никуда не исчезнет. Аргументируется, что код является основой для определения и выполнения требований, и без четкого и точного кода компьютеры не смогут выполнять заданные задачи.

2. **Последствия плохого кода**: Во втором фрагменте обсуждаются негативные последствия плохого кода, включая увеличение времени на загрузку, возникновение ошибок и в конечном итоге — потерю пользователей и финансовый крах компании. Это подчеркивает, что качество кода напрямую влияет на успех продукта на рынке.

3. **Роль программиста**: Третий фрагмент поднимает вопрос ответственности программистов за качество кода. Утверждается, что программисты не должны уступать давлению сроков или требований начальства в ущерб качеству кода, подчеркивая, что профессионализм в программировании включает в себя способность защищать необходимость чистого кода перед лицом внешних ожиданий.

4. **Преодоление трудностей с плохим кодом**: В четвертом фрагменте описывается, как плохой код может привести к замедлению разработки и ухудшению производительности команды. Это служит предупреждением о том, что пренебрежение качеством кода в начале проекта может привести к долгосрочным проблемам, требующим значительных усилий для их решения.

5. **Взгляды на чистый код**: Представлены различные определения и мнения о том, что такое чистый код от известных программистов и авторов. Эти мнения подчеркивают, что чистый код должен быть простым, понятным, легко поддерживаемым и расширяемым. Код должен четко выражать намерения программиста и быть написан так, будто автор действительно заботится о своем ремесле.

Чистый код – это не просто стиль программирования; это философия, которая влияет на качество конечного продукта и эффективность командной работы. В отрывках, обсуждается важность и значение чистого кода в программировании, а также поднимаются вопросы профессионализма и ответственности разработчиков. Основные идеи, можно суммировать следующим образом:

1. **Значение чистого кода**: Чистый код не только облегчает сопровождение и развитие проекта, но и повышает его надежность и эффективность. Это код, который написан аккуратно, логично и понятно не только для автора, но и для всех членов команды.

2. **Ответственность разработчиков**: Поддержание кода в чистоте – это прямая обязанность программистов, независимо от сроков и требований. Пренебрежение этой обязанностью приводит к ухудшению качества продукта и повышению затрат на его поддержку в будущем.

3. **Преодоление парадокса сроков**: Существует распространенное заблуждение, что для укладывания в сроки разработчики вынуждены идти на компромисс по качеству кода. Однако, как показывает практика, поддержание чистоты кода на самом деле способствует более быстрой и эффективной работе над проектом.

4. **Искусство чистого кода**: Написание чистого кода сравнивают с искусством, требующим от разработчика не только технических навыков, но и творческого подхода, умения видеть и реализовывать элегантные решения.

5. **Учение мастеров**: В отрывках приводятся мнения известных разработчиков о том, что такое чистый код. Эти мнения хоть и различаются в деталях, но объединены общей идеей о важности аккуратности, лаконичности и выразительности кода.

6. **Самообразование и развитие**: Разработчики призываются не только следовать установленным практикам, но и постоянно искать способы улучшения собственного мастерства, в том числе через изучение и применение лучших практик других программистов и команд.

Эти принципы подчеркивают, что качество кода напрямую зависит от уровня профессионализма и ответственности разработчика, его готовности к непрерывному обучению и совершенствованию своих навыков.

2.Содержательные имена

Подчеркивается несколько ключевых аспектов выбора имен в программировании, которые важны для создания читаемого, понятного и легко поддерживаемого кода:

1. **Имена должны передавать намерения программиста**: Хорошее имя помогает понять, зачем существует переменная, функция или класс, что она делает и как используется. Использование конкретных и описательных имен упрощает понимание кода и облегчает его дальнейшее использование и модификацию.

2. **Избегайте дезинформации**: Имена не должны вводить в заблуждение или предполагать неверные типы данных, значения или функциональность. Избегайте использования имен, которые похожи друг на друга, но представляют разные сущности или концепции.

3. **Используйте осмысленные различия**: Просто добавление номеров или неинформативных слов к именам для избежания конфликтов имен не помогает читателю понять различия между сущностями. Имена должны четко отражать разные концепции или аспекты сущностей, которые они представляют.

4. **Используйте удобно произносимые имена**: Имена в коде часто обсуждаются устно, поэтому они должны быть легко произносимыми. Это упрощает общение между разработчиками и помогает избежать путаницы.

5. **Примеры плохих и хороших имен**: Автор предоставляет конкретные примеры, демонстрирующие, как плохие имена могут затруднить понимание кода, и как хорошо выбранные имена могут его улучшить. Например, использование абстрактного имени `d` для переменной, хранящей количество дней, менее информативно и понятно, чем ясное и конкретное имя `elapsedTimeInDays`.

6. **Избегайте неинформативных слов**: Слова, которые не добавляют значимости к имени, такие как «Data», «Info», или просто числовые суффиксы, следует избегать. Они не улучшают понимание кода и могут даже его ухудшать.

7. **Выбирайте имена, удобные для поиска**: Использование конкретных, уникальных имен делает код легче для поиска и отладки. Например, использование констант вместо магических чисел делает код более понятным и упрощает его модификацию.

8. **Избегайте схем кодирования имен**: Современные среды разработки и языки программирования обладают достаточными средствами для обеспечения типизации и области видимости без необходимости кодировать эти аспекты в именах переменных или классов.

9. **Используйте удобно произносимые имена**: Имена в коде часто обсуждаются в команде, поэтому важно, чтобы они были легко произносимыми. Это упрощает общение и снижает риск недопонимания.

10. **Избегайте остроумия**: Хотя творческие и остроумные имена могут казаться забавными, они могут сбивать с толку и затруднять понимание кода другими разработчиками.

11. **Выберите одно слово для каждой концепции**: Использование разных слов для обозначения одной и той же концепции в разных частях программы может создать путаницу. Лучше придерживаться одного термина для одной концепции.

12. **Воздержитесь от каламбуров**: Использование одного и того же слова для разных понятий (каламбуры) может затруднить понимание кода и следует его избегать.

13. **Имена классов должны быть существительными**: Классы представляют сущности, поэтому их имена должны быть существительными или фразами, состоящими из существительных.

14. **Имена методов должны быть глаголами**: Методы обычно выполняют действия, поэтому их имена должны отражать это, используя глаголы или глагольные фразы.

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

Автор подчеркивает важность использования понятных и точных имен в программировании, а также дает рекомендации по выбору наиболее подходящих имен для переменных, методов и классов, чтобы облегчить понимание и сопровождение кода. Вот основные моменты:

1. **Используйте имена из пространства решения**: Не стесняйтесь использовать технические термины и понятия, такие как названия алгоритмов, паттернов и математических терминов. Это помогает программистам быстрее понимать код, не прибегая к постоянным разъяснениям.

2. **Используйте имена из пространства задачи**: Если нет подходящего термина в пространстве решения, используйте имена, прямо связанные с предметной областью задачи. Это облегчает общение с экспертами в данной области и помогает в понимании контекста задачи.

3. **Добавьте содержательный контекст**: Важно не только выбирать понятные имена, но и обеспечивать их достаточным контекстом, чтобы было ясно, к какой области они относятся. Использование классов и структур данных может помочь в этом, предоставляя ясную структуру и связи между элементами кода.

4. **Не добавляйте избыточный контекст**: Избегайте излишнего добавления контекста в имена, особенно если они уже находятся в контексте, который ясен из структуры программы или системы. Избыточный контекст может усложнить понимание и работу с кодом.

5. **Выберите одно слово для каждой концепции**: Следует избегать использования разных слов для обозначения одной и той же концепции в разных частях кода. Это помогает избежать путаницы и упрощает понимание кода.

В заключение, автор подчеркивает, что хорошие имена — это не просто вопрос личного предпочтения или стиля, но ключевой аспект создания читаемого, понятного и легко сопровождаемого кода. Они призывают разработчиков не бояться менять имена в пользу улучшения читаемости и понимания кода, даже если это может вызвать удивление у других членов команды.

3.Функции

Автор описывает важность компактности функций в программировании и подчеркивает, как важно, чтобы функции выполняли только одну операцию, были короткими, и каждая из них находилась на одном уровне абстракции. Вот основные моменты:

1. **Компактность функций**: Функции должны быть максимально короткими и выполнять только одну задачу. Идеально, если они не превышают 20 строк кода.

2. **Блоки и отступы**: Блоки внутри условных операторов и циклов должны быть короткими и, желательно, содержать один вызов функции, что облегчает чтение и понимание кода.

3. **Правило одной операции**: Функция должна выполнять одну операцию и делать это хорошо. Это помогает поддерживать код чистым и упрощает его понимание.

4. **Один уровень абстракции на функцию**: Все операции внутри функции должны находиться на одном и том же уровне абстракции, что упрощает понимание логики функции.

5. **Чтение кода сверху вниз**: Код должен быть организован таким образом, чтобы его можно было читать, как рассказ, спускаясь от общих концепций к более детальным.

6. **Использование команд `switch`**: Команды `switch` должны быть минимизированы и использоваться с осторожностью, так как они могут нарушать принципы чистого кода. Лучше использовать полиморфизм и абстрактные фабрики для обработки различных случаев.

7. **Избегание смешения уровней абстракции**: Функции не должны смешивать детали различных уровней абстракции, чтобы избежать путаницы и упростить понимание кода.

8. **Избегание дублирования логики**: Функции не должны дублировать логику в различных частях программы, что поддерживает принцип DRY (Don’t Repeat Yourself).

Подчеркивается, что хорошо написанные функции облегчают понимание кода, упрощают его поддержку и модификацию, а также способствуют созданию чистого и эффективного программного обеспечения.

Далее говорится о важности использования содержательных имен в программировании для улучшения понимания и читаемости кода. Вот ключевые моменты:

1. **Содержательные имена улучшают понимание**: Переименование функций и переменных в более описательные и точные имена помогает ясно передать их назначение и улучшить архитектуру кода.

2. **Не бойтесь длинных имен**: Длинные, но понятные имена предпочтительнее коротких и непонятных. Используйте имена, которые четко описывают назначение переменной или функции.

3. **Последовательность в именах**: Использование одной и той же терминологии и согласованной фразеологии в именах функций помогает создать связную историю в коде и облегчает его понимание.

4. **Аргументы функций должны быть минимальны**: Идеально, когда функция не имеет аргументов. Следующим по предпочтительности является один аргумент, затем два. Функции с тремя и более аргументами следует избегать, поскольку они усложняют понимание и тестирование кода.

5. **Избегайте аргументов-флагов**: Аргументы-флаги указывают на то, что функция выполняет более одной операции. Лучше разделить такие функции на несколько меньших функций.

6. **Бинарные и тернарные функции**: Функции с двумя аргументами сложнее понять, чем с одним, а функции с тремя аргументами ещё более усложняют понимание. По возможности стоит упрощать такие функции, сокращая количество аргументов.

7. **Объекты как аргументы**: Если функции требуются много аргументов, возможно, некоторые из них можно объединить в объект, что упростит работу с функцией и улучшит организацию кода.

8. **Глаголы и ключевые слова в именах функций**: Имя функции должно быть глаголом или содержать глагол, а аргументы функции должны быть интегрированы в её имя для лучшего понимания её действий и аргументов.

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

Важность избегания побочных эффектов в функциях программирования, подчеркивая, что функции должны делать то, что они обещают, без скрытых действий. Основные моменты включают:

1. **Побочные эффекты как ложь**: Функции должны выполнять одно обещанное действие. Включение неожиданных изменений или действий, таких как изменение глобального состояния, вводит непредсказуемость и усложняет понимание кода.

2. **Избегание выходных аргументов**: Аргументы функции должны рассматриваться как входные данные. Использование аргументов как выходных данных может создавать путаницу и усложнять понимание кода.

3. **Разделение команд и запросов**: Функции должны либо выполнять действие (команду), либо возвращать данные (запрос), но не оба одновременно. Это упрощает логику функций и делает их более предсказуемыми.

4. **Использование исключений вместо кодов ошибок**: Исключения предоставляют более чистый способ обработки ошибок по сравнению с кодами ошибок, поскольку они не смешивают обработку ошибок с основной логикой функции.

5. **Изолирование блоков try/catch**: Рекомендуется изолировать обработку исключений в отдельных функциях, чтобы основная логика была чистой и не загромождена обработкой ошибок.

6. **Не повторяться (DRY — Don’t Repeat Yourself)**: Дублирование кода увеличивает сложность и ведет к ошибкам, поскольку при изменениях необходимо вносить корректировки во множестве мест.

7. **Структурное программирование**: Хотя следование принципам структурного программирования может быть полезным, в очень маленьких функциях они не так критичны, и иногда допустимо использование множественных return и других конструкций для повышения читаемости кода.

8. **Непрерывный рефакторинг**: Как и в письменной работе, код часто требует нескольких итераций рефакторинга, чтобы достичь желаемой чистоты и структуры. Модульные тесты обеспечивают безопасность в этом процессе, позволяя улучшать код без страха сломать что-то.

Эти принципы направлены на создание более понятного, управляемого и поддерживаемого кода, минимизируя риск ошибок и упрощая обслуживание и расширение программного обеспечения.

4.Комментарии

Основные моменты включают:

1. **Комментарии как неизбежное зло**: Хорошо спроектированный код часто может обойтись без комментариев. Комментарии могут стать признаком неудачи в четком выражении намерений через код.

2. **Ложь комментариев**: Со временем комментарии могут стать неверными или устаревшими по отношению к коду, который они описывают, тем самым вводя в заблуждение или распространяя дезинформацию.

3. **Комментарии не являются заменой качеству кода**: Вместо того чтобы использовать комментарии для объяснения плохого кода, лучше уделить время и усилия на улучшение самого кода.

4. **Выражение намерений в коде**: В идеале код должен быть написан так, чтобы его было легко понять без комментариев. Использование хорошо подобранных имен функций и переменных может значительно уменьшить необходимость в комментариях.

5. **Полезные комментарии**: Существуют ситуации, когда комментарии действительно необходимы и полезны, например, юридические заявления, информативные комментарии, пояснения намерений или предупреждения о последствиях.

6. **Плохие комментарии**: Большинство комментариев часто не несут полезной информации, могут быть неверными, слишком очевидными или просто излишними. Такие комментарии могут загромождать код и делать его труднее для понимания и поддержки.

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

Этот раздел обсуждает различные виды комментариев в коде, подчеркивая, как легко они могут стать избыточными, недостоверными, или даже вредными. Основные моменты включают:

1. **Избыточные комментарии**: Комментарии, которые повторяют то, что и так ясно из кода, не приносят пользы и лишь отвлекают внимание.

2. **Недостоверные комментарии**: Комментарии могут вводить в заблуждение, если они не обновляются вместе с кодом, к которому они относятся.

3. **Обязательные комментарии**: Автоматически сгенерированные или требуемые стандартами комментарии часто бывают ненужными и загромождают код.

4. **Журнальные комментарии и комментарии шум**: Журнальные комментарии или комментарии, не несущие смысловой нагрузки, только загромождают код и усложняют его понимание.

5. **Комментарии, содержащие HTML или другой непростой для чтения контент**: Такие комментарии могут затруднять чтение и понимание кода напрямую в исходном файле.

6. **Нелокальная информация**: Комментарии, содержащие информацию, не относящуюся непосредственно к блоку кода, к которому они прикреплены, могут вводить в заблуждение.

7. **Слишком много информации**: Иногда комментарии могут содержать излишнюю информацию, отвлекающую от сути кода.

8. **Неочевидные комментарии**: Если связь между комментарием и кодом не является очевидной, это может создать больше вопросов, чем ответов.

Из всего этого следует, что лучше стремиться к написанию чистого, самодокументированного кода, где намерения программиста выражены через имена функций и переменных, структуру кода и выбор абстракций, минимизируя при этом необходимость в комментариях. Комментарии должны использоваться с умом и только тогда, когда они действительно добавляют ценность к пониманию кода, а не просто заполняют пробелы, которые можно было бы заполнить более четким кодированием.

Этот раздел подчеркивает важность сдержанного использования комментариев и отдает предпочтение ясности кода перед избыточным комментированием. Основные идеи включают:

— **Заголовки функций и Javadoc для внутреннего кода**: Короткие и четкие функции обычно не требуют длинных заголовков или комментариев Javadoc, особенно если они не предназначены для общего API. Комментарии должны использоваться умеренно и только там, где это действительно повышает понимание кода.

— **Примеры «плохого» и «хорошего» кода**: Представлены два примера кода для генерации простых чисел. Первый пример (`GeneratePrimes.java`) перегружен комментариями, многие из которых избыточны или не добавляют значимой информации. Второй пример (`PrimeGenerator.java`) демонстрирует улучшенную версию с минимальным количеством комментариев, где код становится самодостаточным и легко читаемым.

— **Комментарии должны добавлять значение**: Комментарии должны использоваться для объяснения «почему» за кодом, а не «что» делает код, особенно когда последнее может быть очевидно из самого кода. Важно, чтобы комментарии улучшали понимание кода, а не просто повторяли его.

— **Важность самодокументированного кода**: Эффективное использование имен переменных, функций и структуры кода может сделать комментарии излишними. Лучше уделить время улучшению читаемости и понятности кода, чем пытаться объяснить его через комментарии.

Этот раздел подчеркивает, что хороший код часто говорит сам за себя, и во многих случаях, если вы чувствуете необходимость в комментарии, это может быть признаком того, что сам код нуждается в улучшении. Комментарии должны быть целенаправленными и использоваться только тогда, когда они действительно необходимы для объяснения сложных алгоритмов, неочевидных решений или для документирования API, предназначенного для использования другими разработчиками.

5.Форматирование

Цель форматирования кода — облегчить передачу информации о программе для других разработчиков, включая будущего вас. Важно понимать, что основная задача разработчика не только «сделать так, чтобы программа работала», но и обеспечить удобочитаемость и сопровождаемость кода на долгосрочной перспективе.

### Вертикальное форматирование

Вертикальное форматирование помогает в организации кода, делая его более понятным и легким для чтения. Это включает в себя:

— **Размер файлов и классов**: Хорошо спроектированные системы могут быть построены из файлов среднего размера (около 200 строк), что облегчает их понимание и сопровождение.

— **Газетная метафора**: Исходный файл должен быть организован подобно газетной статье, где самая важная информация находится вверху, а детали — по мере продвижения вниз по тексту.

— **Вертикальное разделение концепций**: Вертикальные пробелы между блоками кода могут обозначать новую концепцию, в то время как вертикальное сжатие подчеркивает тесную связь между строками кода.

— **Вертикальное расстояние между концепциями**: Тесно связанные концепции должны быть размещены близко друг к другу в коде, чтобы облегчить понимание их взаимосвязей.

— **Объявления переменных**: Переменные следует объявлять как можно ближе к месту их использования, чтобы их назначение было понятно без необходимости прокручивать код вверх и вниз.

— **Зависимые функции**: Функции, вызывающие другие функции, должны располагаться вертикально близко к вызываемым функциям, желательно над ними.

— **Концептуальное родство**: Фрагменты кода, выполняющие аналогичные операции или имеющие другие формы родства, должны быть размещены близко друг к другу.

### Вертикальное упорядочение

Идеально, когда код в исходном файле упорядочен таким образом, чтобы читатель мог легко следовать от высокоуровневых концепций к деталям без необходимости постоянно прыгать по тексту. Это подразумевает упорядочивание кода от общего к частному, от важного к второстепенному, обеспечивая таким образом естественный поток понимания программы.

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

В разделе о горизонтальном форматировании поднимаются вопросы о предпочтительной ширине строки кода, о том, как лучше всего использовать горизонтальные пробелы для улучшения читаемости и о том, следует ли использовать горизонтальное выравнивание для улучшения восприятия кода. Давайте рассмотрим эти аспекты подробнее:

### Ширина строки

— Исследования показывают, что программисты предпочитают строки длиной от 40 до 120 символов. Хотя современные мониторы позволяют размещать на экране строки гораздо большей длины, слишком длинные строки могут затруднить чтение кода и его понимание. Оптимальным решением является ограничение длины строки 120 символами, что облегчает чтение кода и его сопровождение.

### Горизонтальное разделение и сжатие

— Использование пробелов вокруг операторов и после запятых в списках аргументов улучшает читаемость, подчеркивая логическую структуру выражений и упрощая восприятие отдельных элементов кода. Однако избыточное использование пробелов может привести к противоположному эффекту, особенно если оно не сочетается с общепринятыми стандартами форматирования.

### Горизонтальное выравнивание

— Хотя горизонтальное выравнивание может сделать код визуально привлекательным, оно не всегда способствует повышению его читаемости. Кроме того, автоматические инструменты форматирования могут нарушить выравнивание, что приведет к дополнительной работе по его восстановлению. Гораздо важнее следовать принципам чистого кода, чем стремиться к визуальной аккуратности за счет выравнивания.

### Отступы и блоки кода

— Правильное использование отступов критически важно для понимания структуры кода. Отступы помогают визуально выделить блоки кода, относящиеся к различным областям видимости, что облегчает их восприятие и анализ. Сокращение кода в одну строку или неуместное сжатие блоков может затруднить понимание логики программы.

### Соглашения о форматировании в команде

— Важно, чтобы вся команда разработчиков следовала общим стандартам форматирования. Это обеспечивает консистентность стиля кода, упрощает сопровождение и обмен знаниями между разработчиками. Настройка инструментов автоматического форматирования согласно принятым стандартам может существенно упростить эту задачу.

Итак, горизонтальное форматирование — это не только вопрос личных предпочтений, но и средство обеспечения читаемости, понимания и сопровождаемости кода. Следование общепринятым стандартам и соглашениям в команде помогает поддерживать код в хорошем состоянии и облегчает работу всех участников процесса разработки.

6.Объекты и структуры данных

Абстракция данных является ключевым элементом в программировании, позволяющим скрыть детали реализации от пользователя и предоставить ему только необходимый интерфейс для взаимодействия с данными. Этот подход поддерживает концепцию инкапсуляции и способствует созданию модульного и легко сопровождаемого кода. Рассмотрим подробнее представленные концепции абстракции данных, антисимметрии данных и объектов, закона Деметры, и другие сопутствующие идеи.

### Абстракция данных

Сравнение листингов 6.1 и 6.2 показывает разницу между конкретной и абстрактной реализацией класса `Point`. Абстрактная реализация предпочтительнее, поскольку она скрывает детали реализации (например, используются ли прямоугольные или полярные координаты) и предоставляет пользователям интерфейс для взаимодействия с точкой, не требуя от них знания о внутреннем устройстве класса. Это позволяет изменять реализацию без влияния на код, который использует класс `Point`.

### Антисимметрия данных и объектов

Объекты скрывают свои данные и предоставляют методы для работы с этими данными, в то время как структуры данных открывают свои данные и не имеют значимых методов. Эта антисимметрия подчеркивает различие в подходах к проектированию и использованию объектов и структур данных в программировании.

### Закон Деметры

Закон Деметры подчеркивает важность минимизации знаний объекта о других объектах. Это означает, что объект должен взаимодействовать только с непосредственно связанными с ним объектами и избегать «заглядывания» внутрь других объектов через цепочки вызовов. Следование этому закону способствует сокращению зависимостей между компонентами системы, упрощая её архитектуру и облегчая сопровождение.

### Объекты передачи данных (DTO)

DTO представляют собой простые структуры для передачи данных без значимого поведения. Они полезны в контекстах, где необходимо просто передать данные без предоставления какой-либо дополнительной функциональности, например, при общении с базами данных или при сериализации объектов для сетевой передачи.

### Гибриды

Гибридные структуры, сочетающие черты объектов и структур данных, часто приводят к путанице и усложняют архитектуру системы. Вместо использования гибридов рекомендуется четко разграничивать объекты, инкапсулирующие поведение и скрывающие свои данные, и структуры данных, предназначенные для простого хранения и передачи данных.

### Выводы

При проектировании программных систем важно четко различать объекты и структуры данных, правильно применять абстракцию и следовать принципам, таким как закон Деметры, для создания чистой, модульной и легко сопровождаемой архитектуры. Выбор между объектно-ориентированным и процедурным подходами должен определяться конкретными требованиями и целями проекта.

7.Обработка ошибок

Использование исключений вместо кодов ошибок предлагает более чистый, организованный и модульный подход к обработке ошибок в программном обеспечении. В этом подходе логика обработки ошибок отделена от основного потока выполнения кода, что улучшает читаемость и облегчает поддержку кода. Давайте рассмотрим ключевые моменты изложенных рекомендаций:

### Используйте исключения вместо кодов ошибок

— Исключения позволяют изолировать код обработки ошибок от основной бизнес-логики, что способствует написанию более чистого и понятного кода.

— Конструкция `try-catch-finally` помогает определить область видимости, где может произойти ошибка, и гарантирует, что программа останется в целостном состоянии, даже если произойдет исключение.

### Используйте непроверяемые исключения

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

### Передавайте контекст с исключениями

— Исключения должны предоставлять достаточно контекста для понимания причины ошибки, включая описание операции, которая привела к ошибке, и другие релевантные детали.

### Определяйте классы исключений в соответствии с потребностями вызывающей стороны

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

### Определите нормальный путь выполнения

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

### Не возвращайте и не передавайте null

— Возвращение и передача `null` может привести к `NullPointerException` и усложняет обработку ошибок. Вместо этого используйте паттерны, такие как «Особый случай», или выбрасывайте исключения для обозначения особых условий.

В заключение, правильное использование исключений и избегание возвращения или передачи `null` может значительно улучшить качество и надежность программного обеспечения, делая код более читаемым, понятным и легким для сопровождения.

8.Границы

Использование стороннего кода в своих проектах представляет собой стандартную практику в разработке программного обеспечения, предоставляя возможности расширения функциональности без необходимости реализации каждой мелочи с нуля. Однако это также влечёт за собой ряд вызовов, связанных с интеграцией и управлением зависимостями. Вот несколько ключевых моментов, которые следует учитывать при работе со сторонним кодом:

### Инкапсуляция стороннего кода

Инкапсуляция или обёртывание сторонних интерфейсов и библиотек в собственные абстракции позволяет снизить зависимость вашего приложения от конкретных реализаций и упрощает возможную замену или обновление сторонних компонентов. Это также улучшает читаемость и поддерживаемость кода, делая его более устойчивым к изменениям в сторонних библиотеках.

### Учебные тесты для изучения стороннего API

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

### Использование несуществующего кода

При разработке системы, части которой ещё не реализованы, можно определить ожидаемые интерфейсы или «прогнозируемые» API, которые позволят продолжить работу, не дожидаясь реализации всех компонентов. Это также упрощает интеграцию с будущим кодом, минимизируя риски и ускоряя разработку.

### Принципы чистых границ

Важно чётко определять границы между вашим кодом и сторонними библиотеками или компонентами. Это включает в себя:

— Минимизацию прямой зависимости от сторонних интерфейсов путём использования собственных абстракций.

— Создание адаптеров или обёрток для интеграции с внешними библиотеками, что позволяет изолировать изменения в сторонних API от вашего приложения.

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

### Выводы

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

9. Модульные тесты

За последние десятилетия подходы к разработке программного обеспечения претерпели значительные изменения, в частности, благодаря внедрению и популяризации практик Тестирования, Управляемого Разработкой (TDD — Test-Driven Development). Этот подход не просто изменяет порядок выполнения задач разработки, но и в корне меняет отношение к процессу написания кода, ставя тестирование на один уровень с написанием функционального кода.

### Три Закона TDD

TDD основывается на трёх ключевых принципах:

1. **Не пишите код продукта, пока не напишете отказной модульный тест.** Это гарантирует, что разработка начинается только с чёткого понимания требований и ожидаемого поведения системы.

2. **Не пишите больше модульного теста, чем необходимо для отказа; необходимость компиляции также считается отказом.** Это позволяет оставаться сфокусированными на текущей задаче и избегать переусложнения.

3. **Не пишите больше кода продукта, чем необходимо для прохождения текущего неудачного теста.** Этот принцип помогает поддерживать минимальный и наиболее эффективный код, необходимый для удовлетворения текущих требований.

### Важность Чистоты Тестов

Чистота тестового кода играет решающую роль в поддержке гибкости, удобства сопровождения и возможности повторного использования кода продукта. Тесты должны быть такими же читаемыми и понятными, как и функциональный код, если не более. Это достигается за счёт ясности, простоты и выразительности тестов.

### Основные Характеристики Чистых Тестов (F.I.R.S.T.)

— **Fast (Быстрые):** Тесты должны выполняться быстро.

— **Independent (Независимые):** Каждый тест должен быть способен выполняться независимо от других.

— **Repeatable (Повторяемые):** Тесты должны давать одинаковый результат в любой среде.

— **Self-Validating (Самопроверяемые):** Результат теста должен быть однозначным: либо успешен, либо нет.

— **Timely (Своевременные):** Тесты должны создаваться вовремя, непосредственно перед написанием функционального кода, который они проверяют.

### Подход к Тестированию

— **Одна проверка на тест:** Каждый тест должен проверять только одну концепцию.

— **Одна концепция на тест:** Это помогает избежать смешения разных аспектов поведения в одном тесте, что упрощает диагностику проблем.

### Заключение

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

шение к тестам может привести к ухудшению качества кода продукта и увеличению сложности его сопровождения. Поэтому разработчикам следует уделять тестам столько же внимания, сколько и функциональному коду.

10. Классы

Обсуждаемые принципы и рекомендации подчёркивают важность структурирования и организации кода в классах Java, следуя лучшим практикам программирования. Эти рекомендации помогают создавать более читаемый, поддерживаемый и расширяемый код. Давайте подытожим ключевые моменты:

### Строение Класса

— Классы в Java обычно начинаются с списка переменных: сначала идут публичные статические константы, затем приватные статические переменные и приватные переменные экземпляров. Публичные переменные используются редко из-за проблем с инкапсуляцией.

— После переменных следуют публичные функции. Приватные вспомогательные функции размещаются после публичных функций, которые их вызывают, обеспечивая логичность чтения кода.

### Инкапсуляция

— Переменные и вспомогательные функции предпочтительно делать приватными, но иногда для тестирования они могут быть объявлены как защищённые или с пакетным доступом. Всегда ищите способы сохранить приватность, используя ослабление инкапсуляции только как последнюю меру.

### Компактность Классов

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

### Принцип Единой Ответственности (SRP)

— Класс должен иметь одну и только одну причину для изменения. Если класс выполняет слишком много функций, он должен быть разделён на более мелкие классы.

### Связность

— Классы должны иметь высокую степень связности, что означает, что их методы тесно связаны с данными класса. Если методы класса используют только часть данных класса, это может указывать на необходимость разделения класса на более мелкие.

### Изменения и Расширяемость

— Структура классов должна позволять легко расширять функционал без изменения существующего кода. Принцип открытости/закрытости подчёркивает, что классы должны быть открыты для расширения, но закрыты для модификации.

Эти принципы и рекомендации служат фундаментом для создания хорошо спроектированного и легко поддерживаемого программного обеспечения. Они подчёркивают важность чёткого разделения ответственности, гибкости в проектировании и внимания к деталям при организации кода, что в конечном итоге ведёт к созданию более качественных программных продуктов.

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

### Принципы и Практики

— **Абстракция и Инкапсуляция**: Ключевой идеей является отделение концепций от реализации, что позволяет клиентскому коду взаимодействовать с абстракциями, а не с конкретными деталями. Это сокращает взаимозависимости в системе и упрощает внесение изменений.

— **Использование Интерфейсов**: Определение интерфейсов, таких как `StockExchange` в приведенном примере, позволяет создавать множество реализаций, включая тестовые заглушки, что упрощает тестирование и повышает гибкость системы.

— **Принцип Инверсии Зависимостей (DIP)**: Этот принцип подчеркивает, что модули высокого уровня не должны зависеть от модулей низкого уровня, а оба типа модулей должны зависеть от абстракций. Это уменьшает связанность в системе и делает её более модульной.

### Преимущества

— **Упрощение Тестирования**: Как показано в примере с `PortfolioTest`, использование тестовых заглушек позволяет тестировать логику без зависимости от внешних условий, таких как реальное API биржи. Это делает тесты более надежными и предсказуемыми.

— **Гибкость и Расширяемость**: Система становится более адаптируемой к изменениям. Например, можно легко добавить поддержку новой биржи, реализовав интерфейс `StockExchange`, без необходимости изменения существующего клиентского кода.

— **Повторное Использование Кода**: Абстракции упрощают повторное использование компонентов в различных контекстах, поскольку они не привязаны к конкретным реализациям.

### Заключение

Изоляция изменений через абстракции и принцип инверсии зависимостей позволяет создавать системы, которые легче поддерживать, тестировать и расширять. Этот подход повышает гибкость и устойчивость архитектуры программного обеспечения, делая её более адаптируемой к изменениям требований или технологий.

11. Системы

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

### Проблемы Отложенной Инициализации

— **Жесткая Связанность**: Прямая инициализация объектов внутри методов создает жесткую связанность между компонентами системы, делая её менее гибкой и усложняя замену компонентов или их тестирование.

— **Трудности в Тестировании**: Как вы упомянули, тестирование становится сложнее, поскольку необходимо учитывать инициализацию тяжеловесных объектов и их зависимостей, что может потребовать использования моков или стабов.

— **Нарушение Принципа Единой Ответственности (Single Responsibility Principle, SRP)**: Метод, который и занимается инициализацией, и выполнением бизнес-логики, нарушает SRP, так как выполняет более одной задачи.

### Решения

— **Внедрение Зависимостей (Dependency Injection, DI)**: Этот подход позволяет отделить создание объектов от их использования, передавая зависимости объекту извне. Это облегчает управление зависимостями, тестирование и соблюдение SRP.

— **Фабрики и Сервис-Локаторы**: Для управления созданием объектов можно использовать фабричные методы или сервис-локаторы, которые централизуют логику инициализации, делая её более организованной и управляемой.

— **Применение Паттернов Проектирования**: Паттерны, такие как Одиночка (Singleton) для глобального доступа или Ленивая Инициализация (Lazy Initialization) в контролируемом контексте, могут быть использованы с умом, чтобы снизить накладные расходы инициализации, но их следует использовать осторожно, чтобы не усложнить тестирование и не нарушить модульность.

### Заключение

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

Автор обсуждает два важных подхода в программировании и архитектуре программного обеспечения: отделение процесса конструирования объектов от их использования и использование паттерна «Абстрактная фабрика» для динамического создания объектов во время выполнения программы. Эти подходы играют ключевую роль в создании гибких, масштабируемых и легко поддерживаемых систем.

### Отделение конструирования в `main`

Первый подход заключается в централизации процесса создания и конфигурации объектов в начальной точке приложения, часто в функции `main` или в специальных модулях, вызываемых из `main`. Этот метод позволяет явно контролировать зависимости между компонентами системы, упрощая понимание и тестирование приложения. Поскольку вся конфигурация и создание объектов происходят в одном месте, другие части программы могут быть написаны с предположением, что все необходимые объекты уже созданы и корректно сконфигурированы. Это уменьшает связность кода, упрощает управление зависимостями и облегчает тестирование, поскольку компоненты могут быть легко изолированы.

### Фабрики и паттерн «Абстрактная фабрика»

В некоторых случаях объекты должны создаваться динамически во время выполнения программы, в зависимости от определенных условий или параметров, которые не известны на момент запуска приложения. Для таких ситуаций используется паттерн «Абстрактная фабрика» (Abstract Factory), который позволяет отделить детали создания объектов от их использования. Это достигается за счет определения интерфейса для создания семейств взаимосвязанных или взаимозависимых объектов, без указания конкретных классов. Приложение выбирает или настраивает фабрику, способную создавать объекты, соответствующие нуждам приложения, и использует эту фабрику для создания объектов, не заботясь о деталях их реализации.

Применение паттерна «Абстрактная фабрика» обеспечивает дополнительный уровень абстракции и изоляции, что позволяет сделать систему более модульной, гибкой и расширяемой. Приложение может менять способы создания объектов, не изменяя код, который использует эти объекты, что облегчает адаптацию к новым требованиям или условиям без необходимости переписывания существующего кода.

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

Внедрение зависимостей (Dependency Injection, DI) является ключевым паттерном в современном программировании и архитектуре приложений, обеспечивающим гибкость, масштабируемость и упрощение тестирования за счет разделения ответственности между компонентами. DI реализует принцип обращения контроля (Inversion of Control, IoC), согласно которому объекты не создают свои зависимости напрямую, а получают их извне, что способствует снижению связности и повышению модульности кода.

### Основные концепции и преимущества DI

— **Отделение конструирования от использования:** Классы не берут на себя ответственность за создание объектов, от которых они зависят. Это упрощает управление зависимостями, особенно в больших и сложных системах.

— **Упрощение тестирования:** Поскольку зависимости могут быть легко заменены моками или стабами в тестах, DI облегчает изоляцию тестируемого кода и написание модульных тестов.

— **Гибкость и расширяемость:** DI позволяет легко заменять компоненты системы без необходимости изменения кода, использующего эти компоненты, что делает систему более адаптируемой к изменениям.

### Примеры DI

В тексте упоминается использование JNDI в качестве примера ранней формы DI, где объекты запрашивают зависимости из внешнего контейнера, а также более продвинутые формы DI, где зависимости внедряются непосредственно через конструкторы или сеттеры, что позволяет полностью избавиться от необходимости в объекте заниматься разрешением своих зависимостей.

### DI-контейнеры

DI-контейнеры, такие как Spring Framework для Java, автоматизируют процесс внедрения зависимостей, управляя созданием объектов и их жизненным циклом. Они позволяют определять зависимости в конфигурационных файлах или аннотациях, что упрощает изменение и управление зависимостями в приложении.

### Проблемы масштабирования и архитектура

Текст также касается темы масштабирования архитектуры и проблем, связанных с ростом и изменением систем. В контексте программной инженерии, в отличие от физического мира, существует гибкость в адаптации и изменении архитектуры для поддержки новых требований, что демонстрирует важность правильного разделения ответственности и использования паттернов, таких как DI.

### Заключение

Внедрение зависимостей — это мощный инструмент в арсенале современного разработчика, способствующий созданию чистой, модульной и легко поддерживаемой архитектуры программного обеспечения. Оно позволяет разработчикам сосредоточиться на бизнес-логике, минимизируя прямую зависимость между компонентами и облегчая тестирование и рефакторинг.

Поперечные области ответственности (cross-cutting concerns) в программной архитектуре относятся к функциональности или логике, которая пронизывает множество точек в приложении, но не поддаётся естественному разделению в рамках объектно-ориентированного программирования. Примерами таких областей могут быть логирование, безопасность, управление транзакциями и сохранение объектов. Подходы, упомянутые в тексте, включают использование архитектуры EJB2, посредников (proxies), аспектно-ориентированного программирования (АОП) и DI-контейнеров, таких как Spring, для решения проблемы поперечных областей ответственности.

### Архитектура EJB2

EJB2 предоставляла декларативный подход к управлению поперечными областями ответственности через XML-дескрипторы, позволяя отделить логику транзакций, безопасности и сохранения от бизнес-логики. Однако этот подход был критикован за избыточность и сложность, что привело к появлению EJB3 с более простым и декларативным стилем, вдохновленным Spring.

### Посредники (Proxies)

Посредники используются для перехвата вызовов к объекту и могут обеспечивать дополнительную функциональность (например, логирование или транзакции) без изменения исходного кода объекта. Однако создание посредников требует дополнительного кода и может усложнить архитектуру, особенно если требуется работа с классами, а не только с интерфейсами.

### Аспектно-ориентированное программирование (АОП)

АОП предлагает мощный инструмент для модульного описания поперечных областей ответственности с помощью аспектов, которые могут быть автоматически применены к коду без его изменения. Аспекты позволяют декларативно определять, в каких точках и каким образом внедрять дополнительную функциональность, обеспечивая чистоту и модульность кода. Один из наиболее известных инструментов для АОП в Java — это AspectJ.

### DI-контейнеры как Spring Framework

Spring Framework использует DI и АОП для управления поперечными областями ответственности, предоставляя богатые возможности для декларативного описания как основных компонентов приложения, так и аспектов, связанных с транзакциями, безопасностью и прочим. Spring позволяет разрабатывать приложения с легко тестируемыми, слабосвязанными компонентами, при этом упрощая управление сложной инфраструктурой.

### Заключение

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

Важность гибкости в системной архитектуре и предлагает стратегию разработки, которая позволяет архитектуре эволюционировать по мере роста и изменения требований. В основе подхода лежит использование POJO-объектов (Plain Old Java Object) для реализации бизнес-логики, отдельно от архитектурных аспектов, что обеспечивает легкость внесения изменений и масштабируемость.

Основные моменты, на которые делается акцент:

1. **Избегание BDUF (Big Design Up Front)**: Предупреждает о рисках чрезмерного предварительного проектирования, которое может ограничить гибкость и адаптивность системы к изменениям. Вместо этого предлагается итеративный подход, позволяющий системе развиваться естественным образом.

2. **Разделение ответственности через аспектное программирование**: Подчеркивается важность разделения бизнес-логики от архитектурных аспектов (например, безопасности, транзакций), что позволяет легче управлять изменениями и упрощает тестирование.

3. **Гибкость и масштабируемость**: Описывается, как начать с простой архитектуры и постепенно добавлять новые технологии и инфраструктуру по мере необходимости, сохраняя при этом систему легкой для понимания и изменения.

4. **Отличие от строительства зданий**: Указывается на особенности программных продуктов, которые, в отличие от физических конструкций, могут более легко адаптироваться к изменениям благодаря своей нематериальной природе.

5. **Важность хорошего API**: Обсуждается, как хорошо спроектированный API может облегчить разработку, делая большую часть архитектуры «невидимой» для разработчиков, позволяя им сосредоточиться на реализации пользовательских требований.

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

Автор обсуждает ключевые принципы для оптимизации процесса принятия решений в разработке систем, подчеркивая важность модульности, разделения ответственности, гибкости, и использования предметно-ориентированных языков (DSL). Вот основные моменты:

1. **Модульность и разделение ответственности** способствуют децентрализации управления и принятия решений, что критически важно для крупных систем, где один человек не может эффективно управлять всеми аспектами или принимать все решения.

2. **Откладывание принятия решений** до последнего возможного момента позволяет использовать максимальное количество доступной информации и делает решения более информированными и менее рискованными.

3. **Разумное применение стандартов** подчеркивается как способ упрощения повторного использования идей и компонентов и привлечения квалифицированных специалистов. Однако следует избегать слепого следования стандартам, которые могут не подходить для конкретного проекта.

4. **Предметно-ориентированные языки (DSL)** уменьшают коммуникационный разрыв между концепцией предметной области и реализующим её кодом, позволяя разработчикам выражать свои намерения на соответствующем уровне абстракции и снижая риск неправильного представления предметной области в коде.

5. **Чистота архитектуры** так же важна, как и чистота кода. Агрессивная архитектура, скрывающая логику предметной области, может привести к снижению качества кода и гибкости системы, увеличивая вероятность ошибок и затрудняя реализацию пользовательских требований.

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

12. Формирование архитектуры

Четыре правила простой архитектуры, предложенные Кентом Беком, представляют собой фундаментальные принципы, направленные на улучшение качества проектирования и архитектуры программных продуктов. Эти правила формируют структуру и подход, который способствует созданию чистого, понятного и легко поддерживаемого кода. Рассмотрим каждое из правил более подробно:

### Правило №1: Выполнение всех тестов

Это правило подчеркивает значимость тестирования в процессе разработки. Система должна проходить все тесты, что является основой для подтверждения ее функциональности и надежности. Тестирование обеспечивает контролируемость системы и позволяет разработчикам убедиться в том, что система работает как задумано. Подход, ориентированный на тестирование, способствует разработке системы с узкоспециализированными классами, которые легче тестировать и поддерживать.

### Правила №2–4: Переработка кода (рефакторинг)

После создания полного набора тестов разработчики могут сосредоточиться на улучшении качества кода и архитектуры системы через рефакторинг. Эти правила включают в себя:

— **Устранение дублирования кода**: Дублированный код увеличивает сложность системы и вероятность ошибок, так как одна и та же логика может потребовать изменений в нескольких местах. Рефакторинг направлен на устранение дублирования и содействует повышению читаемости и упрощению поддержки кода.

— **Выражение намерений программиста**: Код должен быть написан таким образом, чтобы его было легко читать и понимать. Использование понятных имен переменных, функций и классов, а также четкое структурирование логики, помогает другим разработчикам быстрее разбираться в коде и вносить в него изменения.

— **Минимизация количества классов и методов**: Хотя модульность и разделение ответственности являются ключевыми аспектами хорошей архитектуры, излишняя раздробленность может усложнить понимание системы и ее поддержку. Поэтому важно стремиться к созданию необходимого и достаточного количества классов и методов, чтобы система оставалась удобной для восприятия и эффективной в работе.

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

Дублирование может проявляться в разных формах, начиная от прямого копирования строк кода до более тонких видов, таких как дублирование логики или алгоритма. Устранение дублирования помогает снизить сложность системы, уменьшить риск внесения ошибок при изменении кода и упростить процесс обновления и поддержки системы.

Пример с методами `scaleToOneDimension` и `rotate`, демонстрирующими незначительное дублирование, показывает, как можно устранить повторяющийся код путем выделения общей логики в отдельный метод `replaceImage`. Это не только упрощает код, но и делает его более читаемым и легче для поддержки. Дополнительно, создание отдельного метода может способствовать его повторному использованию в других частях системы, что еще больше уменьшает дублирование и способствует сокращению сложности.

Паттерн «Шаблонный метод» приводится как стандартное решение для устранения дублирования на высоком уровне. Этот паттерн позволяет определить скелет алгоритма в базовом классе, оставляя реализацию некоторых шагов алгоритма на усмотрение подклассов. Такой подход обеспечивает гибкость и повторное использование кода, позволяя различным подклассам специализировать части алгоритма без необходимости переписывать общую структуру.

В примере с политикой отпусков, использование шаблонного метода позволяет избежать дублирования кода между методами `accrueUSDivisionVacation` и `accrueEUDivisionVacation` путем выделения общего алгоритма в базовый класс и предоставления специализированных реализаций для различных условий минимальной продолжительности отпуска в подклассах. Это значительно упрощает поддержку и расширение функциональности системы, так как изменения в общей логике требуют корректировки в одном месте, что снижает вероятность ошибок и упрощает внесение изменений.

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

1. **Выразительность кода**: Код должен четко выражать намерения своего автора, чтобы другим программистам было легко понять его смысл. Хорошие имена для классов и функций, небольшой размер функций и классов, стандартная номенклатура и хорошо написанные модульные тесты способствуют выразительности кода. Это уменьшает время, необходимое для разбора и понимания кода другими разработчиками и уменьшает риск появления дефектов при изменениях в коде.

2. **Минимум классов и методов**: Важно стремиться к минимуму классов и методов, сохраняя при этом компактность функций и классов. Однако следует избегать излишнего догматизма и следовать более прагматичному подходу. Это правило является менее приоритетным по сравнению с выполнением тестов, устранением дублирования и обеспечением выразительности кода, но всё же важно для создания компактной и эффективной архитектуры.

В заключение, эти простые правила помогают разработчикам создавать понятный, выразительный и эффективный код, что способствует уменьшению сложности системы и снижению затрат на её сопровождение. Они представляют собой кристаллизацию многолетнего опыта и помогают разработчикам избегать распространенных ошибок при проектировании программного обеспечения.

13. Многопоточность

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

Однако, как было отмечено, многопоточное программирование сопряжено с множеством трудностей и подводных камней. Основные проблемы включают в себя:

1. **Состояние гонки (Race Conditions):** Когда два или более потока одновременно пытаются изменить общие данные, результат зависит от того, какой поток получит доступ первым, что может привести к непредсказуемым и ошибочным результатам.

2. **Взаимные блокировки (Deadlocks):** Ситуация, когда два или более потоков взаимно ожидают освобождения ресурсов, захваченных друг другом, что приводит к заморозке выполнения.

3. **Проблемы с производительностью:** Несмотря на то что многопоточность может повышать производительность, неправильное использование многопоточности может привести к ухудшению производительности из-за издержек на управление потоками, синхронизацию и контекстные переключения.

Чтобы преодолеть эти трудности и написать чистый, эффективный многопоточный код, рекомендуется следовать нескольким ключевым принципам и лучшим практикам:

— **Изоляция и независимость потоков:** Постарайтесь минимизировать общие данные между потоками и делать каждый поток максимально независимым.

— **Инкапсуляция и сокрытие данных:** Ограничьте доступ к данным, которыми могут пользоваться несколько потоков, и используйте синхронизацию для контроля доступа к этим данным.

— **Использование высокоуровневых абстракций и библиотек:** Вместо ручного управления потоками предпочтительно использовать высокоуровневые абстракции, такие как пулы потоков, фьючерсы, промисы и асинхронные операции, предоставляемые современными языками программирования и фреймворками.

— **Тестирование и отладка:** Многопоточные программы требуют тщательного тестирования, включая стресс-тестирование при высоких нагрузках и использование специализированных инструментов для обнаружения гонок данных, взаимных блокировок и других проблем многопоточности.

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

### Модель «Читатели-писатели»

Модель «читатели-писатели» акцентирует внимание на управлении доступом к общим ресурсам в условиях, когда одновременно присутствуют операции чтения, которые могут безопасно выполняться параллельно, и операции записи, требующие исключительного доступа. Основная задача заключается в обеспечении эффективного параллельного чтения при сохранении корректности данных при обновлениях. Ключевые сложности связаны с необходимостью нахождения баланса между предоставлением доступа к чтению для максимального числа читателей и гарантированием возможности обновления данных писателями без введения излишних задержек и гонок данных.

### Модель «Обедающих философов»

Модель «обедающих философов» иллюстрирует проблемы управления ограниченными ресурсами среди конкурирующих потоков, которые могут вступать в взаимные блокировки или ждать ресурсы бесконечно. Эта модель ярко демонстрирует необходимость внимательного проектирования механизмов синхронизации, чтобы избежать «deadlocks» и «starvation», обеспечивая при этом справедливое распределение ресурсов среди всех участников.

### Общие Рекомендации по Многопоточному Программированию

— **Изучение базовых алгоритмов:** Глубокое понимание классических многопоточных моделей и алгоритмов помогает в разработке и отладке современных приложений.

— **Осторожность с зависимостями между синхронизированными методами:** Избегайте создания сложных взаимозависимостей, которые могут привести к взаимным блокировкам и ухудшению производительности.

— **Минимизация синхронизированных секций:** Стремитесь к уменьшению объема кода, защищенного блокировками, для увеличения параллелизма и производительности.

— **Планирование механизмов корректного завершения:** Разрабатывайте механизмы для безопасного и корректного завершения работы потоков, чтобы избежать висячих процессов и утечек ресурсов.

— **Тщательное тестирование:** Многопоточные приложения требуют комплексного подхода к тестированию, включая стресс-тесты и использование специализированных инструментов для выявления гонок данных, взаимных блокировок и других проблем.

Многопоточное программирование открывает большие возможности для повышения производительности и отзывчивости приложений, но требует аккуратного проектирования, глубокого понимания проблем синхронизации и внимательного отношения к тестированию и отладке. Разработчикам важно быть осведомленными о классических моделях, типичных ошибках и лучших практиках проектирования для создания надежных и эффективных многопоточных решений.

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

### Ручная инструментовка

Ручная инструментовка предполагает внесение изменений в код вручную для имитации различных условий выполнения и выявления потенциальных проблем. Она позволяет разработчикам контролировать точки в коде, в которых могут возникать конкурентные ситуации, и проверять поведение системы в различных сценариях. Однако этот метод требует значительных усилий для правильного выбора мест вставки специальных вызовов и не гарантирует полного покрытия всех возможных сценариев.

### Автоматизированная инструментовка

Автоматизированная инструментовка предлагает более широкие возможности для тестирования многопоточного кода, автоматически вставляя вызовы в код на этапе компиляции или выполнения. Используя инструменты, такие как Aspect-Oriented Programming (AOP) фреймворки, разработчики могут без изменения исходного кода ввести дополнительные проверки и изменения поведения программы. Это позволяет проводить тестирование в различных конфигурациях и условиях, значительно повышая шансы на обнаружение скрытых ошибок.

### Заключение

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

14. Последовательное очищение

Процесс написания чистого кода, как правило, не является одномоментным действием, а скорее итеративным процессом постепенного улучшения и рефакторинга существующего кода. Пример с разработкой системы для разбора аргументов командной строки (Args) иллюстрирует, как может происходить этот процесс: от первоначального прототипа к тщательно спроектированной и чистой реализации.

### Итеративный процесс создания чистого кода

1. **Написание первоначального кода:** В первую очередь, целью является получение работающего решения, которое удовлетворяет поставленным требованиям. На этом этапе важно сконцентрироваться на реализации функционала без излишней заботы о качестве кода.

2. **Рефакторинг:** После того как первоначальная версия кода написана и успешно работает, следует переходить к его улучшению и оптимизации. Рефакторинг направлен на улучшение читаемости кода, уменьшение сложности, удаление дублирования и улучшение архитектуры системы, при этом не изменяя её поведение.

3. **Применение паттернов и принципов:** В процессе рефакторинга полезно применять известные паттерны проектирования и принципы чистого кода, такие как принцип единой ответственности (SRP), принцип открытости/закрытости (OCP) и другие. Это помогает упростить код, сделать его более гибким и легким для дальнейшего расширения.

4. **Тестирование:** Неотъемлемой частью процесса является написание и запуск тестов, которые гарантируют, что после внесения изменений система продолжает корректно работать. Автоматизированные тесты служат сетью безопасности, позволяя проводить рефакторинг с уверенностью в отсутствии регрессии.

5. **Повторение:** Разработка чистого кода — это непрерывный процесс. По мере развития проекта и добавления нового функционала, код следует постоянно рефакторить и улучшать, поддерживая высокое качество и чистоту архитектуры.

Ключевым моментом в разработке программного обеспечения является понимание того, что написание чистого и качественного кода требует времени и усилий. Не стоит ожидать, что идеальное решение будет найдено с первой попытки. Вместо этого, разработчики должны стремиться к постоянному совершенствованию своего кода через рефакторинг и применение лучших практик программирования. Подход, основанный на итеративном улучшении кода, позволяет постепенно добиваться высокого качества продукта и удовлетворения потребностей пользователей.

Процесс создания чистого кода часто начинается с «черновой» версии, которая позволяет решить поставленную задачу, но может оставлять желать лучшего с точки зрения структуры, организации и читаемости. Разработка класса `Args` для разбора аргументов командной строки в Java является отличным примером такого итеративного процесса, в ходе которого первоначальный код постепенно трансформируется в более чистую и оптимизированную версию.

### Первая версия: Простой подход

Изначальный код (листинг 14.9) фокусируется на базовом функционале – обработке булевых аргументов. Этот код, хотя и не идеален, обладает определенной структурой и ясностью. Он представляет собой хорошую отправную точку для дальнейшего развития и добавления новых функций.

### Расширение функционала: Добавление новых типов аргументов

Как только к базовой версии добавляется поддержка строковых и целочисленных аргументов (листинг 14.10), код начинает усложняться. Каждый новый тип аргумента требует добавления новых ветвей условий и обработчиков, что приводит к увеличению объема кода и снижению его читаемости. Это естественный процесс, который отражает увеличение функциональной сложности программы.

### Рефакторинг: Путь к чистоте

Для преобразования начальной версии в более чистую и организованную форму требуется рефакторинг. Основные шаги включают в себя:

— **Выделение повторяющегося кода:** Общие паттерны и структуры в различных ветвях обработки аргументов можно выделить в отдельные методы или классы.

— **Использование полиморфизма:** Вместо множества условных операторов для обработки различных типов аргументов можно применить полиморфизм, определив общий интерфейс или абстрактный класс для аргументов и реализовав его в конкретных классах-обработчиках для каждого типа аргумента.

— **Улучшение обработки ошибок:** Вместо использования общих исключений `ParseException` и `ArgsException` можно создать более специфичные исключения или использовать систему кодов ошибок для более точного описания возникших проблем.

— **Упрощение интерфейсов:** Методы и классы должны иметь четкие и понятные интерфейсы, что упрощает их использование и тестирование.

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

Описанный процесс рефакторинга класса `Args` для разбора аргументов командной строки в Java представляет собой подробный пример того, как можно постепенно улучшать код, делая его более читаемым, понятным и легко поддерживаемым. Этот процесс демонстрирует несколько ключевых моментов разработки программного обеспечения:

1. **Постепенное Улучшение:** Каждое изменение, внесенное в код, должно немедленно проверяться тестами, чтобы убедиться, что код продолжает работать как ожидается. Это подход разработки через тестирование (TDD), который помогает предотвратить регрессии и убедиться, что каждое новое изменение улучшает систему без нарушения существующей функциональности.

2. **Рефакторинг с Целью Упрощения:** Переработка кода направлена на его упрощение и улучшение структуры без изменения поведения. Это позволяет сделать код более модульным, улучшить его читаемость и упростить поддержку.

3. **Использование Полиморфизма:** Введение класса `ArgumentMarshaler` и его наследников позволяет обрабатывать разные типы аргументов (булевы, строковые, числовые) единообразно, используя полиморфизм. Это уменьшает необходимость в условных операторах и упрощает добавление поддержки новых типов аргументов.

4. **Итеративное Добавление Функциональности:** Добавление новых типов аргументов (например, вещественных чисел) продемонстрировало, как можно постепенно расширять функциональность системы, не нарушая существующего кода.

5. **Отделение Логики Обработки Ошибок:** Выделение класса `ArgsException` для обработки исключений сделало код более чистым и позволило централизованно управлять сообщениями об ошибках.

6. **Принцип Единой Ответственности:** Разделение кода на классы и методы, каждый из которых отвечает за одну задачу, улучшило читаемость и упрощает поддержку.

Заключение подчеркивает важность поддержания чистоты кода не только для упрощения текущей разработки и поддержки, но и для обеспечения устойчивости проекта к изменениям в будущем. Регулярный рефакторинг и применение лучших практик разработки помогают избежать деградации кода и обеспечивают его долгосрочную жизнеспособность.

15. Внутреннее строение JUnit

Процесс рефакторинга кода, представленный в примере с `ComparisonCompactor` из JUnit, иллюстрирует важные аспекты разработки программного обеспечения, такие как непрерывное улучшение кода, внимание к деталям и стремление к простоте и читаемости. Данный пример подчеркивает несколько ключевых моментов:

1. **Итеративное Улучшение:** Рефакторинг — это процесс, который требует терпения и внимания к деталям. Каждое маленькое изменение должно улучшать код, делая его более понятным и легко поддерживаемым. Итеративное применение небольших изменений позволяет постепенно улучшать структуру кода без риска его «сломать».

2. **Тестирование:** Важность тестирования не может быть переоценена. Оно обеспечивает уверенность в том, что после внесения изменений код продолжает работать корректно. В примере активно используется набор тестов для подтверждения работы кода на каждом этапе рефакторинга.

3. **Читаемость и Простота:** Цель рефакторинга — не только улучшить внутреннюю структуру кода, но и сделать его более понятным для других разработчиков. Упрощение выражений, имен переменных и структуры кода способствуют лучшему пониманию и облегчают поддержку.

4. **Удаление «Магических» Чисел и Строк:** Замена литеральных значений на константы с понятными именами делает код более читабельным и упрощает его изменение в будущем.

5. **Инкапсуляция:** Сокрытие деталей реализации внутри методов улучшает структуру кода, делая его более модульным и упрощая внесение изменений.

6. **Использование Полиморфизма и Абстракций:** В примере не было показано прямое применение этих концепций, но они часто играют ключевую роль в упрощении и обобщении кода при рефакторинге.

7. **Принцип Единственной Ответственности:** Каждый класс и метод должны иметь одну конкретную задачу. Это упрощает понимание кода и его последующее изменение.

В заключение, процесс, показанный в примере с `ComparisonCompactor`, подчеркивает, что даже хорошо написанный код всегда имеет потенциал для улучшения. Регулярный рефакторинг с учетом принципов чистого кода способствует созданию качественного, легко поддерживаемого программного обеспечения.

16. Переработка SerialDate

Анализ кода и его последующий рефакторинг — важные аспекты разработки программного обеспечения, подчеркнутые в вашем тексте через пример с классом `SerialDate`. Этот процесс демонстрирует, как даже хорошо написанный код может быть улучшен для повышения его читаемости, удобства поддержки и расширения функциональности. В процессе анализа были выявлены и устранены различные проблемы, включая ошибки граничных условий и улучшение тестового покрытия. Вот несколько ключевых выводов из этого процесса:

1. **Тестовое Покрытие:** Даже если тесты покрывают большую часть кода, всегда есть возможность для улучшения. Написание дополнительных тестов для неохваченных случаев помогает обнаружить и исправить ошибки, которые могли быть упущены.

2. **Обработка Исключений:** Правильная обработка исключительных ситуаций важна для создания надежного ПО. Использование исключений вместо возвращения строк с ошибками делает код более чистым и упрощает его использование и обработку ошибок.

3. **Граничные Условия:** Ошибки граничных условий — распространенная проблема. Тщательный анализ и тестирование граничных случаев помогают улучшить качество кода и предотвратить потенциальные сбои в работе программы.

4. **Рефакторинг:** Рефакторинг кода с целью улучшения его структуры и читаемости важен не только для упрощения будущих изменений, но и для улучшения понимания кода другими разработчиками.

5. **Сотрудничество и Критика:** Конструктивная критика и открытость к анализу своего кода другими специалистами — ключевые факторы профессионального роста и улучшения качества разработки.

6. **Открытость Кода:** Распространение кода с открытым исходным кодом и его обсуждение в сообществе разработчиков способствует не только улучшению самого кода, но и развитию навыков всех участников сообщества.

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

Процесс рефакторинга кода, подробно описанный в вашем тексте, является отличным примером методического подхода к улучшению качества программного продукта. Вот несколько ключевых уроков, которые можно извлечь из этого процесса:

1. **Удаление избыточной информации**: Избавление от ненужной информации, такой как история изменений и избыточные комментарии, помогает сосредоточить внимание на самом коде и его функциональности.

2. **Использование перечислений (Enums)**: Преобразование групп связанных констант в перечисления повышает читаемость кода и упрощает его поддержку, делая код более типобезопасным.

3. **Улучшение тестового покрытия**: Добавление и улучшение модульных тестов увеличивает уверенность в том, что код работает правильно и что рефакторинг не вносит новых ошибок.

4. **Рефакторинг для повышения читаемости и поддерживаемости**: Упрощение сложных функций, удаление дублирующегося кода и улучшение названий методов и переменных делают код более понятным и легким для внесения изменений.

5. **Принципы SOLID и паттерны проектирования**: Применение принципов SOLID и паттернов проектирования, таких как Абстрактная Фабрика, помогает сделать код более гибким и расширяемым.

6. **Отделение логики от реализации**: Вынесение абстрактных методов и данных, специфичных для реализации, из общего кода позволяет улучшить архитектуру и облегчить внесение изменений в будущем.

7. **Обработка ошибок**: Изменение подхода к обработке ошибок с возвращения специальных значений на выбрасывание исключений делает код более надежным и упрощает его использование.

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

17. Запахи и эвристические правила

Эти рекомендации охватывают важные аспекты написания и поддержки чистого кода, а также подходы к управлению проектами программного обеспечения. Вот краткий обзор ключевых моментов:

### Комментарии

— **C1: Неуместная информация**: Комментарии должны содержать релевантную техническую информацию, а история изменений и метаданные должны управляться через системы контроля версий.

— **C2: Устаревший комментарий**: Неактуальные комментарии вводят в заблуждение и должны быть обновлены или удалены.

— **C3: Избыточный комментарий**: Комментарии не должны объяснять то, что и так очевидно из кода.

— **C4: Плохо написанный комментарий**: Качество комментариев важно; они должны быть четкими, краткими и грамотно написанными.

— **C5: Закомментированный код**: Неоставляйте закомментированный код; системы контроля версий сохранят его историю.

### Рабочая среда

— **E1: Построение состоит из нескольких этапов**: Сборка проекта должна быть максимально упрощена, желательно до одной команды.

— **E2: Тестирование состоит из нескольких этапов**: Запуск всех модульных тестов также должен быть максимально упрощен.

### Функции

— **F1: Слишком много аргументов**: Стремитесь к минимизации числа аргументов функции.

— **F2: Выходные аргументы**: Избегайте выходных аргументов; лучше изменять состояние объекта.

— **F3: Флаги в аргументах**: Флаги указывают на то, что функция делает слишком много; избегайте их.

— **F4: Мертвые функции**: Неиспользуемые функции следует удалить для чистоты кода.

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

Описания «запахов кода» от g1 до g13 подчеркивают различные аспекты, важные для поддержания качества и чистоты кода в процессе разработки программного обеспечения. Вот краткое изложение каждого из этих правил:

### g1: Несколько языков в одном исходном файле

Смешение нескольких языков программирования в одном файле может привести к путанице и усложнить поддержку кода. Хотя полностью избежать смешения языков может быть невозможно, следует стремиться минимизировать их количество и объем в исходных файлах.

### g2: Очевидное поведение не реализовано

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

### g3: Некорректное граничное поведение

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

### g4: Отключенные средства безопасности

Отключение механизмов безопасности и игнорирование предупреждений компилятора может привести к серьезным ошибкам и уязвимостям в программе. Всегда стоит искать баланс между удобством разработки и необходимостью обеспечения безопасности и надежности кода.

### g5: Дублирование

Дублирование кода увеличивает сложность поддержки и риск ошибок. Принцип DRY (Don’t Repeat Yourself) подчеркивает важность избегания повторений путем абстракции и рефакторинга.

### g6: Код на неверном уровне абстракции

Смешение высокоуровневых и низкоуровневых концепций в одном месте усложняет понимание и поддержку кода. Важно стремиться к четкому разделению абстракций и их корректному размещению.

### g7: Базовые классы, зависящие от производных

В идеальной архитектуре базовые классы не должны зависеть от своих производных. Такое разделение помогает в поддержании модульности и расширяемости кода.

### g8: Слишком много информации

Интерфейсы классов и модулей должны быть максимально простыми и ограниченными. Избыточная информация в интерфейсе увеличивает сложность использования и поддержки.

### g9: Мертвый код

Код, который не выполняется или не используется, следует удалять. Мертвый код усложняет понимание программы и ее поддержку.

### g10: Вертикальное разделение

Объявления переменных и функций должны быть расположены как можно ближе к месту их использования, чтобы упростить чтение и понимание кода.

### g11: Непоследовательность

Еди

нообразие и последовательность в коде упрощают его понимание и поддержку. Важно следовать установленным схемам и конвенциям.

### g12: Балласт

Лишние элементы в коде, такие как неиспользуемые переменные, функции или бессодержательные комментарии, следует удалять, чтобы код был чище и проще для понимания.

### g13: Искусственные привязки

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

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

Описания «запахов кода» от g14 до g25 подчеркивают важность качественного программирования и применения лучших практик в разработке ПО. Эти эвристические правила помогают разработчикам избегать распространенных ошибок и улучшать читаемость, поддержку, а также расширяемость кода. Вот краткое изложение каждого из этих правил:

### g14: Функциональная зависть

Код должен взаимодействовать с данными своего класса, а не прямо обращаться к данным и методам других классов. Если функция сильно зависит от другого класса, стоит рассмотреть возможность ее переноса.

### g15: Аргументы-селекторы

Использование аргументов для изменения поведения функции может усложнить понимание кода. Вместо этого лучше разбить функцию на несколько более мелких функций с четко определенными задачами.

### g16: Непонятные намерения

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

### g17: Неверное размещение

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

### g18: Неуместные статические методы

Статические методы уместны, когда они не зависят от состояния объекта. Однако, если предполагается полиморфное поведение, лучше использовать нестатические методы.

### g19: Используйте пояснительные переменные

Разбивка выражений на промежуточные переменные с понятными именами может значительно улучшить понимание алгоритма и упростить отладку.

### g20: Имена функций должны описывать выполняемую операцию

Выбирайте имена функций так, чтобы они четко отражали выполняемые действия, улучшая тем самым читаемость и понятность кода.

### g21: Понимание алгоритма

Убедитесь, что вы полностью понимаете, как работает ваш код, и что он делает именно то, что от него ожидается. Не полагайтесь только на прохождение тестов.

### g22: Преобразование логических зависимостей в физические

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

### g23: Используйте полиморфизм вместо if/else или switch/case

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

### g24: Соблюдайте стандартные конвенции

Единообразие кода облегчает его понимание и поддержку. Следуйте общепринятым стандартам и конвенциям в вашей команде или проекте.

### g25: Заменяйте «волшебные числа» именованными константами

Использование именованных констант вместо «сырых» числовых значений делает код более понятным и уменьшает вероятность ошибок.

Применение этих правил может потребовать времени и усилий, но в конечном итоге приведет к созданию более качественного, легко поддерживаемого и расширяемого кода.

Эти рекомендации продолжают тему написания чистого, понятного и легко поддерживаемого кода. Вот краткий обзор представленных рекомендаций:

### Будьте точны (G26)

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

### Структура важнее конвенций (G27)

Ваши архитектурные решения должны отражаться в структуре кода, которая важнее, чем следование стандартам и конвенциям. Использование полиморфизма вместо условных операторов может быть более предпочтительным в некоторых ситуациях.

### Инкапсулируйте условные конструкции (G28)

Условные конструкции лучше инкапсулировать в функции, чтобы упростить понимание и поддержку кода.

### Избегайте отрицательных условий (G29)

Отрицательные условия могут усложнить понимание кода. По возможности формулируйте условия положительно.

### Функции должны выполнять одну операцию (G30)

Разделите функции на более мелкие, если они выполняют несколько операций. Это упрощает понимание и поддержку кода.

### Скрытые временные привязки (G31)

Явно выражайте временные привязки в коде, чтобы избежать скрытых зависимостей и ошибок.

### Структура кода должна быть обоснована (G32)

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

### Инкапсулируйте граничные условия (G33)

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

### Функции должны быть написаны на одном уровне абстракции (G34)

Все инструкции в функции должны быть на одном уровне абстракции для упрощения понимания и поддержки.

### Храните конфигурационные данные на высоких уровнях (G35)

Конфигурационные данные и параметры по умолчанию должны быть доступны на высоких уровнях абстракции и передаваться вниз по иерархии.

### Избегайте транзитивных обращений (G36)

Следуйте принципу минимального знания и избегайте создания жестких связей между компонентами системы, чтобы упростить её модификацию и поддержку.

Эти рекомендации подчёркивают необходимость ясности, точности и обоснованности в проектировании и написании кода, что в итоге ведёт к созданию более чистого, понятного и легко поддерживаемого программного обеспечения.

Далее советы касаются оптимизации и организации Java кода для облегчения его понимания, обслуживания и расширения.

### Используйте обобщенные директивы импорта (J1)

Рекомендация упрощает импорт классов из одного пакета, сокращая длинные списки импортов и уменьшая зависимость от конкретных классов. Это может упростить обновление и модификацию кода, не создавая жёстких связей между классами и пакетами.

### Не наследуйте от констант (J2)

Подход с наследованием интерфейса для доступа к константам критикуется за его неявность и усложнение архитектуры кода. Вместо этого рекомендуется использовать статический импорт, который делает код проще и его намерения более ясными.

### Константы против перечислений (J3)

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

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

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

### Используйте содержательные имена (n1)

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

### Выбирайте имена на подходящем уровне абстракции (n2)

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

### По возможности используйте стандартную номенклатуру (n3)

Следование общепринятым именованиям и конвенциям упрощает понимание кода и способствует его интеграции в более широкие контексты и системы.

### Недвусмысленные имена (n4)

Имена должны ясно передавать свою роль и функцию без необходимости дополнительных объяснений, избегая двусмысленностей и недопониманий.

### Используйте длинные имена для длинных областей видимости (n5)

Длина имени должна быть пропорциональна его области видимости: короткие имена для локальных переменных и длинные для переменных с широкой областью видимости.

### Избегайте кодирования (n6)

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

### Имена должны описывать побочные эффекты (n7)

Имена должны точно отражать все, что делает функция или метод, включая любые побочные эффекты, чтобы предотвратить неожиданные поведения и ошибки.

Эти рекомендации подчеркивают, что выбор подходящих имен является ключевым аспектом качественного кодирования, влияя не только на читаемость и поддерживаемость кода, но и на его функциональность и надежность.

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

### Нехватка тестов (T1)

Тестовый пакет должен быть достаточно обширным, чтобы покрыть все возможные случаи использования и потенциальные точки отказа. Это гарантирует, что система будет надежной и устойчивой к различным вариантам входных данных и условий эксплуатации.

### Используйте средства анализа покрытия кода (T2)

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

### Не пропускайте тривиальные тесты (T3)

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

### Отключенный тест как вопрос (T4)

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

### Тестируйте граничные условия (T5)

Граничные условия часто являются источником ошибок. Тщательное тестирование этих условий помогает обеспечить корректность работы программы в критических ситуациях.

### Тщательно тестируйте код рядом с ошибками (T6)

Обнаружение ошибки в каком-то месте кода часто указывает на возможность наличия других проблем вблизи. Эти участки кода заслуживают особого внимания при тестировании.

### Закономерности сбоев и покрытия кода часто несут полезную информацию (T7, T8)

Анализ закономерностей, по которым тесты сбоят или какие части кода покрыты тестами, может помочь выявить скрытые проблемы и улучшить качество тестирования.

### Тесты должны работать быстро (T9)

Быстродействие тестов важно для поддержания высокой скорости разработки и обеспечения частого выполнения тестового пакета в процессе разработки.

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

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *