Шесть заповедей хорошего кода: пишите код, который выдержит испытание временем

Опубликовано: 2022-03-11

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

Я не думаю, что из-за его относительной молодости у нас еще нет единого мнения о правильном определении «хорошего кода», поскольку это определение продолжает развиваться. Некоторые скажут, что «хороший код» — это код со 100-процентным покрытием тестами. Другие скажут, что он очень быстрый, имеет убийственную производительность и будет приемлемо работать на оборудовании 10-летней давности.

Хотя все это похвальные цели для разработчиков программного обеспечения, я рискну добавить еще одну цель: ремонтопригодность. В частности, «хороший код» — это код, который легко и просто поддерживается организацией (а не только его автором!) и который будет жить дольше, чем просто спринт, в котором он был написан. карьера инженера в больших и малых компаниях, в США и за границей, что, кажется, коррелирует с ремонтопригодным, «хорошим» программным обеспечением.

Никогда не соглашайтесь на код, который просто «работает». Пишите превосходный код.
Твитнуть

Заповедь № 1: относитесь к своему коду так, как хотите, чтобы код других относился к вам

Я далеко не первый человек, который пишет, что основной аудиторией вашего кода является не компилятор/компьютер, а тот, кто следующий должен читать, понимать, поддерживать и улучшать код (которым не обязательно будете вы через шесть месяцев). ). Любой достойный инженер может создать код, который «работает»; Что отличает превосходного инженера, так это то, что он может эффективно писать поддерживаемый код, который поддерживает бизнес в долгосрочной перспективе, и обладает навыками простого, понятного и удобного в сопровождении решения проблем.

На любом языке программирования можно написать хороший или плохой код. Предполагая, что мы оцениваем язык программирования по тому, насколько хорошо он облегчает написание хорошего кода (в любом случае, это должно быть, по крайней мере, одним из главных критериев), любой язык программирования может быть «хорошим» или «плохим» в зависимости от того, как он используется (или злоупотребляет им). ).

Примером языка, который многие считают «чистым» и удобочитаемым, является Python. Сам язык обеспечивает некоторый уровень дисциплины пробелов, а встроенные API-интерфейсы многочисленны и довольно последовательны. Тем не менее, можно создавать невыразимых монстров. Например, можно определить класс и определить/переопределить/отменить определение любого и каждого метода в этом классе во время выполнения (часто это называется исправлением обезьяны). Этот метод естественным образом приводит в лучшем случае к несогласованному API, а в худшем — к невозможному отлаживать монстра. Можно наивно подумать: «Конечно, но никто так не делает!» К сожалению, они это делают, и просмотр pypi не займет много времени, прежде чем вы столкнетесь с существенными (и популярными!) библиотеками, которые (ab) широко используют исправление обезьян в качестве ядра своих API. Недавно я использовал сетевую библиотеку, весь API которой меняется в зависимости от сетевого состояния объекта. Представьте, например, что вы вызываете client.connect() и иногда получаете ошибку MethodDoesNotExist вместо HostNotFound или NetworkUnavailable .

Заповедь № 2: Хороший код легко читать и понимать целиком и частично

Хороший код легко читается и понимается, частично и в целом, другими (а также и автором в будущем, пытающимся избежать синдрома «Я действительно это написал?» ).

Под «частично» я подразумеваю, что если я открою какой-нибудь модуль или функцию в коде, я смогу понять, что он делает, без необходимости читать весь остальной код. Он должен быть максимально интуитивным и самодокументируемым.

Код, который постоянно ссылается на мельчайшие детали, влияющие на поведение из других (на первый взгляд, не относящихся к делу) частей кодовой базы, подобен чтению книги, где вы должны ссылаться на сноски или приложение в конце каждого предложения. Вы никогда не прочтете первую страницу!

Некоторые другие мысли о «локальной» читабельности:

  • Хорошо инкапсулированный код, как правило, более читабелен, разделяя проблемы на каждом уровне.

  • Имена имеют значение. Активируйте метод «Быстрое и медленное мышление» в системе 2, с помощью которого мозг формирует мысли и вкладывает в имена переменных и методов реальные, тщательные мысли. Несколько дополнительных секунд могут принести значительные дивиденды. Хорошо названная переменная может сделать код более интуитивным, в то время как неудачно названная переменная может привести к обману и путанице.

  • Ум – враг. При использовании причудливых приемов, парадигм или операций (таких как обработка списков или тернарные операторы) будьте осторожны и используйте их таким образом, чтобы сделать ваш код более читабельным, а не просто короче.

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

  • Разделение забот. Данный проект управляет бесчисленным количеством локально важных допущений в различных точках кодовой базы. Предоставьте каждой части кодовой базы как можно меньше таких проблем. Скажем, у вас есть система управления людьми, в которой объект-человек иногда может иметь нулевую фамилию. Для кого-то, кто пишет код на странице, отображающей объекты-человеки, это может быть очень неудобно! И если вы не ведете справочник «неудобных и неочевидных предположений, которые есть в нашей кодовой базе» (я знаю, что нет), ваш программист отображаемой страницы не будет знать, что фамилии могут быть нулевыми, и, вероятно, будет писать код с нулевым указателем. исключение в нем, когда появляется нулевой регистр последнего имени. Вместо этого обрабатывайте эти случаи с помощью хорошо продуманных API и контрактов, которые различные части вашей кодовой базы используют для взаимодействия друг с другом.

Заповедь № 3: Хороший код имеет хорошо продуманную компоновку и архитектуру, чтобы сделать управление состоянием очевидным

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

Заповедь № 4: Хороший код не изобретает велосипед, он стоит на плечах гигантов

