Русский
Русский
English
Статистика
Реклама

Clean code

SOLID ООП?

03.07.2020 16:05:03 | Автор: admin

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


В современной индустрии уже много десятков лет доминирует парадигма ООП и у многих разработчиков складывается впечатление, что она лучшая или и того хуже единственная. На эту тему есть прекрасное видео Why Isn't Functional Programming the Norm? про развитие языков/парадигм и корни их популярности.


SOLID изначально были описаны Робертом Мартином для ООП и многими воспринимаются как относящиеся только к ООП, даже википедия говорит нам об этом, давайте же рассмотрим так ли эти принципы привязаны к ООП?


Single Responsibility


Давайте пользоваться пониманием SOLID от Uncle Bob:


This principle was described in the work of Tom DeMarco and Meilir Page-Jones. They called it cohesion. They defined cohesion as the functional relatedness of the elements of a module. In this chapter well shift that meaning a bit, and relate cohesion to the forces that cause a module, or a class, to change.

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


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


Open Closed


SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION
Bertrand Meyer

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


При этом функция это одна из лучших абстракций (исходя из принципа сегрегации интерфейсов, о котором позже). Использование функций для обеспечения этого принципа настолько удобно, что подход уже прочно перекочевал из функциональных языков во все основные ООП языки. Для примера можно взять функции map, filter, reduce, которые позволяют менять свой функционал прямой передачей кода в виде функции. Более того, весь этот функционал можно получить используя только одну функцию foldLeft без изменения ее кода!


def map(xs: Seq[Int], f: Int => Int) =   xs.foldLeft(Seq.empty) { (acc, x) => acc :+ f(x) }def filter(xs: Seq[Int], f: Int => Boolean) =   xs.foldLeft(Seq.empty) { (acc, x) => if (f(x)) acc :+ x else acc }def reduce(xs: Seq[Int], init: Int, f: (Int, Int) => Int) =  xs.foldLeft(init) { (acc, x) => f(acc, x) }

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


Liskov Substitution


Обратимся к самой Барбаре:


If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

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


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


static <T> T increment(T number) {  if (number instanceof Integer) return (T) (Object) (((Integer) number) + 1);  if (number instanceof Double) return (T) (Object) (((Double) number) + 1);  throw new IllegalArgumentException("Unexpected value "+ number);}

Тут мы объявляем, что функция принимает тип T, не ограничивая его, что делает все типы его "подтипом" (т.е. компилятор позволяет передать в функцию объект любого типа), при этом функция ведет себя не так, как объявлена работает не для всех типов.


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


Interface Segregation


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


С одной стороны этот принцип говорит об интерфейсах, как о наборе функций, "протоколе" который обязуются выполнять реализации и казалось бы уж этот принцип точно про ООП! Но существуют другие схожие механизмы обеспечения полиморфизма, например классы типов (type classes), которые описывают протокол взаимодействия с типом отдельно от него.


Например вместо интерфейса Comparable в Java есть type class Ord в haskell (пусть слово class не вводит вас в заблуждение haskell чисто функциональный язык):


// упрощенноclass Ord a where    compare :: a -> a -> Ordering

Это "протокол", сообщающий, что существуют типы, для которые есть функция сравнения compare (практически как интерфейс Comparable). Для таких классов типов принцип сегрегации прекрасно применим.


Dependency Inversion


Depend on abstractions, not on concretions.

Этот принцип часто путают с Dependency Injection, но этот принцип о другом он требует использования абстракций где это возможно, причем абстракций любого рода:


int first(ArrayList<Integer> xs) // ArrayList это деталь реализации -> int first(Collection<Integer> xs) // Collection это абстракция -> <T> T first(Collection<T> xs) // но и тип элемента коллекции это только деталь реализации

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


def sum[F[_]: Monad](xs: Seq[F[Int]]): F[Int] =  if (xs.isEmpty) 0.pure  else for (head <- xs.head; tail <- all(xs.tail)) yield head + tailsum[Id](Seq(1, 2, 3)) -> 6sum[Future](Seq(queryService1(), queryService2())) -> Future(6)

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




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

Подробнее..

Из песочницы Академия плохого кода переводы строк, пробелы и отступы

