В этой публикации я хотел бы представить на суд уважаемого читателя некоторые принципы, которыми я руководствуюсь, исполняя свои обязанности в роли Python-разработчика.
С одной стороны хотелось быть поделиться накопленным опытом, который может оказаться полезным для начинающих разработчиков, а с другой стороны получить обратную связь от более опытных разрабочиков, руководителей и специалистов смежных областей.
Принципы условно сгруппированы в три группы: принципы принятия решений; принципы, направленные на повышение качества кода; принципы, направленные на повышение производительности кода.
- Принятие решений
- Любое техническое решение должно быть обосновано
- Ответственность за принятое решение всегда лежит на том или тех, кто принял данное решение
- При принятии технических решений необходимо учитывать их действие во времени и их соответствие потребностям бизнеса
- Одним из основных критериев при принятии технических и иных решений должна быть их наибольшая эффективность
- Смело отступать от правил, методологий, шаблонов и прочих ограничений, если эффект от такого отступления превышает возможные потери (Special cases aren't special enough to break the rules, although practicality beats purity)
- При необходимости сделать работающее, но, возможно, не наилучшее, решение сразу, а позднее улучшить его (Now is better than never, although never is often better than *right* now)
- Если сложно выбрать между двумя альтернативными техническими решениями, то нужно выбрать любое и двигаться с ним дальше, когда появится больше инфорации, то можно будет сделать рефакторинг, если решение оказалось неоптимальным
- Гибкость технических решений крайне желательна, а универсальность не обязательна
- Качество исходного кода
- Качество кода следует оптимизировать на базе сформированной системы критериев, сбалансированной по отношению к затратам в краткосрочном и долгосрочном периодах
- Писать оптимальный код сразу, если это не увеличивает его сложность и сроки разработки (Beautiful is better than ugly)
- Самодокументируемый код имеет приоритет над хорошо прокомментированным (Beautiful is better than ugly)
- Писать TODO и FIXME в коде
- Давать переменным, функциям, методам, классам и другим объектам исходного кода имена точно отражающие их назначение, несмотря на увеличение длины названий (Explicit is better than implicit)
- Меньшее число строк и объем кода предпочтительнее, при сохранении прежней читабельности кода (Simple is better than complex)
- Применять инспекцию кода (code review) как инструмент обнаружения ошибок, выравнивания стиля разработки, знакомства с чужим кодом и обучения в команде
- Применять повторное использование своего и чужого кода
- Использовать специализированные библиотеки для решения конкретных задач, вместо разработки своего аналогичного кода
- Производительность
- Производительность разработки кода имеет приоритет над производительностью исполнения кода
- Оптимизация производительности исполнения кода должна быть обоснована соответствующей потребностью
- Оптимизация производительности исполнения кода должна выполняться за счет устранения наиболее серьезных узких мест
- В первую очередь должны быть использованы наиболее эффективные методы оптимизации производительности исполнения кода
Далее дано развернутое пояснение каждому из перечисленных принципов. Для некоторых принципов в круглых скобках указанны постулаты Zen of Python, которые на мой взгляд имеют отношение к данным принципам, либо их частям.
Принятие решений
Любое техническое решение должно быть обосновано
Любое техническое решение должно быть обосновано: начиная от используемых синтаксических конструкций в коде и заканчивая архитектурными решениям самого высокого уровня. Для меня обоснованность технического решения означает наличие аргументов, подтверждающих его преимущеста, неизбежность такого решения, либо иную целесообразность данного решения. Обоснование должно сопровождаться наличием знаний о недостатках данного решения и планом их преодоления, либо обоснованием их несущественности. Кроме того, должны быть рассмотрены альтернативные решения с обоснованием их меньшей привлекательности по сравнению с выбранным решением. В обосновании должны быть учтены как технические, так организационные аспекты, связанные с конкретным техническим решением. Если различные аргументы вступают в противоречие друг с другом, то следует руководствоваться экономическим эффектом того или иного решения для бизнеса, учитывая краткосрочную и долгосрочную перспективы.
В нетехнических, в том числе организационных, вопросах этот принцип должен применяться аналогично.
Данный принцип противопоставлен шаблонному принятию решений, а также необдуманному принятию первого попавшегося решения без должного анализа альтернатив.
Ответственность за принятое решение всегда лежит на том или тех, кто принял данное решение
Этот принцип направлен на повышение качества принимаемых решений и противопоставлен бессистемному принятию решений.
При принятии технических решений необходимо учитывать их действие во времени и их соответствие потребностям бизнеса
Следует учитывать какое влияние окажет то или иное решение как в краткосрочном, так и в долгосрочном периоде, а также является ли данное техническое решение полезным для бизнеса в этих периодах.
Данный принцип противопоставлен однобокому принятию решений с концетрацией результатов только в краткосрочном периоде, либо только в долгосрочном периоде, а также без учета потребностей бизнеса и учета влияения на бизнес данных решений с концетрацией исключительно на технических преимуществах решения.
Одним из основных критериев при принятии технических и иных решений должна быть их наибольшая эффективность
Под эффективность в данном вопросе я понимаю отношение ценности полученного результата для бизнеса к объему требуемых для этого затрат.
Данный принцип противопоставлен подходу, который учитывает только ценность полученного результата безотносительно требуемым затратам.
Смело отступать от правил, методологий, шаблонов и прочих ограничений, если эффект от такого отступления превышает возможные потери (Special cases aren't special enough to break the rules, although practicality beats purity)
Правила, методологии, шаблоны и иные ограничение — это обобщение предыдущего опыта с целью получения стабильного результата деятельности в определенных условиях. Однако, в конкретных случаях условия могут отличаться, поэтому необходимо осознано подходить к использованию накопленного опыта и вносить свои коррективы при необходимости.
Данный принцип противопоставлен догматичному следованию правилам, методологиям, шаблонам и прочим принятым ограничениям без учета окружающей действительности.
При необходимости следует сделать работающее, но, возможно, не наилучшее, решение сразу, а позднее улучшить его (Now is better than never, although never is often better than *right* now)
Бывают ситуации, в которых необходимо в короткие сроки реализовать определённую функциональность или исправить дефект, и при этом существуют альтернативные решения: быстрое и хорошее. От быстрого эффект можно получить сразу, но в долгосрочной перспективе оно может создать сложности и привести к дополнительным затратам. А хорошее решение делать долго и затратно в краткосрочной перспективе, но в долгосрочной перспективе оно окупится и принесёт существенно больше пользы, чем потребовало затрат. В этом случае эффективным будет комбинация обоих решений, а именно: делаем быстрое решение сейчас и в плановом порядке делаем хорошее решение. Суммарно это окажется, чуть более затратно, чем реализация только одного из решений, но эффект от их комбинации, окупит затраты.
Данный принцип противопоставлен попытке сделать все сразу идеально.
Если сложно выбрать между двумя альтернативными техническими решениями, то нужно выбрать любое и двигаться с ним дальше, когда появится больше инфорации, то можно будет сделать рефакторинг, если решение оказалось неоптимальным
Без комментариев
Гибкость технических решений крайне желательна, а универсальность не обязательна
Гибкость — это возможность адаптировать техническое решение под новые условия, которые могут появиться в будущем. Универсальность — это фактическая адаптация технического решения под возможность использования для набора условий. Часто обеспечение универсальности требует больших трудозатрат, чем обеспечение гибкости, так как учитывает большее количество условий, с которыми должно быть совместимо техническое решение. Также обеспечение универсальности может оказаться неэффективным, так как требует адаптации к условиям, которые еще не наступили и возможно никогда не наступят. Кроме того, иногда унификация требует сложно реализуемых обобщений. Поэтому гибкость технических решений крайне желательна, а универсальность не обязательна.
Данный принцип противопоставлен попытке сразу делать универсальные решения, которые в последующем оказываются невостребованными из-за изменения внешних условий.
Качество исходного кода
Качество кода следует оптимизировать на базе сформированной системы критериев, сбалансированной по отношению к затратам в краткосрочном и долгосрочном периодах
Мой базовый приоритезированный состав критериев оптимизации выглядит следующим образом (при необходимости следует скорректировать под конкретные условия):
- Высокая читабельность кода — чем проще понять код, тем лучше (Readability counts)
- Низкая цикломатическая сложность кода — чем меньше, ветвлений, циклов, условных операций, вызовов функций и методов, тем лучше (Simple is better than complex. Flat is better than nested)
- Высокая гибкость кода — чем проще вносить изменения в код, тем лучше (это критерий оптимизации повышает затраты на разработку в краткосрочном периоде, но снижает их в долгосрочном периоде)
- Малый объем кода — чем меньше кода, тем лучше (Simple is better than complex)
- Производительность исполнения кода — чем быстрее выполняется код, тем лучше
Приоритезация критериев заключается в следующем: во-первых, в первую очередь трудозатраты расходуются на обеспечение критериев с более высоким приоритетом; во-вторых, если критерии вступают в противоречие друг с другом, то применяется решение, соответствующее критерию с более высоким приоритетом.
Данный принцип противопоставлен хоатичной оптимизации качества кода, а также использованию несогласованных между членами команды разработки критериев оптимизации и их приоритетов.
Писать оптимальный код сразу, если это не увеличивает его сложность и сроки разработки (Beautiful is better than ugly)
Иногда имеется несколько функционально эквивалентных конструкций кода, но имеющих разную производительность. Как правило достаточно однократно выяснить какая конструкция имеет бОльшую производительность или требования к памяти и использовать ее во всех аналогичных случаях. Однако следует учитывать, что некоторые подобные конструкции могут ухудшать читабельность кода и увеличивать сроки разработки как в момент написания кода, так и при его поддержке и модификации в будущем. Это следует учитывать, чтобы не столкнуться с недостатками преждевременной оптимизации.
Самодокументируемый код имеет приоритет над хорошо прокомментированным (Beautiful is better than ugly)
Иногда комментарии помогают понять сложный код, однако одновременно они являются индикатором низкой читабельности кода. Поэтому в случаях, когда возникает потребность в написании комментария следует подумать какие изменения возможно внести в код, чтобы потребность в комментарии исчезла.
Вот типичные, на мой взгляд, случаи, когда можно обойтись без комменария:
- В комментарии содержится уточнение смысла содержащихся в переменных данных — исправляется переименованием переменной (аналогично применимо к названиям классов, методов, функций, констант и т.д.)
- Выделение блока кода с помощью комментария — исправляется выносом блока кода в отдельную функцию или метод
- Объянение выполняемого действия на уровне семантики языка — исправляется удалением комментария (пример комментария, который нужно удалить: a = 10 # assign 10 to a)
Случаи, когда комментарии следует написать:
- Документирование публичного API
- Объяснение неочевидных причин, выбора альтернативного технического решения
- Пояснение оптимизированного и как следствие сложного для понимания участка кода
- TODO и FIXME — микропланирование в контексте исходного кода
Комментарии могут не только помогать, но мешать разработке поскольку требуют дополнительного времени на написание, занимают место, снижая количество одновременно видимых на экране строк кода, требуют синхронизации при изменении кода, отнимают дополнительное время на прочтение. Поэтому комментрирование должно быть разумным.
Данный принцип противопоставлен избыточному использованию комментариев.
Писать TODO и FIXME в коде
Не всё и не всегда имеет смысл делать в момент написания конкретного участка кода, особенно это касается рефакторинга и внесения изменений, актуальность которых находится вопросом. В таких случаях разумно написать TODO- или FIXME-комментарий в коде, на которым можно будет поработать в более подходящий момент. Желательно при этом сразу назначать примерный приоритет данному комментарию, чтобы упростить планирование в дальнейшем.
Данный принцип противопоставлен написанию идеального кода без учета реальной потребности и затрат на такой код в конкретный момент времени.
Давать переменным, функциям, методам, классам и другим объектам исходного кода имена точно отражающие их назначение, несмотря на увеличение длины названий (Explicit is better than implicit)
Экономия длины названий не может быть сделана в ущерб понятности кода.
Меньшее число строк и объем кода предпочтительнее, при сохранении прежней читабельности кода (Simple is better than complex)
Существуют исследования, из которых следует, что количество допущенных ошибок в коде в среднем прямо пропорционально длине кода и числу строк кода в частности. Объясняется это следующим образом: во-первых, чем меньшая часть кода помещается одновременно на экране, тем больше требуется усилий по запоминанию невидимых в данный момент участков кода при разработке, а во-вторых, чем длинее код, тем больше в нем может быть допущено опечаток и других ошибок, связанных с человеческим фактором.
Применять инспекцию кода (code review) как инструмент обнаружения ошибок, выравнивания стиля разработки, знакомства с чужим кодом и обучения в команде
Кроме непосредственного предназначения инспекция кода отлично служит для выравнивания стиля разработки, знакомства с чужим кодом и обучения в команде. В идеальном случае в общую ветку должен попадать только код, прошедший инспекцию кода.
Применять повторное использование своего и чужого кода
Данный принцип следует из DRY-принципа. Для того, чтобы успешно применять этот принцип необходимо хорошо владеть кодовой базой, а в этом помогает применение инспекции кода всеми членами команды разработки.
Использовать специализированные библиотеки для решения конкретных задач, вместо разработки своего аналогичного кода
При необходимости решить какую-либо задачу (например, отправить email по smtp), следует ознакомиться с наличием готовых библиотек для этой задачи и изучить возможности их использования. Отказ от использования специализированных решений должен быть обоснован, поскольку специализированные решения имеют ряд преимуществ перед новой разработкой.
Специализированные решения уже разработаны и протестированы. Это означает экономию времени на разрабтку, тестирование и исправление дефектов. Иногда кажется, что проще разработать свой код, выполняющий необходимую функцию, так как для использования готового решения требуется потратить время на изучение и имеется риск того, что оно будет потрачено впустую, потому что окажется, что решение не подходит. Однако такое впечатление часто оказывается обманчевым во-первых, из-за недооценки сложности задачи, а во-вторых из-за наличия других преимуществ.
Особенность специализированных решений состоит в том, что они специально разработны для решения конкретной задачи. Это означает, что в них учтены особенности, которые сложно учесть, решая специфичискую задачу попутно основной. Следует понимать, что кроме функциональности решение должно удовлетворять и нефункциональным требования, таким как требования к производительности, безопасности, удобству обслуживания, совеместимости и другим. Все эти особенности требуют глобокого погружения в задачу и в случае своей разработки могут быть упущены из виду в виду ограниченного времени.
Часто специализированные решения публичны и поэтому используются многими разработчиками. Это означает в них уже выявлены и устранены недостатки, с которыми они столкнулись ходе практического использования. Таким образом, специализированные решения позволяют использовать чужой опыт без дополнительных трудозатрат.
Публичные специализированные решения часто имеют окрытый искохный код. Фактически таковыми являются большинство библиотек на языке Python. Такие решения постоянно развиваются сообществом разработчиков, поэтому появляется возможность повышать качество своего программного обеспечения без дополнительных трудозатрат за счет обновления используемых версий.
Часто разные публичные решения легко интегрируются друг с другом (например Spyne и SQLAlchemy), что позволяют существенно сократить затраты на разработку.
Существует ряд обоснованных причин для отказа от использования специализированных решение. Примеры таких причин:
- В специализированом решению отсутствует необходимая функциональность
- Специализированное решение плохо документировано, а доступ к исходному коду невозможен или ограничен, либо исходный код очень сложный для использования его вместо документации
- Специализированное решение содержит серьезные ошибки и плохо поддерживается, либо отсутствует доступ к исходному коду или возможность самостоятельного исправления ошибок
- Поддержка специализированного решения официально прекращена
- Текущие потребности в функциональности специализированного решения составляют крайне малую долю от всей функциональности, заложенной в специализированное решение
Производительность
Производительность разработки кода имеет приоритет над производительностью исполнения кода
В современных условиях стоимость человеческих ресурсов как правило выше стоимости аппаратного обеспечения, поэтому для меня производительность разработки кода (выраженная в объеме разработанной функциональности за единицу времени) имеет приоритет над производительностью исполнения кода.
Данный принцип противопоставлен преждевременной оптимизации производительности исполнения кода.
Оптимизация производительности исполнения кода должна быть обоснована соответствующей потребностью
Обснование состоит в формировании требований к производительности, которые основаны на практическом или плановом масштабе использования информационной системы, и последующем выявлении несоответствия информационной системы этим требованиям.
Данный принцип противопоставлен преждевременной оптимизации производительности исполнения кода.
Оптимизация производительности исполнения кода должна выполняться за счет устранения наиболее серьезных узких мест
Для этого необходимо выявить узкие места, оценить вклад каждого узкого места в снижение производительности и производить оптимизацию наиболее узкого места в первую очередь.
Данный принцип противопоставлен хоатичной оптимизации производительности исполнения кода.
В первую очередь должны быть использованы наиболее эффективные методы оптимизации производительности исполнения кода
Эффективность методов оптимизации представляет собой отношение доли прироста производительности к общим затратам на выполнение работ по оптимизации. Для меня базовым является следующий приоритезированный список методов оптимизации:
- Изменение алгоритма обработки данных (может дать увеличение производительности до нескольких порядков при относительно низких затратах на разработку)
- Увеличение мощности аппаратных средств (может дать увеличение производительности от десятков процентов до нескольких десятков раз в зависимости от характера деградации производительности; этот метод может потребовать внесение изменений в архитектуру информационной системы для обспечения возможностей масштабирования)
- Обновление готового прикладного и системного программного обеспечения, используемого в информационной системе (предполагает низкие затраты, за исключением случая платного ПО, но это редко дает существенный выигрыш производительности, т.к. прикладное и системное ПО обычно изначально оптимизировано)
- Изменение языка разработки в части узких мест, либо полный переход на более производительный язык программирования (высокие затраты на разработку при увеличение производительности от десятков процентов до нескольких десятков раз в зависимости характера функциональности информационной системы и применяемых языков программирования)
Приоритезация методов заключается том, что в первую очередь осуществляются затраты трудовых и иных ресурсов на использование более приоритетных методов, а переход к следующему методу осуществляется когда возможности оптимизации оказываются исчерпанными, либо эффективность менее приоритетного метода оказывается выше.
Данный принцип противопоставлен хоатичной оптимизации производительности исполнения кода.
Заключение
Часть принципов я нарочно исключил из публикации, так как они в большей степени относятся к управлению людьми, коммуникациям между людьми и построению процессов разработки, чем непосредственно к написанию исходного кода. Некоторые принципы я просто не смог выявить, либо хорошо сформулировать, так как перевод неосознованного знания в осознанное знание — это довольно сложный процесс. Поэтому я возлагаю большие надежды на то, что комментарии к статье окажутся не менее интересными, чем сама статья, и позволят мне написать продолжение, которое будет включать упущенную информацию.
Автор: dmugtasimov