Не погоджуйтесь розробляти те, чого не розумієте

Не погоджуйтесь розробляти те, чого не розумієте

З початку 2018 року я обіймаю посаду ліда/начальника/провідного розробника в команді — називайте це як хочете, але суть у тому, що я повністю відповідаю за один із модулів та за всіх розробників, які над ним працюють. Ця позиція відкриває мені новий погляд на процес розробки, оскільки я задіяний у більшій кількості проектів та активніше беру участь у прийнятті рішень. Нещодавно, завдяки цим двом обставинам, я несподівано усвідомив, як міра розуміння впливає на код і на додаток.

Думка, яку я хочу висловити, зводиться до того, що якість коду (і кінцевого продукту) тісно пов'язана з тим, наскільки люди, які займаються проектуванням та пишуть код, усвідомлюють, що саме вони роблять.

Ви, можливо, зараз думаєте: «Дякую, кеп. Звичайно, непогано було б розуміти, що взагалі пишеш. Інакше з тим самим успіхом можна найняти групу мавп, щоб вони молотили за довільними клавішами, і заспокоїтися». І ви маєте рацію. Відповідно, я сприймаю як даність: ви усвідомлюєте, що мати загальне уявлення про те, що робиш, необхідно. Це можна назвати нульовим рівнем розуміння, і його ми не розбиратимемо докладно. Докладно ми розбиратимемо, що саме потрібно розуміти і як це позначається на рішеннях, які ви приймаєте щодня. Якби я знав ці речі заздалегідь, це позбавило б мене від маси витраченого часу і сумнівного коду.

Хоча нижче ви жодного рядка коду не побачите, я все-таки вважаю, що все сказане тут має велике значення для написання якісного, виразного коду.

Перший рівень розуміння: Чому воно не працює?

До цього рівня розробники зазвичай приходять на ранніх етапах своєї кар'єри, іноді навіть без будь-якої допомоги з боку оточуючих — принаймні, за моїми спостереженнями. Уявіть, що ви отримали багрепорт: якась функція в програмі не працює, її потрібно відремонтувати. Як ви діятимете?

Стандартна схема виглядає так:

  1. Знайти фрагмент коду, який викликає проблему (як це робиться - окрема тема, її я розкриваю у своїй книзі про застарілий код)
  2. Внести до цього фрагмента зміни
  3. Переконатися, що баг виправлено і регресивних помилок не виникло

Тепер зосередимося на другому пункті - внесення змін до коду. Є два підходи до цього процесу. Перший: вникнути у те, що саме відбувається у поточному коді, виявити помилку та виправити її. Другий: просуватися навпомацки — додати, припустимо, +1 в умовний оператор або цикл, подивитися, чи не запрацювала цього функція в потрібному сценарії, потім ще щось спробувати і так до нескінченності.

Правильним є перший підхід. Як пояснює у своїй книзі Code Complete Стів МакКоннелл (до речі, дуже її рекомендую), щоразу, коли ми щось змінюємо в коді, ми маємо змогу з упевненістю передбачити, як це вплине на додаток. Наводжу цитату по пам'яті, але якщо багфікс спрацьовує не так, як ви очікували, вас має це насторожити, ви повинні поставити під питання весь свій план дій.

Узагальнюючи сказане, щоб виконати добротний багфікс, який не погіршить якість коду, потрібно розуміти всю структуру коду, і джерело конкретної проблеми.

Другий рівень розуміння: Чому воно працює?

Цей рівень осягається набагато менш інтуїтивно, ніж попередній. Я, будучи ще розробником-початківцем, засвоїв його завдяки начальнику, а згодом неодноразово сам пояснював суть справи новачкам.

Цього разу давайте уявімо, що вам надійшло одразу два багрепорти: у першому йдеться про сценарій A, у другому — про сценарій B. В обох сценаріях відбувається щось не те. Відповідно, ви приймаєтеся спочатку за перший баг. Керуючись принципами, які ми вивели для першого рівня розуміння, ви добре вникаєте в код, що має відношення до проблеми, з'ясовуєте, чому він змушує додаток вести себе саме так у сценарії А, і вносите розумні корективи, які дають саме той результат, якого ви чекали. Все йде чудово.

Потім ви переходите до сценарію B. Ви повторюєте сценарій у спробі спровокувати помилку, але сюрприз! — тепер усе працює як слід. Щоб підтвердити свій здогад, ви скасовуєте зміни, внесені в процесі роботи над помилкою А, і баг B знову повертається. Ваш багфікс вирішив обидві проблеми. Пощастило!

