Временные переменные и требования к длине строки

10

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

Фаулер рекомендует заменить временные переменные запросом, поэтому вместо этого:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

вы вытаскиваете в помощник метод:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

В целом я согласен, за исключением того, что я использую временные переменные, когда строка слишком длинная. Например:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Если бы я попытался вставить это, строка была бы длиннее, чем 80 символов.

С другой стороны, я получаю цепочки кода, которые сами по себе не так легко читаются:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Каковы некоторые стратегии для примирения двух?

Кевин Берк
источник
10
80 символов - это 1/3 от 1 моего монитора. Вы уверены, что придерживаться 80 символьных линий все еще стоит для вас?
JK.
10
да, см., например, programmers.stackexchange.com/questions/604/…
Кевин Берк,
Ваш пример $hostи $uriпример немного надуманные - если только хост не читался из настроек или другого ввода, я бы предпочел, чтобы они были на одной строке, даже если они переносятся или выходят за границы.
Изката
5
Не нужно быть таким догматичным. Книга представляет собой список техник, которые можно использовать, когда они помогают, а не набор правил, которые вы должны применять везде, каждый раз. Дело в том, чтобы сделать ваш код более понятным и легким для чтения. Если рефакторинг этого не делает, вы его не используете.
Шон МакSomething
Хотя я думаю, что ограничение в 80 символов немного чрезмерно, подобное ограничение (100?) Является разумным. Например, мне часто нравится программировать на портретно-ориентированных мониторах, поэтому очень длинные строки могут раздражать (по крайней мере, если они обычные).
Томас Эдинг

Ответы:

16

Как сделать
1. Существуют ограничения на длину строки, чтобы вы могли видеть + понимать больше кода. Они все еще в силе.
2. Подчеркните суждение о слепой конвенции .
3. Избегайте временных переменных, если не оптимизировать производительность .
4. Избегайте использования глубоких отступов для выравнивания в многострочных операторах.
5. Разбейте длинные операторы на несколько строк вдоль границ идеи :

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

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

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

Оба приведенных вами примера (Java и ... PHP?) Допускают многострочные операторы. Если линии становятся длинными, разбейте их. Источник jquery доводит это до крайности. (Первое утверждение начинается со строки 69!) Не обязательно, что я с этим согласен, но есть другие способы сделать ваш код читабельным, чем использование временных переменных.

Некоторые примеры
1. Руководство по стилю PEP 8 для python (не самый красивый пример)
2. Пол М. Джонс из Руководства по стилю Pear (середина пути)
3. Длина строки Oracle + соглашения об упаковке (полезные стратагемы для сохранения до 80 символов)
4. MDN Java Practices (подчеркивает мнение программиста над соглашением)

Захари Йейтс
источник
1
Другая часть проблемы заключается в том, что временная переменная часто превышает свое значение. Не проблема в маленьких блоках, но в больших, да, большая проблема.
Росс Паттерсон
8
Если вы беспокоитесь о временном изменении, поместите const на него.
Томас Эдинг
3

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

(поправьте меня, если я ошибаюсь)

MHR
источник
3

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

Позвольте мне взглянуть на примеры, которые вы опубликовали.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Мое наблюдение заключается в том, что все вызовы twilio api начинаются с "https://api.twilio.com/2010-04-1/", и поэтому существует очень очевидная функция многократного использования:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

На самом деле, я считаю, что единственная причина для генерации URL-адреса - это сделать запрос, поэтому я бы сделал:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

Фактически, многие URL начинаются с "Accounts / $ accountSid", поэтому я, вероятно, также извлеку это:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

И если мы сделаем twilio api объектом, который содержит номер счета, мы можем сделать что-то вроде:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

Использование объекта $ twilio позволяет упростить модульное тестирование. Я могу дать объекту другой объект $ twilio, который на самом деле не обращается к twilio, который будет быстрее и не будет делать странные вещи с twilio.

Давайте посмотрим на другой

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Здесь я подумаю о любом из них:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

или

$params = MustacheOptions::build($bagcheck->getFlatParams());

или

$params = MustacheOptions::build(flatParams($backCheck));

В зависимости от того, какая идиома повторного использования.

Уинстон Эверт
источник
1

На самом деле, я не согласен с выдающимся мистером Фаулером по этому поводу в общем случае.

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

Однако равной, часто большей концептуальной ценностью является «повторное использование стоимости». Мистер Фаулер называет эти извлеченные методы для замены временных запросов «запросами». Ну, что более эффективно; запрашиваете базу данных каждый раз, когда вам нужно определенное значение, или запрашиваете один раз и сохраняете результат (предполагая, что значение достаточно статично, чтобы вы не ожидали его изменения)?

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

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

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

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

Keiths
источник
1

У вспомогательных методов есть место, но вы должны быть осторожны с обеспечением согласованности данных и ненужного увеличения объема переменных.

Например, ваш собственный пример цитирует:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

Ясно, что оба _quantityи _itemPriceявляются глобальными переменными (или, по крайней мере, на уровне класса), и поэтому существует возможность их изменения за пределамиgetPrice()

Поэтому существует вероятность того, что первый вызов basePrice()вернет значение, отличное от второго вызова!

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


Вы также должны избегать сокращения до абсурда - следует ли рассчитывать discountFactorрасчет на метод? Итак, ваш пример становится:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

Разделение за пределы определенного уровня фактически делает код менее читабельным.

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

Если вы работаете на языке с именованными параметрами (ObjectiveC, Python, Ruby и т. Д.), Временные переменные будут менее полезны.

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

Как и вы, я использую временные переменные для ясности и соображений длины строки.

Я также видел, как программисты делают следующее в PHP. Это интересно и отлично подходит для отладки, но немного странно.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs
Димитрий
источник
0

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

Новый метод может быть использован в других методах

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

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

Если это так, то из-за пространства имен я не буду определять глобальный метод uri () или host (), но буду называть имя с дополнительной информацией, например twilio_host () или archive_request_uri ().

Затем, для проблемы длины строки, я вижу несколько вариантов:

  • Создайте локальную переменную, как uri = archive_request_uri().

Обоснование: в текущем методе вы хотите, чтобы URI был упомянутым. Определение URI все еще разложено.

  • Определите локальный метод, например uri() { return archive_request_uri() }

Если вы часто используете рекомендации Фаулера, вы знаете, что метод uri () - это тот же шаблон.

Если из-за выбора языка вам необходим доступ к локальному методу с помощью «self», я бы порекомендовал первое решение для большей выразительности (в Python я бы определил функцию uri внутри текущего метода).

Марк-Эммануэль Купе де Гра
источник