Отпуск отлично освежает мозги, забитые ежедневной рутиной. Вот и сейчас во время отпуска я натолкнулся на интересную мысль, которой хочу поделиться. Сама идея не нова, однако у меня открылся новый взгляд на эту проблему. Сегодня я расскажу почему ваше API не должно хранить состояние на примере выключателей света.
Простой и знакомый всем еще с детства, этот элемент электропроводки сегодня стоит почти в каждом доме. Однако у него есть существенный недостаток – он не просто является триггером для лампы, а еще и хранит ее состояние. Что это значит? Простыми словами можно описать так: состояние лампы (включена-выключена) и состояние выключателя (вверху-внизу) жестко связаны – конкретное положение выключателя соответствует конкретному состоянию лампы (кстати, для тех, кто как и я не может запомнить, в нашей стране верхнее положение клавиши как правило соответствует включенному свету, а нижнее — выключенному). В принципе такое поведение и так понятно каждому, кто хоть раз таким пользовался. В чем же недостаток?
Используя тот факт, что состояние выключателя вроде бы тождественно таковому лампы, наблюдатель может начать строить предположения о том, что сейчас с лампой на основании состояния выключателя. И здесь его ждут подводные камни, потому что лампа может перегореть. отсутствовать, может быть повреждена проводка или отсутствовать электроэнергия. Здесь это скорее всего будет ошибка наблюдателя, который выбрал неправильный источник данных о состоянии лампы. Действительно – зачем смотреть на триггер, если можно посмотреть на источник света.
Выключатели не масштабируются
Собственно главный минус описанной системы в том, что такой выключатель может быть только один. Конструктивно это просто разделитель цепи, который разрывает соединение, когда находится в выключенном состоянии. Это представляет из себя простое решение “в лоб”. Действительно, что может быть проще в реализации, чем физическое разъединение контакта простым рычагом, приводимым в движение мускульной силой нажимающего? Но часто ли решения в лоб оказываются оптимальными? Например один из лучших алгоритмов сортировки Timsort имеет около 70 строк кода, хотя казалось бы задача тривиальная, “просто менять местами элементы в порядке сортировки“.
Это порождает проблему, которая явственно чувствуется в большой комнате: чтобы воспользоваться лампой, надо до нее дойти, а чтобы до нее дойти, нужно включить свет (аналогично с выключением). Чтобы проиллюстрировать ситуацию, давайте доведем ее до абсурда. Представим себе человека, стоящего у двери бесконечно большой темной комнаты (хорошо, не бесконечно большой, а просто большой, иначе свет от лампы никогда не достигнет его глаз), заполненной всяким хламом. На другом конце ее вторая дверь и, о ужас, единственный выключатель для освещения. Совершенно очевидно, что он едва ли найдет путь к другой двери в таких условиях. Ситуация выглядит очень странной, однако давайте вспомним, что выключатели и комнаты это только метафора. В программировании – а статья в первую очередь о нем – нам постоянно приходится иметь дело именно с подобными крайними случаями. Более того, это одна из вещей, которые должен понять джун на пути к мидлу: крайние ситуации и их обработка даже важнее “обычного” сценария. А теперь спросите себя, как часто приходилось сталкиваться с системами, которые были написаны с мыслями о том, что это будет маленькая комнатка с кроватью и выключателем у двери, а превратились в огромный ангар без освещения, набитый мусором? Уверен, что примеров будет масса…
В жизни описанную проблему как правило решают костылями. Можно в буквальном смысле, если костыль достаточно длинный и с его помощью можно достать до выключателя света. Но самый популярный вариант – это отдельный светильник в непосредственной близости от места, где человек проводит время в комнате. Такое решение позволяет не сломать ноги, блуждая в потемках, но не решает всех проблем. Невыключенный малый свет – это точно такая же лампа с интегрированным выключателем. Так что вам придется дойти до него, чтобы погасить, а потом пользоваться другими источниками света. В общем, для идеального решения проблемы плохих триггеров придется умножать источники освещения либо развивать ноцицепцию.
Что ж, я думаю, что на этом месте не так уж много людей будут готовы поспорить с тезисом статьи. Однако что можно предложить взамен?
Stateless выключатель
На картинке не очень понятно, но это выключатель без состояния. После нажатия на него он возвращается в то же самое положение пружиной и меняет состояние света. Такой стоит у входа в комнату и в изголовье кровати. Состояние хранится где-то еще, а сами кнопки просто посылают сигнал на его изменение. После этого какая-то хитрая система переключает лампу. И нам как пользователям совершенно необязательно знать, что она из себя представляет, она инкапсулирована за кнопкой. Фаулер мог быть доволен автором такой схемы. Эта пластиковая вещица представляет из себя идеальный API к комнатному освещению, а самое главное, предоставляет возможность сколько угодно ее масштабировать. В примере с темной комнатой можно поставить триггер в каждом углу и быть уверенным, что хотя бы один окажется рядом, когда мы захотим войти.
Неудобство, пожалуй, только одно – если лампочка таки перегорит, непонятно в каком она положении. Но для этого существует этакий God Object домашней электросети – щиток. Там централизованно отключается питание комнаты, где произошел казус и не надо гадать, в каком состоянии лампа.
Вообще сегодня есть много решений: умный дом, дистанционные пульты, датчики движения. Но схема, что я сейчас показал, была разработана еще до эпохи цифровой революции, когда альтернатив было немного. Безусловно простой выключатель дешевле, однако первоначальные затраты на установку более сложной системы впоследствии окупятся за счет удобства в использовании. Также эта схема будет легко масштабироваться под потребности заказчика. Простой же вариант придется переделать полностью, если клиент кровь из носу захочет второй выключатель у своей кровати. И снова параллели с разработкой ПО более чем очевидны и даже не нуждаются в комментировании.
Морали тут не будет – основной посыл вынесен во вступлении и в принципе и так всем известен. Я просто проиллюстрировал, ситуацию, когда система делается не в согласии с провозглашенным принципом. Так что теперь, потренировавшись на выключателях и благополучно дойдя до противоположной двери темного ангара, можно создавать качественные интерфейсы, которые не обманут нас, будут удобны, надежны и масштабируемы.
Бонус для тех, кто дочитал
Упомянутая в предыдущем параграфе надежность требует пояснения. Выключатели такой системы (они называются проходные) на самом деле менее надежны: больше проводов, сложнее схема подключения, больше точек отказа. Поэтому, раз уж сегодняшняя статья посвящена инженерной культуре, обеспечивать надежность все-таки придется :). Например подбором маломощных светильников либо установкой проводов большего сечения.
Выбирая то или иное решение всегда надо смотреть не только на его “фичи”, но и на побочные эффекты и риски. И разумеется, данный абзац тоже подходит не только для планирования электрификации помещения.