18.09.2020 14:13:41 | Автор: admin
Привет, Хабр! Представляю вашему вниманию перевод статьи Dark code-style academy: line breaks, spacing, and indentation автора zhikin2207

image

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

Переводы строк, пробелы и отступы могут убивать.


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

Пример 1

Посмотрите на этот кусок кода. Одна идея на одной строке. Код такой чистый, что меня аж тошнит.

return elements    .Where(element => !element.Disabled)    .OrderBy(element => element.UpdatedAt)    .GroupBy(element => element.Type)    .Select(@group => @group.First());

Мы можем объединить все операторы в одну строку, но это было бы слишком просто. В этом случае мозг разработчика поймёт, что здесь что-то не так, и он разобьёт операторы слева направо. Проще простого!

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

return elements.Where(e => !e.Disabled)    .OrderBy(e => e.UpdatedAt).GroupBy(e => e.Type)    .Select(g => g.First());

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

return elements.Where(e => !e.Disabled)               .OrderBy(e => e.UpdatedAt).GroupBy(e => e.Type)               .Select(g => g.First());

Отправьте мне открытку, если этот подход пройдёт код-ревью в вашей команде.

Совет: оставьте пару операторов на одной строке и пару на отдельных строках.

Пример 2

Абсолютно та же идея здесь. Только такой код вы видите намного чаще.

var result =     (condition1 && condition2) ||     condition3 ||     (condition4 && condition5);

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

var result = (condition1 && condition2) || condition3 ||     (condition4 && condition5);

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

var result = (condition1 && condition2) || condition3 ||              (condition4 && condition5);

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

Совет: играйте с переводами строк для получения лучшего результата.

Пример 3

Что насчёт этого?

if (isValid) {     _unitOfWork.Save();    return true; } else {     return false; } 

Та же проблема, но с другой стороны. Здесь лучшим вариантом будет объединить операторы в одну строку, конечно, расставив фигурные скобки.

if (isValid) { _unitOfWork.Save(); return true; } else { return false; } 

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

Совет: объединяйте маленькие if/for/foreach операторы в одну строку.

Пример 4

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

bool IsProductValid(    ComplexProduct complexProduct,     bool hasAllRequiredElements,     ValidationSettings validationSettings){    // code}

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

bool IsProductValid(ComplexProduct complexProduct, bool hasAllRequiredElements, ValidationSettings validationSettings){    // code}

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

Совет: нарочно игнорируйте правило 80 символов.

Пример 5

Пустая строка в правильном месте это сильный инструмент для группировки вашего кода и ускорения его чтения.

ValidateAndThrow(product);product.UpdatedBy = _currentUser;product.UpdatedAt = DateTime.UtcNow;product.DisplayStatus = DisplayStatus.New;_unitOfWork.Products.Add(product);_unitOfWork.Save();return product.Key;

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

ValidateAndThrow(product);product.UpdatedBy = _currentUser;product.UpdatedAt = DateTime.UtcNow;product.DisplayStatus = DisplayStatus.New;_unitOfWork.Products.Add(product);_unitOfWork.Save();return product.Key;

Совет: вставляйте пустые строки рандомно.

Пример 6

Когда вы делаете коммит в репозиторий, у вас есть крошечная возможность посмотреть, что именно вы собираетесь коммитить. НЕ ДЕЛАЙТЕ ЭТОГО! Это нормально, если вы добавили лишнюю пустую строку как здесь.

private Product Get(string key) {    // code}private void Save(Product product) {    // code}

Или, что еще лучше, добавили несколько пробелов на пустой строке (чтобы понять разницу, выделите 5ю строку).

private Product Get(string key) {    // code}    private void Save(Product product) {    // code}

Зачем вам это нужно? Код продолжает работать (но это не точно). Вы продолжаете понимать ваш код, но другой разработчик будет понимать ваш код меньше. Вы не можете просто добавить несколько лишних пробелов в общие методы сразу (код-ревью наш враг), но использование этой практики создаст беспорядок уже через пару недель активной разработки.

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

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

Совет: не смотрите на код перед коммитом.

Пример 7

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

product.Name = model.Name;product.Price = model.Price;product.Count =  model.Count;

Совет: знай своего врага.

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

Развлекайтесь.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru