Подтвердить что ты не робот

Существует ли кратковременная противоположность "пусто"?

Интерфейсы для строковых классов обычно имеют метод с именем IsEmpty (VCL) или empty (STL). Это абсолютно разумно, потому что это особый случай, но код, который использует эти методы, часто должен отрицать этот предикат, что приводит к "оптическому (и даже психологическому) служебным > (восклицательный знак не очень очевиден, особенно после открытия скобки). См., Например, этот (упрощенный) код:

/// format an optional time specification for output
std::string fmtTime(const std::string& start, const std::string& end)
{
    std::string time;
    if (!start.empty() || !end.empty()) {
        if (!start.empty() && !end.empty()) {
            time = "from "+start+" to "+end;
        } else {
            if (end.empty()) {
                time = "since "+start;
            } else {
                time = "until "+end;
            }
        }
    }
    return time;
}

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

Возможно, меня поразила только слепота: как бы вы решили эту проблему?


Изменить:. Прочитав некоторые комментарии, я думаю, что необходимо сказать, что исходный код использует класс System::AnsiString VCL. Этот класс предоставляет метод IsEmpty, который очень читаем:

 if (text.IsEmpty()) { /* ... */ } // read: if text is empty ...

если не отрицается:

 if (!text.IsEmpty()) { /* ... */} // read: if not text is empty ... 

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

4b9b3361

Ответ 1

В большинстве случаев вы можете отменить порядок if и else, чтобы очистить код:

const std::string fmtTime(const std::string& start, const std::string& end)
{
    std::string time;
    if (start.empty() && end.empty()) {
        return time;
    }

    if (start.empty() || end.empty()) {
        if (end.empty()) {
            time = "since "+start;
        } else {
            time = "until "+end;
        }
    } else {
        time = "from "+start+" to "+end;
    }
    return time;
}

Или даже очиститель после некоторого рефакторинга:

std::string fmtTime(const std::string& start, const std::string& end)
{
    if (start.empty() && end.empty()) {
        return std::string();
    }

    if (start.empty()) {
        return "until "+end;
    }    

    if (end.empty()) {
        return "since "+start;
    }

    return "from "+start+" to "+end;
}

И для максимальной компактности (хотя я предпочитаю предыдущую версию, для ее удобочитаемости):

std::string fmtTime(const std::string& start, const std::string& end)
{
    return start.empty() && end.empty() ? std::string()
         : start.empty()                ? "until "+end
         :                  end.empty() ? "since "+start
                                        : "from "+start+" to "+end;
}

Другая возможность - создать вспомогательную функцию:

inline bool non_empty(const std::string &str) {
  return !str.empty();
}

if (non_empty(start) || non_empty(end)) {
...
}

Ответ 2

Я думаю, что я бы устранил условия в пользу небольшой математики:

const std::string fmtTime(const std::string& start, const std::string& end) {

    typedef std::string const &s;

    static const std::function<std::string(s, s)> f[] = {
       [](s a, s b) { return "from " + a + " to " + b; }           
       [](s a, s b) { return "since " + a; },
       [](s a, s b) { return "until " + b; },
       [](s a, s b) { return ""; },
    };

   return f[start.empty() * 2 + end.empty()](start, end);
}

Изменить: если вы предпочитаете, вы можете выразить математику как start.empty() * 2 + end.empty(). Чтобы понять, что происходит, возможно, лучше, если я расскажу о том, как я начал думать о вещах. Я думал о вещах как о двумерном массиве:

enter image description here

(Не стесняйтесь менять "пуст пустым" и "заканчивать пустой", в зависимости от того, предпочитаете ли вы думать в строчном порядке или в столбце с большим порядком).

start.empty() и end.empty() (или логические not из них, если вы предпочитаете), действуют как индекс по одному измерению этой двумерной матрицы. Математика просто "линеаризует" адресацию, поэтому вместо двух строк и двух столбцов мы получаем одну длинную строку, примерно такую:

enter image description here

