Выключатели как пример плохого API

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

Герой сегодняшней статьи

Простой и знакомый всем еще с детства, этот элемент электропроводки сегодня стоит почти в каждом доме. Однако у него есть существенный недостаток – он не просто является триггером для лампы, а еще и хранит ее состояние. Что это значит? Простыми словами можно описать так: состояние лампы (включена-выключена) и состояние выключателя (вверху-внизу) жестко связаны – конкретное положение выключателя соответствует конкретному состоянию лампы (кстати, для тех, кто как и я не может запомнить, в нашей стране верхнее положение клавиши как правило соответствует включенному свету, а нижнее — выключенному). В принципе такое поведение и так понятно каждому, кто хоть раз таким пользовался. В чем же недостаток, что не так?

Во-первых используя тот факт, что состояние выключателя тождественно таковому лампы, наблюдатель может начать строить предположения о том, что сейчас с лампой на основании состояния выключателя. И здесь его ждут подводные камни, потому что лампа может перегореть. отсутствовать, может быть повреждена проводка или отсутствовать электроэнергия. Здесь это скорее всего будет ошибка наблюдателя, который выбрал неправильный источник данных о состоянии лампы. Действительно – зачем смотреть на триггер, если можно посмотреть на источник света. Но давайте на секунду абстрагируемся от ламп, служащих в моей статье лишь примером, и подумаем как это будет выглядеть в случае, если у нас есть плохо документированное API в котором есть метод подключения к БД, ведущий себя подобным образом. Вероятность “клюнуть” на ответ метода, который будет соответствовать наблюдаемому поведению системы очень высока. И вполне возможно такой ответ (состояние триггера) будет использован как показатель того, что все получилось (мини-мораль – не надо так делать 🙂 ). Просто подводные камни работы лампы и выключателя нам уже известны, а проблемы новой системы нет.

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

Выключатели не масштабируются

Собственно главный минус описанной системы в том, что такой выключатель может быть только один. Конструктивно это просто разделитель цепи, который разрывает соединение, когда находится в выключенном состоянии. Это представляет из себя простое решение “в лоб”. Действительно, что может быть проще в реализации, чем физическое разъединение контакта простым рычагом, приводимым в движение мускульной силой нажимающего? Но часто ли решения в лоб оказываются оптимальными? Например один из лучших алгоритмов сортировки Timsort имеет около 70 строк кода, хотя казалось бы задача тривиальная, “просто менять местами элементы в порядке сортировки“.

Очень простая схема выключателя.

Это порождает проблему, которая явственно чувствуется в большой комнате: чтобы воспользоваться лампой надо до нее дойти, а чтобы до нее дойти, нужно включить свет (аналогично с выключением). Чтобы проиллюстрировать ситуацию, давайте доведем ее до абсурда. Представим себе человека, стоящего у двери бесконечно большой темной комнаты (хорошо, не бесконечно большой, а просто большой, иначе свет от лампы никогда не достигнет его глаз), заполненной всяким хламом. На другом конце ее вторая дверь и, о ужас, единственный выключатель для освещения. Совершенно очевидно, что он едва ли найдет путь к другой двери в таких условиях. Ситуация выглядит очень странной, однако давайте вспомним, что выключатели и комнаты это только метафора. В программировании – а статья в первую очередь о нем – нам постоянно приходится иметь дело именно с подобными крайними случаями. Более того, это одна из вещей, которые должен понять джун на пути к мидлу: крайние ситуации и их обработка даже важнее “обычного” сценария. А теперь спросите себя, как часто приходилось сталкиваться с системами, которые были написаны с мыслями о том, что это будет маленькая комнатка с кроватью и выключателем у двери, а превратились в огромный ангар без освещения, набитый мусором? Уверен, что примеров будет масса…

В жизни описанную проблему как правило решают костылями. Можно в буквальном смысле, если костыль достаточно длинный и с его помощью можно достать до выключателя света. Но самый популярный вариант – это отдельный светильник в непосредственной близости от места, где человек проводит время в комнате. Такое решение позволяет не сломать ноги, блуждая в потемках, но не решает всех проблем. Невыключенный малый свет – это точно такая же лампа с интегрированным выключателем. Так что вам придется дойти до него, чтобы погасить, а потом пользоваться другими источниками света. В общем, для идеального решения проблемы плохих триггеров придется умножать источники освещения либо развивать ноцицепцию.

Что ж, я думаю, что на этом месте не так уж много людей будут готовы поспорить с тезисом статьи. Однако что можно предложить взамен?

Stateless выключатель

Вот он, наш спаситель!

На картинке не очень понятно, но это выключатель без состояния. После нажатия на него он возвращается в то же самое положение пружиной и меняет состояние света. Такой стоит у входа в комнату и в изголовье кровати. Состояние хранится где-то еще, а сами кнопки просто посылают сигнал на его изменение. После этого какая-то хитрая система переключает лампу. И нам как пользователям совершенно необязательно знать, что она из себя представляет, она инкапсулирована за кнопкой. Фаулер мог быть доволен автором такой схемы. Эта пластиковая вещица представляет из себя идеальный API к комнатному освещению, а самое главное, предоставляет возможность сколько угодно ее масштабировать. В примере с темной комнатой можно поставить триггер в каждом углу и быть уверенным, что хотя бы один окажется рядом, когда мы захотим войти.

Неудобство, пожалуй, только одно – если лампочка таки перегорит, непонятно в каком она положении. Но для этого существует этакий God Object домашней электросети – щиток. Там централизованно отключается питание комнаты, где произошел казус и не надо гадать, в каком состоянии лампа. Кстати, кто запомнил, что “вкл”, а что “выкл” в обычном выключателе? Я писал об этом тремя абзацами выше и, признаться, уже запамятовал 😉 . В общем, описанное неудобство вряд ли способно перевесить плюсы.

Вообще сегодня есть много решений: умный дом, дистанционные пульты, датчики движения. Но схема, что я сейчас показал, была разработана еще до эпохи цифровой революции, когда альтернатив было немного. Безусловно простой выключатель дешевле, однако первоначальные затраты на установку более сложной системы впоследствии окупятся за счет удобства в использовании. Также эта схема будет легко масштабироваться под потребности заказчика. Простой же вариант придется переделать полностью, если клиент кровь из носу захочет второй выключатель у своей кровати. И снова параллели с разработкой ПО более чем очевидны и даже не нуждаются в комментировании.

Морали тут не будет – основной посыл вынесен во вступлении и в принципе и так всем известен. Я просто проиллюстрировал, ситуацию, когда система делается не в согласии с провозглашенным принципом. Так что теперь, потренировавшись на выключателях и благополучно дойдя до противоположной двери темного ангара, можно создавать качественные интерфейсы, которые не обманут нас, будут удобны, надежны и масштабируемы.

Бонус для тех, кто дочитал

Упомянутая в предыдущем параграфе надежность требует пояснения. Выключатели такой системы (они называются проходные) на самом деле менее надежны: больше проводов, сложнее схема подключения, больше точек отказа. Поэтому, раз уж сегодняшняя статья посвящена инженерной культуре, обеспечивать надежность все-таки придется :). Например подбором маломощных светильников либо установкой проводов большего сечения.

Схема проходного выключателя. Выглядит сложно, но тем не менее вполне осуществима.

Выбирая то или иное решение всегда надо смотреть не только на его “фичи”, но и на побочные эффекты и риски. И разумеется, данный абзац тоже подходит не только для планирования электрификации помещения.