Взгляните на код коллеги и узнайте секрет элегантного кода

задняя часть программист
Взгляните на код коллеги и узнайте секрет элегантного кода

У настоящего мастера всегда сердце ученика

введение

Для программной платформы качество кода программной платформы напрямую влияет на общее качество и стабильность платформы. В то же время это также повлияет на творческую страсть студентов, которые пишут код. Представьте, если код проекта, который вы клонировали из git, беспорядочный, непонятный, сложный для быстрого старта и есть желание подтолкнуть его к переписыванию, то первоначальный энтузиазм программистов писать хороший код в этом проекте пропал. . Наоборот, если структура кода при клонировании ясна, а код элегантен и прост для понимания, то вам будет неловко писать плохой код, когда вы пишете код. Я считаю, что студенты, которые работали, глубоко поняли разницу, поэтому после того, как мы увидели так много кода, какой код является хорошим кодом? Есть ли у них какие-то общие характеристики или принципы? В этой статье рассказывается о том, как писать хороший код, объясняя принципы проектирования элегантного кода.

Принципы разработки кода

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

SRP

Так называемый принцип SRP (Single Responsibility Principle) — это принцип единой ответственности. Но то, что мы видим, не обязательно означает, что мы будем это использовать, иногда мы думаем, что будем знать это сами, но столкнемся с той или иной проблемой в практическом применении. Причина в том, что на самом деле мы не продумали проблему, не продумали глубоко, знание — это просто знание, и оно не трансформировалось в наши способности. Например, упомянутый здесь принцип единой ответственности относится к чьей единственной ответственности: классу, модулю или домену? Домены могут содержать несколько модулей, а модули могут содержать несколько классов, и все это проблемы.

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

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

image.png

Основываясь на своем собственном опыте, автор резюмирует несколько областей, которые необходимо учитывать при разделении одной ответственности. Я надеюсь, что у меня будет простой стандарт для оценки необходимости разделения: 1. Различные домены бизнеса необходимо разделить, как в приведенном выше примере, и если слишком много зависимостей с другими классами, также необходимо рассмотреть, следует ли их разделять; 2. Если мы обнаружим, что при написании кода в классе есть определенные общие методы, такие как определение законности IP, анализ xml и т. д., то мы можем рассмотреть возможность извлечения этих методов для формирования общедоступного класса инструментов, чтобы другие классы также можно использовать удобно. Кроме того, идея дизайна с единой ответственностью используется не только в дизайне кода, мы также будем в определенной степени следовать этому принципу при разделении микросервисов.

OCP

OCP (Open Closed Principle) — принцип закрытости для модификации и открытости для расширения Лично я считаю, что это самый сложный принцип в принципах проектирования. Мало того, что существует определенный порог для понимания, но это нелегко сделать в реальном процессе кодирования. Прежде всего, мы должны выяснить, в чем разница между упомянутой здесь модификацией и расширением.Честно говоря, когда я впервые увидел этот принцип, я всегда чувствовал, что модификация и открытие — это не одно и то же? Думая об этом, я чувствую себя немного смущенным. Позже, в непрерывной проектной практике, понимание этого принципа проектирования постепенно углублялось.

image.png

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

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

public class Alarm {
	private AlarmRule alarmRule;
    private AlarmNotify alarmNotify;
    
    public Alarm(AlarmRule alarmRule, AlarmNotify alarmNotify) {
        this.alarmRule = alarmRule;
        this.alarmNotify = alarmNotify;
    }
    
    public void checkServiceStatus(String serviecName, int cpu, int memory) {
        if(cpu > alarmRule.getRule(ServiceConstant.Status).getCpuThreshold) {
            alarmNotify.notify(serviecName +  alarmRule.getRule(ServiceConstant.Status).getDescription)
        }
        
         if(memory > alarmRule.getRule(ServiceConstant.Status).getMemoryThreshold) {
            alarmNotify.notify(serviecName +  alarmRule.getRule(ServiceConstant.Status).getDescription)
        } 
    
    }

}

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

public class Alarm {
	private AlarmRule alarmRule;
    private AlarmNotify alarmNotify;
    
    public Alarm(AlarmRule alarmRule, AlarmNotify alarmNotify) {
        this.alarmRule = alarmRule;
        this.alarmNotify = alarmNotify;
    }
    
