А как бы это выглядело на C? Спрашивайте чаще
Современные компиляторы дают массу полезных гарантий и делают часть работы за нас. Это хорошо, но у всего есть обратная сторона. Нельзя сказать, что такая помощь провоцирует плохую архитектуру. Но она...
Современные компиляторы дают массу полезных гарантий и делают часть работы за нас. Это хорошо, но у всего есть обратная сторона. Нельзя сказать, что такая помощь провоцирует плохую архитектуру. Но она делает возможным написать плохо спроектированную программу – и не свихнуться.
Как понять, что именно держит вашу программу – архитектура или компилятор? Можно задать себе несколько неудобных вопросов. Например: «А как бы это всё выглядело на C?»
В романе Хайнлайна «Туннель в небе» есть интересный эпизод. Главному герою, Роду Уокеру, предстоит экзамен: выжить в одиночку на незнакомой планете. Перед экзаменом он обсуждает снаряжение с сестрой Хелен и спрашивает, какое ружьё или пистолет взять. Сестра – опытная военная, капитан корпуса «Амазонок» – советует взять только нож и объясняет почему:
С пистолетом на боку ты готов ко всему, и тебе даже хочется приключений. Вот тут-то и кроется опасность, потому что всё это тебе только кажется. <…> Я отправляю на разведку с голыми руками, иногда босиком, без всего вообще.
Инструмент, который обещает защитить, снижает осторожность. В программировании так же.
Примеры дальше – на C++, потому что это мой основной язык. Но вопрос подходит для любого языка, который что-то делает за вас или что-то за вас проверяет: сборщик мусора в Java, C# и Python, ARC в Swift, borrow checker в Rust. C здесь не столько язык, сколько точка отсчёта – язык, который не гарантирует почти ничего.
На C можно сделать почти всё: наследование, инкапсуляцию, полиморфизм, а с кодогенерацией – и квазишаблоны (на сгенерированный код даже можно посмотреть и отладить его по шагам). Можно прикрутить и сборщик мусора (Boehm GC), но это уже экзотика. Чего в C нет, так это гарантий.
Начнём с того, что компилятор не добавляет вызовы конструктора и деструктора. Ручной вызов конструктора пережить можно: переменная объявляется один раз. А точек выхода из функции может быть много, и забыть вызвать деструктор или вызвать его дважды – проще простого. Принцип single entry, single exit как раз здесь помогает.
Проблема не в том, что придётся явно вызывать foo_init() и foo_deinit(). Проблема в том, что компилятор не гарантирует, что вы ничего не забудете.
Что даст мысленное переписывание программы на C? Оно поможет увидеть, какую работу за нас выполняет компилятор – и насколько сложные правила стоят за этой работой.