понедельник, 15 мая 2017 г.

Рецепты Elegant Object:

В статье собраны большинство рекомендаций Егора Бугаенко касающиеся постижения дзен стиля Elegant Object.


Менеджмент:

  • Код ревью - требует вознаграждения, особенно когда не принимается по управленческим причинам. На эту работу люди назначаются случайным образом. Вся команда приглядывает друг-за другом;
  • Документация - вытягивающая и минимально-достаточная. Не пишется заранее, а собирается из запросов разработчиков пояснить тот или иной участок кода;
  • Таски - весь проект разбивается на микро-задачи, между которыми существуют зависимости. Оплата только по факту закрытия таски, а таска привязана к результат (коду или документации). Оплачивается также и само создание таски. Возможно подведение рейтинга по общему количеству выполненных тасков. Совместные посиделки-обсуждения заменить на конкретные тасками по аналитике;
  • Эксперты в команде опасны - не допускать их возникновения (все общение людей только через систему, которая и будет накапливать знания);
  • Непрерывная интеграция (CI) - запретить объединять ветку разработчика с мастер веткой напрямую, только через посредника - скрипт, который обязательно проверяет код перед попыткой его внести в мастер. Если хотя бы один тест провален слияние автоматически отклоняется;
  • Мотивация лидеров в команде - люди делятся на 20% лидеров, 70% работяг и 10% вредителей. От последних нужно избавляться, вторых растить до первых. Первые самые важные и они делают основную массу самого важного. Именно тип А нужно поддерживать и мотивировать. Не общее болото, где комфортно большинству, а выстраивать систему под профессионалов;
  • Компоненты качественного CI: Автоматизированная сборка, VCS, Pull Requests, Code review, тесты, Static Analysis, Pre-Flight Builds, Production Simulation, Stress tests
  • Элегантный (успешный) проект - требует иерархии, четких правил и дисциплины под предводительством сурового (военного) диктатора (PM) не ведающего сомнений и ведущего наемников к максимально скорому завершению их работы. Демократия же, наоборот, ставит целью максимально долгое выживание команды в максимально "плоской" структуре;   >> 
  • Поддерживаемость кода, читабельность, автоматизация - гораздо более важные вещи, чем оптимизация, минимальное потребление ресурсов и скорость работы. И они тоже важны, но приоритеты другие. >>
  • Хороший менеджер никогда не должен сообщать своим подчиненным, как выполнять свои задачи. Вместо этого вы должны определить, какие решения и результаты ожидаются. И, конечно же, что будет в случае успеха или неудачи.
  • XDSD - каждому платят только за проверенные результаты - т.к. свободный человек продает результаты своей работы, а не время. Тогда, как раб обменивает свободу (свое время) на еду и защиту(блажь) хозяина и, главное, считает этот обмен справедливым: «Хороший раб лоялен, трудолюбив и бдителен».>>
  • Хороший архитектор никогда не будет гордиться сложной проблемой. >>
  • Регулярные отчеты несколько раз в неделю: 1) статус области, 2) вопросы и 3) риски. >>
  • Это работа, не школа. Цель проекта - построить дом, а не обучать строителей.  Если работник учится за ваш счет - он ворует.  Умный руководитель нанимает профессионалов, которые уже способны выполнять свои обязанности. Однако, программист может сказать - вы меня наняли, но не предоставили достаточно инструментов (достаточной документации, не решены связанные вопросы и т.п.) >>

Код:

