Успешный программный продукт обычно проходит за свою жизнь через руки множества разработчиков. Вы — лишь одно из звеньев в цепочке опекунов вашего проекта, и каждая строчка кода, которую Вы написали — это оставленный Вами артефакт, который когда-нибудь будет изучаться Будущим Разработчиком. Также, как Вы унаследовали решения разработчиков, которые были до Вас, другие разработчики унаследуют решения, которые Вы делаете сегодня. Они получат от нас в наследство все наши недоразумения, срезанные нами углы, примененные нами недопонятые паттерны и техники, наше невнимание к деталям, нашу лень, наши изменения, сделанные на скорую руку, наших скелетов в шкафах, наше грязное белье. И гораздо реже — выгоду от нашей дисциплинированности, наших обсуждений и подготовок.
Как разработчик своего проекта, Вы находитесь в лучшей из возможных позиций чтобы предвидеть и позаботиться о нуждах Будущего Разработчика. Каждое хорошее решение, которое мы принимаем для нашего проекта, для его или её продуктивности будет иметь волновой эффект. Почему это важно? Как Боб Мартин спрашивает в книге Clean Code: «Вы когда-нибудь испытывали проблемы из-за плохого кода? Тогда почему Вы пишите его?». Те же стратегии, что улучшат условия поколениям команд, которые будут работать над вашим проектом в будущем, будут служить добром вашей команде и в настоящем. Когда Вы возвращаетесь в темные уголки кода, налепленного Вами полгода назад, Вы знаете о нем чуточку больше, чем будет знать Будущий Разработчик, когда он или она увидит его в первый раз. От подсказок и глянца, которые Вы оставите другим разработчикам, в будущем выиграете и Вы сами. Проекты которые находятся в плохом состоянии, и в которые сложно внести вклад, приводят к истощению команды. Инвестиции в качество и в простоту поддержки в будущем продукта, который Вы создаете — это инвестиции в счастливое, продуктивное рабочее место для будущего и настоящего.
Я хочу разобрать несколько практик (без какого-либо определенного порядка), которые мы можем использовать, чтобы направить Будущего Разработчика прямиком к успеху.
1. Целостный рефакторинг
По мере взросления проекта и усложнения требований, мы стремимся применять новые паттерны и приемы, чтобы контроллировать эту сложность. Нельзя сразу сказать, справится ли паттерн или подход с задачей, возложенной на него. Большую часть времени, подтверждение или опровержение его значимости появляется когда другой разработчик должен внести изменения в эту часть кода. Иногда эти паттерны вырастают в соглашения, которые мы принимаем для решения проблем.
От этого есть огромная польза: это поясняет наши намерения. Если мы пытаемся решать задачи в коде похожим образом, Будущий Разработчик может начать предугадывать, как куски кода работают вместе, уменьшая тем самым количество времени, необходимого для диагностики и внесения изменений.
Часто то, что мы оставляем за собой, является мешаниной паттернов, которые так и не переросли в соглашения или были проигнорированы в коде как старый хлам. Это случается по разным причинам: принятые соглашения не работают настолько хорошо, чтобы применять их в других областях проекта, или, может быть, новые разработчики не знали о существовании подходящего соглашения или паттерна для достижения поставленной цели.
Внутри Ruby on Rails обосновалось много мощных взглядов и соглашений. Они позволяют разработчикам подключаться к проекту и сразу же становиться продуктивными, при том условии, что они имели опыт общения с этим фреймворком ранее. Иногда мы относимся пренебрежительно к этим соглашениям, тем самым ослабляя их силу. К примеру, в рельсовых проектах периодически можно заметить контроллеры, построенные в нескольких разных стилях. Одни составлены с помощью чего-нибудь вроде resource_controller
, другие следуют стандартному соглашению о работе с ресурсами, принятому в Rails, тогда как третьи просто являются свалкой мусора из случайных экшенов. Другой частовстречающийся анти-паттерн состоит в рассыпаных по всему проекту и инициализирующихся везде, где возможно, конфигурационных данных.
Не надо поддерживать полдюжины разных способов настройки Вашей системы, сделайте ясным и понятным то, по каким принципам должен строится контроллер внутри проекта. Поэкспериментировав с каким-то одним подходом и приняв его, уделите время на то, чтобы вернуться к старому коду и отрефакторить его на новый лад. Это не означает, что Вы должны накладывать какие-то произвольные ограничения. Есть смысл в том, чтобы, например, держать часть конфига внутри проекта, а часть в переменных окружения для помощи при деплое, но при этом должна быть одна общая структура и шаблон доступа к использованию конфигурационных данных.
Добавьте соглашения в Ваш README или в репозиторий документации. На первых порах это упростит Будущему Разработчику добавление нового функционала в систему и даст понимание того, как построены ее компоненты.
2. Искореняйте Мертвый Код
Другая общая черта систем, которые уже успели просуществовать некоторое время, это слой мха в форме мертвого кода. Это компоненты Вашего проекта, которые могли какое-то время назад представлять важность для бизнеса, но устарели и долгое время никому не были нужны. Скорее всего в проекте можно найти большое количество full-stack acceptance тестов, проверяющих такие части функционала, и сейчас эти тесты лишь замедляют прохождение остальных.
Иногда мы неохотно удаляем такой код, потому что не всегда можем быть уверены, что эта фича никогда больше не будет востребована. Если спросить у руководство, то оно ответит на твой вопрос: «Оставь, когда-нибудь пригодится». Это ложная дилемма — нужно поддерживать медлено подгнивающий кусок кода, потому что когда-нибудь в будущем, возможно, его снова можно будет использовать, всего-лишь щелкнув пальцами. Если мы обходим этот код стороной, по причине его неактуальности, вряд ли получится вот так просто щелкнуть пальцами и заставить его работать без значительного труда. Вас волнует, что старый код тянет Вас якорем на дно, заставляет тратить время на его поддержку, всего лишь из за того, что существует мизерная вероятность, что он однажды все-таки потребуется. Может быть. Вы не замечаете, но Вы тратите много времени на это, и вместо того, чтобы удалить такой код, Вы позволяете ему медленно догнивать в репозитории.
Но что еще более затратно, так это то, что существование этого кода представляет собой потенциальный капкан для Будущего Разработчика. Это отвлекает внимание от компонентов системы, которые на самом деле живы, и является красной тряпкой, когда он или она пытается понять или отладить какой-то аспект системы. В экстренных ситуациях, когда все упало, старый, мертвый код — это шум, который ждет момента, чтобы отнять ценное время. Поддерживая в своем репозитории только актуальный, живой и на самом деле функционирующий код, Вы снизите общие затраты на поддержку проекта и позволите Будущему Разработчику гораздо быстрее понять всю систему целиком.
Удалите код, который давно заброшен. В конце концов, он все еще находится под контролем версий, на случай, если к нему нужно будет позже вернуться. Не обманывайте себя тем, что нужно будет «снова вернуться к нему позже». Если этот код имел хоть какое-нибудь значение, то почему он уже не используется?
3. Оставляйте за собой информацию
Помимо, собственно, нашего проекта, некоторые инструменты, которые мы используем для написания кода, тоже имеют свои артефакты. Например, существует много принятых практик о том, что составляет гигиену хорошего сообщения при git commit, и при этом проекты все равно продолжают собирать такие истории коммитов:
jp@oeuf:~/workspace/blog(master*)$ git log --oneline app/controllers/application_controller.rb
8ec7f99 fuck i dunno lol
ffa919a shut up, atom parser
a33e9fa fixing again
cecc9dc one more time
968a28f fixing
3e3aeb2 ws
1fc597e pagination
edea155 adding dumb feature
На тот случай, когда Будущий Разработчик неизбежно придет к использованию git blame, чтобы понять контекст данной фичи, оставьте ему или ей подробности, которые помогут понять происходящее в файлах на самом деле. Используйте --squash
, commit --amend
, rebase
и прочие инструменты, чтобы привести коммиты в годное состояние прежде чем интегрировать свою ветку. Перефразируйте свои коммиты, когда закончите — найдите минуту, чтобы включить все, что кажется подходящим и подводит итог проделанной работы. Откорректируйте грамматику и правописание; Вы публикуете нечто такое, что придется читать и понимать другому человеку. Окажите Будущему Разработчику услугу и убедитесь в том, что Вы оставляете за собой понятную запись, содержащую подробностей ровно столько, сколько необходимо.
jp@oeuf:~/workspace/blog(master*)$ git log app/controllers/application_controller.rb
commit 8ec7f998fb74a80886ece47f0a51bd03b0460c7a
Author: John Pignata <john@pignata.com>
Date: Sat Nov 3 14:11:12 2012 -0400
Add Google Analytics helper
commit 968a28f366e959081307e65253118a65301466f2
Author: John Pignata <john@pignata.com>
Date: Sat Nov 3 13:49:50 2012 -0400
Correct ATOM feed validation issues
Using the W3C Validator (http://validator.w3.org/appc/), a few trivial
errors were reported:
* <author> should have a <name> node within it and not text
* Timestamps should be in ISO8601 format
This change fixes these issues and improves the spec coverage for the XML
document.
commit 3e3aeb27ea99ecd612c436814c5a2b0dab69c2c3
Author: John Pignata <john@pignata.com>
Date: Sat Nov 3 13:46:24 2012 -0400
Fixing whitespace
We're no longer indenting methods after `private` or `protected` directives
as a matter of style. This commit fixes whitespace in all remaining
classes.
commit 1fc597e788442e8cc774c6d11e7ac5e77b6c6e14
Author: John Pignata <john@pignata.com>
Date: Sat Nov 3 12:34:50 2012 -0400
Implement Kaminari pagination
Move from will_paginate to kaminari in all controllers. The
motivation is to be able to paginate simple Array collections
without the monkey patching that will_paginate uses.
* Consolidate helpers
* Clean up whitespace
commit edea15560595bab044143149a7d6e528e8ae65d2
Author: John Pignata <john@pignata.com>
Date: Sat Nov 3 12:27:16 2012 -0400
Add ATOM feed for RSS readers
* Include Nokogiri in Gemfile for its builder
* Add AtomFieldBuilder model
* Add link to feed from index page
4. Наведите глянец на свои интерфейсы
Некоторые Рубисты избегают видимые методы в своих объектах. В чем цель? Любой метод на самом деле вызывается через send, как ни крути. Зачем заковывать в кандалы некоторые из них? Просто добавьте метод до кучи к остальным и если Будущий Разработчик захочет использовать его, то он или она сможет! Мы ведь все взрослые, не так ли?
Если каждый объект в вашей системе это просто свалка методов, то каждому (включая Вас) становится сложно понять как каждый отдельный объект предполагалось использовать и какие сообщения ему предназначались. Дизайн публичного интерфейса объекта должен делать абсолютно понятным то, как другие объекты в системе могут взаимодействовать с ним. Когда роль каждого объекта и взаимодействия между ними в твоей системе не очевидны, время, которое нужно затратить, чтобы понять не только каждый из них в отдельности, но и систему в целом, растет.
Прячьте как можно больше внутренностей компонентов, чтобы содержать интерфейс маленьким и сосредоточенным. Приложите дополнительные усилия, чтобы убедиться в том, что публичные интерфейсы Вашего объекта очевидны, целостны и имеют хорошие названия. Это даст Будущему Разработчику понятные сигналы о том, как Вы задумывали использовать каждый из объектов и подчеркнет то, как каждый из них может быть использован повторно. Явно указывайте видимость методов, чтобы передать эти предпосылки и соблюсти четкие границы публичного интерфейса.
5. Оставляйте комментарии, не слишком много, в основном RDoc
Наши чувства, как разработчиков, к комментариям в коде наиболее точно могут быть описаны как противоречивые. С одной стороны комментарии очень помогают читателю в понимании того, как работает определенный кусок кода. С другой стороны ничего не гарантирует их корректность, комментарии к коду — это ложь, которая ждет своего часа. Если спросить разработчиков, то они ответят, что ценят и почитают документацию, но большинство проектов на деле имеют небольшой, почти-потерявший-актуальность README файл и могилку wiki где-нибудь рядом. Более того, работая с open source библиотеками, мы частенько ожидаем подробную RDoc документацию, свежий README файл и хорошие примеры кода, а когда не обнаруживаем ничего из этого, грязно ругаемся. Чертов разработчик: не поддерживает документацию, наверное ждет, что кто-то сделает это за него.
Чем больше мы уделяем внимания вещам вроде Single Responsibility Principle и используем паттерны слабой связности между объектами, мы начинаем видеть системы составленные из множества маленьких объектов, объединенных вместе на время работы программы. Хотя это и делает системы более гибкими, а объекты становится проще использовать многократно, присутствует компромисс: понять место объекта внутри большой системы становится сложнее, это требует больше усилий и времени. Можно применить все привычные приемы рефакторинга, чтобы удалить надоедливые междустрочные комментарии и сделать объект настолько читабельным, насколько это возможно, но то, как объект должен взаимодействовать в системе, все еще может сбить с толку Будущего Разработчика.
Документация в стиле RDoc может быть найдена во многих open source проектах. Когда Вы пользуетесь Google, чтобы понять, вызывает update_attribute
коллбэки или нет, и какой сигнатурой обладает select_tag
, Вы обычно попадаете на страницу RDoc для Ruby On Rails. Написание аналогичной документации для Вашего проекта даст Будущему Разработчику понять больше, когда он или она будет пытаться определить роль объекта в огромном контексте твоей системы. Наличие короткого, декларативного предложения наверху класса и/или метода, показывающего, за что он отвечает, может иметь существенное значение для читающего код. Но без хорошо распространенной культуры поддержки актуальности этих комментариев, они также могут оказать и негативное влияние, дизориентировав читателя кода. Помните, единственное, что может быть хуже отсутствия документации — это плохая документация.
6. Пишите тесты, раскрывающие Ваши замыслы
Один из способов снабжения проекта документацией — это тесты. Тесты не только описывают поведение компонентов, но и гарантируют, что такая документация — качественная, т.к. она выполняется. В отличие от комментариев, мы не можем наврать в тестах. Они либо зеленые, либо нет. Такие инструменты, как RSpec и minitest/spec помогают нам генерировать эту побочную документацию, поощряя прозу внутри блока тестового примера. К сожалению, иногда мы смотрим мимо Английских слов, пытаясь добиться работающего кода во время цикла red-green-refactor. В результате пренебрежения Англоязычным описанием, иногда получается так, что тесты отображают поведение наших объектов не так хорошо, как мы думаем.
Почти так же болезненно, как найти проект без тестов вообще, найти проект, чьи тесты не помогают понять, как система работает. Тесты это код, который также требует поддержки, а поэтому они должны очень четко пояснять свое предназначение будущему их читателю.
it "works" do
data = File.open("fixtures/projects.txt").read
index = ProjectIndex.new(data)
index.should have(40).projects
last_project = projects.last
last_project.title.should eq("ORCA")
last_project.customer.should eq("Romney-Ryan 2012")
last_project.deploy_date.should eq(Date.parse("2012-11-06"))
end
Хорошо, что здесь «работает»? Это описание в одно слово бессмысленно, и в примере происходит сразу несколько проверок, что совсем не оставляет за собой никакого смысла.
Если Вы пишите тесты в стиле спецификаций, Англоязычные описания должны ходить у Вас по струнке. Один из способов добиться этого, это запускать RSpec в формате документации:
jp@oeuf:~/workspace/project-manager(master)$ be rspec --format documentation spec
ProjectIndex
.new
instantiates an index given the contents of a project CSV file
#projects
returns a collection of projects from the index
Project
#title
returns the project title
#customer
returns the Customer record for the project
#deploy_date
calculates the deploy date from the latest project status
Вместо леса зеленых точек, формат документации выдает вложеные описания, контексты и заголовки примеров, которые Вы указали. Это позволяет Вам бегло оценить, насколько полно тесты раскрывают актуальное поведение объекта. Сфокусировавшись на выдаче в этом формате, можно значительно улучшить коммуникативную ценность набора тестов. Используйте шаг refactor в цикле red-green-refactor, чтобы превратить Ваши тесты во внятное объяснение причины существования объекта, того, как он себя ведет, и почему он делает это именно так.
Будущий Разработчик в восторге
Это всего лишь несколько способов, которыми мы можем подготовить проект к изменениям, с разумным предположением, что кто-то другой будет платить по счетам. Подумайте о последующих парах глаз, что будут отвечать за строительство и эксплуатацию Вашего текущего проекта, пока Вы еще работаете над ним. Все мы испытывали когда-нибудь угрызения совести из-за сложности поддержи или плохого качества того, что выпускали. Так что перестаньте питать теплые чувства ко всем тем милым граблям, которые Вы оставили в коде, и вместо этого начните потихоньку подсчитывать количество выпивки, которую Будущий Разработчик Вам будет должен за то, как хорошо Вы подготовили почву к его приходу.
Спасибо Dave Yeu у которого я позаимствовал (читай: украл) термин “будущий разработчик”.
Есть мысли, вопросы или комментарии? Делитесь ими, пожалуйста! Меня можно найти в Твиттере под ником @jpignata, а также я доступен по электронной почте john@pignata.com. Спасибо за чтение!
Автор: shebanoff