Философия создания и развития ПО
"Speed depends on stability" - Martin Fowler
Vladimir Minkin (vk.com/vladimir.minkin)
Когда требования определяются настолько подробно, чтобы они могли бы быть выполнены компьютером,
это и есть программирование.
“The code itself, no matter how ideal it is, is not worth a penny. What matters is the business functionality that this code implements.” - Nikolay Ashanin
Логика должна быть достаточно прямолинейной,
чтобы ошибкам было трудно спрятаться
Бьёрн Страуступ
В автостроении основная часть работы связано не с производством, в с сопровождением продуктов.
"The code does not “care” about how cohesive or decoupled it is; people do." - Tago Fabic
При чтении чистого кода вы улыбаетесь, как при виде искусно сделанной музыкальной шкатулки или хорошо сконструированной машины.
Поддержание чистоты кода не только окупает затраченное время; оно является делом профессионального выживания.
"Fools ignore complexity. Pragmatists suffer it.
Some can avoid it. Geniuses remove it." - Alan Perlis
Чистота кода упрощает его доработку другими людьми. Код который легко читается, и код, который легко изменить - не одно и то же.
Make it work
Make it right
Make it fast
"Any fool can write code that a computer can understand.
Good programmers write code that humans can understand."
- Martin Fowler
Чистый код должен читаться как хорошо написанная проза, слова словно изчезают, заменяясь зрительными образами! Так чтобы читатель воскликнул:
«Ага! Ну конечно!»
Качество возникает в результате миллиона
проявлений небезразличного отношения к делу!
"After you finish the first 90% of a project,
you have to finish the other 90%." - Michael Abrash
Мы способны отличить хорошую картину от плохой, но это ещё не значит что мы умеем рисовать.
Бесполезно пытаться написать чистый код,
если вы не знаете что это такое!
"Good programming is good writing."
- John Shore
Допускать высказывания типа
"тестирование - это не мое дело"
нельзя.J. Hank Rainwater
Код без тестов не может быть назван чистым!
Работа программиста не заканчивается написанием кода - он должен доказать что код работает правильно.
"Tests are the Programmer's stone, transmuting fear into boredom." - Kent Beck
Если у вас есть тесты, вы не боитесь вносить изменения в код! Именно тесты делают наш код гибким, пригодным для сопровождения и повторного использования.
Без тестов каждое изменение — возможная ошибка.
- должны передавать намерение
- удобные для поиска (не однобуквенные*)
- одно слово для каждой концепции
- классы - существительные, методы - глаголы
- из пространства решений (структур)
"A sentence should be enough to get the idea across." - 37Signals
Не язык делает программы простыми.
Программа выглядит простой благодаря
работе программиста!
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live."
- Martin Golding
глагол
+ существительное
+ прилагательное
Одно из различий между умным и профессиональным программистом заключается в том, что профессионал понимает: ясность превыше всего.
Из пространства решений
- Первое правило: функции должны быть компактными.
- Второе правило: функции должны быть еще компактнее.
- Желательно чтобы длина функции не превышала 20 строк.
- Максимальный уровень отступов не превышает одного-двух.
- Не должны содержать вложенный структур (*).
"Functions should do one thing. They should do it well. They should do it only." - Robert C. Martin
Функции пишутся прежде всего для разложения более крупной концепции.
Функция должна что-то делать или отвечать на какой то вопрос, но не одновременно.
Либо функция изменяет состояние объекта,
либо возвращает информацию
Слишком много действий, сложно понять что и как она делает.
В общей сумме получили 10 + 6 строчек кода
Обработка ошибок - это одна операция.
Значит, функция, обрабатывающая ошибки,
ничего другого делать не должна!
Искусство программирования является искусством языкового проектирования.
Опытные программисты рассматривают систему как историю, которую они должны рассказать.
Ваша цель - "рассказать историю" системы, а написанные вами функции должны четко складываться в понятный и точный язык, который поможет вам в этом.
- Абстракции над абстракциями - функции в функциях - Алгебраические структуры: "monoids", "semigroups", "functors", "monads" - "Чистые" (Pure) функции
"Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function." - John Carmack
(link)
Если в некоторой системе нас прежде всего интересует гибкость в добавлении новых типов данных, то в этой части системы продпочтитение отдается объектной реализации.
В других случаях нам нужна гибкость расширения поведения, и тогда в этой части используются типы данных и процедуры.
- Комментарии в лучшем случае являются неизбежным злом.
- Неточные комментарии гораздо вреднее, чем отсутствие.
- Предупреждение о последствиях.
- Заметки на будующее - комментарий TODO: (билет).
- Комментарии к API общего пользования
"Don't comment bad code - rewrite it."
- Brian W. Kernighan & P. J. Plaugher
В программирование редко встречаются привычки более отвратительные, чем закрытие комментариями неиспользуемого кода.
Никогда так не делайте!
- Тесты и код продукта пишутся вместе.
- Тестовый код не менее важен, чем код продукта.
- Тесты как средство обеспечения изменений.
- Если не поддерживать чистоту тестов, то вы их лишитесь.
- Без тестов любое изменение становится потенциальной ошибкой!
"A clever person solves a problem.
A wise person avoids it." — Albert Einstein
Тесты важнее кода продукта, потому что тесты сохраняют и улучшают гибкость, удобство сопровождения и возможность повторного использования кода.
- Классы должны быть компактными (*). - Открытых переменных обычно нет. - Приватные вспомогательные функции, вызываемые открытыми. - Ослабление инкапсуляции должно быть последней мерой. - Классы это "ответсвенности" (описывается в имени).
"90% of the functionality delivered now is better than 100% delivered never." - Brian W. Kernighan
Инкапсулирует
абстракцию IDatabaseService
Dependencies Inversion
Гибридные структуры - наполовину объекты, наполовину структуры данных; они усложняют как добаление новых функций, так и новых структур данных (*).
Функции или типы
Не обладает поведением (может отражать структуру базы данных)
Классы должны иметь небольшое количество переменных. Каждый метод класса должен оперировать с одной или несколькими из этих переменных.
Модуль не должен знать устройство тех объектов,
с которыми он работает.
Метод не должен вызывать методы объектов,
возвращаемых любым их разрещенных функций.
Trainwreck: b.getC().getD().getE().doThing();
Код должен выражать намерение автора.
S: Single Responsibility O: Open — Closed L: Liskov Substitution I: Interface Segregation D: Dependency Inversion
"Theory and practice sometimes clash. And when that happens, theory loses. Every single time." - Linus Torvalds
(link)
- Каждый класс инкапсулирует одну ответсвенность, имеет одну причину для изменения.
- Структура системы должна быть такой, чтобы обновление (добавление или изменения) создавало как можно меньше проблем! - В идеале новая функциональность должна реализовываться расширением системы, а не внесением изменений в существующий код.
- Изменения в программных модулях (классах) не должны влиять на работу остальных частей программы при замене этих модулей на другие.
- По мере роста вашего приложения у вас возникает соблазн добавить метод к существующему интерфейсу. - Если метод не связан с интерфейсом, лучше выделить этот новый метод в отдельный интерфейс.
(sub-interfaces)
- Классы должны зависить от абстракций, а не от конкретных подробностей, для сведение к минимуму логических привязок. - Отсутствие жестких привязок означает, что элементы системы лучше изолируются друг от друга и от изменений.
- Чем с большим количеством переменных работает метод, тем выше связанность этого метода со своим классом.
- Связанность класса должна быть высокой, это означает, что методы и переменные класса зваимозависимы и существуют как единое целое.
- Если классы утрачивают связанность, разбейти их!
Большинство систем находятся в процессе непрерывных изменений. Каждое изменение создает риск того, что остальные части системы будут работать не так, как мы ожидаем. В чистой системе классы организованы таким образом, чтобы риск от изменений был сведен к минимуму.
Возможность построить
"правильную систему с первого раза" -
миф.
- Конструирование и использование системы - два совершенно разных процесса. - Фаза инициализации присутствует в каждом приложении. - Перемещение всех аспектов конструирования в main. - Приложение ничего не знает о процессе конструирования.
"Anything that can possibly go wrong, will go wrong."
- Murphy's Law
Код должен легко читаться,
даже если это затрудняет его написание.
- Механизм отделения конструирования от использования. - Практическое применение обращения контроля (IoC) - SRP. - Объект не должен брать на себя ответственность за создание экземпляров зависимостей. - Объекты могут быть с отложенной инициализацией.
"One man's crappy software is another man's full time job."
- Jessica Gaston
Невозможно написать код без предварительного
чтения окружающего кода.
В некоторых ситуациях, момент создания объекта должен определяется приложением.
Дублирование - главный враг
хорошо спроектированной системы.
DRY
Сегодня мы реализуем текущие потребности, а завтра перерабатываем и расширяем систему для реализации новых потребностей.
Разработка через тестирование, рефакторинг и полученный в результате их применения чистый код обеспечивает работу этой схемы на уровне кода.
Модульность и разделение ответсвенности позволяют децентрилизовать управление и принятие решений.
Принятие решений лучше всего откладывать до последнего момента, это позволяет принять информированное решение.
"Machines should work. People should think."
- IBM Pollyanna Principle
Основные затраты программного проекта связаны с его долгосрочным сопровождением. Чтобы свести к минимуму риск появления дефектов в ходе внесения изменений, очень важно понимать, как работает система.
"The longer it takes for a bug to surface, the harder it is to find."
- Roedy Green
Чем понятнее будет код, тем меньше времени понадобиться другим программистам, чтобы разобраться в нем. Это способствует уменьшению количества дефектов и снижению затрат на сопровождение.
"At some point software design becomes less about what and more about when." - Kent Beck
- Обеспечение прохождения всех тестов - Не содержит дублирующего кода - Выражает намерение программиста - Использует минимальное количество классов и методов
"Architecture is not about making decisions earlier,
but making decisions late." - Robert C. Martin
Большинство систем работают лучше всего, если они остаются простыми, а не усложняются. Из всех возможных вариантов выбирайте наиболее простой!
“Big fish in a small pond”
- Malcolm Gladwell (link)
- Устранение дубликатов - Устранение жестких привязок - Повышение связяности - Разделение ответственности - Изоляция системных областей (plugins) - Сокращение объема функций и классов - Выбор более содержательных имен
Следующим человеком которому придется разбираться в вашем коде, с большой вероятностью окажитесь вы сами.
С1: Неуместная информация С2: Устаревший комментарий С3: Избыточный комментарий С4: Плохо написанный комментарий С5: Закомментированный код
"When you feel the need to write a comment, first try to refactor the code so that any comment becomes superflous." - Martin Fowler
F1: Слишком много аргументов F2: Выходные аргументы F3: Флаги в аргументах F4: Мертвые функции
"A design is a plan or specification for the construction of
an object or system." - Wikipedia
G1: Несколько языков в одном исходном файле G2: Очевидное поведение не реализовано G3: Некорректное граничное поведение G4: Отключенные средства безопасности G5: Дублирование G6: Код на неверном уровне абстракции G7: Базовые классы, зависящие от производных G8: Слишком много информации
G9: Мертвый код G10: Вертикальное разделение G11: Непоследовательность G12: Балласт G13: Искусственные привязки 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: Избегайте транзитивных обращений ("закон Деметры")
"These days, the problem isn't how to innovate; it's how to get society to adopt the good ideas that already exist." - Douglas Engelbart
N1: Используйте содержательные имена N2: Выбирайте имена на подходящем уровне абстракции N3: По возможности используйте стандартную номенклатуру N4: Недвусмысленные имена N5: Используйте длинные имена для длинных областей видимости N6: Избегайте кодирования N7: Имена должны описывать побочные эффекты
T1: Нехватка тестов T2: Используйте средства анализа покрытия кода T3: Не пропускайте тривиальные тесты T4: Отключенный тест как вопрос T5: Тестируйте граничные условия T6: Тщательное тестируйте код рядом с ошибками T7: Закономерности сбоев часто несут полезную информацию
Github: Code
"The only way to go fast, is to go well."
- Robert C. Martin