Декораторы

  • Декораторы - дополнительный функционал объектам обеспечивается не ростом навыков объектов, а их обертками - другими такими де маленькими и специализированными объектами;
  • Всевозможные проверки входящих данных должны выполняться в декораторах объекта. Это позволит иметь небольшие объекты и сколько угодно проверок без нагромождения кода. >>
  • Цепочка методов .метод().метод().метод() - которая реализуется через декораторы, весьма оптимальный дизайн в ООП. >>
  • Поведение объекта не должно конфигурироваться (множество параметров в конструкторе) - иначе это приводит к жуткому коду. Приходится выносить параметры конструктора в объект, но это не спасает. Лучше использовать небольшие (неизменные) классы, объединенные в цепочку декорирования >>
  • Вертикальная (туннельная) декомпозиция лучше горизонтальной. Хотя, та и та борются с одним и тем же - слишком большим объектом, но горизонтальное связывание провоцирует запутанный клиентский код, который должен содержать информацию о всех связанных объектах. В случае же туннельного декоратора достаточно знать информацию только о предыдущем слое, а все другие слои инкапсулированы. >>

 

Тестирование

  • TDD - нужен не в целом на весь проект, а только в выявленных проблемных зонах, как критерий их независимого аудита (доказательство что проблема все-таки действительно существует и предыдущие тесты слабы);
  • Один тест - одно тестовое утверждение >>
  • Сообщения об ошибках должны быть подробными. Не просто "Файла не существует", а где, когда и почему - это значительно облегчает отладку >>
  • Модульное тестирование вместо отладки (дебага) кода - в конечном итоге экономит время и силы в ООП  в принципе предотвращает повторное появление проблемы (дебаг это только для процедурного подхода), если же тест слишком сложный, значит код нужно рефакторить; >>
  • Методы тестирования должны быть изолированы друг от друга и не использовать общую информацию.  >>
  • Fail Fast - чем быстрее и легче произойдет сбой, тем быстрее он будет исправлен. И исправление будет проще и более заметным. лучший подход к ремонтопригодности. Подход Fail Safe скрывает проблемы и делает код менее ремонтопригодным, >>
public int size(File file) {
  if (!file.exists()) {
    return 0;
  }
  return file.length();
}
 Fail Fast:
public int size(File file) {
  if (!file.exists()) {
    throw new IllegalArgumentException(
      "There is no such file; I can't get its length."
    );
  }
  return file.length();
}
  • Только checked исключения. Существование Unchecked исключений - ошибка самой Java, т.к. они скрывают сам факт ошибки, позволяя программе дальше работать (просто примитивно логгируя ситуацию). Но поскольку в Java просто нет механизмов продолжения прерванной команды (метода), и, соответственно, в принципе отсутствует возможность восстановления ситуации, то абсолютно и не нужно различать разные типы Исключений, абсолютно не нужна их иерархия. Метод, ранее прерванный, можно перевызвать заново, а саму аварию как-то обработать... и все. Наличие уже самого исключения (всегда одного типа) и его контекста - вполне достаточно. Метод или безопасный и возникшая ошибка обрабатывается, или не безопасный, точка. >> 
  • Пойманное исключение нужно обязательно повторно пробрасывать дальше (re-throwing), чтобы легче выявить всю цепочку зависимостей от ошибки. Исключения были изобретены для упрощения, отделяя весь код обработки ошибок от основной логики и концентрируя его в одном месте. Основная цель исключений - собрать как можно больше информации об ошибке и перенести ее на самый высокий уровень, где пользователь способен что-то сделать с этим. Цепочка исключений помогает собрать нужную информацию на своем пути.   >>
  • Каждый тестовый метод должен работать со своим собственным набором входных значений. Не стоит использовать общие переменные. Иначе тесты получаются связанными.
  • Не используйте исключения для управления потоком. Этот код неверен:
    int size;
    try {
      Size = this.fileSize ();
    } Catch (IOException ex) {
      Size = 0;
    }
         