В математических терминах это простой вопрос о столбцах "строка * столбцы + столбец" (или, опять же, наоборот, в зависимости от того, предпочитаете ли вы порядок строк или столбцов). Первоначально я выражал часть * 2 как бит-сдвиг, а добавление как бит-бит or (зная, что младший старший бит пуст, из-за предыдущего сдвига влево). Я считаю, что с этим легко справиться, но я думаю, я могу понять, где другие могут не быть.

Я, вероятно, должен добавить: хотя я уже упомянул строку-майора против столбца-майора, должно быть довольно очевидно, что отображение из двух значений "x.пусто" в позиции в массиве в основном произвольно. Значение, которое мы получаем из .empty(), означает, что мы получаем 0, когда значение отсутствует, и 1, когда оно есть. Таким образом, прямое сопоставление исходных значений с позициями массива, вероятно, выглядит следующим образом:

enter image description here

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

  • просто упорядочьте массив, чтобы он соответствовал значениям по мере их получения.
  • инвертировать значение для каждого измерения индивидуально (это в основном привело к исходному вопросу - постоянное использование !x.empty())
  • Объедините два входа в один линейный адрес, затем "инвертируйте", вычитая из 3.

Для тех, кто сомневается в эффективности этого, он фактически сводится к этому (с VС++):

mov eax, ebx
cmp QWORD PTR [rsi+16], rax
sete    al
cmp QWORD PTR [rdi+16], 0
sete    bl
lea eax, DWORD PTR [rbx+rax*2]
movsxd  rcx, eax
shl rcx, 5
add rcx, r14
mov r9, rdi
mov r8, rsi
mov rdx, rbp
call    <ridiculously long name>::operator()

Даже однократная конструкция для f не так плоха, как могут подумать некоторые. Это не связано с динамическим распределением или чем-либо в этом порядке. Имена достаточно длинны, чтобы сначала выглядеть немного страшно, но в конце концов, это в основном четыре повторения:

lea rax, OFFSET FLAT:[email protected][email protected]<lambda_f466b26476f0b59760fb8bb0cc43dfaf>@@[email protected]@[email protected]@[email protected][email protected][email protected][email protected]@[email protected]@[email protected]@[email protected]@[email protected]@[email protected]@@[email protected]@@[email protected][email protected][email protected]@[email protected]@[email protected]@[email protected]@[email protected]@[email protected]@[email protected]@[email protected]
mov QWORD PTR f$[rsp], rax

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

Ответ 3

Можно сказать

if (theString.size()) { .... }

То, что является более читаемым, - это другое дело. Здесь вы вызываете метод, основной целью которого является не говорить вам, что предмет пуст, и полагаться на неявное преобразование в bool. Я бы предпочел версию !s.empty(). Я мог бы использовать not вместо удовольствия:

if (not theString.empty()) { .... }

Возможно, было бы интересно увидеть взаимосвязь между людьми, которые сбивают с толку версии ! и not.

Ответ 4

Я должен реорганизовать это, чисто из анального ретентивного расстройства...

std::string fmtTime( const std::string & start, const std::string & end ) {
    if ( start.empty() ) {
        if ( end.empty() ) return ""; // should diagnose an error here?

        return "until " + end;
    }

    if ( end.empty() ) return "since " + start;

    return "from " + start + " to " + end;
}

Там... чистая чистота. Если что-то здесь трудно читать, добавьте комментарий, а не другое предложение if.

Ответ 5

Обычно лучше не использовать такой сложный условный код. Почему бы не сохранить его простым?


const std::string fmtTime(const std::string& start, const std::string& end)
{
    if (start.empty() && end.empty())
    {
        return "";
    }

    // either start or end or both are not empty here.

    std::string time;

    if (start.empty())
    {
        time = "until "+end;
    }
    else if (end.empty())
    {
        time = "since "+start;
    }
    else // both are not empty
    {
        time = "from "+start+" to "+end;
    }

    return time;
}

Ответ 6

Без использования отрицания..;)

