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

Но это сложно!

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

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

О паттернах

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

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

Выкройка 1: Абстрактная фабрика

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

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

С точки зрения кода это обычно выглядит примерно так:

interface Factory<T> {
T build(Metadata d)
}
class ClientFactory implements Factory<Client> {
Client build(Metadata d) {
        // Build actual object and return      
    }
}

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

Схема 2: Делегатор

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

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

С точки зрения кода это выглядит примерно так:

interface Validator {
    
    bool validate(Object o)
}
class ValidatorHelper implements Validator {
Set<Validator> delegates;
    
    bool validate(Object o) { 
        for (Validator v : delegates) { 
            if (!v.validate(o)) return false
        }
        return true
    }
}
class RestController {
ValidationHelper helper;
Response addObject(Object o) {
        if (helper.validate(o)) return ErrorResponse
// Normal processing
    }
}

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

Паттерн 3: Строитель / Именованные параметры

Из всех шаблонов, которые изменили способ написания кода, шаблон построителя - мой лучший. Я с самого начала пишу каждый из своих DTO (объекты передачи данных) с помощью конструктора. Строители создают гибкий и расширяемый код без особой работы в реальности, и они имеют преимущество неизменности, если вы этого хотите!

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

В коде это будет выглядеть примерно так:

class Dto {
String s
    int i
private Dto(String s, int i) {
        this.s = s
        this.i = i
    }
public DtoBuilder builder() {
        return new DtoBuilder()
    }
public static class DtoBuilder {
        
        private String s = "some string"
        private int i = 0
public DtoBuilder withString(String s) {
            this.s = s
            return this
        }
public DtoBuilder withInt(int it) {
            this.i = i
            return this
        }
public Dto build() {
            return new Dto(s, i)
        }
    }
}

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

Причина, по которой этот шаблон сделал мой код таким простым, заключается в том, что когда все ваши объекты используют конструктор, создание нового происходит автоматически. В наших кодовых базах в Bandwidth мы всегда добавляем статический фабричный метод к классу, который мы хотим построить, чтобы вернуть построитель. Оттуда мы просто следуем цепочке свободного API, передаем ваши переменные и затем набираем .build(). Выполнено. Вы не тратите время на созерцание конструкторов. Вы не тратите время, даже глядя на код компоновщика. Он просто есть, и вы используете его во время написания. В современных IDE автозаполнение буквально сообщает вам, какие переменные вы можете установить. Легкий.

Паттерн 4: Обогащение

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

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

interface Enricher<T> {
T enrich(T thing);
}
class HeadersEnricher implements Enricher<Headers> {
 
    Headers enrich(Headers headers) {
        headers.add("x-header", "something")
        return headers
    }
}

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

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

Удачного кодирования!