Ви на це зовсім не розраховували. Ви придумали спосіб виправити помилку в сценарії А і поняття не маєте, чому він спрацював і для сценарію B. На цьому етапі дуже велика спокуса вирішити, що обидві завдання успішно виконані. Це цілком логічно: адже сенс був у тому, щоб усунути помилки, хіба ні? Але робота ще не закінчена: вам ще доведеться розібратися, чому ваші дії виправили помилку у сценарії B. Навіщо? Тому, що він, можливо, працює на неправильних принципах, і тоді вам потрібно буде шукати інший вихід. Ось кілька прикладів таких випадків:

  • Так як рішення не підбиралося прицільно під помилку B з урахуванням усіх факторів, ви, можливо, самі того не підозрюючи зламали функцію C.
  • не виключено, що десь причаївся ще й третій баг, пов'язаний з тією ж функцією, і ваш багфікс зав'язує коректну роботу системи сценарію B на ньому. Зараз все виглядає добре, але одного прекрасного дня цей третій баг помітять і виправлять. Тоді в сценарії B знову виникне помилка, і добре, якщо там.

Все це вносить в код хаотичність і коли-небудь звалиться вам на голову - швидше за все, у невідповідний момент. Доведеться зібрати волю в кулак, щоб змусити себе витрачати час на розуміння того, чому все на вигляд працює, але воно того варте.

Третій рівень розуміння: Навіщо воно працює?

Моє недавнє осяяння пов'язане саме з цим рівнем, і, напевно, саме він дав би мені найбільше переваг, якби я прийшов до цієї думки раніше.

Щоб було зрозуміліше, розберемо на прикладі: ваш модуль потрібно зробити сумісним з функцією X. Ви не дуже близько знайомі з функцією X, але вам сказали, що для сумісності з нею потрібно використовувати фреймворк F. Інші модулі, які інтегруються з X, працюють саме з ним.

Ваш код з першого дня свого життя взагалі не стикався з фреймворком F, тому впровадити його буде не так просто. Це спричинить серйозні наслідки для деяких складових модуля. Тим не менш, ви з головою йдете в розробку: тижнями пишете код, тестуєте, викочуєте пілотні версії, отримуєте фідбек, виправляєте регресійні помилки, виявляєте непередбачені ускладнення, не вкладаєтеся в спочатку обумовлені терміни, пишете ще скільки коду, тестуєте зв'язок, виправляєте регресійні помилки - все це задля того, щоб запровадити фреймворк F.

І в якийсь момент ви раптом усвідомлюєте - або, можливо, чуєте від когось - що, можливо, фреймворк F зовсім і не дасть вам сумісності з функцією X. Можливо, весь цей час і сили були прикладені зовсім не до того.

Щось подібне одного разу сталося під час роботи над проектом, за який я відповідав. Чому так вийшло? Тому що я погано розумів, у чому суть функції X і як вона пов'язана з фреймворком F. Як мені слід було вчинити? Попросити людину, яка ставить завдання на розробку, дохідливо пояснити, як намічений план дій призводить до бажаного результату, замість просто повторювати те, що робилося для інших модулів, або вірити на слово, що так потрібно для роботи функції X.

Досвід цього проекту навчив мене відмовлятися розпочинати процес розробки, поки ми не маємо ясного розуміння, чому нас просять виконати ті чи інші дії. Відмовлятись прямим текстом. Коли отримуєш завдання, перший імпульс — негайно взятися за неї, щоб не марнувати часу. Але політика «заморожуємо проект, доки не увійдемо у вся зокрема» може скоротити марнований час на цілі порядки.

Навіть якщо на вас намагаються натиснути, змусити розпочати роботу, хоч ви не розумієте, чим це обґрунтовано — чиніть опір. Спочатку розберіться, з якою метою вам ставлять таке завдання, і вирішіть, чи це вірний шлях до мети. Мені довелося все це дізнатися на гіркому досвіді — сподіваюся тим, хто це читає, мій приклад полегшить життя.

Четвертий рівень розуміння: ???

У програмуванні завжди є чому повчитися, і вважаю, я тільки торкнувся найвищих верств теми розуміння. Які рівні розуміння ви виявили за роки роботи з кодом? Які приймали рішення, які добре позначилися на якості коду та додатку? Які рішення виявилися хибними і дали вам цінний урок? Діліться досвідом у коментарях.

Джерело: habr.com

Додати коментар або відгук