пятница, 12 февраля 2016 г.

[prog.c++.flame] Учимся читать Google C++ Style Guide касательно исключений

Редкий наброс о том, какой C++ отстой и насколько лучше программировать на теплой ламповой сишечке, обходится без обмазывания C++ных исключений известной субстанцией. Главным аргументом у персонажей с лопатами говна в руках, неизменно является раздел Google C++ Style Guide, посвященный исключениям. Ну что же, давайте заглянем в этот раздел и внимательно прочтем то, что там написано...

Итак, на 2016.02.12 мы видим там следующее:

We do not use C++ exceptions.

Pros:

  • Exceptions allow higher levels of an application to decide how to handle "can't happen" failures in deeply nested functions, without the obscuring and error-prone bookkeeping of error codes.
  • Exceptions are used by most other modern languages. Using them in C++ would make it more consistent with Python, Java, and the C++ that others are familiar with.
  • Some third-party C++ libraries use exceptions, and turning them off internally makes it harder to integrate with those libraries.
  • Exceptions are the only way for a constructor to fail. We can simulate this with a factory function or an Init() method, but these require heap allocation or a new "invalid" state, respectively.
  • Exceptions are really handy in testing frameworks.

Cons:

  • When you add a throw statement to an existing function, you must examine all of its transitive callers. Either they must make at least the basic exception safety guarantee, or they must never catch the exception and be happy with the program terminating as a result. For instance, if f() calls g() calls h(), and h throws an exception that f catches, g has to be careful or it may not clean up properly.
  • More generally, exceptions make the control flow of programs difficult to evaluate by looking at code: functions may return in places you don't expect. This causes maintainability and debugging difficulties. You can minimize this cost via some rules on how and where exceptions can be used, but at the cost of more that a developer needs to know and understand.
  • Exception safety requires both RAII and different coding practices. Lots of supporting machinery is needed to make writing correct exception-safe code easy. Further, to avoid requiring readers to understand the entire call graph, exception-safe code must isolate logic that writes to persistent state into a "commit" phase. This will have both benefits and costs (perhaps where you're forced to obfuscate code to isolate the commit). Allowing exceptions would force us to always pay those costs even when they're not worth it.
  • Turning on exceptions adds data to each binary produced, increasing compile time (probably slightly) and possibly increasing address space pressure.
  • The availability of exceptions may encourage developers to throw them when they are not appropriate or recover from them when it's not safe to do so. For example, invalid user input should not cause exceptions to be thrown. We would need to make the style guide even longer to document these restrictions!

Decision:

On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.

Given that Google's existing code is not exception-tolerant, the costs of using exceptions are somewhat greater than the costs in a new project. The conversion process would be slow and error-prone. We don't believe that the available alternatives to exceptions, such as error codes and assertions, introduce a significant burden.

Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. Because we'd like to use our open-source projects at Google and it's difficult to do so if those projects use exceptions, we need to advise against exceptions in Google open-source projects as well. Things would probably be different if we had to do it all over again from scratch.

This prohibition also applies to the exception-related features added in C++11, such as noexcept, std::exception_ptr, and std::nested_exception.

There is an exception to this rule (no pun intended) for Windows code

.

Такое впечатление, что противники исключений читают только первую строку "We do not use C++ exceptions" после чего пробегают перечисление пунктов "за" и "против" и на этом останавливаются. Мол, раз исключения не используются, значит причины, перечисленные в разделе "против", гораздо серьезнее, чем причины "за". А раз так, то исключения идут на помоечку.

Между тем, все самое важное и интересное находится в разделе Decision.

Причем сразу же: "On their face, the benefits of using exceptions outweigh the costs, especially in new projects." Т.е., в переводе на русский: преимущества от использования исключений перевешивают недостатки, особенно в новых проектах.

В принципе, уже на этом стоит ставить точку в обсуждении аргумента "Исключения не следует использовать поскольку Google их не использует". Но чтобы эта точка была жирной, нужно показать пальцем на причину того, что Google не использует исключения. И эта причина прямо, подробно и логично описана в Google-овских гайдах. Смотрим внимательно:

Часть первая: "However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions." Или, пересказывая по-русски: "Тем не менее, для существующего кода добавление исключений окажет влияние на все, что зависит от этого старого кода. Если исключения выпускаются за пределы нового проекта, то это так же станет проблемой при интеграции нового проекта в старый код, который был написан без исключений. Поскольку большая часть существующего C++ного кода в Google не готова к встрече с исключениями, то довольно сложно добавлять новый код, который генерирует исключения."

Т.е. Google прямо и открыто говорит: если старый код не работает с исключениями, то интегрировать в него новые куски кода с исключениями тяжело. А т.к. такого старого кода в Google овердофига, то писать новый код с исключениями слишком дорого.

Эта простая штука еще раз подтверждается в следующем абзаце: "Given that Google's existing code is not exception-tolerant, the costs of using exceptions are somewhat greater than the costs in a new project. The conversion process would be slow and error-prone. We don't believe that the available alternatives to exceptions, such as error codes and assertions, introduce a significant burden." В вольном пересказе на русский это звучит как: "В действительности существующий в Google код не приспособлен к исключениям, поэтому стоимость использования исключений в старом коде оказывается повыше, чем стоимость использования в новом коде. Процесс преобразования старого кода будет медленным и чреватым ошибками. Ну и мы вообще не верим в то, что доступные альтернативы для исключений, т.к. коды ошибок и assert-ы, ведут к большему геморру."

А в следующем абзаце Google говорит об еще одной стороне этой же медали: "Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. Because we'd like to use our open-source projects at Google and it's difficult to do so if those projects use exceptions, we need to advise against exceptions in Google open-source projects as well." Т.е. пересказывая по-русски: "Наш совет не использовать исключения не имеет философских или моральных обоснований, только практические. Т.к. мы любим использовать open-source проекты у себя в Google и очень тяжело это делать, если проекты задействуют исключения, то нам приходится давать рекомендации воздерживаться от использования исключений в open-source проектах самого Google."

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

Ну и вишенка на торте: "Things would probably be different if we had to do it all over again from scratch." Т.е. будь у нас возможность сделать все еще раз с нуля, то, вероятно, все могло быть несколько иначе.


Надеюсь, с рекомендациями Google все понятно. Кроме того, желающих сослаться на авторитетность Google в этих вопросах следует попросить обосновать эту самую авторитетность. Где и кем было доказано, что качество разработок Google заметно выше, чем в целом по индустрии? Да и цена достижения хотя бы того уровня качества, который есть у Google-овских разработок, так же интересна. Многие ли компании и/или коллективы способы платить такую цену?

А вопрос цены, которую нужно платить за качество, далеко не праздный. Возьмем, к примеру, крайне простой C++ный код, который специалисты из Google сочли бы exception-tolerant:

class person
{
   std::string m_name;
   std::string m_surname;
public :
   person() = default;
   person( const std::string & name, const std::string & surname )
      :  m_name{ name }, m_surname{ surname }
   {}

   const std::string & name() const { return m_name; }
   void name( const std::string & v ) { m_name = v; }

   const std::string & surname() const { return m_surname; }
   void surname( const std::string & v ) { m_surname = v; }

   void change( const std::string & name, const std::string & surname )
   {
      person tmp{ name, surname };
      swap( tmp );
   }

   void swap( person & o ) noexcept
   {
      std::swap( m_name, o.m_surname );
      std::swap( m_surname, o.m_surname );
   }
};

Желающие попрограммировать на C++ без исключений пусть попробуют написать его аналог на C++ хотя бы с таким же уровнем контроля за ошибками выделения памяти. Интересно было бы посмотреть на объем кода. Ну а те, кто ратует за чистую С-шечку, могут проделать то же самое на C. Опять же с контролем ошибок. И да: звать abort() когда malloc() возвращает NULL нельзя (кто не согласен или, например, уверен, что в Linux-е malloc никогда NULL не возвращает, тот пусть отправится на первый курс соответствующего ВУЗа и, пройдя должно обучение, получит таки профильное образование).

PS. Очевидно, что есть ниши, где исключения не используются. Например, жесткое реальное время. Или суровая embedded-щина. Однако, отказ от использования исключений вне этих ниш должен быть обоснован более тщательно, нежели ссылкой на Google C++ Style Guide.

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