Я большой сторонний структурного подхода к работе. Программистам игр приходится создавать сложные системы и поэтому недостаточно просто плыть по течению. Я написал два документа о том, как мы в Ronimo пишем код, которые должен изучить в первый день работы каждый программист и стажёр. Наш документ по методологии объясняет рабочий процесс, а руководство по стилю рассказывает о системе нашего кода. Для начала я хочу раскрыть методологию и рассказать о причинах, стоящих за представленными в документе правилами.
В нашей двухмерной MOBA Awesomenauts на текущий момент две тысячи классов и больше 400 тысяч строк кода. Чтобы такая объёмная кодовая база не превратилась в хаос, необходим структурный подход.
Следует учесть, что содержание этого документа не слишком оригинально: по большей части это сочетание понравившихся мне практик Agile.
Давайте начнём с изучения самого документа о методологии:
Бо?льшая часть принципов достаточно ясна, но интересно обсудить причины, стоящие за этими правилами. Достаточно часто я видел кодеров-стажёров, с кучей недоделанных частей в коде, потому что они работали над пятью задачами одновременно и забывали тестировать, подчищать и завершать некоторые из них. Поэтому наша методология кода требует, чтобы программисты завершали задачи, и только потом переходили к следующим. Именно поэтому я требую, чтобы большие задачи делились на несколько мелких: память человека ограничена, и чем больше задач вы выполняете одновременно, тем больше вероятность упустить что-то важное.
В то же время я предпочитаю agile-подход: делай только то, что действительно нужно, и расширяй кодовую базу в процессе. В начале разработки вы не знаете всех функций, которые вам потребуются, как не знаете и решения всех проблем. Однако постепенное добавление функционала часто раздувает код и размывает его смысл, создаёт смутные обязанности классов. Поэтому код нужно часто рефакторизировать. Рефакторинг требует дисциплины. Часто можно написать хак новой функции всего за несколько часов или сначала потратить полдня на рефакторинг, чтобы эта функция удачно встраивалась в код. Очень легко пропустить или отложить рефакторинг на потом, но в таком случае очень часто в долгосрочной перспективе такой код становится нерабочим. Вот почему рефакторинг чётко упоминается в нашей методологии.
Но не стоит воспринимать призыв работать только над одной мелкой задачей слишком буквально. Хоть я и считаю, что важно не заниматься ничем другим, пока вы не закончите и не сделаете код чистым, это не значит, что не надо заглядывать вперёд. При создании чего-то сложного важно думать, как вы заставите работать всю систему. Однажды у меня был стажёр, которому пришлось переделывать большую часть инструмента, потому что он воспринял методологию кода слишком буквально и совсем не думал наперёд. Самые сложные функции инструмента было совершенно невозможно реализовать с тем, что он создал. Самое главное здесь — правильный баланс между мыслями о будущем и сосредоточенности на одной задаче.
При создании инструмента, который должен быстро обрабатывать дерево из 100 тысяч настроек, нужно с самого начала думать о скорости выполнения, хотя наша методология и говорит обратное. Правила нельзя воспринимать слишком буквально.
Ещё один важный для меня аспект заключается в непосредственном общении программистов с дизайнерами и художниками. Мы считаем, что подробные дизайн-документы редко пригождаются, поэтому единственный способ узнать задачу точно — поговорить с дизайнером или художником, которому нужна новая функция. Часто этот человек не может определиться с точными требованиями к функции, поэтому кодеру нужно общаться с ним, выяснить все особенности с дизайнерской и художественной точки зрения. Если программист имеет какой-то дизайнерский и художественный опыт, то это очень помогает в общении, но даже если такого опыта нет, то я считаю, что задача программиста — говорить на языке художника или дизайнера, но никак не наоборот. Дизайнеру очень сложно говорить о коде, а программист может рассказать о своей работе на доступном английском (или нидерландском, в нашем случае).
В нашей методологии кода на удивление нет юнит-тестирования. Мы очень строго придерживаемся тщательного тестирования собственного кода, но в документе ничего не говорится о написании юнит-тестов. Причина в том, что я считаю игровой процесс слишком хаотичным и непредсказуемым, чтобы его можно было достаточно хорошо охватить юнит-тестами. Некоторые функции можно проверить юнит-тестами, но встречаемые нами ошибки часто нельзя ими обнаружить. Часто это фрагменты, которые работают нормально, но приводят к неправильному игровому процессу.
Я осознаю, что отсутствие юнит-тестов делает нас уязвимее к ошибкам по сравнению с разработчиками, пишущими юнит-тесты. Именно поэтому мы подчёркиваем, что при обнаружении сбоя или серьёзного бага его необходимо исправлять немедленно. Возможно, у нас больше ошибок, чем у разработчиков ПО, пишущих подробные юнит-тесты, но по крайней мере, мы исправляем их быстро. Думаю, что мы должны использовать юнит-тесты скорее для таких аспектов. как серверная архитектура. Юнит-тестирование не присуще нашей компании, но, возможно, его стоит использовать хотя бы немного.
Вне зависимости от того, согласны вы или нет с правилами нашей методологии, я считаю, что всем программистам важнo думать о своём рабочем процессе. Недостаточно просто делать то, что вам кажется правильным. Дисциплина и структурированность важны для всех, кто работает над большими и сложными системами. В чём заключается ваша методология кода? Если вы работаете в компании, есть ли в ней похожий официальный документ?
Итак, вот она — методология кода Ronimo! Ниже я расскажу о руководстве по стилю кода, который немного строже, чем привычный для большинства программистов.
Теперь мы рассмотрим, как выглядит наш код, описанный в руководстве по стилю кода. Основная идея руководства по стилю заключается в том, что если весь код отформатирован в общем стиле, то код своих коллег проще читать и изменять. Например, на привыкание к другому способу расстановки фигурных скобок или присвоения имён требуется время. Благодаря наличию строгого руководства по стилю, которому должны следовать все программисты Ronimo, этого удаётся избежать.
За несколько лет в кодовую базу Awesomenauts внесли свой вклад 18 программистов (включая стажёров). Благодаря строгим правилам оформления весь код читается одинаково.
Я видел не так много руководств по стилю других компаний, но из того, что слышал, я понял, что наше руководство гораздо строже обычного. Не уверен, правда ли это, но могу в это поверить, потому что известен своей дотошностью (иногда слишком сильной). Однако наше руководство не «увековечено в камне»: у каждого правила есть исключения. Если в какой-то ситуации руководство по стилю не имеет смысла, то кодер вполне может его иногда игнорировать. Но только если на это есть веская причина.
Некоторые выбранные решения в этом документе довольно произвольны. Иногда альтернативы столь же хороши, но без чётко выбранного варианта невозможно добиться одинакового форматирования у всех программистов. Это особенно справедливо для стиля расстановки фигурных скобок. Я знаю, что это острая тема, и хотя у меня есть чёткие предпочтения, я знаю, что хорошие аргументы можно привести и в пользу альтернативных стилей расстановки. (И было бы неплохо, если бы защитники любого другого популярного стиля не называли его Единственно Верным… ;) )
Важнейший элемент нашего руководства по стилю заключается в том, что я хочу по возможности читать код как текст на английском. Названия переменных и функций должны быть описательными, допускаются только самые распространённые аббревиатуры. Меня волнует не краткость, а читаемость.
Однако не все пункты нашего руководства по стилю относятся к форматированию. Часть описывает конструкции языка. C++ — это богатый язык с огромными возможностями, но некоторые из них слишком сбивают с толку или представляют опасность возникновения ошибок. Например, в C++ вполне можно использовать вложенные тернарные операторы, но результат редко удобно читать, поэтому мы полностью отказались от них.
Этот относительно простой пример вложенного тернарного оператора уже слишком сложно читать. В нашем руководстве по стилю предпочтение отдаётся читаемости.
Кроме того, в нашем руководстве содержатся правила для упрощения кросс-платформенной разработки. На консолях обычно невозможно выбирать компилятор, поэтому приходится работать с тем, что выбрали Nintendo, Sony или Microsoft, с учётом ограничений их компиляторов. Мы изучили, какие особенности C++ поддерживаются всеми этими компиляторами и запретили использование некоторых новых конструкций C++, которые, по нашему мнению, могут не работать на одной из консолей. Поскольку мы сейчас не занимаемся разработкой для некоторых консолей, то делаем выводы только по документации, но лучше быть слишком строгим, чем слишком снисходительным.
В руководстве по стилю можно также заметить мою нелюбовь к сложным конструкциям языка. C++ позволяет выполнять довольно впечатляющие трюки, особенно с использованием шаблонов и макросов. Конечно я согласен, что иногда такие трюки очень полезны, но в целом отвергаю их, когда их становится слишком сложно читать. В редких случаях, когда они действительно необходимы, их допускают, но обычно я предпочитаю избегать сложных конструкций.
Пример того, как выглядит заголовок C++, написанный по правилам нашего руководства по стилю кода.
Один из самых горячо обсуждаемых пунктов в стиле написания кода — нужно ли помечать переменные элементов класса. Если у класса Car есть
Однако есть и логичная причина, по которой многие программисты помечают переменные элементов классов: в коде очень важно знать, является ли переменная элементом класса, параметром функции или локальной переменной. Эта точка зрения тоже справедлива, но думаю, мы решили эту проблему иначе: в нашем руководстве по стилю есть ограничения на длину классов и функций. Если функция короткая и умещается на одном экране, то очень просто сразу же увидеть, откуда берутся переменные. Я считаю, что если классы и функции достаточно короткие, то метки переменных элементов на самом деле не нужны.
Кстати, заметьте, что правило о длине функций и классов в компании нарушается чаще всего. Иногда очень сложно разделить класс или функцию красиво. В конце концов, цель нашего руководства по стилю — написание чистого кода, а не усложнение его неловкими разделениями. Для красивого разделения классов на мелкие, более удобные в сопровождении модули требуется настоящее искусство. Так что если вы не слишком опытны, то чаще всего вы не увидите возможные красивые варианты разбиения. По-моему, идеальный размер класса — где-то между 200 и 400 строками, но столь строгое правило невозможно выполнить, поэтому в руководстве по стилю оно сформулировано более мягко.
Мы обсудили причины решений, выбранных в руководстве по стилю кода, давайте же наконец посмотрим, как выглядит оно само!
На этом всё, это наше руководство по стилю кода! Думаю, вы можете не согласиться с некоторыми из правил, но любой компании всё равно полезно создать собственное руководство по стилю. Наше руководство может стать неплохой основой для создания своего руководства. Мне любопытно: какое руководство по стилю кода используется в вашей компании и нравится ли оно вам? И есть ли оно вообще?
В нашей двухмерной MOBA Awesomenauts на текущий момент две тысячи классов и больше 400 тысяч строк кода. Чтобы такая объёмная кодовая база не превратилась в хаос, необходим структурный подход.
Следует учесть, что содержание этого документа не слишком оригинально: по большей части это сочетание понравившихся мне практик Agile.
Давайте начнём с изучения самого документа о методологии:
Методология кода Ronimo
Стандартный способ реализации новой функции:
- Проанализируйте то, что нужно сделать. Обсудите подход с конечными пользователями (обычно это художники и/или дизайнеры) и с ведущим разработчиком.
- Разделите работу на мелкие задачи, которые можно решить максимум за день.
- Создайте простой план для мелких задач. Реализацию ядра функционала и самых сложных частей поставьте в начало плана.
- Реализуйте каждую из мелких задач.
- Оцените результат с конечными пользователями и ведущим разработчиком.
- Объясните конечным пользователям, что делает новая функция, чтобы её на самом деле использовали.
Реализация мелкой задачи:
- Проанализируйте то, что нужно сделать.
- Убедитесь, что в уже имеющемся коде нет ошибок (выполните тесты!).
- Изучите и поэкспериментируйте со всеми новыми технологиями, необходимыми для этой функции.
- Создайте дизайн кода новой функции. Не слишком акцентируйте внимание на теоретическом использовании в будущем, но имейте его в виду.
- Выполниет рефакторинг имеющегося кода, чтобы создать пространство для новой функции.
- Выполните тесты, чтобы убедиться, что старые функции по-прежнему работают.
- Реализуйте новую функцию.
- Протестируйте работу новой функции.
- Закончите код: добавьте комменты, подчистите код и проверьте деструкторы.
- Проверьте тестами, что новая функция по-прежнему работает.
- Протестируйте работу старых функций.
- Проанализируйте результат. Если возможно, покажите промежуточный результат конечному пользователю.
Другие правила:
- Храните личный список всех небольших задач, которые нужно сделать. При возникновении проблемы, которую нельзя решить сразу же, или когда вас просят добавить функцию, которую вы обещаете добавить позже, запишите её в список. Не думайте, что сможете запомнить всё.
- Не продолжайте работу над задачей, если возник сбой или серьёзная ошибка. Всегда сначала исправляйте сбой.
- «Преждевременная оптимизация — корень всех зол» (Дональд Кнут). Сначала реализуйте новую функцию самым простым или чистым из возможных способом, проанализируйте, работает ли она, затем проанализируйте скорость выполнения, и только при необходимости вносите оптимизации.
- Не очень беспокойтесь о том, чтобы в код удобно было добавлять неизвестные будущие расширения. При необходимости новой функции код можно рефакторизировать. Разумеется, если для этого не нужно много труда, то делайте всё как можно более общим.
Бо?льшая часть принципов достаточно ясна, но интересно обсудить причины, стоящие за этими правилами. Достаточно часто я видел кодеров-стажёров, с кучей недоделанных частей в коде, потому что они работали над пятью задачами одновременно и забывали тестировать, подчищать и завершать некоторые из них. Поэтому наша методология кода требует, чтобы программисты завершали задачи, и только потом переходили к следующим. Именно поэтому я требую, чтобы большие задачи делились на несколько мелких: память человека ограничена, и чем больше задач вы выполняете одновременно, тем больше вероятность упустить что-то важное.
В то же время я предпочитаю agile-подход: делай только то, что действительно нужно, и расширяй кодовую базу в процессе. В начале разработки вы не знаете всех функций, которые вам потребуются, как не знаете и решения всех проблем. Однако постепенное добавление функционала часто раздувает код и размывает его смысл, создаёт смутные обязанности классов. Поэтому код нужно часто рефакторизировать. Рефакторинг требует дисциплины. Часто можно написать хак новой функции всего за несколько часов или сначала потратить полдня на рефакторинг, чтобы эта функция удачно встраивалась в код. Очень легко пропустить или отложить рефакторинг на потом, но в таком случае очень часто в долгосрочной перспективе такой код становится нерабочим. Вот почему рефакторинг чётко упоминается в нашей методологии.
Но не стоит воспринимать призыв работать только над одной мелкой задачей слишком буквально. Хоть я и считаю, что важно не заниматься ничем другим, пока вы не закончите и не сделаете код чистым, это не значит, что не надо заглядывать вперёд. При создании чего-то сложного важно думать, как вы заставите работать всю систему. Однажды у меня был стажёр, которому пришлось переделывать большую часть инструмента, потому что он воспринял методологию кода слишком буквально и совсем не думал наперёд. Самые сложные функции инструмента было совершенно невозможно реализовать с тем, что он создал. Самое главное здесь — правильный баланс между мыслями о будущем и сосредоточенности на одной задаче.
При создании инструмента, который должен быстро обрабатывать дерево из 100 тысяч настроек, нужно с самого начала думать о скорости выполнения, хотя наша методология и говорит обратное. Правила нельзя воспринимать слишком буквально.
Ещё один важный для меня аспект заключается в непосредственном общении программистов с дизайнерами и художниками. Мы считаем, что подробные дизайн-документы редко пригождаются, поэтому единственный способ узнать задачу точно — поговорить с дизайнером или художником, которому нужна новая функция. Часто этот человек не может определиться с точными требованиями к функции, поэтому кодеру нужно общаться с ним, выяснить все особенности с дизайнерской и художественной точки зрения. Если программист имеет какой-то дизайнерский и художественный опыт, то это очень помогает в общении, но даже если такого опыта нет, то я считаю, что задача программиста — говорить на языке художника или дизайнера, но никак не наоборот. Дизайнеру очень сложно говорить о коде, а программист может рассказать о своей работе на доступном английском (или нидерландском, в нашем случае).
В нашей методологии кода на удивление нет юнит-тестирования. Мы очень строго придерживаемся тщательного тестирования собственного кода, но в документе ничего не говорится о написании юнит-тестов. Причина в том, что я считаю игровой процесс слишком хаотичным и непредсказуемым, чтобы его можно было достаточно хорошо охватить юнит-тестами. Некоторые функции можно проверить юнит-тестами, но встречаемые нами ошибки часто нельзя ими обнаружить. Часто это фрагменты, которые работают нормально, но приводят к неправильному игровому процессу.
Я осознаю, что отсутствие юнит-тестов делает нас уязвимее к ошибкам по сравнению с разработчиками, пишущими юнит-тесты. Именно поэтому мы подчёркиваем, что при обнаружении сбоя или серьёзного бага его необходимо исправлять немедленно. Возможно, у нас больше ошибок, чем у разработчиков ПО, пишущих подробные юнит-тесты, но по крайней мере, мы исправляем их быстро. Думаю, что мы должны использовать юнит-тесты скорее для таких аспектов. как серверная архитектура. Юнит-тестирование не присуще нашей компании, но, возможно, его стоит использовать хотя бы немного.
Вне зависимости от того, согласны вы или нет с правилами нашей методологии, я считаю, что всем программистам важнo думать о своём рабочем процессе. Недостаточно просто делать то, что вам кажется правильным. Дисциплина и структурированность важны для всех, кто работает над большими и сложными системами. В чём заключается ваша методология кода? Если вы работаете в компании, есть ли в ней похожий официальный документ?
Итак, вот она — методология кода Ronimo! Ниже я расскажу о руководстве по стилю кода, который немного строже, чем привычный для большинства программистов.
Руководство по стилю кода
Теперь мы рассмотрим, как выглядит наш код, описанный в руководстве по стилю кода. Основная идея руководства по стилю заключается в том, что если весь код отформатирован в общем стиле, то код своих коллег проще читать и изменять. Например, на привыкание к другому способу расстановки фигурных скобок или присвоения имён требуется время. Благодаря наличию строгого руководства по стилю, которому должны следовать все программисты Ronimo, этого удаётся избежать.
За несколько лет в кодовую базу Awesomenauts внесли свой вклад 18 программистов (включая стажёров). Благодаря строгим правилам оформления весь код читается одинаково.
Я видел не так много руководств по стилю других компаний, но из того, что слышал, я понял, что наше руководство гораздо строже обычного. Не уверен, правда ли это, но могу в это поверить, потому что известен своей дотошностью (иногда слишком сильной). Однако наше руководство не «увековечено в камне»: у каждого правила есть исключения. Если в какой-то ситуации руководство по стилю не имеет смысла, то кодер вполне может его иногда игнорировать. Но только если на это есть веская причина.
Некоторые выбранные решения в этом документе довольно произвольны. Иногда альтернативы столь же хороши, но без чётко выбранного варианта невозможно добиться одинакового форматирования у всех программистов. Это особенно справедливо для стиля расстановки фигурных скобок. Я знаю, что это острая тема, и хотя у меня есть чёткие предпочтения, я знаю, что хорошие аргументы можно привести и в пользу альтернативных стилей расстановки. (И было бы неплохо, если бы защитники любого другого популярного стиля не называли его Единственно Верным… ;) )
Важнейший элемент нашего руководства по стилю заключается в том, что я хочу по возможности читать код как текст на английском. Названия переменных и функций должны быть описательными, допускаются только самые распространённые аббревиатуры. Меня волнует не краткость, а читаемость.
Однако не все пункты нашего руководства по стилю относятся к форматированию. Часть описывает конструкции языка. C++ — это богатый язык с огромными возможностями, но некоторые из них слишком сбивают с толку или представляют опасность возникновения ошибок. Например, в C++ вполне можно использовать вложенные тернарные операторы, но результат редко удобно читать, поэтому мы полностью отказались от них.
Этот относительно простой пример вложенного тернарного оператора уже слишком сложно читать. В нашем руководстве по стилю предпочтение отдаётся читаемости.
Кроме того, в нашем руководстве содержатся правила для упрощения кросс-платформенной разработки. На консолях обычно невозможно выбирать компилятор, поэтому приходится работать с тем, что выбрали Nintendo, Sony или Microsoft, с учётом ограничений их компиляторов. Мы изучили, какие особенности C++ поддерживаются всеми этими компиляторами и запретили использование некоторых новых конструкций C++, которые, по нашему мнению, могут не работать на одной из консолей. Поскольку мы сейчас не занимаемся разработкой для некоторых консолей, то делаем выводы только по документации, но лучше быть слишком строгим, чем слишком снисходительным.
В руководстве по стилю можно также заметить мою нелюбовь к сложным конструкциям языка. C++ позволяет выполнять довольно впечатляющие трюки, особенно с использованием шаблонов и макросов. Конечно я согласен, что иногда такие трюки очень полезны, но в целом отвергаю их, когда их становится слишком сложно читать. В редких случаях, когда они действительно необходимы, их допускают, но обычно я предпочитаю избегать сложных конструкций.
Пример того, как выглядит заголовок C++, написанный по правилам нашего руководства по стилю кода.
Один из самых горячо обсуждаемых пунктов в стиле написания кода — нужно ли помечать переменные элементов класса. Если у класса Car есть
float speed
, нужно ли называть её speed
, mSpeed
, _speed
или как-то иначе? Я решил назвать её просто speed
. Здесь я снова придерживаюсь того, что код должен как можно больше походить на англоязычный текст. Чем больше префиксов и символов подчёркивания, тем дальше мы уходим от естественного языка и тем сложнее просто читать код и понимать его.Однако есть и логичная причина, по которой многие программисты помечают переменные элементов классов: в коде очень важно знать, является ли переменная элементом класса, параметром функции или локальной переменной. Эта точка зрения тоже справедлива, но думаю, мы решили эту проблему иначе: в нашем руководстве по стилю есть ограничения на длину классов и функций. Если функция короткая и умещается на одном экране, то очень просто сразу же увидеть, откуда берутся переменные. Я считаю, что если классы и функции достаточно короткие, то метки переменных элементов на самом деле не нужны.
Кстати, заметьте, что правило о длине функций и классов в компании нарушается чаще всего. Иногда очень сложно разделить класс или функцию красиво. В конце концов, цель нашего руководства по стилю — написание чистого кода, а не усложнение его неловкими разделениями. Для красивого разделения классов на мелкие, более удобные в сопровождении модули требуется настоящее искусство. Так что если вы не слишком опытны, то чаще всего вы не увидите возможные красивые варианты разбиения. По-моему, идеальный размер класса — где-то между 200 и 400 строками, но столь строгое правило невозможно выполнить, поэтому в руководстве по стилю оно сформулировано более мягко.
Мы обсудили причины решений, выбранных в руководстве по стилю кода, давайте же наконец посмотрим, как выглядит оно само!
Руководство по стилю кода RonimoУ каждого правила есть исключения. Однако по возможности стоит придерживаться этих правил, чтобы сохранять постоянные систему и стиль во всей кодовой базе. Во многом они зависят от вкуса, поэтому сохранение постоянной структуры кода требует оказаться о собственного вкуса и придерживаться этих правил. Если придерживаться правил, то читать код становится легче. При работе с другими языками (не с C++) старайтесь как можно ближе придерживаться стандарта кода C++, но, разумеется, в логичных пределах. В конце руководства представлены особые примечания по C#. C++
C#
|
На этом всё, это наше руководство по стилю кода! Думаю, вы можете не согласиться с некоторыми из правил, но любой компании всё равно полезно создать собственное руководство по стилю. Наше руководство может стать неплохой основой для создания своего руководства. Мне любопытно: какое руководство по стилю кода используется в вашей компании и нравится ли оно вам? И есть ли оно вообще?
Поделиться с друзьями
Комментарии (4)
netch80
29.07.2017 18:52> Возможно, у нас больше ошибок, чем у разработчиков ПО, пишущих подробные юнит-тесты, но по крайней мере, мы исправляем их быстро.
Как-то не очень убедительно звучит. :)
> Весь код и комментарии пишутся на британском английском.
Боюсь, на наши условия это не переносимо.
AlexZaharow
Я сам не пишу на c++, но какие инструменты для проверки этих правил вы используете?
AlexZaharow
Простите, не сразу заметил, что это перевод. )))
prefrontalCortex
Можно использовать clang-tidy, например.