Вчера написал небольшой блог пост с примером собственного стремления использовать const и иммутабельность как можно чаще. А сегодня на Reddit-е обнаружил ссылку на статью "const all the things" от Arthur O'Dwyer. Которая на ту же самую тему. И несколько противоречит моим предпочтениям.
Статья крайне толковая, очень рекомендую к ознакомлению.
Однако, как мне показалось, она написана с точки зрения человека, который пишет новый код. Если же попробовать взглянуть на те же самые аспекты немного с другой стороны, то по двум пунктам я с O'Dwyer-ом не соглашусь.
Первый пункт связан с константными членами класса.
Тов.O'Dwyer говорит, что это практически всегда плохая идея. И приводит веские аргументы.
Но есть нюанс. Даже два.
Во-первых, когда мы не делаем собственный класс с нуля, а наследуемся от готового класса, предоставленного каким-то фреймворком, то запросто может оказаться, что наш класс уже и non-copyable, и non-moveable.
Взять хотя бы наш SObjectizer: агент наследуется от agent_t и автоматически становится не копируемым и не перемещаемым. Такая же картина, насколько я помню, во всех продвинутых GUI-фреймворках и не только.
Так что во многих случаях коде аргумент о том, что const-член класса будет вступать в противоречие с copy- и movability окажется бесполезным и несостоятельным. Нас там просто-напросто не будут занимать вопросы copy- и movability от слова совсем. По крайней мере когда мы разрабатываем прикладной код, а не библиотеки.
Во-вторых, классы могут быть довольно большими, с десятками методов. И код некоторых методов может быть довольно объемным.
Да, это не есть хорошо, но это реальная жизнь. Иногда большой класс -- это реально проще, быстрее и дешевле, чем мудреная декомпозиция на множество мелких сущностей только для того, чтобы сущности были мелкими.
И вот когда у нас большой и толстый класс, да еще и с длинной историей жизни, к которому в разное время руку приложили совершенно разные разработчики, вот тут-то const для членов может оказаться очень выгодным. Т.к. он защищает от модификации внутри класса то, что не должно изменяться в принципе.
Если const не использовать, то запросто может оказаться так, что в первой версии класса объемом в 2500 LOC все хорошо, но когда он несколько раз модифицировался и вырос до 4000 LOC, то в каком-то месте кто-то случайно (по незнанию или недосмотру) модифицировал что-то, что не нужно было модифицировать. И все, ищи потом причину.
А этого не было бы, если бы const использовался.
Добавлю сюда еще и то, что иногда члены класса бывают не только public/private, но и protected. И они не просто так protected сделаны, а для того, чтобы с ними можно было работать в производных классах.
Да, это так же можно объявить плохим стилем, как и большие по объему классы.
Но, опять таки, есть наши благие намерения, и есть реальная жизнь, в которой приходится идти на компромиссы. В некоторых случаях protected-члены упрощают нам жизнь. И в этих самых редких случаях вполне можно использовать const для тех членов, которые не должны модифицироваться в классах-наследниках.
В общем, в идеальном мире рекомендация тов.O'Dwyer-а избегать const-членов класса выглядела бы уместно. В реальности же все не так однозначно.
Второй пункт связан с const внутри функций.
Когда мы пишем небольшую по объему функцию с нуля, то const может быть и не нужен. А местами (про которые тов.O'Dwyer говорит в своей статьи) даже вреден.
Но давайте представим, что мы имеем дело с чужой функций. Небольшой, всего на пятнадцать-двадцать строчек. С пятью-семью переменными внутри.
Когда я читаю чужой код и вижу переменную, то у меня сразу фиксируется, что это переменная, что она должна где-то изменяться. Поэтому часть моего внимания отдается на отслеживание судьбы переменной (мол, здесь мы присвоили одно, здесь другое, поэтому в этой точке должно быть вот это, но если у нас пошло вот так, то вот это). Соответственно, на другие аспекты изучаемой функции внимания останется меньше.
Тогда как видя в коде константу, я перестаю отслеживать ее судьбу вообще, т.к. понимаю, что не может константа измениться. Мне достаточно понять какое значение и почему она получила, дальше тратить свое внимание на эту константу не нужно.
Соответственно, изучать чужую функцию на двадцать строк с семью переменными сложнее, чем такую же функцию, но с пятью константами и всего двумя переменными.
Более того, если я вижу, что объявляется переменная, получает значение один раз при объявлении и больше не меняется, то невольно начинаю искать подвох. Возможно, логика функции реализована не до конца. Может быть здесь должно быть еще что-то.
Может быть изменение переменной идет как-то неявно. Вдруг ссылка/указатель на эту переменную куда-то отдается и она модифицируется извне.
В общем, переменная, которая только инициализируется и не меняется, вызывает гораздо больше подозрений и вопросов, чем переменная, которая явным образом изменяется по ходу дела несколько раз.
Так что совет не использовать const внутри функций, как по мне, так вообще откровенно вредный. И здесь я с тов.O'Dwyer сильно не согласен. И, думается, в этом аспекте я более прав.
3 комментария:
Это здорово, когда есть люди, которые понимают почему делать переменную неизменяемой лучше, даже если это не обязательно. Я лично всегда чувствовал, что как-будто оправдываюсь, когда в очередной раз отвечал на вопрос зачем здесь const. И даже после моего объяснения оставалось ощущение, что мою точку зрения не разделяют.
@Stas Mischenko
Ну вот, вы не один :)
Я не такой уж сторонник const, но где его использование выглядит естественным, стараюсь использовать. И были случаи, когда const на этапе написания нового кода вскрывал ошибки дизайна.
Отправить комментарий