Приветствую вас, коллеги!
В предыдущей колонке я рассказал о проблеме сложности — код пишется небольшими фрагментами, а читается целиком — в связи с чем, написать программу часто намного легче, чем впоследствии прочитать, понять и изменить. А большинство программ, как это ни странно, после написания не выбрасываются — а, наоборот, активно развиваются, изменяются, в них вносятся новые функции и удаляются старые. На мой взгляд, для того, чтобы бороться с проблемой сложности, нужно понять причину ее возникновения. Что значит — «код сложно читать»?
Чтобы разобраться в этом, проведем небольшой мысленный эксперимент. Ученый написал короткую фразу, отражающую какую-нибудь закономерность. Она понятна ему и другим ученым. Затем он взял часть этой фразу и углубил ее, добавив несколько слов. Фраза стала длиннее, но все еще понятна и ему, и коллегам. Усложняя фразу раз за разом, мы будем наблюдать интересный эффект — с каждым разом самому ученому будет все труднее и труднее прочесть и понять фразу целиком. Но, несмотря на это, небольшие ее фрагменты все еще можно будет относительно безболезненно понимать и расширять. В конце концов, мы придем к ситуации, когда сам автор не сможет прочесть и понять фразу целиком. Почему это происходит? Если несколько теорий и множество гипотез.
Кошелек Миллера
Из всех объяснений сложности чтения и понимания кода мне больше всего нравится закономерность, обнаруженная в 1956 году американским учёным-психологом Джорджем Миллером. Работая в Bell, он исследовал возможности памяти людей, работавших с компьютером. Согласно его наблюдениям, человек может запомнить и удерживать в кратковременной памяти от 5 до 9 разных элементов. В своих работах Миллер сравнивает человеческую память с «кошельком» фиксированного объема, в который можно одновременно положить не больше девяти монет произвольного номинала. Если же объектов больше девяти, то наш мозг начинает группировать их, чтобы количество групп также не превышало емкости кошелька Миллера.
На мой взгляд, именно это фундаментальное ограничение человеческой памяти лежит в основе проблемы сложности. Когда мы пишем код, мы оперируем небольшими фрагментами, одновременно удерживая в памяти маленький текущий контекст и несколько объектов, из которых в данный момент составляем текст кода. Когда же код написан, и мы пытаемся его прочитать, то в случае, если в нем больше примерно семи элементов — у нас начинаются проблемы.
Многие разработчики знают такие фундаментальные правила разработки:
- методы должны быть не длиннее экрана;
- строчки кода не должны быть длиннее примерно 80 символов;
- в классе не должно быть много методов и полей;
- сложные условия необходимо разбивать на части;
- и так далее.
Таких правил в популярных книгах по разработке можно найти сотни. И все они косвенно указывают на одно и то же — любой участок программы для своего понимания не должен требовать удержания в памяти более семи элементов. Это (на мой взгляд) ключевое правило я очень мало где видел сформулированное именно таким образом. А ведь именно его несоблюдение приводит к тому, что проекты с многомиллионным бюджетом «гниют» изнутри, и их приходится раз в несколько лет переписывать с нуля.
Способы борьбы с кошельком Миллера
Многие менеджеры считают, что «ну раз человек в школе научился на бейсике программировать, то он профессионал и сам со всем разберется, нужно только задачи ему ставить». Я это мнение не разделяю — хотя бы потому, что образовательной базы для разработчиков в мире сейчас нет. Да и вообще, я пессимист. Что можно сделать с организацией процесса разработки, чтобы проект не сгнил через три года от проблемы сложности?
- Рассказать про нее всем участникам процесса и перебороть hindsight bias. Второе очень важно, так как вам хором ответят «это очевидно, мы всегда об этом знали» и к завтрашнему дню забудут. Не потому, что люди плохие, а потому, что мозг так работает.
- Использовать практику code review, когда код, написанный одним человеком, начерно, по диагонали, просматривается другим. Это нужно не для того, чтобы найти какие-то ошибки в программах, а для того, чтобы проверить, насколько код легко читается. Как показывает практика, даже зная о кошельке Миллера, первые годы трудно отслеживать количество упоминаемых в коде объектов. Зато для другого человека это будет самоочевидно — «я не могу быстро понять вот этот фрагмент, да и в целом непонятно, что и как новый код делает». С практикой code review тоже не все так просто — при ее введении нужно объяснить для чего она вводится, потому что есть разные code review для разных целей. Например, при написании софта для критичных приложений, к примеру, управления самолетами, код просматривается несколькими людьми с целью проверить, соответствует ли он принятым стандартам и не имеет ли ошибок. Для борьбы со сложностью достаточно, чтобы разработчики просматривали код друг друга — нет необходимости его детально вычитывать, это займет больше времени, вдумчивое чтение кода занимает больше времени, чем его написание.
- Использовать систему continuous integration, проверяющую формальные метрики кода — максимальную длину строк кода, размер функций, количество методов в классе и т.д. Если подходить к этому без фанатизма, то практика может стать хорошим подспорьем, особенно на начальных этапах, если большинство разработчиков в команде не готовы к тому, чтобы как-то взаимодействовать со сложностью кода.
На этом, пожалуй, все. О проблеме сложности я могу говорить очень много, и в последующих колонках еще не раз вернусь к этому вопросу, но надо и меру знать. Управление разработкой таит в себе множество ловушек и подводных камней, и о многих хочется рассказать — чем раньше, тем лучше. Поэтому в следующей публикации я расскажу о предсказывании будущего и о том, почему проектирование программ не такое простое дело, как кажется.