bookmate game
ru

Роберт Мартин

  • Александр Маруевhas quoted2 years ago
    G27: Структура важнее конвенций
    Воплощайте архитектурные решения на уровне структуры кода; она важнее стандартов и конвенций. Содержательные имена полезны, но структура, заставляющая пользователя соблюдать установленные правила, важнее. Например, конструкции switch/case с хорошо выбранными именами элементов перечисления уступают базовым классам с абстрактными методами. Ничто не вынуждает пользователя применять одинаковую реализацию switch/case во всех случаях; с другой стороны, базовые классы заставляют его реализовать все абстрактные методы в конкретных классах.
  • Александр Маруевhas quoted2 years ago
    G28: Инкапсулируйте условные конструкции
    В булевской логике достаточно трудно разобраться и вне контекста команд if или while. Выделите в программе функции, объясняющие намерения условной конструкции. Например, команда

    if (shouldBeDeleted(timer))

    выразительнее команды

    if (timer.hasExpired() && !timer.isRecurrent())
  • Александр Маруевhas quoted2 years ago
    G29: Избегайте отрицательных условий
    Отрицательные условия немного сложнее для понимания, чем положительные. Таким образом, по возможности старайтесь формулировать положительные условия. Например, запись

    if (buffer.shouldCompact())

    предпочтительнее записи

    if (!buffer.shouldNotCompact())
  • Александр Маруевhas quoted2 years ago
    G30: Функции должны выполнять одну операцию
    Часто возникает искушение разделить свою функцию на несколько секций для выполнения разных операций. Такие функции выполняют несколько операций; их следует преобразовать в группу меньших функций, каждая из которых выполняет только одну операцию.

    Пример:

    public void pay() {

    for (Employee e : employees) {

    if (e.isPayday()) {

    Money pay = e.calculatePay();

    e.deliverPay(pay);

    }

    }

    }

    Эта функция выполняет сразу три операции: она перебирает всех работников; проверяет, начислены ли работнику какие-то выплаты; и наконец, производит оплату. Код лучше записать в следующем виде:

    public void pay() {

    for (Employee e : employees)

    payIfNecessary(e);

    }

    private void payIfNecessary(Employee e) {

    if (e.isPayday())

    calculateAndDeliverPay(e);

    }

    private void calculateAndDeliverPay(Employee e) {

    Money pay = e.calculatePay();

    e.deliverPay(pay);

    }

    Каждая из этих функций выполняет только одну операцию (см. «Правило одной операции», с. 59).
  • Александр Маруевhas quoted2 years ago
    G31: Скрытые временн*ые привязки
    Временн*ые привязки часто необходимы, но они не должны скрываться. Структура аргументов функций должна быть такой, чтобы последовательность вызова была абсолютно очевидной. Рассмотрим следующий пример:

    public class MoogDiver {

    Gradient gradient;

    List splines;

    public void dive(String reason) {

    saturateGradient();

    reticulateSplines();

    diveForMoog(reason);

    }

    ...

    }

    Порядок вызова трех функций важен. Сначала вызывается saturateGradient(), затем reticulateSplines() и только после этого diveForMoog(). К сожалению, код не обеспечивает принудительного соблюдения временной привязки. Ничто не мешает другому программисту вызвать reticulateSplines до saturateGradient, и все кончится исключением UnsaturatedGradientException.

    Более правильное решение выглядит так:

    public class MoogDiver {

    Gradient gradient;

    List splines;

    public void dive(String reason) {

    Gradient gradient = saturateGradient();

    List splines = reticulateSplines(gradient);

    diveForMoog(splines, reason);

    }

    ...

    }

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

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

    Обратите внимание: переменные экземпляров остались на своих местах. Предполагается, что они используются приватными методами класса. Использование их в аргументах лишь явно выражает факт существования временной привязки.
  • Александр Маруевhas quoted2 years ago
    G32: Структура кода должна быть обоснована
    Структура кода должна выбираться не произвольно, а по строго определенным причинам. Позаботьтесь о том, чтобы эти причины были выражены в структуре кода. Если при чтении кода создается впечатление, что его структура выбрана произвольно, другим пользователям может показаться, что ее можно изменить. Если во всей системе последовательно используется единая структура кода, другие пользователи примут ее и сохранят действующие правила. Например, недавно я занимался объединением изменений в FitNesse и обнаружил, что один из наших авторов использовал следующую запись:

    public class AliasLinkWidget extends ParentWidget

    {

    public static class VariableExpandingWidgetRoot {

    ...

    ...

    }

    Проблема в том, что VariableExpandingWidgetRoot незачем находиться в облсти видимости AliasLinkWidget. Более того, класс AliasLinkWidget.VariableExpandingWidgetRoot использовался посторонними классами, которые не имели никакого отношения к AliasLinkWidget.

    Возможно, программист разместил VariableExpandingWidgetRoot в AliasWidget по соображениям удобства, а может, он действительно полагал, что область видимости этого класса должна находиться внутри области видимости AliasWidget. Какими бы причинами он ни руководствовался, результат выглядит необоснованным. Открытые классы, не являющиеся вспомогательными по отношению к другому классу (то есть используемыми только в его внутренних операциях), не должны размещаться внутри других классов. По стандартным правилам такие классы объявляются на верхнем уровне своих пакетов.
  • Александр Маруевhas quoted2 years ago
    G33: Инкапсулируйте граничные условия
    Отслеживать граничные условия нелегко. Разместите их обработку в одном месте. Не позволяйте им «растекаться» по всему коду. Не допускайте, чтобы в вашей программе кишели многочисленные +1 и –1. Возьмем простой пример из FIT:

    if(level + 1 < tags.length)

    {

    parts = new Parse(body, tags, level + 1, offset + endTag);

    body = null;

    }

    Обратите внимание: level+1 здесь встречается дважды. Это граничное условие, которое следует инкапсулировать в переменной — например, с именем nextLevel:

    int nextLevel = level + 1;

    if(nextLevel < tags.length)

    {

    parts = new Parse(body, tags, nextLevel, offset + endTag);

    body = null;

    }
  • Александр Маруевhas quoted2 years ago
    public String render() throws Exception

    {

    HtmlTag hr = new HtmlTag("hr");

    if (size > 0) {

    hr.addAttribute("size", ""+(size+1));

    }

    return hr.html();

    }

    На этой стадии я стремился к тому, чтобы создать необходимое разделение, и обеспечить прохождение тестов. Мне удалось легко добиться этой цели, но в созданной функции по-прежнему смешивались разные уровни абстракции — на этот раз конструирование тега HR и интерпретация/форматирование переменной size. Таким образом, при разбиении функции по уровням абстракции иногда обнаруживаются новые уровни, скрытые прежней структурой.
  • Александр Маруевhas quoted2 years ago
    Сила хорошо выбранных имен заключается в том, что они дополняют структуру кода описаниями. На основании этих описаний у читателя формируются определенные предположения по поводу того, что делают другие функции модуля.
  • Александр Маруевhas quoted2 years ago
    N4: Недвусмысленные имена
    Выбирайте имена, которые максимально недвусмысленно передают назначение функции или переменной.
fb2epub
Drag & drop your files (not more than 5 at once)