    public void checkServiceStatus(String serviecName, int cpu, int memory, int status) {
        if(cpu > alarmRule.getRule(ServiceConstant.Status).getCpuThreshold) {
            alarmNotify.notify(serviecName +  alarmRule.getRule(ServiceConstant.Status).getDescription)
        }
        
         if(memory > alarmRule.getRule(ServiceConstant.Status).getMemoryThreshold) {
            alarmNotify.notify(serviecName +  alarmRule.getRule(ServiceConstant.Status).getDescription)
        } 
        
         if(status == alarmRule.getRule(ServiceConstant.Status).getStatusThreshold) {
            alarmNotify.notify(serviecName +  alarmRule.getRule(ServiceConstant.Status).getDescription)
        } 
    
    }

}

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

@Data
public class ServiceStatus {
    String serviecName;
    int cpu;
    int memory;
    int status;

}

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

LSP

LSP (принцип замещения Лискова) Принцип замещения Лисков. Я думаю, что этот принцип проектирования проще, чем два предыдущих принципа проектирования. Его содержание состоит в том, что объект подкласса (объект подтипа/производного класса) может заменить любое место, где объект родительского класса (объект базового/родительского класса) появляется в программе (программе), и гарантировать, что логическое поведение исходной программы остается неизменным и корректность не нарушается.

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

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

ISP

Принцип изоляции интерфейса ISP (Interface Segregation Principle), простое понимание которого состоит в том, чтобы предоставить вызывающему абоненту только тот интерфейс, который ему нужен, и не навязывать его ему, если он ему не нужен. Вот берем каштан, далее идет интерфейс о товаре, который включает в себя интерфейс создания товара, удаления товара, получения товара по ID, обновления товара. Что нам делать, если нам нужно предоставить интерфейс для получения продуктов в соответствии с категорией продукта в настоящее время? Многие студенты скажут, что это непросто Мы можем напрямую добавить интерфейс для запроса продуктов по категориям в этом интерфейсе. Давайте подумаем, нет ли проблем с этим планом.

public interface ProductService { 
    boolean createProduct(Product product); 
    boolean deleteProductById(long id); 
    Product getProductById(long id); 
    int updateProductInfo(Product product);
}

public class UserServiceImpl implements UserService { //...}

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

public interface ProductService { 
    boolean createProduct(Product product); 
    boolean deleteProductById(long id); 
    Product getProductById(long id); 
    int updateProductInfo(Product product);
}

public interface ThirdSystemProductService{
    List<Product> getProductByType(int type);
}

public class UserServiceImpl implements UserService { //...}

LOD

LOD (Закон Деметры) — это Закон Деметры. Это последнее правило разработки кода, которое мы собираемся ввести. Просто глядя на название, оно кажется немного неясным, и я не понимаю, что оно означает. Давайте посмотрим, как исходный текст описывает этот принцип дизайна. Каждый юнит должен иметь лишь ограниченные знания о других юнитах: только юниты, «близко» связанные с текущим юнитом Или: каждый юнит должен разговаривать только со своими друзьями; не разговаривать с незнакомцами. Согласно моему собственному пониманию, основная идея или наиболее желаемая цель этого принципа проектирования Dimit состоит в том, чтобы минимизировать влияние модификации кода на исходную систему, насколько это возможно. Следовательно, необходимо реализовать классы, модули или службы для достижения высокой связанности и низкой связанности. Не должно быть зависимостей между классами, у которых не должно быть прямых зависимостей, между классами с зависимостями старайтесь опираться только на необходимые интерфейсы. Закон Деметры состоит в том, чтобы уменьшить связь между классами и сделать классы как можно более независимыми. Каждый класс должен меньше знать об остальной части системы. Как только происходит изменение, меньшему количеству классов необходимо знать об изменении. Например, это как подпольная организация во время антияпонской войны.Родственные агрегированы вместе, но поддерживают как можно меньше контактов с внешним миром, то есть низкую связанность.

image.png

Суммировать

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


Всем привет, я Му Фэн, спасибо за лайки, лайки и комментарии, статья будет постоянно обновляться, увидимся в следующем выпуске! Поиск WeChat: технические заметки Mufeng, высококачественные статьи постоянно обновляются, у нас есть группа для обучения и перфокарт, которые могут привлечь вас, и усердно работать, чтобы поразить большую фабрику Кроме того, есть много учебных материалов и материалов для интервью для все.