const std::string fmtTime(const std::string& start, const std::string& end)
{
   std::string ret;
   if (start.empty() == end.empty())
   {
     ret = (start.empty()) ? "" : "from "+start+" to "+end;
   }
   else
   {
     ret = (start.empty()) ? "until "+end : "since "+start;
   }
   return ret;
}

ИЗМЕНИТЬ: хорошо очистили немного больше...

Ответ 7

Поскольку никто не заботился о том, чтобы набрать полный ответ с моим комментарием, вот он:

Создайте локальные переменные, которые упрощают чтение выражений:

std::string fmtTime(const std::string& start, const std::string& end)
{
    std::string time;
    const bool hasStart = !start.empty();
    const bool hasEnd   = !end.empty();
    if (hasStart || hasEnd) {
        if (hasStart && hasEnd) {
            time = "from "+start+" to "+end;
        } else {
            if (hasStart) {
                time = "since "+start;
            } else {
                time = "until "+end;
            }
        }
    }
    return time;
}

Компилятор достаточно умен, чтобы исключить эти переменные, и даже если это не так, это будет не менее эффективно, чем оригинал (я ожидаю, что это будет единственный тест переменной). Код теперь немного читаем для человека, который может просто прочитать условия:

если есть начало или конец, то

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

Ответ 8

В глобальном масштабе у меня нет проблем с тем, как вы его написали; его конечно, чище, что альтернативы, которые другие предлагая. Если вы беспокоитесь об исчезновении ! (который является законным беспокойством), используйте больше пробелов.

if ( ! start.empty() || ! end.empty() ) ...

Или попробуйте вместо этого использовать ключевое слово not:

if ( not start.empty() || not end.empty() ) ...

(В большинстве редакторов not будет выделено как ключевое слово, который привлечет к себе еще большее внимание.)

В противном случае две вспомогательные функции:

template <typename Container>
bool
isEmpty( Container const& container )
{
    return container.empty();
}

template <typename Container>
bool
isNotEmpty( Container const& container )
{
    return !container.empty();
}

Это имеет дополнительное преимущество, предоставляя функциональность лучшее имя. (Названия функций - это глаголы, поэтому c.empty() логически означает "пустой контейнер", а не "контейнер" empty ". Но если вы начнете обертывать все функции в стандартная библиотека с бедными именами, у вас есть работа для вас.)

Ответ 9

Я тоже боюсь с психологической накладкой отрицательной логики.

Одним из решений этого (когда его нельзя избежать) является проверка на явное условие, рассмотрим:

if (!container.empty())

против

if (container.empty() == false)

Вторая версия легче читать, поскольку она протекает так, как вы ее читали вслух. Это также дает понять, что вы проверяете ложное состояние.

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

Например, со строками:

class MyString : public std::string
{
   public:
     bool NotEmpty(void)
     { 
       return (empty() == false); 
     }
};

Теперь он становится просто:

if (container.NotEmpty())...

Ответ 10

Если все, что вас беспокоит, это легкость, с которой можно игнорировать !, вы можете вместо этого использовать стандартный альтернативный токен С++ not:

const std::string fmtTime(const std::string& start, const std::string& end)
{
    std::string time;
    if (not start.empty() or not end.empty()) {
        if (not start.empty() and not end.empty()) {
            time = "from "+start+" to "+end;
        } else {
            if (end.empty()) {
                time = "since "+start;
            } else {
                time = "until "+end;
            }
        }
    }
    return time;
}

(см. [lex.digraph] в стандарте для альтернативных токенов)

Ответ 11

Вы считали бы назначенной хорошей противоположностью?

#include <string>

template <typename CharType>
bool assigned(const std::basic_string<CharType>& s)
{
    return !s.empty();
}

std::string fmtTimeSpec(const std::string& from, const std::string& to)
{
    if (assigned(from)) {
        if (assigned(to)) {
            return "from "+from+" to "+to;
        }
        return "since "+from;
    }
    if (assigned(to)) {
        return "until "+to;
    }
    return std::string();
}

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