Чистый код создание, анализ и рефакторинг [2019] Роберт Мартин.pdf
Чистый код Роберт Мартин
Вы должны узнать принципы, паттерны приемы и эвристические правила и втереть полученные знания в свои пальцы. При накоплении плохого кода и внесении в него изменений жестко зависищих, происходит уменьшение скорости разработки и не возможности вносить новые фичи. Все это происходит под давлением сроков и лени. Соотношение чтение и написания кода 10:1 так что очень важно как легко его читать. Имя переменной,класса,ф-ции должно сообщить почему она существует, что делает, как используется код который не содержит имен не индефицируемый.
Избегайте имен без смысловой нагрузки или дизинформирующих имен и схожих с другими. Названия интерфейсов без I => FactoryImpl но не IFactory Имена классов/типов - существительные worker , имена методов/ф-ций - глаголы 3-го(He,She) лица prints Функция должна выполнять одну задачу иначе разделите на несколько функций. Избавьтесь от побочных эффектов внутри ф-ции о которых не ясно из ее названия и контекста. Привер: ф-ция UserValidator проверяет имя пользователя но при этом инициирует в случае успеха новую сессию! Тогда тот кто решит проверить имя пользователя потеряет сеансовые данные. Разделение ф-ции на команду и запрос Пример: Ф-ция set(name) не должна проверять имя она просто его устанавливает, а вот другая ф-ция эти должна заниматься exists(name)
Ф-ции не должны возвращать коды ошибок, а должны выбрасывать исключения, таким образом убираются лесенки проверок кодов. Изолируйте блок try/catch в отдельную ф-цию, это упростит основной код для понимания и улучшит его модификацию
Магнит зависимостей. Если класс зависит от перечисления с ошибками то при его изменении потребуется пересобирать зависищие от него классы, лучше использовать класс исключения для ошибок тогда новый тип ошибки будет просто производным от класса исключения и пересобирать зависимые классы не придется. Это SOLID open/close. Не повторяйтесь. Повторяющийся код увеличивает обьем кода и количество мест для изменения, выносите в отдельную ф-цию.
Open/close что бы не нужно было удалять определенный функционал из всего кода, используйте не зависимый код от изменений.
Методы без side-effects. Все get методы не меняют данные. Если метод что то возвращает то он не должен ничего изменять, входящие параметры остаются не тронутыми и состояние. Если метод void он что-то проверяет или изменяет. Метод что-то проверяющий должен быть boolean. Если в методе есть выборка и сортировка данных и возврат этих данных то нужно разделить эти обязанности на два метода. Не возвращайте аргументы в качестве возврата, создайте новый обьект из входящих аргументов и верните, из-за того чтоусложняется рефакторинг(!?)
Глава 4 Комментарии
Писать Комментарии:
- TODO когда нужно описать что-то с кодом в будущем когда будут подходящий момент
- Описать намерение или предупреждение все что в коде будет затрудненно понять из него
- Если поведение не понятно то можно его прокомментировать
- Предупреждение о последствиях
Не писать комментарии:
- Лучше написать выразительный код, дать описывающее имя ф-ции и переменной
- Не пишите не помогающий или безполезный комментарий еще хуже не верный
- Не оставляйте закомментированный код, он будет всегда висеть мусором
- Цель комментария - обьяснить код который не может обьяснить сам себя.
Глава 5 Форматирование
Относитесь к форматированию кода как к газете, в глаза бросаются главные моменты, а детали не мешают быстрому пониманию. Вертикальное форматирование. Сбивайте строки вместе если они связаны. Разбивайте строки,блоки если они не тесно связаны. Горизонтальное форматирование 80-120 символов.
Глава 6 Обьекты и структуры данных
Отличайте класс предметной области (имеют поведение, валидация) и структуры DTO data transfer object не имеют поведения (нет валидации,проверок, содержат только данные и могут быть переданны в другую систему так как не тянут за собой связей).
Главный вопрос задайте себе - что и где потребуется изменить в случае добавлении нового типа или поведения типа(способа работы с ним)! Приватные свойства служан не просто для скрытия внутренней реализации,а для ограничения зависимости их от реализаци. Мы хотим свободно менять тип и способ работы с приватными свойствами. Тогда следует предоставлять абстрацию для работы с ними. Но является ошибкой использовать приватные свойства как открытые, что тянет за собой изменение всего зависимого кода при изменении этих свойств!
Подход определяет функции работающие с обьектом, если функция реализована в самом обьекте то это ООП, если функция реализованна как внешняя то это процедурный подход, а если смешано то это Гибрид и это плохо! Скрытые реализации не сводятся просто к созданию прослойки ф-ции между переменными. Они направленны на формирование абстракций, класс должен предоставить абстрактный доступ через интерфейс к приватным свойствам и пользователь будет оперировать уже с сущьностью данных, знать как данные реализованны ему не нужно. Отличие обьектов от структур данных то и другое может быть простым классом отличие в работе со свойствами.
ООП подход оперирует обьектом который скрывает приватные свойства за абстракцией. Процедурный подход оперирует структурой данных которая не реализует в себе работу с ней. Они взаимоисключающие подходы предлагают.
Отличие подходов в том что будет в программе изменяется. Так как ООП подход предполагает работу с обьектом в самом обьекте то используйте ООП подход (т.е. реализация через приватные свойства с доступом через абстракцию интерфейса) когда будут добавлятся новые тип но не методы работы с ними. Иначе если будут добавлятся методы работы используйте процедурный стиль в противном случае придется менять все обьекты, добавляя в каждый свой методод работы с ним.
Так как процедурный подход не держит в обьекте методы работы с ним, а предполагает наличие внешних обьектов использующих такой тип обьекта то при добавлении новых методов работы с обьектом не придется переделывать все остальные обьекты так как код содержится во внешней среде, но если будут добавлятся новые типы обьектов то это потребует изменение внешней стреды(т.е. всех функций где эти обьекты используются) работы с ними и лучше взять ООП подход.
Закон Деметры
Не вызывайте у метода его методов методы. Накладывающий ограничения на взаимодействия объектов. Функциональная зависть т.е. мы завидуем обьекту у которго есть эта функциональность и хотим ее себе. (не лезть во внутреннее устройство возвращаемых обьектов ООП подхода, для процедурного подхода эти свойства открыты не распространяется закон Деметры) Аналогия из жизни: Если Вы хотите, чтобы собака побежала, глупо командовать её лапами, лучше отдать команду собаке, а она уже разберётся со своими лапами сама.
Основной идеей является то, что объект должен иметь как можно меньше представления о структуре и свойствах чего угодно (включая собственные подкомпоненты). Модули должны обладать информацией только о тех модулях,с которыми они непосредственно взаимодействуют,а не обладать навигационной картой всей системы. Пример транзитивного вызова: вызов a.getB().getC().doSomething() Будет трудно изменить архитектуру и вставить промежуточное звено, потребуется все места такого кода переписывать Не заставляейте пользователя странствовать по всему графу компонентов.
Гибриды Если вы не можете спроектировать программу так что вы не знаете что защищать от изменений ф-ции в случае с проедурным подходом или типы в случае ООП подхода то это самый худший вариант, вы в любом случае будете терять время. Это такой обьект без абстракции работающий как структура данных, т.е. присутствуют внутрение методы самого обьекта без абстракции и есть открытые свойства .
Обьекты передачи данных DTO. DTO это структура данных процедурный подход, с открытыми переменными и без функций.лужат для преобразования данных полуученных от низкоуровневых слоев базы данных или сокета.
Форма bean-компонет - это тоже процедурный подход т.е. хоть и есть приватные свойства и методы чтения/записи с ними но ф-ций нет.
Ф-ция в обьекте работает со всеми свойства обьекта Метод в обьекте работает на чтение/запись с одним свойством Active Records активные записи это разновидность DTO т.е. это структуры данных процедурного подхода т.е. это не обьект из ООП! Хотя в нем и присутствуют ф-ции работы с ним save,find но это не обьект и свои ф-ции и бизнес логику в нем реализовывать не следует. Для их использования следует создать обьект ООП (т.е. поведение в нем реализовать )в нем реализовывать бизнес-логику при этом скрывая свои внутренние данные которые могут быть структурой данных Active Records Заключение Обьекты предоставляют поведение (через внутренние ф-ции) и скрывают данные за абстракцией.Это позволяет программисту добавлять новые виды обьектов, не изменяя поведения.С другой стороны обьекты усложняют добавление нового поведения.
Структуры данных предоставляют данные но не обладают поведение, все снаружи. Они упрощаю добавление нового поведения в существующие структуры данных, но затрудняют добавление новых структур данных в существующие ф-ции.
Глава 7 Обработка ошибок
Рекомендуется не использовать коды ошибок так как они загромождают код, а использовать исключения. Для большого количества варинтов типов исключений используейте классы обвертки Не возвращайте null. Это ведет к постоянной проверки и возможным обкам при пропуске этой проверки, возвращайте пустую обвертку ""особый случай"" или исключение или дефолтное значение по ситуации. Код становится чище и нет вероятности вызова на null. Не передавайте null (исключение методы сторонних API ) можно реализовать исключение но что будет делать обработчик этого исключения? Лучше запретить передачу null по умолчанию.
Глава 8 Границы
Рекомендация ограничить от деталей реализации обьектов сторонних библиотек.Допустим если мы оперируем коллекцией Map то она предоставляет избыточный функционал который может изменится к тому же, просто создайте класс и спрячте обьект сторонней библиотеки за своей абстракцией. Предлагают писать тесты для самих внешних библиотек, что бы понимать что ошибка не у нас.И в плане учебной практики самой библиотеки. Меньше зависимости от стороннего кода посредством своей абстракции и паттерна АДАПТЕР
Глава 9 Модульные Тесты
FIRST Быстрые Независимые Повторяемые Очевидные Своевременные
Заключение Тесты сохраняют и улучшают гибкость, удобство сопровождения и возможность повторного использования кода продукта.
Глава 10 Классы
Классы должны быть короткими. Сохраняйте приватность классов, ослабление инкапуляции всегда должно быть последней мерой. Имя класса должно описывать его ответственность. При описании класса если встречаются слова (и,или,если,но) возможно класс наделен избыточной ответственностью. SRP Тоесть должна быть одна ответственность что тянет за собой лишь одну причину для изменения, в случае множества ответственностей и изменений будет много.
Структурирование с учетом изменений. Следующее изменение не должно затрагивать существующий код. В идеале новая функциональность должна реализовываться расширением системы, а не внесением изменений в существующее решение. Пример: Класс Sql содержит методы select,create и каждый из них еще использует приватные вспомогательные методы только для него, а метод update и другие будут добавлены посже. Тут нарушается принцип единственной ответственности Singl responsible так как потребуется больше одной причины для изменения класса из-за нескольких обязанностей. И нарушается принцип open/close т.е. при его расширении путем добавления нового метода нам придется его изменять,а ведь он должен быть закрыт для изменений но открыт для расширения. Выход: Реализовать абстрактный класс и для каждой обязанности select,create,update создать наследуемые классы со своими приватными методами характерными только для них или общим методов в самом абстрактном классе. Тогда добавление новой обязаности не будет нарушать принцип open/close так ка мы не меняем сам абстрактный класс и не изменяем его производные классы они остаются закрытыми, а только расширяем его.
Принцип DIP - классы системы должны зависеть от абстракции, а не от конкретной подробности.
Пример: Как протестировать класс получающий данные от внешнего API который присылает постоянно меняющиеся данные. Вывод: Класс должен зависеть не от конкретного обьекта API, а от абстрактного класса реализующего интерфейс , тогда можно будет при тестировании подставить обьект реализующий этот интерфейс и отправлять постоянные данные.
Именование:
- Названия должны нести смысл
- Не обманывайте
- Произносимые названия
- Поиск по названию должен быть осуществим
- Не кодируйте название
- Классы длжны быть существительными,а методы - глаголами.
- Однотипные названия для избежания написания дубля
- Используйте слова из предметной области исключая разночтения и ограниченый их список
- Включайте названия в контекст для понимания (название поля state не ясно к чему оно относится,а AddressState ясно )
- Не используйте лишнего контекста
Глава 11 Системы
Отделение старта системы от использования, так что бы они не знали друг о друге,не размазывайте весь старт системы на весь код. Система должна иметь будущую возможность к масштабированию. Обсуждается модульность и разделение ответственности и принятие решения в последний момент с достаточными данными.Все это уменьшает сложность системы. Заключение: Агрессивная "всепроникающая" архитектура скрывает логику предметной области и снижает гибкость.Используйте самое простое решение из всех возможных.
Правила простого дизайна (по Кенту Беку):
- Запускаютсявсе тесты
- Не содержит повторений
- Выражает намерение программиста
- Минимальное количество классов и методов
Глава 12 Формирование архитектуры
Вы должны знать принципы проектирования:
- повышение связанности
- устранение жестких привязок
- разделение ответственности
- изоляция системных областей ответственности
- сокращение обьема класса и ф-ции
- выбор содержательных имен,документация,тестирование
ШАБЛОННЫЙ МЕТОД устраняет дублирование, работает через конкретную реализацию классом нужного метода абстракции.
Модули со схожими алгоритмами, но содержащие похожие куски кода. Можно применять и ШАБЛОННЫЙ СТРАТЕГИЯ Догматичность Плохо слишком дотошно подходить к принципам проектирования что может привести к избыточности. К примеру ошибочно делать для каждого класса свой интерфейс или разделять поле данных и поведение класса на данные и класс поведения.Целью должна быть компактная система при одновременной компактности классов и ф-ций.
Глава 13 Многопоточность
Модель производитель-потребитель проблема доступа к общему ресурсу очереди сообщений для координации используйте передачу сигнала. Модель "читатели-писатели" при операции писателя блокируется общий ресурс и читатели ждут и наоборот при очереди читателей операция писателя ждет.Необходим баланс. Модель "обедающие философы" проблема в конкуренции потоков к общим ограниченным ресурсам когда один поток блокирует ресурс необходимый другому потоку. Вывод: изучайте алгоритмы и тестируйте их для решения этих задач. Остерегайтесь зависимостей между синхроннизированными методами когда используется несколько методов одного совместного обьекта.
Глава 14 Последовательное очищение
После рабочей грязной версии кода его следует очистить и привести в порядок, причесать.
Глава 15 JUnit
"Правило бойскаута" оставить чужой код хотя бы немного лучше, чем он был.
Глава 17 Запахи и эвристические правила
Комментарии:
- С1:Неуместная информация Должна быть техническая инфолрмация и о архитектуре кода, но не имя автора или история изменений для этого есть другие способы.
- С2:Устаревший комментарий Комментарии могут устаревать и как следствие содержат неверную информацию
- С3:Избыточный комментарий Когда описывает очевидные вещи. К примеру сигнатуру ф-ции которая и так читается.
- С4:Плохо написанный комментарий Тщательно выбирайте слова.Следите за правильностью орфографии и пунктуации.Не пишите сумбурно.Будьте лаконичны.
- С4:Закомментированный код Удаляйте закомментированный код, он лишний и никогда не будет использоваться, только мешает. Напишите TODO лучше комментарий
Функции:
-
F1:Слишком много аргументов В идеале без аргументов далее с одним, но больше 4-х аргументов весьма сомнительны.
-
F2:Выходные аргументы Противоестественно возвращать аргументы переданные ф-ции на вход как входные.Если ваша ф-ция собираентся изменять чье-либо состояние, пусть она изменяет состояние обьекта, для которого она вызывалась.
-
F3:Флага в аргументах Логические аргументы явно указывают на наличие более одной операции, а должна быть одна, и это сильно запутывает код.Исключите флаги из кода.
-
F4:Мертвые ф-ции Если метод ни разу не вызывался в программе, то его следует удалить. если пригодится то есть история версии.
-
G2:Очевидное поведение не реализованно Ф-ция или класс должны реализовывать ожидающее поведение иначе затрудняет понимаение.
-
G3:Некорректное граничное поведение Каждый особый случай способны сломать код не поленитесь и напишите тестовый код для таких случаев.
-
G6:Код на не верном уровне абстракции Высокоуровневые и низкоуровневые концепции не должны смешиваться, разделяйте абстракции.Функции заглушки врут и это не решает проблему неверного размещения абстракции.
-
G8:Слишком много информации Делайте компактные интерфейсы.Чем меньше методов содержит класс тем лучше.Скрывайте свои данные,вспомогательные ф-ции.Не создавайте классы с большим количеством переменных и ф-ций.Сокращайте логические привязки за счет ограничения информации.
-
G11:Последовательность Выбрав эдиный стиль продолжайте его что бы не нарушать принцип наименьшего удивления, продолжайте линию во всем коде, продолжайте называть переменные как делали до этого.
-
G15:Аргументы-селекторы Висячий в конце ф-ции агрумент false не дает ясности что он делает и к томуже эта ф-ция содержит два поведения.Следует разбить ее на две ф-ции.
-
G18:Неуместные статические методы Если от функции не будет требоваться полиморфное поведение тогда она может быть статической не иначе.
-
G22:Преобразование логическое зависимости в физическую Зависимый модуль не должен делать предположений (иначе говоря создавать логические связи) относительно зависещего модуля.Вместо этого он должен явно запросить всю необходимую информацию. Пример:Константа класса которую использует другой класс, должен быть метод возвращающий эту константу что бы зависимый класс к ней не обращался на прямую.
-
G23:Используйте полиморфизм вместо if/else или switch/case
-
G33:Инкапсулируйте граничные условия Расместите их обработку в одном месте.Пример:В коде встречается запись "level+1" в нескольких местах, это граничное условие, которое следует инкапсулировать(упаковать) в переменную например "nextLevel"
-
N2:Выбирайте имена на подходящем уровне абстракции Не используйте имена, передающие информацию о реализации.Имена должны отражать уровень абстракции, на котором работает класс или ф-ция.Сложно сделать из-за частого смешения уровней абстракции. Тоесть если класс работает с множеством разных устройств не нужно давать имена методам как буд-то он работает с одним типом устройств.
-
N4:Недвусмысленные имена Выбирайте имена , которые недвусмысленно передают назначение ф-ции или переменной N5:Используйте длянные имена для длинных областей видимости, короткие имена для малых областей видимости но содержательные
Дополнительный материал: Sergey Nemchinskiy Чистый код (clean code) или Как помыть кота