Объект

  • Геттеры, сеттеры - зло, они вторгаются в объект, превращают его из самостоятельной сущности в простое хранилище данных. Лучше пересоздать объект чем его менять. Объект сам должен что-то делать с данными, которые у него есть, ошибка у него их забирать или менять;
  • Utility классы - это анти-шаблоны: StringUtils, FileUtils и IOUtils
  • null - это ошибка, его вообще не должно быть, вместо непонятного пустого объекта, должны быть конкретные разные объекты;
  • дублирование данных - создайте новый класс и вынесите в него данные в виде private свойства (или private static свойства). >>
  • статический метод - процедурная проблема, т.к. уничтожает ООП как концепцию;
  • упаковка данных в объект - ошибка, поскольку настоящий объект - это не только данные, но и поведение. Объект нужно просить что-то сделать с данными, которыми он обладает, а не выковыривать их из него;
  • ветвление кода (if-else-then, switch) - процедурный код должен быть заменен на взаимодействие объектов (через декораторы, полиморфизм и т.п.). Это только на первый взгляд кажется, что сложность возрастет (городить объекты, чтобы обойти простой оператор ветвления). Но суть глубже. Мы превозмогаем ситуацию, когда ветвление используется для запроса информации и принятия решения на основе значений, т.к. это полностью процедурный подход. В объектном стиле происходит четкое разделение ответственности: объект просто выполняет свою конкретную работу, а вот выбор нужного из возможных объектов выносится наружу;
  • инкапсуляция не просто принцип сокрытия реализации от внешнего доступа, но также и принцип закрытия данных с которыми работает эта реализация. Остальные объекты могут только попросить объект обладающий информацией что-то сделать с этими данными, но сами ими воспользоваться не могут. Правильная инкапсуляция приводит к полному отсутствию «голых данных»;
  • наследование - это процедурный способ повторного использования кода. Нарушает независимость объекта (он крайне зависит от родителя. Сложнее тестировать. Нет возможности использовать множественное наследование. Нет возможности наследоваться от final класса. В сложных иерархиях возникает путаница: кто от кого наследуется. Композиция (ссылка на объект), лучше наследования; >>
  • градиенты неизменности (imutable) - константа (неизменны и сам объект, и его данные), не константа (неизменный объект работающий с изменяемыми данными), неизменный объект работающий с другими изменяемыми объектами, и неизменный объект инкапсулирующий изменяемый объект.
  • Идеальный дизайн - полностью из неизменных объектов. Но что тогда делать с изменяемыми данными? Прямого доступа к памяти у Java нет. Но можно попробовать реализовать хранение данных в массиве, суррогат того как может работать С++. >>
  • Аннотации - раздирают границы объекта, возникает функционал за пределами объекта, и объект за это не отвечает и никак это не контролирует. >>
  • null - это плохо, проверка на null тоже. Nul порождает неопредленность, кроме результата или его отсутствия, вдруг возникает еще третье состояние "ничто" и блок if начинает его обрабатывать - нагромождение. Поэтому следует возвращать только реальный объект, нулевой объект или бросать исключение. Не надо бояться делать код хрупким. Пусть быстро ломается и сообщает, где проблема, а не скрывает ее. И, также, пусть метод создания кеширует объект - это экономит память. >>
  • IoC (принцип инверсии контроля) - мы не должны иметь дело с данными, только с композицией объектов >>
  • Методы должны быть сконструированы в объектном стиле. Простое перечисление действий (строка за строкой) - это процедурный подход (когда количество строк увеличится, понять что происходит будет крайне трудно). И, если, действительно, нужно сохранить временную последовательность действий и данных, то организация методом должна быть связанной цепочкой и данные д.б. инкапсулировать метод(данные1).метод(данные2)>>
  • Объект это представитель данных - не хранитель (ведь, например, объект может хранить только ссылку), и думать о нем как о хранилище ошибочно, это порождает процедурный подход; >>
  • Хороший объект - прокси реальной сущности. Объект не содержит данных. Он их аниматор. Его  роль - сделать кусок данных живыми, снабдить поведением, но не стать их частью. Да, объекту могут требоваться какие-то знания для доступа к данным и выполнения работы, но никогда он не должен восприниматься как данные. Объект - это поведение >>
  • В конструкторах не должно быть кода - иначе это ограничивает композицию объектов и делает код и все связки очень жесткими. >>
  • Только один главный конструктор - позволяет избегать дублирования кода >>
  • Должен быть только один конструктор, который сохраняет данные в объектных переменных. Все остальные конструкторы должны вызывать эту функцию с разными аргументами.  
public class Server {
  private String address;
  public Server(String uri) {
    this.address = uri;
  }
  public Server(URI uri) {
    this(uri.toString());
  }
}
  • Избыточная переменная - зло, снижает читабельность кода. Вообще всех переменных должно быть не больше 4 штук чтобы не перегружать внимание, и лучше всего их выносить в отдельные классы: >>
String fileName = "test.txt";
print("Length is " + new File(fileName).length());

Заменить на:
print("Length is " + new File("test.txt").length());
  • Только один return >>
Обычно:
public int max(int a, int b) {
  if (a > b) {
    return a;
  }
  return b;
}
Еще ужаснее:
public int max(int a, int b) {
  int m;
  if (a > b) {
    m = a;
  } else {
    m = b;
  }
  return m;
}
Тернарный оператор:
    public int max(int a, int b) {
        return a > b ? a : b;
    } 
  • Ветвление кода должно быть правильно оформлено >>
плохой код
if (x < 0) {
  throw new Exception("X can't be negative");
} else {
  System.out.println("X is positive or zero");
}
 
лучше:
if (x < 0) {
  System.out.println("X is negative");
} else {
  System.out.println("X is positive or zero");
} 

нужно:
if (x < 0) {
  throw new Exception("X can't be negative");
}
System.out.println("X is positive or zero");
 
плохой код:
for (int x : numbers) {
  if (x < 0) {
    continue;
  } else {
    System.out.println("found positive number");
  }
}
 
лучше:
for (int x : numbers) {
  if (x < 0) {
    continue;
  }
  System.out.println("found positive number");
}
  
нужно: 
if (x < 0) {
  throw new Exception("X is negative");
  System.exit(1);
} 

Паттерны

  • Синглтон (Singletone, паттерн Одиночка) - зло, но чтобы реализовать единую глобальную точку, его нужно заменить на зависимости (зависимость пула подключений от контроллера и убедиться, что объект предоставляется через конструктор). Надлежащий объектно-ориентированный дизайн передает экземпляр базы данных всем объектам, которые могут нуждаться в ней через их конструкторы.
  • Объектно-ориентированные паттерны:

Абстрактная фабрика (Abstract Factory)
Адаптер (Adapter)
Мост (Bridge)
Цепочка ответственности (Chain of Responsibility)
Command
Композитный (Composite)
Декоратор (Decorator)
Ленивая инициализация (Lazy Initialization)
Нулевой объект (Null Object)
Пул объектов (Object Pool)
Proxy
RAII
Спецификация (Specification)
Стратегия (Strategy)
Состояние (State)
Опытный образец(Prototype)
Наблюдатель (Observer).
Фабричный метод (Factory method)
Переводчик(Interpreter)
concarency patterns
>>
  • Антипаттерны
NULL References
Utility Classes
Mutable Objects
Getters and Setters
Data Transfer Object (DTO)
Object-Relational Mapping (ORM)
Singletons
Controllers, Managers, Validators
Public Static Methods
Class Casting
Traits and Mixins
Строитель (Builder)
Объект передачи данных (Data Transfer Object)
Фасад (Fasade)
Легковес (Flyweight)
Передний контроллер(Front Controller)
Итератор (iterator)
Маркер (Marker)
Посредник (Mediator)
Снимок (Memento)
Модуль (Module)
Multitone
Слуга (Servant)
Одиночка (Singleton)
Шаблонный метод (Template method)
Посетитель (Visitor)
>> 

Имена, названия и оформление кода

    • -er в окончании имен классов - признак неуважения к объекту. Он не исполнитель чужой воли (sorter), он вещь в себе sorted(apple) >> 
    • В методе не должно быть частей. Пустая разделительная строка (блоков кода) в методе - признак дурно пахнущего кода. Если разделяет, значит метод объединяет две вещи вместо того чтобы выполнять только одну >>
    • Имя интерфейса - это имя реального объекта, а имя класса должно объяснять детали реализации (class Validated implements Content {};). Ошибочно их именовать одинаково с префиксом или суффиксом (IRecord, IfaceEmployee или RecordInterface). Если ничего не говорится о реализации, назовите ее Default, Simple (class DefaultRecord implements Record {};)
    • Имена методов в тестах JUnit должны быть созданы как английские предложения без пробелов returnsItsContentInUnicode
    • JavaDoc в тестах должен начинаться с имени тестируемого класса, и быть похож на «кто-то может что-то сделать». 
    /**
     * HttpRequest can return its content in Unicode.
     * @throws Exception If test fails
     */
    • Имя метода тестирования не использует "can" (может). can используется только в JavaDoc 
    • Хорошая практика - всегда объявлять тестовые методы, как бросающие исключения.  
    @Test
    public void returnsItsContentInUnicode() throws Exception {
    }
    • Имя метода - это не глагол,  имя должно объяснять, что метод возвращает
    String content(); int ageOf(File file);
    либо что метод делает (если void)
    void save(File file);
    void process(Work work);
    void append(File file, String line);
    • Нет составным именам переменных (timeOfDay, firstItem или httpRequest). Простое существительное (names);
    • Создавать аббревиатуры, удаляя гласные Конфликты между параметрами конструктора и свойствами в классе, если конструктор сохраняет входящие данные в экземпляре объекта -
          public class Message {
            private String recipient;
            public Message(String rcpt) {
              this.recipient = rcpt;
            }
          }
    • Использовать прилагательное, когда есть несколько переменных с разными характеристиками:
      String contact(String left, String right);
    • Избегать одноразовых переменных, данные сразу вписывать в код (использовать одноразовую переменную только, если это способствуют читабельности кода)
      Return new File ("data.txt");
    • Скобка должна либо заканчивать линию, либо закрываться в той же строке (обратное правило применяется к закрывающей скобке)
    StringUtils.join (
      Arrays.asList (
        "первая линия",
        "вторая линия",
        StringUtils.join (
          Arrays.asList ("a", "b")
        )
      ),
      "separator"
    );
    • Разумно заполнять строки (в пределах 80 символов)
     StringUtils.join (
      Arrays.asList (
        «Первая строка», «вторая строка»,
        StringUtils.join (Arrays.asList ("a", "b"))
      ),
      "separator"
    );
    • Использовать константу класса только, когда нужно обмениваться информацией между методами класса, и эта информация является признаком (!) класса. Не используйте константы в качестве замены строковых или числовых литералов.
    Class Document {
      Private static final String D_LETTER = "D"; // плохо
      Private static final String EXTENSION = ".doc"; // хорошо
    }
    • Чем больше классов, тем выразительнее код - словно словарный запас они позволяет понятнее и полнее выражать желаемое. Нужно стремиться к простым названиям. Интерфейсы - это существительные (запрос, директива или домен). Классы всегда реализуют интерфейсы, имеют в имени префикс от интерфейса или имя интерфейса и префикс предназначения класса. Имена методов должны быть только одним словом. >>
    а) все публичные методы должны быть объявлены в интерфейсах; 
    б) объекты не должны иметь более четырех атрибутов; 
    с) статические методы не допускаются; 
    d) конструкторы должны быть без кода; 
    e) объекты должны содержать менее пяти открытых методов


    Софт, библиотеки и прочее:

    • JSON - простой формат данных без каких-либо дополнительных функций (использовать AJAX). Во всех остальных случаях XML >> 
    • Private Maven Repository in Amazon S3 >>
    • Удобная конкатенация строк с форматированием (шаблон и заместители) важнее скорости работы >>
    String.format ()
    когда текст длинее ширины экрана 
    Apache StringUtils.join ()
    от Apache commons-lang3:


    список будет пополняться

    Комментариев нет:

    Отправить комментарий