Отзыв на книгу «The Art of Readable Code»

Cover of «The Art of Readable Code»

Недавно наконец дочитал книгу «The Art of Readable Code» авторов Dustin Boswell и Trevor Foucher. Об этой книге я узнал из одного блога, на который я подписан.

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

Книга сама по себе невелика — всего 278 страниц на моем PocketBook с шестидюймовым дисплеем и 12 размером шрифта. Она состоит из 4 разделов, которые охватывают:

  • Правильное именование переменных и функций;
  • Правильное написание комментариев;
  • Приемы рефакторинга кода для повышения читабельности.

и многое другое (вплоть до юнит-тестирования).

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

Диапазоны

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

# does this print [2,3] or [2,3,4] (or something else) ?
print integer_range(start=2, stop=4)

После чего предлагают решение в виде правильного именования переменных.

first и last
Если именовать переменные так, то по мнению авторов становится очевидно, что это inclusive/inclusive диапазон ([first; last]) — диапазон, в котором задающие переменные входят в него.
begin и end
А если переменные именовать так, то вполне понятно, что это inclusive/exclusive диапазон ([begin; end)) — диапазон, в котором только нижний порог входит в него.

В книге также говорится, что begin и end не очень подходят для задания inclusive/exclusive диапазона, но в английском языке нет слов которые подходили бы больше. А begin и end, как минимум, исторически символизируют данный тип диапазона благодаря стандартной библиотеки C++.

Что же касается меня, то я считаю, что имена first/last и begin/end подходят для задания любых диапазонов, и считать что код написан непонятно, если в качестве inclusive/exclusive диапазона используются имена first/last — абсурдно. Указывать тип диапазона лучше в документации и в комментариях к коду, а не пытаться понять это из имен переменных. Хотя сама идея, несомненно, хороша, но если бы ее придерживалась бы масса..

list.size()

Далее в разделе, Dustin Boswell и Trevor Foucher начинают критиковать метод size() стандартного контейнера C++ — std::list<>. На их взгляд данное имя некорректно, так как метод не просто возвращает свой размер, а производит его вычисление. В следствии чего данный метод выполняется за O(n) операций, чего может не учесть программист и написать не оптимизированный код:

Note

Для неопытного брата-программиста отмечу, что проблема нижеприведенного кода заключается в том, что благодаря вызову list.size() его сложность стала O(n^2).

void ShrinkList(list<Node>& list, int max_size)
{
    while (list.size() > max_size) {
        FreeNode(list.back());
        list.pop_back();
    }
}

Авторы говорят, что метод size() стоило лучше назвать countSize(), так как это больше подходит его сути. Они также упоминают тот факт, что size() был так назван для единства интерфейса всех контейнеров C++.

Но с этим советом я в корне не согласен. Я считаю, что всегда нужно смотреть на контекст. То, что разработчики STL постарались однообразить интерфейс всех контейнеров — само по себе является веской причиной.

Но даже если бы и не было других контейнеров, и мы рассматривали исключительно отдельно взятый list, я бы все равно назвал бы метод size(). Почему? Да потому что я предпочитаю работать с абстракциями. Размер списка — это его свойство, его обязательный атрибут. Размер — статическое понятия: для отдельно взятого списка, размер будет постоянным. Так почему же тогда я должен называть метод countSize()? Разве мне не прозрачно, что происходит за занавесом: вычисление размера или возвращение известного числа?

Я считаю, что именование методов не должно зависить от его реализации (если это, конечно, не класс, который содержит в себе различные варианты реализаций того или иного алгоритма). Более того, разработчики компиляторов в своей реализации STL могли по разному реализовать данный метод; и то, что выполнялось со сложностью O(n) в одной реализации, может выполнятся со сложностью O(1) в другой реализации.

А начиная со стандарта C++11, в требованиях к контейнерам (пункт 23.1) ясно указывается, что метод size() у всех контейнеров должен иметь сложность O(1).

filter()

Еще в книге поднимается вопрос об именовании метода filter().

results = Database.all_objects.filter("year <= 2011")

Дескать не понятно: results содержит объекты с годом <= 2011, или наоборот? Иными словами, название метода не говорит о том что должно произойти: отсеивание или выборка по условию. В качестве альтернативы предлагаются имена select() и exclude() для выборки и отсеивания соответственно.

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

Вывод

За исключением некоторых моментов, я остался доволен от книги. Да, пусть многие советы очевидны и к ним я пришел сам благодаря опыту. Пусть, она многому не научила, но зато она читается легко и интересно. И теперь я буду её рекомендовать всем, у кого проблемы со стилистикой или кто пишет через чур сложный код для простых вещей.