Всё нижеизложенное вымысел, основанный на реальных событиях.
Не являясь мастером писать заметки, пытался «с пылу с жару» изложить это вчера. Но просто и доступно выстроить мысли в стройном порядке «по горячим следам» не вышло.
Кроме того на личном опыте выяснилось, что описываемый подход, хоть он, на первый взгляд и кажется лично мне очень простым, статистически таким не является. Известное высказывание о том, что «простые вещи, они самые сложные» оказывается в данном случае верным.
«Практическое» применение данного подхода требует усилий и кропотливой работы над собой, до тех пор, пока он не станет безусловным рефлексом, пока допускаемая «неточность» не будет заметна ещё до её совершения.
Поэтому, споткнувшись об одну и ту же проблему в «надцатый» раз, и в «надцатый» же раз наблюдая идентичное её решение, полагаю, что изложение данного «подхода» в письменном виде поможет мне ещё прочней закрепить его как «навык».
В связи с чем, публикуя эту заметку здесь, ожидаю, что вторым «застреленным зайцем» может стать помощь кому-нибудь ещё в избавлении от подобных ситуаций.
Итак, есть два программиста. Нормальные парни, знают много, работают достойно.
Оба занимаются UI, оба пишут код для браузера на JavaScript. Оба понимают код друг друга, и оба же без серьёзных проблем его исправляют.
Но при этом раз за разом повторяется следующая ситуация: если второй исправляет код первого, то у него это никогда не получается с первого раза. Первый же всегда сразу исправляет код второго без задержек и проблем.
Тут можно было бы подумать, что второй просто «хуже». Но на самом деле это не совсем так, они просто «разные», и сталкивались с разными вещами. А сейчас делают «одно и то же дело».
И вот настал момент, когда первый ушел в отпуск. Естественно вскоре ситуация усугубилась, и второму пришлось править хоть и знакомый, но всё же «чужой» код.
Он переделывал его трижды. Почти целый рабочий день на тридцать строк кода. При том, что этот код был ему знаком и существенно не изменялся уже несколько месяцев.
И нужно-то было, даже не исправление, а мелкая доработка, вывод сообщения в случае одного из трёх возможных Use Case.
Когда он вывел «Сообщение» в первый раз, в Jira появился баг о том, что отвалилось два других Use Case. Он исправил ошибку, но в Jira тут же появились два бага о том, что больше нет нужного «Сообщения» и не работает третий Case. В итоге, так повторилось несколько раз, суммарно накопилось 10 багов.
Так как данная ситуация была уже более чем привычна, я решил вспомнить когда и в каких конкретно условиях это повторялось.
Оказалось, что это всегда происходит в примерно одинаковой ситуации.
Неизменные дано похожей ситуации:
- Производится обработка UI событий (действий) пользователя: нажатие кнопки, выбор из списка, в общем – «интерактив».
- Обычно ситуация происходит в очень простом, можно даже сказать «элементарном» алгоритме, когда кажется, что исправить это можно за пять минут.
- В обработке разных Use Case используется один метод или одна функция. Т.е. одна и та же «точка входа в алгоритм» но от разной последовательности пользовательских действий. Часто даже от разных экранов.
В последний раз «второй» закопался в ситуации из трёх кнопок:
- «Сохранить» и «Сохранить и создать ещё» на экране «Создания» записи,
- «Сохранить» на экране «Редактирования».
На все три кнопки «первым» был поставлен один обработчик, т.е. все три кнопки «сходились» в один метод. Получилось три разных комбинации, выполняющих похожие, но всё же немного разные алгоритмические последовательности. В методе так же были задействованы пара других последовательностей, доступных через «статусы» по IF. Тогда номеру два потребовалось ещё больше времени, так как суммарное число Use Case в одном методе было 7.
Попытавшись понять, почему «номер один» пишет такой код без ошибок, а «номер два» не может потом его исправить, при том, что там нет никаких конструкций из разряда «динамики внешних сфер», я пришел к выводу, что парни отличаются в рамках какого-то базового противоречия.
Первый половину жизни работал со строго типизированными языками программирования, требующими компиляции перед запуском.
Второй больше всего работал со скриптовыми языками со слабой типизацией, можно сказать, что всю жизнь использовал JavaScript.
Первый разбирался в ООП на уровне переопределения методов посредством разного набора входных переменных. Второй без проблем реализовывал наследование на базе прототипов.
Первый работал с многопоточными приложениями. Второй собирался начать разбираться с реализацией многопоточности в рамках Node.JS.
Получилась интересная картинка:
- Первый при отладке может думать о том, что этот код будет работать в разных Use Case.
- Второй вспоминает о том, что здесь несколько Use Case только получив пачку багов.
То есть, второй «сразу» вообще не воспринимает ситуации, когда в один и тот же алгоритм сходится несколько разных поведений пользователя.
Осмыслив эту «отправную точку» в различиях, я решил посмотреть смотреть на код второго более пристально, вдруг он вообще не использует «общие» методы и не пишет «разделяемый» код.
Оказалось, что в отличие от первого, он пишет общие методы по-другому. Он выносит то, что повторяется, в отдельные методы, но то, что мы уже прозвали «точкой входа пользователя в Use Case» у него всегда реализовано отдельным методом.
Тогда я спросил его: «Почему ты так делаешь?»
Ответ меня шокировал: «Так я никогда не забуду, что здесь отрабатывает что-то ещё, т.к. ничего больше здесь и не описывается».
То есть, как только он сталкивался с ситуацией, в которой появлялся второй «входной» Use Case в рамках одного метода, он делил код на два метода, а «повторяющиеся» части выносил в отдельные сопутствующие методы. Причем обычно он делал это ещё в процессе обдумывания алгоритма, анализа того, что он собирается написать.
Таким образом, у него получалось, что в «отладчике» у него может быть только одна реализация обработки последовательности действий от пользователя. И он никогда не сталкивался с тем, что может что-то забыть или упустить, т.к. было просто нечего забывать.
В тот момент, когда он начинал исправлять «разделяемый код» «номера один», его «однопоточный» подход ломался, и ему требовалось дополнительное время, чтобы вспомнить, что может быть совершенно другой способ писать код.
И тут я решил посмотреть, а нет ли у «номера один» подобных ситуаций. Оказалось, что, как это ни странно, но – есть. Просто справлялся он с ними сразу по появлении соответствующего бага. Т.е., в силу привычки он просто помнил, что здесь сходится несколько Use Case, но от ошибок он тоже не был застрахован.
Тогда я проанализировал «баги» «номера два» и выяснил, что в его коде вообще нет подобных багов, т.е. он никогда не возвращается к тому, что уже работает, до тех пор, пока не нужно внести изменения. Нет, ошибки были и у него, но они не были связаны с тем, что точкой входа в разные Use Case был один и тот же метод, т.к. у него просто не было такого кода.
Получилось, что в похожих ситуациях «номер два» тратит на больше времени на исправление подходов «номера один», чем сам «номер один», но у «номера два» со своим собственным кодом вообще не бывает таких ситуаций.
Остался последний вопрос – почему «номер один» тоже попадает в такие ситуации. Пытаясь это понять, я подумал о самом языке. JavaScript «однопоточен». И «номер два» думает в один поток и «отлаживается» всегда только в «один поток». «Номер один» думает о многих ситуациях в одно время, но даже он ошибается при «смешивании».
«Номер два» зная эту свою особенность, старается сделать так, чтобы отлаживать было нечего. «Номер один» просто пишет код, т.к. ему вроде-бы не о чем задумываться.
Не знаю, как назвать этот подход и откуда он пришел. Может быть это один из принципов KISS, может быть это graceful degradation, может быть это часть unix way (одно действие, но «хорошо»).
В вики, кстати, есть что-то похожее en.wikipedia.org/wiki/Separation_of_concerns, но не уверен, что это прям вот совсем то.
Но в любом случае мне нравится, что у «номера два» может в один момент сломаться только что-то одно, и не нравится, что у «номера один», если вообще ломается, то всё сразу.
Автор: wentout