Прежде чем потенциально изобретать велосипед, подумайте, насколько распространена проблема, которую вы пытаетесь решить, или функция, которую вы пытаетесь выполнить. Возможно, кто-то уже реализовал решение, которое вы можете использовать. Потратьте время, чтобы подумать и изучить любые такие варианты, если они уместны и доступны.

Тем не менее вполне разумным контраргументом является то, что зависимости не приходят «бесплатно» без каких-либо недостатков. Используя стороннюю библиотеку или библиотеку с открытым исходным кодом, которая добавляет некоторые интересные функции, вы берете на себя обязательство и становитесь зависимым от этой библиотеки. Это большое обязательство; если это гигантская библиотека, и вам нужна только небольшая часть функциональности, вы действительно хотите бремя обновления всей библиотеки, если вы обновляете, например, до Python 3.x? Более того, если вы столкнулись с ошибкой или хотите улучшить функциональность, вы либо зависите от автора (или поставщика) в предоставлении исправления или улучшения, либо, если это открытый исходный код, вы оказываетесь в положении изучения ( потенциально существенной) кодовой базе, с которой вы совершенно незнакомы, пытаясь исправить или изменить малоизвестную часть функциональности.

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

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

Базы данных

Выясните, какая из CAP вам нужна для вашего проекта, а затем выберите базу данных с нужными свойствами. База данных больше не означает MySQL, вы можете выбирать из:

  • SQL с «традиционной» схемой: Postgres/MySQL/MariaDB/MemSQL/Amazon RDS и т. д.
  • Хранилища ключевых ценностей: Redis / Memcache / Riak
  • NoSQL: MongoDB/Кассандра
  • Размещенные БД: AWS RDS / DynamoDB / AppEngine Datastore
  • Тяжелая работа: Amazon MR / Hadoop (Hive/Pig) / Cloudera / Google Big Query
  • Сумасшедшие вещи: Mnesia от Erlang, Core Data от iOS

Уровни абстракции данных

В большинстве случаев вам не следует писать необработанные запросы к любой базе данных, которую вы решили использовать. Скорее всего, между БД и кодом вашего приложения существует библиотека, отделяющая задачи управления одновременными сеансами базы данных и детали схемы от вашего основного кода. По крайней мере, у вас никогда не должно быть необработанных запросов или встроенного SQL в середине кода вашего приложения. Вместо этого оберните его функцией и сосредоточьте все функции в файле с каким-нибудь очевидным именем (например, «queries.py»). Например, такую ​​строку, как users = load_users() , гораздо легче читать, чем users = db.query(“SELECT username, foo, bar from users LIMIT 10 ORDER BY ID”) . Этот тип централизации также значительно упрощает использование единообразного стиля в ваших запросах и ограничивает количество мест, куда можно перейти для изменения запросов при изменении схемы.

Другие распространенные библиотеки и инструменты, которые стоит рассмотреть возможность использования

  • Службы очередей или Pub/Sub. Выберите поставщика AMQP, ZeroMQ, RabbitMQ, Amazon SQS.
  • Место хранения. Amazon S3, облачное хранилище Google
  • Мониторинг: Graphite/Hosted Graphite, AWS Cloud Watch, New Relic
  • Сбор/обобщение журналов. Логгли, Спланк

Автоматическое масштабирование

  • Автоматическое масштабирование. Heroku, AWS Beanstalk, AppEngine, AWS Opsworks, Digital Ocean

Заповедь №5: Не переходи ручьи!

Есть много хороших моделей для дизайна программирования, pub/sub, актеров, MVC и т. д. Выберите то, что вам больше нравится, и придерживайтесь его. Различные виды логики, работающие с разными видами данных, должны быть физически изолированы в кодовой базе (опять же, это разделение концепций и снижение когнитивной нагрузки на будущего читателя). Например, код, который обновляет ваш пользовательский интерфейс, должен физически отличаться от кода, который вычисляет, что входит в пользовательский интерфейс.

Заповедь № 6: По возможности позволяйте компьютеру делать всю работу

Если компилятор может ловить логические ошибки в вашем коде и предотвращать плохое поведение, ошибки или прямые сбои, мы обязательно должны воспользоваться этим. Конечно, для некоторых языков есть компиляторы, которые делают это проще, чем для других. Haskell, например, имеет знаменитый строгий компилятор, из-за которого программисты тратят большую часть своих усилий только на то, чтобы компилировать код. Однако после компиляции «он просто работает». Для тех из вас, кто никогда не писал на строго типизированном функциональном языке, это может показаться нелепым или невозможным, но не верьте мне на слово. Серьезно, нажмите на некоторые из этих ссылок, абсолютно возможно жить в мире без ошибок во время выполнения. И это действительно так волшебно.

По общему признанию, не каждый язык имеет компилятор или синтаксис, который поддается значительной (или в некоторых случаях любой!) проверке во время компиляции. Для тех, кто этого не делает, уделите несколько минут изучению того, какие необязательные проверки строгости вы можете включить в свой проект, и оцените, имеют ли они смысл для вас. Краткий, неполный список некоторых распространенных, которые я использовал в последнее время для языков с мягким временем выполнения, включает:

  • Python: pylint, pyflakes, предупреждения, предупреждения в emacs
  • Руби: предупреждения
  • JavaScript: jslint

Заключение

Это ни в коем случае не исчерпывающий и не идеальный список заповедей для создания «хорошего» (т. е. легко поддерживаемого) кода. Тем не менее, если бы каждая кодовая база, которую мне когда-либо приходилось собирать в будущем, соответствовала хотя бы половине концепций из этого списка, у меня было бы гораздо меньше седых волос, и, возможно, я даже смог бы добавить к концу своей жизни дополнительные пять лет. И я, безусловно, найду работу более приятной и менее напряженной.