Культура программирования
Введение
Поводом написания данной статьи послужил вакуум русскоязычной литературы в области культуры программирования. Данная статья не претендует на абсолютную полноту и истинность – она призвана отразить точку зрения автора на эту тему.
В первую очередь необходимо определиться с терминами. Что же такое культура и что же такое программирование? Для обоих этих понятий существует множество определений, мы же воспользуемся приведёнными ниже.
Под культурой понимается набор правил (часто неформальных), стереотипов и норм поведения.
В качестве определения понятия «программирование» автор предлагает следующее, лаконичное, но ёмкое определение: программирование это часть производства программного обеспечения. Это не кодирование, не трансляция ваших мыслей в выражения какого-либо языка программирования, это производство, организация которого должна быть на очень высоком уровне.
Следующий вопрос, на который необходимо ответить: для кого пишутся программы, для конечных пользователей или программистов? Кому-то очевидным покажется ответ, что программы пишутся для конечных пользователей. Тот же, кто занимался поддержкой кода, написанного другим человеком, может весьма эмоционально сказать, что программы надо писать так, чтобы их было легко поддерживать. А поддерживать программы надо, потому что программа, которая не развивается – это мёртвая программа. Автор считает, что хорошая программа, написанная культурным программистом, одинаково проста как в использовании, так и в поддержке.
В статье «Двоекультурие» Джоэл Сполски (“http://russian.joelonsoftware.com/Articles/Biculturalism.html”) говорит, что unix-программы пишутся для программистов, в то время как windows-программы пишутся для пользователей. Мы не будем доказывать или опровергать это утверждение. Мы отметим, что писать программы только для пользователей или только для программистов нельзя. Например, автомобили создаются для водителей или для работников сервис-центра? Наверное все согласятся с тем, что машину, которую сложно ремонтировать будут покупать не лучше, чем ту, которой сложно управлять. То же самое, на взгляд автора, относится или должно относиться, и к программному обеспечению: программы должны быть легкими как в использовании, так и в поддержке и развитии.
Давайте приведём список тех «правил, стереотипов и норм поведения», которые делают программиста культурным, а создаваемое им программное обеспечение лёгким, как в поддержке и использовании. Упорядочивать этот список по значимости элементов это весьма не простая задача, решение которой сугубо субъективно для каждого человека, поэтому они следуют в произвольном порядке.
Шаблоны проектирования
Приведём ещё одно определение культуры: «Культурой называется позитивный опыт и знания человека или группы людей, ассимилированный в одной из сфер жизни (в человеке, в политике, в искусстве и т. д.)». В нашем контексте ключевыми словами этого определения являются: «позитивный опыт», что по сути является определением шаблонов проектирования.
Культурный программист должен знать и использовать в разрабатываемом программном обеспечении шаблоны проектирования потому, что:
- Неоднократно проверенное решение. Это позитивный опыт, который, при корректной реализации, почти гарантированно приведёт к хорошему коду программного обеспечения и, как следствие, к высокому уровню самого ПО;
- Устоявшаяся терминология. Если другой программист, знакомый с шаблонами проектирования, встретит в вашем коде знакомые названия, то ему будет проще разобраться в нём;
- Устоявшаяся терминология. Заниматься проектированием намного проще, если оперировать терминами «Abstract Factory» и «Observer», нежели «класс, который порождает экземпляры этого» и «класс, чьи методы вызываются по мере работы того». Но для этого необходимо, чтобы все разработчики знали что такое «Abstract Factory».
Стандарты и соглашения
Воспользуемся ещё одним определением культуры: «Культура — то, что противостоит хаосу». То, что твориться в коде программы написанной для личного пользования – это ваше личное дело. Но производство ПО это обычно работа команды. И в этом случае то, что твориться в вашем коде – это дело коллектива.
По-хорошему, единственным способом узнать автора данного исходного кода, должен быть заголовок файла (наличие которого, тоже относится к культуре программирования). В вашей команде должен быть документ «Стандарт программирования и оформления кода», который должен описывать все тонкости программирования и оформления кода, от «имена переменных должны быть значащими, за исключением тривиальных случаев (например, счётчики циклов)», до «Любые взаимодействия с базой данных должны осуществляться по средствам соответствующего фреймворка, подключение к БД напрямую запрещено».
Этот стандарт должен поддерживаться и постоянно обновляться, а знать его должен каждый, кто пишет код. Это упростит чтение чужого кода, чужой стиль не будет «резать глаз» и повысит общую дисциплину команды, что для программистов особенно важно.
Так же важно оформлять все соглашения в виде официального документа. Один раз автор, потратил несколько часов на выяснение следующего соглашения: «имена полей классов-контейнеров должны отличаться от имён параметров процедур отсутствием заключающего символа «_»». Был бы документ, описывающий соглашения – не было бы проблемы.
Стандарт должен включать в себя пункт о комментариях. Комментарии должны быть. Но тут важно чувствовать грань и не писать комментарий к каждой строчке кода. Автор использует следующую стратегию комментирования объектно-ориентированного кода:
- Должно быть описано назначение каждого класса;
- Каждый элемент класса так же должен быть прокомментирован;
- Сложные, не прозрачные участки кода, должны снабжаться комментариями;
- Если спустя некоторое время возникли проблемы с чтением того или иного участка кода, он так же должен быть прокомментирован.
Если вы работаете не с зарубежным заказчиком и не за рубежом, то комментарии лучше писать на русском языке, потому что англоязычные комментарии в русскоязычной среде похожи на глухой телефон. Пробуйте собрать группу из нескольких человек, возьмите более менее сложный комментарий и переведите его по очереди с русского языка на английский и обратно. Автором был произведён подобный эксперимент, в результате фраза «чистка списка событий(если какие-то события не забираются никаким потоком, что бы не было переполнения)» превратилась в «Если сообщение не доставлено, тогда список сообщений должен быть очищен в целях избегания переполнения». Если внимательно вчитаться, то заметно, что смысл исказился значительно.
Использование средства контроля версий
Каждый из нас может ошибаться. Каждый из нас, прежде чем поймёт ошибку, может уйти настолько далеко от некогда верной идеи, что проще написать всё заново, чем из имеющегося кода восстановить то, что было. Для того чтобы не пришлось начинать всё заново, необходимо использовать средство контроля версий. При том фиксировать надо не только ключевые версии, но и вообще весь процесс разработки. Возможна, например, следующая стратегия: код фиксируется каждый вечер, а ключевые версии тэгируются.
Если разработка ведётся группой программистов, то необходимо средство для нейтрализации конфликтов параллельной разработки, что тоже обеспечивается большинством систем контроля версий.
Многие средства позволяют вести параллельно две версии кода, например, первую версию, в которой исправляются ошибки, выявленные при её эксплуатации и вторую версию продукта.
Так же, в серьёзных организациях, используя подобную систему, вы перекладываете ответственность за сохранность кода на администраторов. Они обычно успешно справляются с этой функцией, позволяя вам не хвататься за голову в случае выхода из строя жёсткого диска.
Простой код
Код хорошей программы должен быть на столько простым, на сколько это возможно. «Не надо усложнять задачу», «Keep it simple, stupid! (правило KISS)», «выбирайте простейшее из работающих решений», «не надо делать того, что не предусмотрено тех. заданием», будьте проще!
Простой код проще в чтении, а значит в поддержке. Простой код обычно короче и понятнее, а значит, содержит меньше ошибок.
Например, зачем вам быстрая или пирамидальная сортировка, если скорость сортировки пузырьком вас устраивает? Не надо делать что-то универсальное и на все случаи жизни, если вам надо решить вполне конкретную и простую задачу. Не реализуйте функцию, если за неё не заплатят.
Рефакторинг
Рефакторинг – это процесс изменения кода без изменения функциональности. То есть с точки зрения пользователя программа не меняется. Но с точки зрения программиста она меняется, при том в лучшую сторону, если рефакторинг проводится грамотно.
Рефакторинг заключается в чистке программного кода от заплаток, костылей, «хаков» и т.п. и изменения архитектуры в целях сделать её более понятной, гибкой и т.д. Подобную чистку необходимо проводить регулярно. Это сделает его более простым, надёжным, гибким и т.д. Если вы не знакомы с шаблонами проектирования, то можно с ними ознакомиться, выявить те, которые применимы у вас и переписать приложение с использованием выбранных шаблонов.
Другим хорошим кандидатом на рефакторинг является код, который писался без предварительного проектирования или при неполном понимании задачи. Получив опыт решения задачи, вы сможете сделать ваш код более качественным.
Код который несколько раз переписывался или изменялся на скорую руку, так же может требовать рефакторинга. Сразу написать идеальный код – это очень сложная задача. Код должен эволюционировать, и в этом вам поможет рефакторинг.
Рефакторинг необходим, если:
- Имеет место сложность модификации. Поддержка требует всё больше и больше ресурсов;
- Имеет место сложность чтения. Есть комментарии типа: «Как это работает – не понятно. НЕ ТРОГАТЬ!»;
Прежде чем приступить к рефакторингу подготовьте средство подтверждения корректности кода. В качестве такого средства автор предлагает использовать Юнит тесты (см. соответствующий раздел).
Управление ресурсами
Управление ресурсами – это тема многих уже написанных книг. Поэтому мы только лишь затронем несколько ключевых моментов, не углубляясь в подробности.
Культурный программист не оставит за собой десяток открытых файлов, пару подключений к БД и гору не освобождённой памяти. Потому что, ПО, которое «вешает» всю систему и блокирует половину файловой системы нельзя назвать хорошим.
Программа должна захватывать ровно те ресурсы, которые ей необходимы в данный момент, и освобождать их сразу, после того как потребность в них удовлетворена. Сюда же входят и исключительные ситуации. Если у вас оборвалось подключение к БД, это совсем не повод оставлять заблокированным локальный файл.
Так же стоит отметить тот факт, что обычно, в целях оптимизации, ресурс захватывают один раз и держат его до тех пор, пока он может требоваться. Но если же ресурс является критическим, то его лучше постоянно захватывать и освобождать. Например, если у вас система рассчитана на одновременное использование не более чем 30-тью пользователями, то можно каждому из них выделить по подключению к БД. Если же у вас 10.000 пользователей одновременно работают с системой, то единственным вариантом является пул подключений, откуда берутся подключения и куда они возвращаются как можно быстрее.
Оптимизация
Перед началом разработки необходимо ответить на вопрос: «С какого момента мы начнём работать над оптимизацией?». Он довольно сложен по следующей причине: с одной стороны, если вы приняли медленную архитектуру, то ускорить приложение, не переписав его заново, будет очень сложно. А с другой существуют подтверждённые временем крылатые фразы: «90% времени тратится на 10% кода» (которые ещё надо выявить) и «Сначала заставьте программу работать, потом заставьте её работать правильно и только потом заставьте её работать быстро». Автор склонен выбирать архитектурные решения среди шаблонов проектирования, хорошо проанализировав последствия того или иного выбора и заниматься оптимизацией только после того как появится такая необходимость. И не забывайте, что оптимизированный код, обычно сложнее (см. пункт «Простой код»).
Наконец, приведём следующее правило: «Для того чтобы сэкономить время надо потратить память. Для того чтобы сэкономить память надо потратить время». Грубо говоря, это значит, что промежуточные данные вы можете либо хранить в памяти (экономия времени), либо всякий раз вычислять (экономия памяти). Но помните, что память переиспользовать можно, а время - нет.
Планирование и отчётность
Это, наверное, самые сложные задачи для программиста: спланировать работу, следовать плану и отчитываться о ходе его выполнения. Но это делать надо. По крайней мере, к этому надо стремиться.
Если этого не делать, то вы рискуете стать героями баек из серии «Два месяца пили пиво, а потом за двое суток «наколбасили» проект». О качестве таких «проектов» говорить не хочется.
Отчитываться тоже надо: это позволит начальству не дать вам заниматься два месяца ерундой.
Плюс такая организация производства повышает дисциплину, как отдельного члена коллектива, так и всего коллектива в целом, что не может не сказать на повышении качества производственного процесса.
Так же в этом разделе стоит сказать о графике работы. Я понимаю, что практически невозможно заставить программистов приходить на работу к 9.00 и уходить оттуда в 18.00. Но ваше расписание должно быть согласованно с теми людьми, которые работают вместе с вами над общими задачами. Это очень важно, так как недостаток общения может привести к плачевным последствиям.
Диалог с пользователем
Хорошее программное обеспечение должно быть быстрым. Пользователь не должен гадать, то ли он кнопку не нажал, то ли мышка не сработала, то ли программа всё-таки что-то делает.
Если вам надо выполнить длительную операцию – запускайте её в отдельном, быть может самом приоритетном, потоке, в то время как в основном сообщайте пользователю о начале выполнения операции, о ходе выполнения и реагируйте на его действия.
С одной стороны программное обеспечение, по крайней мере, его интерфейсная часть, должно постоянно вести активный диалог с пользователем. Хорошее ПО не «зависает» не сказав ни слова и не делает того, чего пользователь не заказывал.
С другой стороны, когда какая-то частая операция сопровождается вопросом, на который постоянно надо отвечать – это тоже раздражает. Я хочу подвести читателя к мысли о том, что не следует забывать делать галочки «Запомнить мой ответ».
Так же стоит отметить, что диалог должен осуществляться на грамотном и понятном русском языке. И это не входит в компетенцию программиста: если вы можете хорошо писать на Java, то это не значит, что вы хорошо пишите на русском языке.
Выводимые программой сообщения должны быть максимально полными и понятными, с одной стороны, но и не содержать лишней информации с другой. Не надо в сообщении об ошибке выводить её номер – пользователю это ничего не даст, лучше запишите её в логи (исключением является, если по номеру ошибки тех. поддержка может помочь пользователю решить его проблему). Не надо писать: «База данных выбросила ошибку ORA-XXXX», пишите: «Произошла ошибка работы с БД. Отправьте файл ХХХХ в службу поддержки пользователей».
Не надо выводить лишних сообщений. Сообщение «Приятного вам «дня, вечера, ночи, утра»» в зависимости от времени суток, лишь раздражает и говорит о некомпетентности разработчика.
Программа ведёт диалог с пользователем по средствам интерфейса. А создание качественного интерфейса и построение хорошего диалога – это отдельная наука (юзабилити, удобство использования), которая мало пересекается с программированием. Поэтому пользовательский интерфейс хорошего ПО разрабатывается не программистами, а дизайнерами пользовательского интерфейса. Конечно, возможно, что хороший программист также является и хорошим дизайнером, но в большинстве случаев это не так.
Гибкая настройка
Автор считает, что чем больше аспектов работы программы можно настроить, тем программа лучше. С одной стороны гибкая настройка даёт в руки пользователю оружие в борьбе с вашими предположениями о его нуждах. С другой стороны программа должна запускаться и нормально функционировать без какой-либо настройки, то есть должна быть настройка по умолчанию и она должна быть работоспособной.
Естественно, что параметры работы программы должны храниться в файле, при том формат этого файла должен быть «человекочитаемым». У пользователя (администратора) должна быть возможность изменить настройки, не запуская программу.
Это может помочь и службе поддержки: вместо того, чтобы долго мучить пользователя вопросами типа «Каково значение параметра такого-то?» достаточно попросить его выслать файл настроек.
Так же хорошее программное обеспечение должно поддерживать как минимум 2 режима настройки: для рядового и продвинутого пользователя.
Система справки
На всякий случай отметим, что хорошее ПО сопровождается хорошими документацией и руководством пользователя. Но здесь речь пойдёт не об этом.
В этом разделе говориться о том, что каждый элемент (блок, окно, видеокадр и т.п.) должен иметь кнопку «Справка».
По нажатию этой кнопки пользователь должен получить исчерпывающую информацию о назначении элемента в целом и каждой его части в отдельности, о том, как пользоваться тем или иным элементом.
Справка формы ввода должна содержать примеры вводимых данных. При том не только корректных, но и не корректных. Справка формы вывода должна описывать все выводимые данные. Естественно, справка должна быть на не менее хорошем языке, чем диалоги.
Логгирование
Хорошее программное обеспечение должно полностью описывать свои действия и состояние. Это позволит быстро исправлять даже трудновоспроизводимые ошибки.
При том уровень логгирования должен задаваться установкой соответствующего параметра. По умолчанию должен быть установлен максимально допустимый уровень логгирования.
Сообщения должны записываться не в stdout и stderr, а в долговременное хранилище (файлы, БД).
С другой стороны необходимо учитывать читаемость кода и скорость работы: код, перенасыщенный журнальными сообщениями, долго работает и сложен для чтения. Степень и правила логгирования должны быть продуманы до начала разработки и зафиксированы в стандарте.
Обработка ошибок
Ошибки можно разделить на три вида: ошибки пользователя, ошибки программиста и ошибки оборудования. Все три вида хорошее ПО должно грамотно обрабатывать.
Ошибки пользователя чаще всего сводятся к различным опечаткам и непониманию назначения элементов управления. Такие ошибки не должны становиться причинами программных ошибок: вводимые данные должны проверяться, и если обнаружена ошибка, то должна быть предоставлена возможность для её исправления. При том всё это должно быть хорошо прокомментировано. Нельзя просто прекращать выполнение операции, пользователь должен понять, почему операция не выполнена. Старайтесь давать пользователю рекомендации по исправлению ошибок.
Обработка ошибок программиста включает в себя их выявление (перехват), попытку исправления и восстановление работы программы. Если ошибка исправлена (вы должны быть абсолютно в этом уверенны), то не надо сообщать о ней пользователю. Если исправить не удалось, то необходимо вернуть программу в корректное состояние и сообщить пользователю, в какое состояние программа возращена. Если ошибка критическая, то необходимо аккуратно завершить программу, сообщив пользователю о причинах завершения и дать ему возможность сохранить как можно больше данных.
Ошибки программиста включают в себя и непредвиденное поведение программы. По этому в точках ветвления необходимо учитывать непредвиденные варианты. Например, в блоке switch всегда должна быть метка default, которая, останавливает выполнение операции с соответствующими сообщениями пользователю и в журнал.
Ошибки оборудования обычно исправить программными средствами невозможно. Поэтому лучшей стратегией будет сообщение о ней пользователю, отмена операции, если программа может продолжить работу или корректное завершение в противном случае.
Все ошибки, за исключением, быть может, пользовательских, должны качественно журналироваться.
Юнит тесты
Хорошее ПО, на предусмотренных входных данных, ВСЕГДА правильно работает. Автор не утверждает, что надо писать программы без ошибок - так не бывает – автор утверждает, что надо быть уверенным, что предусмотренное поведение работает без ошибок.
Если функция вчера работала корректно, то это совсем не значит, что она и сегодня работает корректно. В сложных приложениях, со сложными связями бывает очень трудно предугадать последствия того или иного изменения.
Для того чтобы не гадать «А всё ли правильно работает?» существуют юнит тесты. Они позволяют относительно быстро (на много быстрее, чем вручную) проверить работоспособность всей предусмотренной функциональности и убедиться, что, исправив одну ошибку, вы не сделали другую.
Лучше всего, если время выполнения позволяет, при тестировании текущей работы выполнять тесты всей системы или модуля, чтобы сразу выявить ошибки появившиеся в других участках кода. Если же время не позволяет, то юнит тесты необходимо запускать как минимум 1 раз в день и обязательно перед интеграцией (фиксацией в систему контроля версий).
Локализация
Самый короткий раздел: все сообщения для пользователя выносите в отдельный файл, а какой файл использовать задавайте параметром. В этом случае список проблем с локализацией сократится до списка неизбежных проблем (например разделителем дробной части числа может быть как «.», так и «,» и это надо обработать).
Субкультуры
Практически каждая область программирования имеет свои культурные особенности. Например, существуют такие понятия как «Культура параллельного программирования», «Культура сетевого программирования» и т.п.
Изучайте культуру той области, в которой работаете. Это сэкономит вам много времени и сил.
Здесь же можно сказать и о разных языках. Например, в C и C++ широко развита культура работы с памятью и указателями, в то время как в Java и C#, за счёт сборщика мусора значение навыков работы с памятью сильно ослаблено.
По мнению автора существуют языки (чтобы не вызывать священных войн не будем их называть), которые подталкивают к бескультурью или, наоборот, к повышению культуры программирования. Это зависит от задач, которые обычно решаются с использованием того или иного языка программирования. Для решения сложных задач привлекают высококвалифицированных специалистов, которые задают высокую культуру программирования. Соответственно если язык используется для решения сложных задач, то и его сообщество, во многом, состоит из высококвалифицированных специалистов, которые повышают общую культуру сообщества. Обратите внимание на то, не оказались ли вы «не в той компании».
Заключение
Перечитав написанное, автор заметил, что эта статья во многом является подборкой идей и цитат классиков, с которыми он познакомился за те 5 лет, что занимается программированием. Поразмыслив на эту тему, автор понял, что это правильно: было бы глупо пренебречь тем, что лучшие умы всего мира нарабатывали десятилетиями. Ведь ни кто не будет спорить, что культурный человек должен знать Пушкина. А культурный программист, на взгляд автора, должен знать Кнута.
Понятно, что не все (в том числе и автор) и не всегда могут или хотят следовать этим и подобным им рекомендациям, но к этому надо стремиться. Точнее, стремиться надо не к следованию этим правилам, а к повышению собственного мастерства, стоимости своего труда. Автор надеется, что благодаря данной статье, появиться ещё некоторое число культурных программистов, и ПО, которым мы пользуемся, станет лучше.
Май 2007 г.